Skip to content

Commit 717dafd

Browse files
committed
Implemented StructNullable to support ValueOption fields
1 parent 859c99c commit 717dafd

File tree

4 files changed

+144
-5
lines changed

4 files changed

+144
-5
lines changed

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

+4
Original file line numberDiff line numberDiff line change
@@ -359,6 +359,10 @@ module SchemaDefinitions =
359359
/// to take option of provided value.
360360
let Nullable(innerDef : #TypeDef<'Val>) : NullableDef<'Val> = upcast { NullableDefinition.OfType = innerDef }
361361

362+
/// Wraps a GraphQL type definition, allowing defining field/argument
363+
/// to take voption of provided value.
364+
let StructNullable(innerDef : #TypeDef<'Val>) : StructNullableDef<'Val> = upcast { StructNullableDefinition.OfType = innerDef }
365+
362366
/// Wraps a GraphQL type definition, allowing defining field/argument
363367
/// to take collection of provided value.
364368
let ListOf(innerDef : #TypeDef<'Val>) : ListOfDef<'Val, 'Seq> = upcast { ListOfDefinition.OfType = innerDef }

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

+40
Original file line numberDiff line numberDiff line change
@@ -1560,6 +1560,46 @@ and internal NullableDefinition<'Val> = {
15601560
| :? ListOfDef as list -> "[" + list.OfType.ToString () + "]"
15611561
| other -> other.ToString ()
15621562

1563+
/// GraphQL type definition for nullable/optional types.
1564+
/// By default all GraphQL types in this library are considered
1565+
/// to be NonNull. This definition applies reversed mechanics,
1566+
/// allowing them to take null as a valid value.
1567+
and StructNullableDef<'Val> =
1568+
interface
1569+
/// GraphQL type definition of the nested type.
1570+
abstract OfType : TypeDef<'Val>
1571+
inherit InputDef<'Val voption>
1572+
inherit OutputDef<'Val voption>
1573+
inherit NullableDef
1574+
end
1575+
1576+
and internal StructNullableDefinition<'Val> = {
1577+
OfType : TypeDef<'Val>
1578+
} with
1579+
1580+
interface InputDef
1581+
1582+
interface TypeDef with
1583+
member _.Type = typeof<'Val voption>
1584+
member x.MakeNullable () = upcast x
1585+
member x.MakeList () =
1586+
let list : ListOfDefinition<_, _> = { OfType = x }
1587+
upcast list
1588+
1589+
interface OutputDef
1590+
1591+
interface NullableDef with
1592+
member x.OfType = upcast x.OfType
1593+
1594+
interface StructNullableDef<'Val> with
1595+
member x.OfType = x.OfType
1596+
1597+
override x.ToString () =
1598+
match x.OfType with
1599+
| :? NamedDef as named -> named.Name
1600+
| :? ListOfDef as list -> "[" + list.OfType.ToString () + "]"
1601+
| other -> other.ToString ()
1602+
15631603
/// GraphQL tye definition for input objects. They are different
15641604
/// from object types (which can be used only as outputs).
15651605
and InputObjectDef =

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

+16-5
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,9 @@ type TestSubject = {
3333
and DeepTestSubject = {
3434
a: string
3535
b: string
36-
c: string option list
36+
c: string option
37+
d: string voption
38+
l: string option list
3739
}
3840

3941
and DUArg =
@@ -62,7 +64,9 @@ let ``Execution handles basic tasks: executes arbitrary code`` () =
6264
{
6365
a = "Already Been Done"
6466
b = "Boring"
65-
c = [Some "Contrived"; None; Some "Confusing"]
67+
c = Some "Contrived"
68+
d = ValueSome "Donut"
69+
l = [Some "Contrived"; None; Some "Confusing"]
6670
}
6771

6872
let ast = parse """query Example($size: Int) {
@@ -81,6 +85,8 @@ let ``Execution handles basic tasks: executes arbitrary code`` () =
8185
a
8286
b
8387
c
88+
d
89+
l
8490
}
8591
}
8692
@@ -102,7 +108,9 @@ let ``Execution handles basic tasks: executes arbitrary code`` () =
102108
"deep", upcast NameValueLookup.ofList [
103109
"a", "Already Been Done" :> obj
104110
"b", upcast "Boring"
105-
"c", upcast ["Contrived" :> obj; null; upcast "Confusing"]
111+
"c", upcast "Contrived"
112+
"d", upcast "Donut"
113+
"l", upcast ["Contrived" :> obj; null; upcast "Confusing"]
106114
]
107115
]
108116

@@ -111,8 +119,11 @@ let ``Execution handles basic tasks: executes arbitrary code`` () =
111119
"DeepDataType", [
112120
Define.Field("a", StringType, (fun _ dt -> dt.a))
113121
Define.Field("b", StringType, (fun _ dt -> dt.b))
114-
Define.Field("c", (ListOf (Nullable StringType)), (fun _ dt -> dt.c))
122+
Define.Field("c", Nullable StringType, (fun _ dt -> dt.c))
123+
Define.Field("d", StructNullable StringType, (fun _ dt -> dt.d))
124+
Define.Field("l", (ListOf (Nullable StringType)), (fun _ dt -> dt.l))
115125
])
126+
116127
let rec DataType =
117128
DefineRec.Object<TestSubject>(
118129
"DataType",
@@ -121,7 +132,7 @@ let ``Execution handles basic tasks: executes arbitrary code`` () =
121132
Define.Field("a", StringType, resolve = fun _ dt -> dt.a)
122133
Define.Field("b", StringType, resolve = fun _ dt -> dt.b)
123134
Define.Field("c", StringType, resolve = fun _ dt -> dt.c)
124-
Define.Field("d", StringType, fun _ dt -> dt.d)
135+
Define.Field("d", StringType, resolve = fun _ dt -> dt.d)
125136
Define.Field("e", StringType, fun _ dt -> dt.e)
126137
Define.Field("f", StringType, fun _ dt -> dt.f)
127138
Define.Field("pic", StringType, "Picture resizer", [ Define.Input("size", Nullable IntType) ], fun ctx dt -> dt.pic(ctx.TryArg("size")))

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

+84
Original file line numberDiff line numberDiff line change
@@ -432,6 +432,46 @@ let ``Nullabe field type definitions are considered nullable`` () =
432432
empty errors
433433
data |> equals (upcast expected)
434434

435+
[<Fact>]
436+
let ``StructNullabe field type definitions are considered nullable`` () =
437+
let root = Define.Object("Query", [ Define.Field("onlyField", StructNullable StringType) ])
438+
let schema = Schema(root)
439+
let query = """{ __type(name: "Query") {
440+
fields {
441+
name
442+
type {
443+
kind
444+
name
445+
ofType {
446+
kind
447+
name
448+
ofType {
449+
kind
450+
name
451+
ofType {
452+
kind
453+
name
454+
}
455+
}
456+
}
457+
}
458+
}
459+
} }"""
460+
let result = sync <| Executor(schema).AsyncExecute(query)
461+
let expected =
462+
NameValueLookup.ofList [
463+
"__type", upcast NameValueLookup.ofList [
464+
"fields", upcast [
465+
box <| NameValueLookup.ofList [
466+
"name", upcast "onlyField"
467+
"type", upcast NameValueLookup.ofList [
468+
"kind", upcast "SCALAR"
469+
"name", upcast "String"
470+
"ofType", null]]]]]
471+
ensureDirect result <| fun data errors ->
472+
empty errors
473+
data |> equals (upcast expected)
474+
435475
[<Fact>]
436476
let ``Default field args type definitions are considered non-null`` () =
437477
let root = Define.Object("Query", [ Define.Field("onlyField", StringType, "", [ Define.Input("onlyArg", IntType) ], fun _ () -> null) ])
@@ -523,6 +563,50 @@ let ``Nullable field args type definitions are considered nullable`` () =
523563
empty errors
524564
data |> equals (upcast expected)
525565

566+
[<Fact>]
567+
let ``StructNullable field args type definitions are considered nullable`` () =
568+
let root = Define.Object("Query", [ Define.Field("onlyField", StringType, "", [ Define.Input("onlyArg", StructNullable IntType) ], fun _ () -> null) ])
569+
let schema = Schema(root)
570+
let query = """{ __type(name: "Query") {
571+
fields {
572+
args {
573+
name
574+
type {
575+
kind
576+
name
577+
ofType {
578+
kind
579+
name
580+
ofType {
581+
kind
582+
name
583+
ofType {
584+
kind
585+
name
586+
}
587+
}
588+
}
589+
}
590+
}
591+
}
592+
} }"""
593+
let result = sync <| Executor(schema).AsyncExecute(query)
594+
let expected =
595+
NameValueLookup.ofList [
596+
"__type", upcast NameValueLookup.ofList [
597+
"fields", upcast [
598+
box <| NameValueLookup.ofList [
599+
"args", upcast [
600+
box <| NameValueLookup.ofList [
601+
"name", upcast "onlyArg"
602+
"type", upcast NameValueLookup.ofList [
603+
"kind", upcast "SCALAR"
604+
"name", upcast "Int"
605+
"ofType", null ]]]]]]]
606+
ensureDirect result <| fun data errors ->
607+
empty errors
608+
data |> equals (upcast expected)
609+
526610
[<Fact>]
527611
let ``Introspection executes an introspection query`` () =
528612
let root = Define.Object("QueryRoot", [ Define.Field("onlyField", StringType) ])

0 commit comments

Comments
 (0)