From e7d39ed9215b17dbbe45b5d3243ef69e544fe191 Mon Sep 17 00:00:00 2001 From: Andrii Chebukin Date: Wed, 14 Feb 2024 17:25:40 +0400 Subject: [PATCH 1/3] Fixed ability to skip interface implementations and union cases in query #455 --- src/FSharp.Data.GraphQL.Server/Execution.fs | 17 +- src/FSharp.Data.GraphQL.Server/IO.fs | 4 +- .../AbstractionTests.fs | 178 +++++++++++++++++- .../Helpers and Extensions/AsyncValTests.fs | 4 +- 4 files changed, 193 insertions(+), 10 deletions(-) diff --git a/src/FSharp.Data.GraphQL.Server/Execution.fs b/src/FSharp.Data.GraphQL.Server/Execution.fs index 4e5ca4dd5..3b897a5f9 100644 --- a/src/FSharp.Data.GraphQL.Server/Execution.fs +++ b/src/FSharp.Data.GraphQL.Server/Execution.fs @@ -256,7 +256,12 @@ let private resolveField (execute: ExecuteField) (ctx: ResolveFieldContext) (par type ResolverResult<'T> = Result<'T * IObservable option * GQLProblemDetails list, GQLProblemDetails list> +[] module ResolverResult = + + let data data = Ok (data, None, []) + let defered data deferred = Ok (data, Some deferred, []) + let mapValue (f : 'T -> 'U) (r : ResolverResult<'T>) : ResolverResult<'U> = Result.map(fun (data, deferred, errs) -> (f data, deferred, errs)) r @@ -280,7 +285,7 @@ let private unionImplError unionName tyName path ctx = resolverError path ctx (G let private deferredNullableError name tyName path ctx = resolverError path ctx (GQLMessageException (sprintf "Deferred field %s of type '%s' must be nullable" name tyName)) let private streamListError name tyName path ctx = resolverError path ctx (GQLMessageException (sprintf "Streamed field %s of type '%s' must be list" name tyName)) -let private resolved name v : AsyncVal>> = AsyncVal.wrap <| Ok(KeyValuePair(name, box v), None, []) +let private resolved name v : AsyncVal>> = KeyValuePair(name, box v) |> ResolverResult.data |> AsyncVal.wrap let deferResults path (res : ResolverResult) : IObservable = let formattedPath = path |> List.rev @@ -370,7 +375,8 @@ let rec private direct (returnDef : OutputDef) (ctx : ResolveFieldContext) (path | kind -> failwithf "Unexpected value of ctx.ExecutionPlan.Kind: %A" kind match Map.tryFind resolvedDef.Name typeMap with | Some fields -> executeObjectFields fields name resolvedDef ctx path value - | None -> raiseErrors <| interfaceImplError iDef.Name resolvedDef.Name path ctx + | None -> KeyValuePair(name, null) |> ResolverResult.data |> AsyncVal.wrap + //| None -> raiseErrors <| interfaceImplError iDef.Name resolvedDef.Name path ctx | Union uDef -> let possibleTypesFn = ctx.Schema.GetPossibleTypes @@ -382,7 +388,8 @@ let rec private direct (returnDef : OutputDef) (ctx : ResolveFieldContext) (path | kind -> failwithf "Unexpected value of ctx.ExecutionPlan.Kind: %A" kind match Map.tryFind resolvedDef.Name typeMap with | Some fields -> executeObjectFields fields name resolvedDef ctx path (uDef.ResolveValue value) - | None -> raiseErrors <| unionImplError uDef.Name resolvedDef.Name path ctx + | None -> KeyValuePair(name, null) |> ResolverResult.data |> AsyncVal.wrap + //| None -> raiseErrors <| unionImplError uDef.Name resolvedDef.Name path ctx | _ -> failwithf "Unexpected value of returnDef: %O" returnDef @@ -393,7 +400,7 @@ and deferred (ctx : ResolveFieldContext) (path : FieldPath) (parent : obj) (valu executeResolvers ctx path parent (toOption value |> AsyncVal.wrap) |> Observable.ofAsyncVal |> Observable.bind(ResolverResult.mapValue(fun d -> d.Value) >> deferResults path) - AsyncVal.wrap <| Ok(KeyValuePair(info.Identifier, null), Some deferred, []) + ResolverResult.defered (KeyValuePair (info.Identifier, null)) deferred |> AsyncVal.wrap and private streamed (options : BufferedStreamOptions) (innerDef : OutputDef) (ctx : ResolveFieldContext) (path : FieldPath) (parent : obj) (value : obj) = let info = ctx.ExecutionInfo @@ -444,7 +451,7 @@ and private streamed (options : BufferedStreamOptions) (innerDef : OutputDef) (c |> Array.mapi resolveItem |> Observable.ofAsyncValSeq |> buffer - AsyncVal.wrap <| Ok(KeyValuePair(info.Identifier, box [||]), Some stream, []) + ResolverResult.defered (KeyValuePair (info.Identifier, null)) stream |> AsyncVal.wrap | _ -> raise <| GQLMessageException (ErrorMessages.expectedEnumerableValue ctx.ExecutionInfo.Identifier (value.GetType())) and private live (ctx : ResolveFieldContext) (path : FieldPath) (parent : obj) (value : obj) = diff --git a/src/FSharp.Data.GraphQL.Server/IO.fs b/src/FSharp.Data.GraphQL.Server/IO.fs index f33e56566..b9fe6e73b 100644 --- a/src/FSharp.Data.GraphQL.Server/IO.fs +++ b/src/FSharp.Data.GraphQL.Server/IO.fs @@ -61,9 +61,9 @@ type GQLExecutionResult = static member Invalid(documentId, errors, meta) = GQLExecutionResult.RequestError(documentId, errors, meta) static member ErrorAsync(documentId, msg : string, meta) = - asyncVal.Return (GQLExecutionResult.Error (documentId, msg, meta)) + AsyncVal.wrap (GQLExecutionResult.Error (documentId, msg, meta)) static member ErrorAsync(documentId, error : IGQLError, meta) = - asyncVal.Return (GQLExecutionResult.Error (documentId, error, meta)) + AsyncVal.wrap (GQLExecutionResult.Error (documentId, error, meta)) // TODO: Rename to PascalCase and GQLResponseContent = diff --git a/tests/FSharp.Data.GraphQL.Tests/AbstractionTests.fs b/tests/FSharp.Data.GraphQL.Tests/AbstractionTests.fs index 6307294e1..bc99354bb 100644 --- a/tests/FSharp.Data.GraphQL.Tests/AbstractionTests.fs +++ b/tests/FSharp.Data.GraphQL.Tests/AbstractionTests.fs @@ -75,7 +75,12 @@ let schemaWithInterface = [ Define.Field ( "pets", ListOf PetType, - fun _ _ -> [ { Name = "Odie"; Woofs = true } :> IPet; upcast { Name = "Garfield"; Meows = false } ] + fun _ _ -> [ { Name = "Odie"; Woofs = true } :> IPet; { Name = "Garfield"; Meows = false } ] + ) + Define.Field ( + "nullablePets", + ListOf (Nullable PetType), + fun _ _ -> [ { Name = "Odie"; Woofs = true } :> IPet |> Some; { Name = "Garfield"; Meows = false } :> IPet |> Some ] ) ] ), config = { SchemaConfig.Default with Types = [ CatType; DogType ] } @@ -111,6 +116,79 @@ let ``Execute handles execution of abstract types: isTypeOf is used to resolve r empty errors data |> equals (upcast expected) +[] +let ``Execute handles execution of abstract types: not specified Interface types produce error`` () = + let query = + """{ + pets { + ... on Dog { + name + woofs + } + } + }""" + + let result = sync <| schemaWithInterface.Value.AsyncExecute (parse query) + ensureRequestError result <| fun [ petsError ] -> + petsError |> ensureValidationError "Field 'pets' does not allow nulls and list values." [ "pets"; "0" ] + + let query = + """{ + pets { + ... on Cat { + name + meows + } + } + }""" + + let result = sync <| schemaWithInterface.Value.AsyncExecute (parse query) + ensureRequestError result <| fun [ petsError ] -> + petsError |> ensureValidationError "Field 'pets' does not allow nulls and list values." [ "pets"; "0" ] + +[] +let ``Execute handles execution of abstract types: not specified Interface types must be filtered out if they allow null`` () = + let query = + """{ + nullablePets { + ... on Dog { + name + woofs + } + } + }""" + + let result = sync <| schemaWithInterface.Value.AsyncExecute (parse query) + + let expected = + NameValueLookup.ofList + [ "nullablePets", upcast [ NameValueLookup.ofList [ "name", "Odie" :> obj; "woofs", upcast true ] :> obj; null ] ] + + ensureDirect result <| fun data errors -> + empty errors + data |> equals (upcast expected) + + let query = + """{ + nullablePets { + ... on Cat { + name + meows + } + } + }""" + + let result = sync <| schemaWithInterface.Value.AsyncExecute (parse query) + + let expected = + NameValueLookup.ofList + [ "nullablePets", + upcast [ null; NameValueLookup.ofList [ "name", "Garfield" :> obj; "meows", upcast false ] :> obj ] ] + + ensureDirect result <| fun data errors -> + empty errors + data |> equals (upcast expected) + [] let ``Execute handles execution of abstract types: absent field resolution produces errors for Interface`` () = let query = @@ -155,6 +233,26 @@ let ``Execute handles execution of abstract types: absent type resolution produc catError |> ensureValidationError "Field 'unknownField2' is not defined in schema type 'Cat'." [ "pets"; "unknownField2" ] dogError |> ensureValidationError "Inline fragment has type condition 'UnknownDog', but that type does not exist in the schema." [ "pets" ] + let query = + """{ + pets { + name + ... on Dog { + woofs + unknownField1 + } + ... on UnknownCat { + meows + unknownField2 + } + } + }""" + + let result = sync <| schemaWithInterface.Value.AsyncExecute (parse query) + ensureRequestError result <| fun [ catError; dogError ] -> + catError |> ensureValidationError "Field 'unknownField1' is not defined in schema type 'Dog'." [ "pets"; "unknownField1" ] + dogError |> ensureValidationError "Inline fragment has type condition 'UnknownCat', but that type does not exist in the schema." [ "pets" ] + let schemaWithUnion = lazy @@ -184,6 +282,11 @@ let schemaWithUnion = "pets", ListOf PetType, fun _ _ -> [ DogCase { Name = "Odie"; Woofs = true }; CatCase { Name = "Garfield"; Meows = false } ] + ) + Define.Field ( + "nullablePets", + ListOf (Nullable PetType), + fun _ _ -> [ DogCase { Name = "Odie"; Woofs = true } |> Some; CatCase { Name = "Garfield"; Meows = false } |> Some ] ) ] ) ) @@ -219,6 +322,79 @@ let ``Execute handles execution of abstract types: isTypeOf is used to resolve r empty errors data |> equals (upcast expected) +[] +let ``Execute handles execution of abstract types: not specified Union types produce error`` () = + let query = + """{ + pets { + ... on Dog { + name + woofs + } + } + }""" + + let result = sync <| schemaWithUnion.Value.AsyncExecute (parse query) + ensureRequestError result <| fun [ petsError ] -> + petsError |> ensureValidationError "Field 'pets' does not allow nulls and list values." [ "pets"; "0" ] + + let query = + """{ + pets { + ... on Cat { + name + meows + } + } + }""" + + let result = sync <| schemaWithUnion.Value.AsyncExecute (parse query) + ensureRequestError result <| fun [ petsError ] -> + petsError |> ensureValidationError "Field 'pets' does not allow nulls and list values." [ "pets"; "0" ] + +[] +let ``Execute handles execution of abstract types: not specified Union types must be filtered out`` () = + let query = + """{ + nullablePets { + ... on Dog { + name + woofs + } + } + }""" + + let result = sync <| schemaWithUnion.Value.AsyncExecute (parse query) + + let expected = + NameValueLookup.ofList + [ "nullablePets", upcast [ NameValueLookup.ofList [ "name", "Odie" :> obj; "woofs", upcast true ] :> obj; null ] ] + + ensureDirect result <| fun data errors -> + empty errors + data |> equals (upcast expected) + + let query = + """{ + nullablePets { + ... on Cat { + name + meows + } + } + }""" + + let result = sync <| schemaWithUnion.Value.AsyncExecute (parse query) + + let expected = + NameValueLookup.ofList + [ "nullablePets", + upcast [ null; NameValueLookup.ofList [ "name", "Garfield" :> obj; "meows", upcast false ] :> obj ] ] + + ensureDirect result <| fun data errors -> + empty errors + data |> equals (upcast expected) + [] let ``Execute handles execution of abstract types: absent field resolution produces errors for Union`` () = let query = diff --git a/tests/FSharp.Data.GraphQL.Tests/Helpers and Extensions/AsyncValTests.fs b/tests/FSharp.Data.GraphQL.Tests/Helpers and Extensions/AsyncValTests.fs index f96f66c44..6ea2920c5 100644 --- a/tests/FSharp.Data.GraphQL.Tests/Helpers and Extensions/AsyncValTests.fs +++ b/tests/FSharp.Data.GraphQL.Tests/Helpers and Extensions/AsyncValTests.fs @@ -72,14 +72,14 @@ let ``AsyncVal computation defines zero value`` () = [] let ``AsyncVal can be returned from Async computation`` () = - let a = async { return! asyncVal.Return 1 } + let a = async { return! AsyncVal.wrap 1 } let res = a |> sync res |> equals 1 [] let ``AsyncVal can be bound inside Async computation`` () = let a = async { - let! v = asyncVal.Return 1 + let! v = AsyncVal.wrap 1 return v } let res = a |> sync res |> equals 1 From 5b3c30d34a0d916feb1889d48d766275c139c107 Mon Sep 17 00:00:00 2001 From: Andrii Chebukin Date: Wed, 14 Feb 2024 19:18:17 +0400 Subject: [PATCH 2/3] Fixed deffered tests --- src/FSharp.Data.GraphQL.Server/Execution.fs | 13 +++++++------ tests/FSharp.Data.GraphQL.Tests/AbstractionTests.fs | 12 ++++++------ 2 files changed, 13 insertions(+), 12 deletions(-) diff --git a/src/FSharp.Data.GraphQL.Server/Execution.fs b/src/FSharp.Data.GraphQL.Server/Execution.fs index 3b897a5f9..6f7532227 100644 --- a/src/FSharp.Data.GraphQL.Server/Execution.fs +++ b/src/FSharp.Data.GraphQL.Server/Execution.fs @@ -376,7 +376,6 @@ let rec private direct (returnDef : OutputDef) (ctx : ResolveFieldContext) (path match Map.tryFind resolvedDef.Name typeMap with | Some fields -> executeObjectFields fields name resolvedDef ctx path value | None -> KeyValuePair(name, null) |> ResolverResult.data |> AsyncVal.wrap - //| None -> raiseErrors <| interfaceImplError iDef.Name resolvedDef.Name path ctx | Union uDef -> let possibleTypesFn = ctx.Schema.GetPossibleTypes @@ -389,7 +388,6 @@ let rec private direct (returnDef : OutputDef) (ctx : ResolveFieldContext) (path match Map.tryFind resolvedDef.Name typeMap with | Some fields -> executeObjectFields fields name resolvedDef ctx path (uDef.ResolveValue value) | None -> KeyValuePair(name, null) |> ResolverResult.data |> AsyncVal.wrap - //| None -> raiseErrors <| unionImplError uDef.Name resolvedDef.Name path ctx | _ -> failwithf "Unexpected value of returnDef: %O" returnDef @@ -451,7 +449,7 @@ and private streamed (options : BufferedStreamOptions) (innerDef : OutputDef) (c |> Array.mapi resolveItem |> Observable.ofAsyncValSeq |> buffer - ResolverResult.defered (KeyValuePair (info.Identifier, null)) stream |> AsyncVal.wrap + ResolverResult.defered (KeyValuePair (info.Identifier, box [])) stream |> AsyncVal.wrap | _ -> raise <| GQLMessageException (ErrorMessages.expectedEnumerableValue ctx.ExecutionInfo.Identifier (value.GetType())) and private live (ctx : ResolveFieldContext) (path : FieldPath) (parent : obj) (value : obj) = @@ -511,18 +509,21 @@ and private executeResolvers (ctx : ResolveFieldContext) (path : FieldPath) (par | Ok None when ctx.ExecutionInfo.IsNullable -> return Ok (KeyValuePair(name, null), None, []) | Error errs -> return Error errs | Ok None -> return Error (nullResolverError name path ctx) - | Ok (Some v) -> return! onSuccess ctx path parent v + | Ok (Some v) -> + match! onSuccess ctx path parent v with + | Ok (kvp, _, _) when not ctx.ExecutionInfo.IsNullable && kvp.Value = null -> return Error (nullResolverError name path ctx) + | result -> return result } match info.Kind, returnDef with | ResolveDeferred innerInfo, _ when innerInfo.IsNullable -> // We can only defer nullable fields deferred - |> resolveWith { ctx with ExecutionInfo = { innerInfo with IsNullable = false } } + |> resolveWith { ctx with ExecutionInfo = innerInfo } | ResolveDeferred innerInfo, _ -> raiseErrors <| deferredNullableError (innerInfo.Identifier) (innerInfo.ReturnDef.ToString()) path ctx | ResolveStreamed (innerInfo, mode), HasList innerDef -> // We can only stream lists streamed mode innerDef - |> resolveWith { ctx with ExecutionInfo = innerInfo; } + |> resolveWith { ctx with ExecutionInfo = innerInfo } | ResolveStreamed (innerInfo, _), _ -> raiseErrors <| streamListError innerInfo.Identifier (returnDef.ToString()) path ctx | ResolveLive innerInfo, _ -> diff --git a/tests/FSharp.Data.GraphQL.Tests/AbstractionTests.fs b/tests/FSharp.Data.GraphQL.Tests/AbstractionTests.fs index bc99354bb..6db12f74e 100644 --- a/tests/FSharp.Data.GraphQL.Tests/AbstractionTests.fs +++ b/tests/FSharp.Data.GraphQL.Tests/AbstractionTests.fs @@ -116,7 +116,7 @@ let ``Execute handles execution of abstract types: isTypeOf is used to resolve r empty errors data |> equals (upcast expected) -[] +[] let ``Execute handles execution of abstract types: not specified Interface types produce error`` () = let query = """{ @@ -130,7 +130,7 @@ let ``Execute handles execution of abstract types: not specified Interface types let result = sync <| schemaWithInterface.Value.AsyncExecute (parse query) ensureRequestError result <| fun [ petsError ] -> - petsError |> ensureValidationError "Field 'pets' does not allow nulls and list values." [ "pets"; "0" ] + petsError |> ensureExecutionError "Non-Null field pets resolved as a null!" [ "pets"; 1 ] let query = """{ @@ -144,7 +144,7 @@ let ``Execute handles execution of abstract types: not specified Interface types let result = sync <| schemaWithInterface.Value.AsyncExecute (parse query) ensureRequestError result <| fun [ petsError ] -> - petsError |> ensureValidationError "Field 'pets' does not allow nulls and list values." [ "pets"; "0" ] + petsError |> ensureExecutionError "Non-Null field pets resolved as a null!" [ "pets"; 0 ] [] let ``Execute handles execution of abstract types: not specified Interface types must be filtered out if they allow null`` () = @@ -322,7 +322,7 @@ let ``Execute handles execution of abstract types: isTypeOf is used to resolve r empty errors data |> equals (upcast expected) -[] +[] let ``Execute handles execution of abstract types: not specified Union types produce error`` () = let query = """{ @@ -336,7 +336,7 @@ let ``Execute handles execution of abstract types: not specified Union types pro let result = sync <| schemaWithUnion.Value.AsyncExecute (parse query) ensureRequestError result <| fun [ petsError ] -> - petsError |> ensureValidationError "Field 'pets' does not allow nulls and list values." [ "pets"; "0" ] + petsError |> ensureExecutionError "Non-Null field pets resolved as a null!" [ "pets"; 1 ] let query = """{ @@ -350,7 +350,7 @@ let ``Execute handles execution of abstract types: not specified Union types pro let result = sync <| schemaWithUnion.Value.AsyncExecute (parse query) ensureRequestError result <| fun [ petsError ] -> - petsError |> ensureValidationError "Field 'pets' does not allow nulls and list values." [ "pets"; "0" ] + petsError |> ensureExecutionError "Non-Null field pets resolved as a null!" [ "pets"; 0 ] [] let ``Execute handles execution of abstract types: not specified Union types must be filtered out`` () = From 1556194acb4000f8656722dca781b178e2fcddb0 Mon Sep 17 00:00:00 2001 From: Andrii Chebukin Date: Thu, 15 Feb 2024 19:33:19 +0400 Subject: [PATCH 3/3] Fixed ability to skip interface implementations and union cases in query according to spec --- src/FSharp.Data.GraphQL.Server/Execution.fs | 9 +- .../AbstractionTests.fs | 112 ++++-------------- 2 files changed, 23 insertions(+), 98 deletions(-) diff --git a/src/FSharp.Data.GraphQL.Server/Execution.fs b/src/FSharp.Data.GraphQL.Server/Execution.fs index 6f7532227..ea4053c81 100644 --- a/src/FSharp.Data.GraphQL.Server/Execution.fs +++ b/src/FSharp.Data.GraphQL.Server/Execution.fs @@ -375,7 +375,7 @@ let rec private direct (returnDef : OutputDef) (ctx : ResolveFieldContext) (path | kind -> failwithf "Unexpected value of ctx.ExecutionPlan.Kind: %A" kind match Map.tryFind resolvedDef.Name typeMap with | Some fields -> executeObjectFields fields name resolvedDef ctx path value - | None -> KeyValuePair(name, null) |> ResolverResult.data |> AsyncVal.wrap + | None -> KeyValuePair(name, obj()) |> ResolverResult.data |> AsyncVal.wrap | Union uDef -> let possibleTypesFn = ctx.Schema.GetPossibleTypes @@ -387,7 +387,7 @@ let rec private direct (returnDef : OutputDef) (ctx : ResolveFieldContext) (path | kind -> failwithf "Unexpected value of ctx.ExecutionPlan.Kind: %A" kind match Map.tryFind resolvedDef.Name typeMap with | Some fields -> executeObjectFields fields name resolvedDef ctx path (uDef.ResolveValue value) - | None -> KeyValuePair(name, null) |> ResolverResult.data |> AsyncVal.wrap + | None -> KeyValuePair(name, obj()) |> ResolverResult.data |> AsyncVal.wrap | _ -> failwithf "Unexpected value of returnDef: %O" returnDef @@ -509,10 +509,7 @@ and private executeResolvers (ctx : ResolveFieldContext) (path : FieldPath) (par | Ok None when ctx.ExecutionInfo.IsNullable -> return Ok (KeyValuePair(name, null), None, []) | Error errs -> return Error errs | Ok None -> return Error (nullResolverError name path ctx) - | Ok (Some v) -> - match! onSuccess ctx path parent v with - | Ok (kvp, _, _) when not ctx.ExecutionInfo.IsNullable && kvp.Value = null -> return Error (nullResolverError name path ctx) - | result -> return result + | Ok (Some v) -> return! onSuccess ctx path parent v } match info.Kind, returnDef with diff --git a/tests/FSharp.Data.GraphQL.Tests/AbstractionTests.fs b/tests/FSharp.Data.GraphQL.Tests/AbstractionTests.fs index 6db12f74e..05bc9d148 100644 --- a/tests/FSharp.Data.GraphQL.Tests/AbstractionTests.fs +++ b/tests/FSharp.Data.GraphQL.Tests/AbstractionTests.fs @@ -76,11 +76,6 @@ let schemaWithInterface = "pets", ListOf PetType, fun _ _ -> [ { Name = "Odie"; Woofs = true } :> IPet; { Name = "Garfield"; Meows = false } ] - ) - Define.Field ( - "nullablePets", - ListOf (Nullable PetType), - fun _ _ -> [ { Name = "Odie"; Woofs = true } :> IPet |> Some; { Name = "Garfield"; Meows = false } :> IPet |> Some ] ) ] ), config = { SchemaConfig.Default with Types = [ CatType; DogType ] } @@ -117,7 +112,7 @@ let ``Execute handles execution of abstract types: isTypeOf is used to resolve r data |> equals (upcast expected) [] -let ``Execute handles execution of abstract types: not specified Interface types produce error`` () = +let ``Execute handles execution of abstract types: not specified Interface types must be empty objects`` () = let query = """{ pets { @@ -129,48 +124,18 @@ let ``Execute handles execution of abstract types: not specified Interface types }""" let result = sync <| schemaWithInterface.Value.AsyncExecute (parse query) - ensureRequestError result <| fun [ petsError ] -> - petsError |> ensureExecutionError "Non-Null field pets resolved as a null!" [ "pets"; 1 ] - let query = - """{ - pets { - ... on Cat { - name - meows - } - } - }""" - - let result = sync <| schemaWithInterface.Value.AsyncExecute (parse query) - ensureRequestError result <| fun [ petsError ] -> - petsError |> ensureExecutionError "Non-Null field pets resolved as a null!" [ "pets"; 0 ] - -[] -let ``Execute handles execution of abstract types: not specified Interface types must be filtered out if they allow null`` () = - let query = - """{ - nullablePets { - ... on Dog { - name - woofs - } - } - }""" - - let result = sync <| schemaWithInterface.Value.AsyncExecute (parse query) - - let expected = - NameValueLookup.ofList - [ "nullablePets", upcast [ NameValueLookup.ofList [ "name", "Odie" :> obj; "woofs", upcast true ] :> obj; null ] ] + let expected = NameValueLookup.ofList [ "name", "Odie" :> obj; "woofs", upcast true ] ensureDirect result <| fun data errors -> empty errors - data |> equals (upcast expected) + let [| dog; emptyObj |] = data["pets"] :?> obj array + dog |> equals (upcast expected) + emptyObj.GetType() |> equals typeof let query = """{ - nullablePets { + pets { ... on Cat { name meows @@ -180,14 +145,13 @@ let ``Execute handles execution of abstract types: not specified Interface types let result = sync <| schemaWithInterface.Value.AsyncExecute (parse query) - let expected = - NameValueLookup.ofList - [ "nullablePets", - upcast [ null; NameValueLookup.ofList [ "name", "Garfield" :> obj; "meows", upcast false ] :> obj ] ] + let expected = NameValueLookup.ofList [ "name", "Garfield" :> obj; "meows", upcast false ] ensureDirect result <| fun data errors -> empty errors - data |> equals (upcast expected) + let [| emptyObj; cat|] = data["pets"] :?> obj array + cat |> equals (upcast expected) + emptyObj.GetType() |> equals typeof [] let ``Execute handles execution of abstract types: absent field resolution produces errors for Interface`` () = @@ -282,11 +246,6 @@ let schemaWithUnion = "pets", ListOf PetType, fun _ _ -> [ DogCase { Name = "Odie"; Woofs = true }; CatCase { Name = "Garfield"; Meows = false } ] - ) - Define.Field ( - "nullablePets", - ListOf (Nullable PetType), - fun _ _ -> [ DogCase { Name = "Odie"; Woofs = true } |> Some; CatCase { Name = "Garfield"; Meows = false } |> Some ] ) ] ) ) @@ -323,7 +282,7 @@ let ``Execute handles execution of abstract types: isTypeOf is used to resolve r data |> equals (upcast expected) [] -let ``Execute handles execution of abstract types: not specified Union types produce error`` () = +let ``Execute handles execution of abstract types: not specified Union types must be empty objects`` () = let query = """{ pets { @@ -335,48 +294,18 @@ let ``Execute handles execution of abstract types: not specified Union types pro }""" let result = sync <| schemaWithUnion.Value.AsyncExecute (parse query) - ensureRequestError result <| fun [ petsError ] -> - petsError |> ensureExecutionError "Non-Null field pets resolved as a null!" [ "pets"; 1 ] - let query = - """{ - pets { - ... on Cat { - name - meows - } - } - }""" - - let result = sync <| schemaWithUnion.Value.AsyncExecute (parse query) - ensureRequestError result <| fun [ petsError ] -> - petsError |> ensureExecutionError "Non-Null field pets resolved as a null!" [ "pets"; 0 ] - -[] -let ``Execute handles execution of abstract types: not specified Union types must be filtered out`` () = - let query = - """{ - nullablePets { - ... on Dog { - name - woofs - } - } - }""" - - let result = sync <| schemaWithUnion.Value.AsyncExecute (parse query) - - let expected = - NameValueLookup.ofList - [ "nullablePets", upcast [ NameValueLookup.ofList [ "name", "Odie" :> obj; "woofs", upcast true ] :> obj; null ] ] + let expected = NameValueLookup.ofList [ "name", "Odie" :> obj; "woofs", upcast true ] ensureDirect result <| fun data errors -> empty errors - data |> equals (upcast expected) + let [| dog; emptyObj |] = data["pets"] :?> obj array + dog |> equals (upcast expected) + emptyObj.GetType() |> equals typeof let query = """{ - nullablePets { + pets { ... on Cat { name meows @@ -386,14 +315,13 @@ let ``Execute handles execution of abstract types: not specified Union types mus let result = sync <| schemaWithUnion.Value.AsyncExecute (parse query) - let expected = - NameValueLookup.ofList - [ "nullablePets", - upcast [ null; NameValueLookup.ofList [ "name", "Garfield" :> obj; "meows", upcast false ] :> obj ] ] + let expected = NameValueLookup.ofList [ "name", "Garfield" :> obj; "meows", upcast false ] ensureDirect result <| fun data errors -> empty errors - data |> equals (upcast expected) + let [| emptyObj; cat|] = data["pets"] :?> obj array + cat |> equals (upcast expected) + emptyObj.GetType() |> equals typeof [] let ``Execute handles execution of abstract types: absent field resolution produces errors for Union`` () =