From 7585751cdaf97655b03152157299b2036ddd345d Mon Sep 17 00:00:00 2001 From: Andrii Chebukin Date: Thu, 15 Feb 2024 19:33:19 +0400 Subject: [PATCH] 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`` () =