Skip to content

Commit eb4b157

Browse files
authored
Merge pull request #362 from mickhansen/fix-input-list-arrays
allow input lists from query/AST to be mapped to an internal array type
2 parents 0e4a0e8 + bd8f702 commit eb4b157

File tree

3 files changed

+35
-10
lines changed

3 files changed

+35
-10
lines changed

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

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -65,18 +65,25 @@ let rec internal compileByType (errMsg: string) (inputDef: InputDef): ExecuteInp
6565
| None -> null
6666
| _ -> null
6767
| List (Input innerdef) ->
68+
let isArray = inputDef.Type.IsArray
6869
let inner = compileByType errMsg innerdef
6970
let cons, nil = ReflectionHelper.listOfType innerdef.Type
71+
7072
fun value variables ->
7173
match value with
7274
| ListValue list ->
7375
let mappedValues = list |> List.map (fun value -> inner value variables)
74-
nil |> List.foldBack cons mappedValues
76+
if isArray then
77+
ReflectionHelper.arrayOfList innerdef.Type mappedValues
78+
else
79+
nil |> List.foldBack cons mappedValues
7580
| Variable variableName -> variables.[variableName]
7681
| _ ->
7782
// try to construct a list from single element
7883
let single = inner value variables
79-
if single = null then null else cons single nil
84+
if single = null then null else
85+
if isArray then ReflectionHelper.arrayOfList innerdef.Type [single]
86+
else cons single nil
8087
| Nullable (Input innerdef) ->
8188
let inner = compileByType errMsg innerdef
8289
let some, none, _ = ReflectionHelper.optionOfType innerdef.Type

src/FSharp.Data.GraphQL.Shared/Prolog.fs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,18 @@ module internal ReflectionHelper =
118118
fun item list -> cons.Invoke (null, [| item; list |])
119119
(cons, nil)
120120

121+
/// <summary>
122+
/// used to create array from list of type <paramref name="t"/> given at runtime.
123+
/// </summary>
124+
/// <param name="t">runtime type for array type construction</param>
125+
/// <param name="l">input list</param>
126+
let arrayOfList (t: Type) (l : _ list) =
127+
let array = System.Array.CreateInstance(t, l.Length)
128+
l |> List.iteri (fun i v ->
129+
array.SetValue(v, i)
130+
)
131+
array :> obj
132+
121133
/// <summary>
122134
/// Returns pair of function constructors for `some(value)` and `none`
123135
/// used to create option of type <paramref name="t"/> given at runtime.

tests/FSharp.Data.GraphQL.Tests/VariablesTests.fs

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -24,15 +24,21 @@ type TestInput = {
2424
b: string option seq option
2525
c: string
2626
d: string option
27+
e: string option array option
2728
}
29+
30+
let InputArrayOf(innerDef : #TypeDef<'Val>) : ListOfDef<'Val, 'Val array> =
31+
ListOf innerDef
32+
2833
let TestInputObject =
2934
Define.InputObject<TestInput>(
3035
name = "TestInputObject",
3136
fields = [
3237
Define.Input("a", Nullable String)
33-
Define.Input("b", Nullable(ListOf (Nullable String)))
38+
Define.Input("b", Nullable( ListOf (Nullable String)) )
3439
Define.Input("c", String)
3540
Define.Input("d", Nullable TestComplexScalar)
41+
Define.Input("e", Nullable( InputArrayOf (Nullable String)) )
3642
])
3743

3844
type TestNestedInput = {
@@ -85,9 +91,9 @@ let schema = Schema(TestType)
8591

8692
[<Fact>]
8793
let ``Execute handles objects and nullability using inline structs with complex input`` () =
88-
let ast = parse """{ fieldWithObjectInput(input: {a: "foo", b: ["bar"], c: "baz"}) }"""
94+
let ast = parse """{ fieldWithObjectInput(input: {a: "foo", b: ["bar"], c: "baz", e: ["baf"]}) }"""
8995
let actual = sync <| Executor(schema).AsyncExecute(ast)
90-
let expected = NameValueLookup.ofList [ "fieldWithObjectInput", upcast """{"a":"foo","b":["bar"],"c":"baz","d":null}""" ]
96+
let expected = NameValueLookup.ofList [ "fieldWithObjectInput", upcast """{"a":"foo","b":["bar"],"c":"baz","d":null,"e":["baf"]}""" ]
9197
match actual with
9298
| Direct(data, errors) ->
9399
empty errors
@@ -98,7 +104,7 @@ let ``Execute handles objects and nullability using inline structs with complex
98104
let ``Execute handles objects and nullability using inline structs and properly parses single value to list`` () =
99105
let ast = parse """{ fieldWithObjectInput(input: {a: "foo", b: "bar", c: "baz"}) }"""
100106
let actual = sync <| Executor(schema).AsyncExecute(ast)
101-
let expected = NameValueLookup.ofList [ "fieldWithObjectInput", upcast """{"a":"foo","b":["bar"],"c":"baz","d":null}""" ]
107+
let expected = NameValueLookup.ofList [ "fieldWithObjectInput", upcast """{"a":"foo","b":["bar"],"c":"baz","d":null,"e":null}""" ]
102108
match actual with
103109
| Direct(data, errors) ->
104110
empty errors
@@ -120,7 +126,7 @@ let ``Execute handles objects and nullability using inline structs and doesn't u
120126
let ``Execute handles objects and nullability using inline structs and proprely coerces complex scalar types`` () =
121127
let ast = parse """{ fieldWithObjectInput(input: {c: "foo", d: "SerializedValue"}) }"""
122128
let actual = sync <| Executor(schema).AsyncExecute(ast)
123-
let expected = NameValueLookup.ofList [ "fieldWithObjectInput", upcast """{"a":null,"b":null,"c":"foo","d":"DeserializedValue"}"""]
129+
let expected = NameValueLookup.ofList [ "fieldWithObjectInput", upcast """{"a":null,"b":null,"c":"foo","d":"DeserializedValue","e":null}"""]
124130
match actual with
125131
| Direct(data, errors) ->
126132
empty errors
@@ -133,9 +139,9 @@ let ``Execute handles variables with complex inputs`` () =
133139
fieldWithObjectInput(input: $input)
134140
}"""
135141
let params' : Map<string, obj> =
136-
Map.ofList ["input", upcast { a = Some "foo"; b = Some (upcast [ Some "bar"]) ; c = "baz"; d = None }]
142+
Map.ofList ["input", upcast { a = Some "foo"; b = Some (upcast [| Some "bar"|]) ; c = "baz"; d = None; e = None }]
137143
let actual = sync <| Executor(schema).AsyncExecute(ast, variables = params')
138-
let expected = NameValueLookup.ofList [ "fieldWithObjectInput", upcast """{"a":"foo","b":["bar"],"c":"baz","d":null}""" ]
144+
let expected = NameValueLookup.ofList [ "fieldWithObjectInput", upcast """{"a":"foo","b":["bar"],"c":"baz","d":null,"e":null}""" ]
139145
match actual with
140146
| Direct(data, errors) ->
141147
empty errors
@@ -148,7 +154,7 @@ let ``Execute handles variables with default value when no value was provided``
148154
fieldWithObjectInput(input: $input)
149155
}"""
150156
let actual = sync <| Executor(schema).AsyncExecute(ast)
151-
let expected = NameValueLookup.ofList [ "fieldWithObjectInput", upcast """{"a":"foo","b":["bar"],"c":"baz","d":null}""" ]
157+
let expected = NameValueLookup.ofList [ "fieldWithObjectInput", upcast """{"a":"foo","b":["bar"],"c":"baz","d":null,"e":null}""" ]
152158
match actual with
153159
| Direct(data, errors) ->
154160
empty errors

0 commit comments

Comments
 (0)