Skip to content

Commit 1f077eb

Browse files
committed
Fixed ability to skip interface implementations and union cases in query
#455
1 parent c19c80e commit 1f077eb

File tree

4 files changed

+193
-10
lines changed

4 files changed

+193
-10
lines changed

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

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -256,7 +256,12 @@ let private resolveField (execute: ExecuteField) (ctx: ResolveFieldContext) (par
256256

257257
type ResolverResult<'T> = Result<'T * IObservable<GQLDeferredResponseContent> option * GQLProblemDetails list, GQLProblemDetails list>
258258

259+
[<RequireQualifiedAccess>]
259260
module ResolverResult =
261+
262+
let data data = Ok (data, None, [])
263+
let defered data deferred = Ok (data, Some deferred, [])
264+
260265
let mapValue (f : 'T -> 'U) (r : ResolverResult<'T>) : ResolverResult<'U> =
261266
Result.map(fun (data, deferred, errs) -> (f data, deferred, errs)) r
262267

@@ -280,7 +285,7 @@ let private unionImplError unionName tyName path ctx = resolverError path ctx (G
280285
let private deferredNullableError name tyName path ctx = resolverError path ctx (GQLMessageException (sprintf "Deferred field %s of type '%s' must be nullable" name tyName))
281286
let private streamListError name tyName path ctx = resolverError path ctx (GQLMessageException (sprintf "Streamed field %s of type '%s' must be list" name tyName))
282287

283-
let private resolved name v : AsyncVal<ResolverResult<KeyValuePair<string, obj>>> = AsyncVal.wrap <| Ok(KeyValuePair(name, box v), None, [])
288+
let private resolved name v : AsyncVal<ResolverResult<KeyValuePair<string, obj>>> = KeyValuePair(name, box v) |> ResolverResult.data |> AsyncVal.wrap
284289

285290
let deferResults path (res : ResolverResult<obj>) : IObservable<GQLDeferredResponseContent> =
286291
let formattedPath = path |> List.rev
@@ -370,7 +375,8 @@ let rec private direct (returnDef : OutputDef) (ctx : ResolveFieldContext) (path
370375
| kind -> failwithf "Unexpected value of ctx.ExecutionPlan.Kind: %A" kind
371376
match Map.tryFind resolvedDef.Name typeMap with
372377
| Some fields -> executeObjectFields fields name resolvedDef ctx path value
373-
| None -> raiseErrors <| interfaceImplError iDef.Name resolvedDef.Name path ctx
378+
| None -> KeyValuePair(name, null) |> ResolverResult.data |> AsyncVal.wrap
379+
//| None -> raiseErrors <| interfaceImplError iDef.Name resolvedDef.Name path ctx
374380

375381
| Union uDef ->
376382
let possibleTypesFn = ctx.Schema.GetPossibleTypes
@@ -382,7 +388,8 @@ let rec private direct (returnDef : OutputDef) (ctx : ResolveFieldContext) (path
382388
| kind -> failwithf "Unexpected value of ctx.ExecutionPlan.Kind: %A" kind
383389
match Map.tryFind resolvedDef.Name typeMap with
384390
| Some fields -> executeObjectFields fields name resolvedDef ctx path (uDef.ResolveValue value)
385-
| None -> raiseErrors <| unionImplError uDef.Name resolvedDef.Name path ctx
391+
| None -> KeyValuePair(name, null) |> ResolverResult.data |> AsyncVal.wrap
392+
//| None -> raiseErrors <| unionImplError uDef.Name resolvedDef.Name path ctx
386393

387394
| _ -> failwithf "Unexpected value of returnDef: %O" returnDef
388395

@@ -393,7 +400,7 @@ and deferred (ctx : ResolveFieldContext) (path : FieldPath) (parent : obj) (valu
393400
executeResolvers ctx path parent (toOption value |> AsyncVal.wrap)
394401
|> Observable.ofAsyncVal
395402
|> Observable.bind(ResolverResult.mapValue(fun d -> d.Value) >> deferResults path)
396-
AsyncVal.wrap <| Ok(KeyValuePair(info.Identifier, null), Some deferred, [])
403+
ResolverResult.defered (KeyValuePair (info.Identifier, null)) deferred |> AsyncVal.wrap
397404

398405
and private streamed (options : BufferedStreamOptions) (innerDef : OutputDef) (ctx : ResolveFieldContext) (path : FieldPath) (parent : obj) (value : obj) =
399406
let info = ctx.ExecutionInfo
@@ -444,7 +451,7 @@ and private streamed (options : BufferedStreamOptions) (innerDef : OutputDef) (c
444451
|> Array.mapi resolveItem
445452
|> Observable.ofAsyncValSeq
446453
|> buffer
447-
AsyncVal.wrap <| Ok(KeyValuePair(info.Identifier, box [||]), Some stream, [])
454+
ResolverResult.defered (KeyValuePair (info.Identifier, null)) stream |> AsyncVal.wrap
448455
| _ -> raise <| GQLMessageException (ErrorMessages.expectedEnumerableValue ctx.ExecutionInfo.Identifier (value.GetType()))
449456

450457
and private live (ctx : ResolveFieldContext) (path : FieldPath) (parent : obj) (value : obj) =

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -61,9 +61,9 @@ type GQLExecutionResult =
6161
static member Invalid(documentId, errors, meta) =
6262
GQLExecutionResult.RequestError(documentId, errors, meta)
6363
static member ErrorAsync(documentId, msg : string, meta) =
64-
asyncVal.Return (GQLExecutionResult.Error (documentId, msg, meta))
64+
AsyncVal.wrap (GQLExecutionResult.Error (documentId, msg, meta))
6565
static member ErrorAsync(documentId, error : IGQLError, meta) =
66-
asyncVal.Return (GQLExecutionResult.Error (documentId, error, meta))
66+
AsyncVal.wrap (GQLExecutionResult.Error (documentId, error, meta))
6767

6868
// TODO: Rename to PascalCase
6969
and GQLResponseContent =

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

Lines changed: 177 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,12 @@ let schemaWithInterface =
7575
[ Define.Field (
7676
"pets",
7777
ListOf PetType,
78-
fun _ _ -> [ { Name = "Odie"; Woofs = true } :> IPet; upcast { Name = "Garfield"; Meows = false } ]
78+
fun _ _ -> [ { Name = "Odie"; Woofs = true } :> IPet; { Name = "Garfield"; Meows = false } ]
79+
)
80+
Define.Field (
81+
"nullablePets",
82+
ListOf (Nullable PetType),
83+
fun _ _ -> [ { Name = "Odie"; Woofs = true } :> IPet |> Some; { Name = "Garfield"; Meows = false } :> IPet |> Some ]
7984
) ]
8085
),
8186
config = { SchemaConfig.Default with Types = [ CatType; DogType ] }
@@ -111,6 +116,79 @@ let ``Execute handles execution of abstract types: isTypeOf is used to resolve r
111116
empty errors
112117
data |> equals (upcast expected)
113118

119+
[<Fact(Skip = "Not implemented")>]
120+
let ``Execute handles execution of abstract types: not specified Interface types produce error`` () =
121+
let query =
122+
"""{
123+
pets {
124+
... on Dog {
125+
name
126+
woofs
127+
}
128+
}
129+
}"""
130+
131+
let result = sync <| schemaWithInterface.Value.AsyncExecute (parse query)
132+
ensureRequestError result <| fun [ petsError ] ->
133+
petsError |> ensureValidationError "Field 'pets' does not allow nulls and list values." [ "pets"; "0" ]
134+
135+
let query =
136+
"""{
137+
pets {
138+
... on Cat {
139+
name
140+
meows
141+
}
142+
}
143+
}"""
144+
145+
let result = sync <| schemaWithInterface.Value.AsyncExecute (parse query)
146+
ensureRequestError result <| fun [ petsError ] ->
147+
petsError |> ensureValidationError "Field 'pets' does not allow nulls and list values." [ "pets"; "0" ]
148+
149+
[<Fact>]
150+
let ``Execute handles execution of abstract types: not specified Interface types must be filtered out if they allow null`` () =
151+
let query =
152+
"""{
153+
nullablePets {
154+
... on Dog {
155+
name
156+
woofs
157+
}
158+
}
159+
}"""
160+
161+
let result = sync <| schemaWithInterface.Value.AsyncExecute (parse query)
162+
163+
let expected =
164+
NameValueLookup.ofList
165+
[ "nullablePets", upcast [ NameValueLookup.ofList [ "name", "Odie" :> obj; "woofs", upcast true ] :> obj; null ] ]
166+
167+
ensureDirect result <| fun data errors ->
168+
empty errors
169+
data |> equals (upcast expected)
170+
171+
let query =
172+
"""{
173+
nullablePets {
174+
... on Cat {
175+
name
176+
meows
177+
}
178+
}
179+
}"""
180+
181+
let result = sync <| schemaWithInterface.Value.AsyncExecute (parse query)
182+
183+
let expected =
184+
NameValueLookup.ofList
185+
[ "nullablePets",
186+
upcast [ null; NameValueLookup.ofList [ "name", "Garfield" :> obj; "meows", upcast false ] :> obj ] ]
187+
188+
ensureDirect result <| fun data errors ->
189+
empty errors
190+
data |> equals (upcast expected)
191+
114192
[<Fact>]
115193
let ``Execute handles execution of abstract types: absent field resolution produces errors for Interface`` () =
116194
let query =
@@ -155,6 +233,26 @@ let ``Execute handles execution of abstract types: absent type resolution produc
155233
catError |> ensureValidationError "Field 'unknownField2' is not defined in schema type 'Cat'." [ "pets"; "unknownField2" ]
156234
dogError |> ensureValidationError "Inline fragment has type condition 'UnknownDog', but that type does not exist in the schema." [ "pets" ]
157235

236+
let query =
237+
"""{
238+
pets {
239+
name
240+
... on Dog {
241+
woofs
242+
unknownField1
243+
}
244+
... on UnknownCat {
245+
meows
246+
unknownField2
247+
}
248+
}
249+
}"""
250+
251+
let result = sync <| schemaWithInterface.Value.AsyncExecute (parse query)
252+
ensureRequestError result <| fun [ catError; dogError ] ->
253+
catError |> ensureValidationError "Field 'unknownField1' is not defined in schema type 'Dog'." [ "pets"; "unknownField1" ]
254+
dogError |> ensureValidationError "Inline fragment has type condition 'UnknownCat', but that type does not exist in the schema." [ "pets" ]
255+
158256

159257
let schemaWithUnion =
160258
lazy
@@ -184,6 +282,11 @@ let schemaWithUnion =
184282
"pets",
185283
ListOf PetType,
186284
fun _ _ -> [ DogCase { Name = "Odie"; Woofs = true }; CatCase { Name = "Garfield"; Meows = false } ]
285+
)
286+
Define.Field (
287+
"nullablePets",
288+
ListOf (Nullable PetType),
289+
fun _ _ -> [ DogCase { Name = "Odie"; Woofs = true } |> Some; CatCase { Name = "Garfield"; Meows = false } |> Some ]
187290
) ]
188291
)
189292
)
@@ -219,6 +322,79 @@ let ``Execute handles execution of abstract types: isTypeOf is used to resolve r
219322
empty errors
220323
data |> equals (upcast expected)
221324

325+
[<Fact(Skip = "Not implemented")>]
326+
let ``Execute handles execution of abstract types: not specified Union types produce error`` () =
327+
let query =
328+
"""{
329+
pets {
330+
... on Dog {
331+
name
332+
woofs
333+
}
334+
}
335+
}"""
336+
337+
let result = sync <| schemaWithUnion.Value.AsyncExecute (parse query)
338+
ensureRequestError result <| fun [ petsError ] ->
339+
petsError |> ensureValidationError "Field 'pets' does not allow nulls and list values." [ "pets"; "0" ]
340+
341+
let query =
342+
"""{
343+
pets {
344+
... on Cat {
345+
name
346+
meows
347+
}
348+
}
349+
}"""
350+
351+
let result = sync <| schemaWithUnion.Value.AsyncExecute (parse query)
352+
ensureRequestError result <| fun [ petsError ] ->
353+
petsError |> ensureValidationError "Field 'pets' does not allow nulls and list values." [ "pets"; "0" ]
354+
355+
[<Fact>]
356+
let ``Execute handles execution of abstract types: not specified Union types must be filtered out`` () =
357+
let query =
358+
"""{
359+
nullablePets {
360+
... on Dog {
361+
name
362+
woofs
363+
}
364+
}
365+
}"""
366+
367+
let result = sync <| schemaWithUnion.Value.AsyncExecute (parse query)
368+
369+
let expected =
370+
NameValueLookup.ofList
371+
[ "nullablePets", upcast [ NameValueLookup.ofList [ "name", "Odie" :> obj; "woofs", upcast true ] :> obj; null ] ]
372+
373+
ensureDirect result <| fun data errors ->
374+
empty errors
375+
data |> equals (upcast expected)
376+
377+
let query =
378+
"""{
379+
nullablePets {
380+
... on Cat {
381+
name
382+
meows
383+
}
384+
}
385+
}"""
386+
387+
let result = sync <| schemaWithUnion.Value.AsyncExecute (parse query)
388+
389+
let expected =
390+
NameValueLookup.ofList
391+
[ "nullablePets",
392+
upcast [ null; NameValueLookup.ofList [ "name", "Garfield" :> obj; "meows", upcast false ] :> obj ] ]
393+
394+
ensureDirect result <| fun data errors ->
395+
empty errors
396+
data |> equals (upcast expected)
397+
222398
[<Fact>]
223399
let ``Execute handles execution of abstract types: absent field resolution produces errors for Union`` () =
224400
let query =

tests/FSharp.Data.GraphQL.Tests/Helpers and Extensions/AsyncValTests.fs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -72,14 +72,14 @@ let ``AsyncVal computation defines zero value`` () =
7272

7373
[<Fact>]
7474
let ``AsyncVal can be returned from Async computation`` () =
75-
let a = async { return! asyncVal.Return 1 }
75+
let a = async { return! AsyncVal.wrap 1 }
7676
let res = a |> sync
7777
res |> equals 1
7878

7979
[<Fact>]
8080
let ``AsyncVal can be bound inside Async computation`` () =
8181
let a = async {
82-
let! v = asyncVal.Return 1
82+
let! v = AsyncVal.wrap 1
8383
return v }
8484
let res = a |> sync
8585
res |> equals 1

0 commit comments

Comments
 (0)