Skip to content

Commit ec11590

Browse files
njlrxperiandri
andauthored
bugfix/issue-414-enum-unpacking (#415)
* Implemented use of enum names to pick a value * Modernized error raising * Fixed AST values error messages Co-authored-by: Andrii Chebukin <xperiandri@live.ru>
1 parent 298b58b commit ec11590

File tree

3 files changed

+197
-86
lines changed

3 files changed

+197
-86
lines changed

src/FSharp.Data.GraphQL.Server/Values.fs

Lines changed: 102 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -14,40 +14,54 @@ open Microsoft.FSharp.Reflection
1414

1515
/// Tries to convert type defined in AST into one of the type defs known in schema.
1616
let inline tryConvertAst schema ast =
17-
let rec convert isNullable (schema: ISchema) (ast: InputType) : TypeDef option =
17+
let rec convert isNullable (schema : ISchema) (ast : InputType) : TypeDef option =
1818
match ast with
1919
| NamedType name ->
2020
match schema.TryFindType name with
2121
| Some namedDef ->
22-
Some (if isNullable then upcast namedDef.MakeNullable() else upcast namedDef)
22+
Some (
23+
if isNullable then
24+
upcast namedDef.MakeNullable ()
25+
else
26+
upcast namedDef
27+
)
2328
| None -> None
2429
| ListType inner ->
2530
convert true schema inner
2631
|> Option.map (fun i ->
27-
if isNullable
28-
then upcast i.MakeList().MakeNullable()
29-
else upcast i.MakeList())
30-
| NonNullType inner ->
31-
convert false schema inner
32+
if isNullable then
33+
upcast i.MakeList().MakeNullable ()
34+
else
35+
upcast i.MakeList ())
36+
| NonNullType inner -> convert false schema inner
37+
3238
convert true schema ast
3339

34-
let inline private notAssignableMsg (innerDef: InputDef) value : string =
40+
let inline private notAssignableMsg (innerDef : InputDef) value : string =
3541
sprintf "value of type %s is not assignable from %s" innerDef.Type.Name (value.GetType().Name)
3642

37-
let rec internal compileByType (errMsg: string) (inputDef: InputDef): ExecuteInput =
43+
let rec internal compileByType (errMsg : string) (inputDef : InputDef) : ExecuteInput =
3844
match inputDef with
39-
| Scalar scalardef ->
40-
variableOrElse (scalardef.CoerceInput >> Option.toObj)
45+
| Scalar scalardef -> variableOrElse (scalardef.CoerceInput >> Option.toObj)
4146
| InputObject objdef ->
4247
let objtype = objdef.Type
4348
let ctor = ReflectionHelper.matchConstructor objtype (objdef.Fields |> Array.map (fun x -> x.Name))
49+
4450
let mapper =
45-
ctor.GetParameters()
46-
|> Array.map(fun param ->
47-
match objdef.Fields |> Array.tryFind(fun field -> field.Name = param.Name) with
51+
ctor.GetParameters ()
52+
|> Array.map (fun param ->
53+
match
54+
objdef.Fields
55+
|> Array.tryFind (fun field -> field.Name = param.Name)
56+
with
4857
| Some x -> x
4958
| None ->
50-
failwithf "Input object '%s' refers to type '%O', but constructor parameter '%s' doesn't match any of the defined input fields" objdef.Name objtype param.Name)
59+
failwithf
60+
"Input object '%s' refers to type '%O', but constructor parameter '%s' doesn't match any of the defined input fields"
61+
objdef.Name
62+
objtype
63+
param.Name)
64+
5165
fun value variables ->
5266
match value with
5367
| ObjectValue props ->
@@ -57,7 +71,8 @@ let rec internal compileByType (errMsg: string) (inputDef: InputDef): ExecuteInp
5771
match Map.tryFind field.Name props with
5872
| None -> null
5973
| Some prop -> field.ExecuteInput prop variables)
60-
let instance = ctor.Invoke(args)
74+
75+
let instance = ctor.Invoke (args)
6176
instance
6277
| Variable variableName ->
6378
match Map.tryFind variableName variables with
@@ -73,6 +88,7 @@ let rec internal compileByType (errMsg: string) (inputDef: InputDef): ExecuteInp
7388
match value with
7489
| ListValue list ->
7590
let mappedValues = list |> List.map (fun value -> inner value variables)
91+
7692
if isArray then
7793
ReflectionHelper.arrayOfList innerdef.Type mappedValues
7894
else
@@ -81,21 +97,30 @@ let rec internal compileByType (errMsg: string) (inputDef: InputDef): ExecuteInp
8197
| _ ->
8298
// try to construct a list from single element
8399
let single = inner value variables
84-
if single = null then null else
85-
if isArray then ReflectionHelper.arrayOfList innerdef.Type [single]
86-
else cons single nil
100+
101+
if single = null then
102+
null
103+
else if isArray then
104+
ReflectionHelper.arrayOfList innerdef.Type [ single ]
105+
else
106+
cons single nil
87107
| Nullable (Input innerdef) ->
88108
let inner = compileByType errMsg innerdef
89109
let some, none, _ = ReflectionHelper.optionOfType innerdef.Type
90110

91111
fun variables value ->
92112
let i = inner variables value
113+
93114
match i with
94115
| null -> none
95116
| coerced ->
96117
let c = some coerced
97-
if c <> null then c
98-
else raise(GraphQLException (errMsg + notAssignableMsg innerdef coerced))
118+
119+
if c <> null then
120+
c
121+
else
122+
raise
123+
<| GraphQLException (errMsg + notAssignableMsg innerdef coerced)
99124
| Enum enumdef ->
100125
fun value variables ->
101126
match value with
@@ -105,35 +130,50 @@ let rec internal compileByType (errMsg: string) (inputDef: InputDef): ExecuteInp
105130
| None -> failwithf "Variable '%s' not supplied.\nVariables: %A" variableName variables
106131
| _ ->
107132
let coerced = coerceEnumInput value
133+
108134
match coerced with
109135
| None -> null
110-
| Some s -> ReflectionHelper.parseUnion enumdef.Type s
136+
| Some s ->
137+
enumdef.Options
138+
|> Seq.tryFind (fun v -> v.Name = s)
139+
|> Option.map (fun x -> x.Value :?> _)
140+
|> Option.defaultWith (fun () -> ReflectionHelper.parseUnion enumdef.Type s)
111141
| _ -> failwithf "Unexpected value of inputDef: %O" inputDef
112142

113-
let rec private coerceVariableValue isNullable typedef (vardef: VarDef) (input: obj) (errMsg: string) : obj =
143+
let rec private coerceVariableValue isNullable typedef (vardef : VarDef) (input : obj) (errMsg : string) : obj =
114144
match typedef with
115145
| Scalar scalardef ->
116146
match scalardef.CoerceValue input with
117147
| None when isNullable -> null
118148
| None ->
119-
raise (GraphQLException <| errMsg + (sprintf "expected value of type %s but got None" scalardef.Name))
149+
raise (
150+
GraphQLException
151+
<| $"%s{errMsg}expected value of type '%s{scalardef.Name}' but got 'None'."
152+
)
120153
| Some res -> res
121154
| Nullable (Input innerdef) ->
122155
let some, none, innerValue = ReflectionHelper.optionOfType innerdef.Type
123156
let input = innerValue input
124157
let coerced = coerceVariableValue true innerdef vardef input errMsg
125-
if coerced <> null
126-
then
158+
159+
if coerced <> null then
127160
let s = some coerced
128-
if s <> null
129-
then s
130-
else raise (GraphQLException <| errMsg + (sprintf "value of type %O is not assignable from %O" innerdef.Type (coerced.GetType())))
131-
else none
161+
162+
if s <> null then
163+
s
164+
else
165+
raise
166+
<| GraphQLException ($"%s{errMsg}value of type '%O{innerdef.Type}' is not assignable from '%O{coerced.GetType ()}'.")
167+
else
168+
none
132169
| List (Input innerdef) ->
133170
let cons, nil = ReflectionHelper.listOfType innerdef.Type
171+
134172
match input with
135173
| null when isNullable -> null
136-
| null -> raise(GraphQLException <| errMsg + (sprintf "expected value of type %s, but no value was found." (vardef.TypeDef.ToString())))
174+
| null ->
175+
raise
176+
<| GraphQLException ($"%s{errMsg}expected value of type '%s{vardef.TypeDef.ToString ()}', but no value was found.")
137177
// special case - while single values should be wrapped with a list in this scenario,
138178
// string would be treat as IEnumerable and coerced into a list of chars
139179
| :? string as s ->
@@ -148,37 +188,52 @@ let rec private coerceVariableValue isNullable typedef (vardef: VarDef) (input:
148188
|> Seq.toList
149189
|> List.rev
150190
|> List.fold (fun acc coerced -> cons coerced acc) nil
191+
151192
mapped
152-
| other -> raise (GraphQLException <| errMsg + (sprintf "Cannot coerce value of type '%O' to list." (other.GetType())))
153-
| InputObject objdef ->
154-
coerceVariableInputObject objdef vardef input (errMsg + (sprintf "in input object '%s': " objdef.Name))
193+
| other ->
194+
raise
195+
<| GraphQLException ($"{errMsg}Cannot coerce value of type '%O{other.GetType ()}' to list.")
196+
| InputObject objdef -> coerceVariableInputObject objdef vardef input (errMsg + (sprintf "in input object '%s': " objdef.Name))
155197
| Enum enumdef ->
156198
match input with
157-
| :? string as s ->
158-
ReflectionHelper.parseUnion enumdef.Type s
199+
| :? string as s -> ReflectionHelper.parseUnion enumdef.Type s
159200
| null when isNullable -> null
160-
| null -> raise(GraphQLException <| errMsg + (sprintf "Expected Enum '%s', but no value was found." enumdef.Name))
161-
| u when FSharpType.IsUnion(enumdef.Type) && enumdef.Type = input.GetType() -> u
162-
| o when Enum.IsDefined(enumdef.Type, o) -> o
201+
| null ->
202+
raise
203+
<| GraphQLException ($"%s{errMsg}Expected Enum '%s{enumdef.Name}', but no value was found.")
204+
205+
| u when
206+
FSharpType.IsUnion (enumdef.Type)
207+
&& enumdef.Type = input.GetType ()
208+
->
209+
u
210+
| o when Enum.IsDefined (enumdef.Type, o) -> o
163211
| _ ->
164-
raise (GraphQLException <| errMsg + (sprintf "Cannot coerce value of type '%O' to type Enum '%s'" (input.GetType()) enumdef.Name))
165-
| _ -> raise (GraphQLException <| errMsg + "Only Scalars, Nullables, Lists and InputObjects are valid type definitions.")
212+
raise (
213+
GraphQLException
214+
<| $"%s{errMsg}Cannot coerce value of type '%O{input.GetType ()}' to type Enum '%s{enumdef.Name}'."
215+
)
216+
| _ ->
217+
raise
218+
<| GraphQLException ($"%s{errMsg}Only Scalars, Nullables, Lists, and InputObjects are valid type definitions.")
166219

167-
and private coerceVariableInputObject (objdef) (vardef: VarDef) (input: obj) errMsg =
220+
and private coerceVariableInputObject (objdef) (vardef : VarDef) (input : obj) errMsg =
168221
//TODO: this should be eventually coerced to complex object
169222
match input with
170223
| :? Map<string, obj> as map ->
171224
let mapped =
172225
objdef.Fields
173226
|> Array.map (fun field ->
174227
let valueFound = Map.tryFind field.Name map |> Option.toObj
175-
(field.Name, coerceVariableValue false field.TypeDef vardef valueFound (errMsg + (sprintf "in field '%s': " field.Name))))
228+
(field.Name, coerceVariableValue false field.TypeDef vardef valueFound $"%s{errMsg}in field '%s{field.Name}': "))
176229
|> Map.ofArray
230+
177231
upcast mapped
178232
| _ -> input
179233

180-
let internal coerceVariable (vardef: VarDef) (inputs) =
234+
let internal coerceVariable (vardef : VarDef) (inputs) =
181235
let vname = vardef.Name
236+
182237
match Map.tryFind vname inputs with
183238
| None ->
184239
match vardef.DefaultValue with
@@ -189,5 +244,7 @@ let internal coerceVariable (vardef: VarDef) (inputs) =
189244
| None ->
190245
match vardef.TypeDef with
191246
| Nullable _ -> null
192-
| _ -> raise (GraphQLException (sprintf "Variable '$%s' of required type %s has no value provided." vname (vardef.TypeDef.ToString())))
247+
| _ ->
248+
raise
249+
<| GraphQLException ($"Variable '$%s{vname}' of required type '%s{vardef.TypeDef.ToString ()}' has no value provided.")
193250
| Some input -> coerceVariableValue false vardef.TypeDef vardef input (sprintf "Variable '$%s': " vname)

0 commit comments

Comments
 (0)