Skip to content

Commit

Permalink
Implemented StructNullable to support ValueOption fields
Browse files Browse the repository at this point in the history
  • Loading branch information
xperiandri committed Jan 31, 2025
1 parent 859c99c commit 717dafd
Show file tree
Hide file tree
Showing 4 changed files with 144 additions and 5 deletions.
4 changes: 4 additions & 0 deletions src/FSharp.Data.GraphQL.Shared/SchemaDefinitions.fs
Original file line number Diff line number Diff line change
Expand Up @@ -359,6 +359,10 @@ module SchemaDefinitions =
/// to take option of provided value.
let Nullable(innerDef : #TypeDef<'Val>) : NullableDef<'Val> = upcast { NullableDefinition.OfType = innerDef }

/// Wraps a GraphQL type definition, allowing defining field/argument
/// to take voption of provided value.
let StructNullable(innerDef : #TypeDef<'Val>) : StructNullableDef<'Val> = upcast { StructNullableDefinition.OfType = innerDef }

/// Wraps a GraphQL type definition, allowing defining field/argument
/// to take collection of provided value.
let ListOf(innerDef : #TypeDef<'Val>) : ListOfDef<'Val, 'Seq> = upcast { ListOfDefinition.OfType = innerDef }
Expand Down
40 changes: 40 additions & 0 deletions src/FSharp.Data.GraphQL.Shared/TypeSystem.fs
Original file line number Diff line number Diff line change
Expand Up @@ -1560,6 +1560,46 @@ and internal NullableDefinition<'Val> = {
| :? ListOfDef as list -> "[" + list.OfType.ToString () + "]"
| other -> other.ToString ()

/// GraphQL type definition for nullable/optional types.
/// By default all GraphQL types in this library are considered
/// to be NonNull. This definition applies reversed mechanics,
/// allowing them to take null as a valid value.
and StructNullableDef<'Val> =
interface
/// GraphQL type definition of the nested type.
abstract OfType : TypeDef<'Val>
inherit InputDef<'Val voption>
inherit OutputDef<'Val voption>
inherit NullableDef
end

and internal StructNullableDefinition<'Val> = {
OfType : TypeDef<'Val>
} with

interface InputDef

interface TypeDef with
member _.Type = typeof<'Val voption>
member x.MakeNullable () = upcast x
member x.MakeList () =
let list : ListOfDefinition<_, _> = { OfType = x }
upcast list

interface OutputDef

interface NullableDef with
member x.OfType = upcast x.OfType

interface StructNullableDef<'Val> with
member x.OfType = x.OfType

override x.ToString () =
match x.OfType with
| :? NamedDef as named -> named.Name
| :? ListOfDef as list -> "[" + list.OfType.ToString () + "]"
| other -> other.ToString ()

/// GraphQL tye definition for input objects. They are different
/// from object types (which can be used only as outputs).
and InputObjectDef =
Expand Down
21 changes: 16 additions & 5 deletions tests/FSharp.Data.GraphQL.Tests/ExecutionTests.fs
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,9 @@ type TestSubject = {
and DeepTestSubject = {
a: string
b: string
c: string option list
c: string option
d: string voption
l: string option list
}

and DUArg =
Expand Down Expand Up @@ -62,7 +64,9 @@ let ``Execution handles basic tasks: executes arbitrary code`` () =
{
a = "Already Been Done"
b = "Boring"
c = [Some "Contrived"; None; Some "Confusing"]
c = Some "Contrived"
d = ValueSome "Donut"
l = [Some "Contrived"; None; Some "Confusing"]
}

let ast = parse """query Example($size: Int) {
Expand All @@ -81,6 +85,8 @@ let ``Execution handles basic tasks: executes arbitrary code`` () =
a
b
c
d
l
}
}
Expand All @@ -102,7 +108,9 @@ let ``Execution handles basic tasks: executes arbitrary code`` () =
"deep", upcast NameValueLookup.ofList [
"a", "Already Been Done" :> obj
"b", upcast "Boring"
"c", upcast ["Contrived" :> obj; null; upcast "Confusing"]
"c", upcast "Contrived"
"d", upcast "Donut"
"l", upcast ["Contrived" :> obj; null; upcast "Confusing"]
]
]

Expand All @@ -111,8 +119,11 @@ let ``Execution handles basic tasks: executes arbitrary code`` () =
"DeepDataType", [
Define.Field("a", StringType, (fun _ dt -> dt.a))
Define.Field("b", StringType, (fun _ dt -> dt.b))
Define.Field("c", (ListOf (Nullable StringType)), (fun _ dt -> dt.c))
Define.Field("c", Nullable StringType, (fun _ dt -> dt.c))
Define.Field("d", StructNullable StringType, (fun _ dt -> dt.d))
Define.Field("l", (ListOf (Nullable StringType)), (fun _ dt -> dt.l))
])

let rec DataType =
DefineRec.Object<TestSubject>(
"DataType",
Expand All @@ -121,7 +132,7 @@ let ``Execution handles basic tasks: executes arbitrary code`` () =
Define.Field("a", StringType, resolve = fun _ dt -> dt.a)
Define.Field("b", StringType, resolve = fun _ dt -> dt.b)
Define.Field("c", StringType, resolve = fun _ dt -> dt.c)
Define.Field("d", StringType, fun _ dt -> dt.d)
Define.Field("d", StringType, resolve = fun _ dt -> dt.d)
Define.Field("e", StringType, fun _ dt -> dt.e)
Define.Field("f", StringType, fun _ dt -> dt.f)
Define.Field("pic", StringType, "Picture resizer", [ Define.Input("size", Nullable IntType) ], fun ctx dt -> dt.pic(ctx.TryArg("size")))
Expand Down
84 changes: 84 additions & 0 deletions tests/FSharp.Data.GraphQL.Tests/IntrospectionTests.fs
Original file line number Diff line number Diff line change
Expand Up @@ -432,6 +432,46 @@ let ``Nullabe field type definitions are considered nullable`` () =
empty errors
data |> equals (upcast expected)

[<Fact>]
let ``StructNullabe field type definitions are considered nullable`` () =
let root = Define.Object("Query", [ Define.Field("onlyField", StructNullable StringType) ])
let schema = Schema(root)
let query = """{ __type(name: "Query") {
fields {
name
type {
kind
name
ofType {
kind
name
ofType {
kind
name
ofType {
kind
name
}
}
}
}
}
} }"""
let result = sync <| Executor(schema).AsyncExecute(query)
let expected =
NameValueLookup.ofList [
"__type", upcast NameValueLookup.ofList [
"fields", upcast [
box <| NameValueLookup.ofList [
"name", upcast "onlyField"
"type", upcast NameValueLookup.ofList [
"kind", upcast "SCALAR"
"name", upcast "String"
"ofType", null]]]]]
ensureDirect result <| fun data errors ->
empty errors
data |> equals (upcast expected)

[<Fact>]
let ``Default field args type definitions are considered non-null`` () =
let root = Define.Object("Query", [ Define.Field("onlyField", StringType, "", [ Define.Input("onlyArg", IntType) ], fun _ () -> null) ])
Expand Down Expand Up @@ -523,6 +563,50 @@ let ``Nullable field args type definitions are considered nullable`` () =
empty errors
data |> equals (upcast expected)

[<Fact>]
let ``StructNullable field args type definitions are considered nullable`` () =
let root = Define.Object("Query", [ Define.Field("onlyField", StringType, "", [ Define.Input("onlyArg", StructNullable IntType) ], fun _ () -> null) ])
let schema = Schema(root)
let query = """{ __type(name: "Query") {
fields {
args {
name
type {
kind
name
ofType {
kind
name
ofType {
kind
name
ofType {
kind
name
}
}
}
}
}
}
} }"""
let result = sync <| Executor(schema).AsyncExecute(query)
let expected =
NameValueLookup.ofList [
"__type", upcast NameValueLookup.ofList [
"fields", upcast [
box <| NameValueLookup.ofList [
"args", upcast [
box <| NameValueLookup.ofList [
"name", upcast "onlyArg"
"type", upcast NameValueLookup.ofList [
"kind", upcast "SCALAR"
"name", upcast "Int"
"ofType", null ]]]]]]]
ensureDirect result <| fun data errors ->
empty errors
data |> equals (upcast expected)

[<Fact>]
let ``Introspection executes an introspection query`` () =
let root = Define.Object("QueryRoot", [ Define.Field("onlyField", StringType) ])
Expand Down

0 comments on commit 717dafd

Please sign in to comment.