Skip to content

Commit 755d913

Browse files
Andrii Chebukinxperiandri
authored andcommitted
Aligned response checking in tests, implemented error extensions including errorKind
1 parent 2873ed0 commit 755d913

32 files changed

+1469
-1173
lines changed

samples/star-wars-api/MultipartRequest.fs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -82,12 +82,12 @@ module MultipartRequest =
8282
| NamedType tname -> tname = "Upload" || tname = "UploadRequest"
8383
| ListType t | NonNullType t -> isUpload t
8484
let ast = Parser.parse operation.Query
85-
let vardefs =
85+
let varDefs =
8686
ast.Definitions
8787
|> List.choose (function OperationDefinition def -> Some def.VariableDefinitions | _ -> None)
8888
|> List.collect id
89-
let vardef = vardefs |> List.find (fun x -> x.VariableName = varName)
90-
if isUpload vardef.Type
89+
let varDef = varDefs |> List.find (fun x -> x.VariableName = varName)
90+
if isUpload varDef.Type
9191
then
9292
match varValue.ValueKind with
9393
| JsonValueKind.Object ->
Lines changed: 63 additions & 119 deletions
Original file line numberDiff line numberDiff line change
@@ -1,73 +1,31 @@
11
// The MIT License (MIT)
2-
namespace FSharp.Data.GraphQL
2+
namespace FSharp.Data.GraphQL
33

44
open System
55
open System.Collections.Generic
6-
open System.Collections.Immutable
7-
open System.Collections.ObjectModel
86
open FsToolkit.ErrorHandling
97
open FSharp.Data.GraphQL.Types
108

11-
type InputRoot =
12-
| Variable of VarDef: VarDef
13-
| Argument of ArgDef: InputFieldDef
14-
15-
type ErrorKind =
16-
| InputCoercion
17-
| InputObjectValidation
18-
| Execution
19-
20-
type internal CoercionError =
21-
{
22-
InnerError : IGQLError
23-
ErrorKind : ErrorKind
24-
ObjectType : string voption
25-
FieldType : string voption
26-
Path : FieldPath
27-
}
28-
interface IGQLError with
29-
30-
member this.Message = this.InnerError.Message
31-
32-
interface IGQLErrorExtensions with
33-
34-
member this.Extensions =
9+
type InputSource =
10+
| Variable of VarDef : VarDef
11+
| Argument of ArgDef : InputFieldDef
12+
| Unknown
3513

36-
[
37-
yield KeyValuePair("kind", this.ErrorKind |> box)
38-
if this.ObjectType.IsSome then yield KeyValuePair("objectType", this.ObjectType.Value |> box)
39-
if this.FieldType.IsSome then yield KeyValuePair("fieldType", this.FieldType.Value |> box)
14+
[<Struct>]
15+
type internal ObjectFieldErrorDetails = { ObjectDef : InputDef; FieldDef : InputFieldDef voption }
4016

41-
match this.Path with
42-
| [] -> ()
43-
| path -> yield KeyValuePair("path", path |> Seq.rev |> box)
17+
type internal IInputSourceError =
18+
inherit IGQLError
19+
abstract member InputSource : InputSource with get, set
4420

45-
match this.InnerError with
46-
| :? IGQLErrorExtensions as ext ->
47-
match ext.Extensions with
48-
| ValueSome extensions -> yield! extensions
49-
| ValueNone -> ()
50-
| _ -> ()
51-
]
52-
|> Dictionary<_,_>
53-
|> ReadOnlyDictionary<_,_>
54-
:> IReadOnlyDictionary<string, obj>
55-
|> ValueSome
21+
type internal CoercionError = {
22+
mutable InputSource : InputSource
23+
Message : string
24+
ErrorKind : ErrorKind
25+
Path : FieldPath
26+
FieldErrorDetails : ObjectFieldErrorDetails voption
27+
} with
5628

57-
[<Struct>]
58-
type internal ObjectFieldErrorDetails = {
59-
ObjectDef : InputObjectDef
60-
FieldDef : InputFieldDef voption
61-
}
62-
63-
type internal CoerceVariableError =
64-
{
65-
Message : string
66-
ErrorKind : ErrorKind
67-
Variable : VarDef
68-
Path : FieldPath
69-
FieldErrorDetails : ObjectFieldErrorDetails voption
70-
}
7129
interface IGQLError with
7230

7331
member this.Message = this.Message
@@ -77,35 +35,46 @@ type internal CoerceVariableError =
7735
member this.Extensions =
7836

7937
[
80-
yield KeyValuePair("kind", this.ErrorKind |> box)
81-
yield KeyValuePair("variableName", this.Variable.Name |> box)
82-
yield KeyValuePair("variableType", (this.Variable.TypeDef :?> NamedDef).Name |> box)
38+
yield KeyValuePair (CustomErrorFields.Kind, this.ErrorKind |> box)
8339
match this.Path with
8440
| [] -> ()
85-
| path -> yield KeyValuePair("path", path |> Seq.rev |> box)
41+
| path -> yield KeyValuePair (CustomErrorFields.Path, path |> List.rev |> box)
42+
43+
match this.InputSource with
44+
| Variable varDef ->
45+
yield KeyValuePair (CustomErrorFields.VariableName, varDef.Name |> box)
46+
yield KeyValuePair (CustomErrorFields.VariableType, (string varDef.TypeDef) |> box)
47+
| Argument argDef ->
48+
yield KeyValuePair (CustomErrorFields.ArgumentName, argDef.Name |> box)
49+
yield KeyValuePair (CustomErrorFields.ArgumentType, (string argDef.TypeDef) |> box)
50+
| Unknown -> ()
8651

8752
match this.FieldErrorDetails with
8853
| ValueSome details ->
89-
yield KeyValuePair("objectType", details.ObjectDef.Name |> box)
54+
yield KeyValuePair (CustomErrorFields.ObjectType, (string details.ObjectDef) |> box)
9055
match details.FieldDef with
91-
| ValueSome fieldDef ->
92-
yield KeyValuePair("fieldType", (fieldDef.TypeDef :?> NamedDef).Name|> box)
56+
| ValueSome fieldDef -> yield KeyValuePair (CustomErrorFields.FieldType, (string fieldDef.TypeDef) |> box)
9357
| ValueNone -> ()
9458
| ValueNone -> ()
9559
]
96-
|> Dictionary<_,_>
97-
|> ReadOnlyDictionary<_,_>
60+
|> Dictionary<_, _>
9861
:> IReadOnlyDictionary<string, obj>
9962
|> ValueSome
10063

101-
type internal CoerceVariableErrorWrapper =
102-
{
103-
InnerError : IGQLError
104-
ErrorKind : ErrorKind
105-
Variable: VarDef
106-
Path : FieldPath
107-
FieldErrorDetails : ObjectFieldErrorDetails voption
108-
}
64+
interface IInputSourceError with
65+
66+
member this.InputSource
67+
with get () = this.InputSource
68+
and set (value) = this.InputSource <- value
69+
70+
type internal CoercionErrorWrapper = {
71+
mutable InputSource : InputSource
72+
InnerError : IGQLError
73+
ErrorKind : ErrorKind
74+
Path : FieldPath
75+
FieldErrorDetails : ObjectFieldErrorDetails voption
76+
} with
77+
10978
interface IGQLError with
11079

11180
member this.Message = this.InnerError.Message
@@ -115,19 +84,25 @@ type internal CoerceVariableErrorWrapper =
11584
member this.Extensions =
11685

11786
[
118-
yield KeyValuePair("kind", this.ErrorKind |> box)
119-
yield KeyValuePair("variableName", this.Variable.Name |> box)
120-
yield KeyValuePair("variableType", (this.Variable.TypeDef :?> NamedDef).Name |> box)
87+
yield KeyValuePair (CustomErrorFields.Kind, this.ErrorKind |> box)
12188
match this.Path with
12289
| [] -> ()
123-
| path -> yield KeyValuePair("path", path |> Seq.rev |> box)
90+
| path -> yield KeyValuePair (CustomErrorFields.Path, path |> List.rev |> box)
91+
92+
match this.InputSource with
93+
| Variable varDef ->
94+
yield KeyValuePair (CustomErrorFields.VariableName, varDef.Name |> box)
95+
yield KeyValuePair (CustomErrorFields.VariableType, (string varDef.TypeDef) |> box)
96+
| Argument argDef ->
97+
yield KeyValuePair (CustomErrorFields.ArgumentName, argDef.Name |> box)
98+
yield KeyValuePair (CustomErrorFields.ArgumentType, (string argDef.TypeDef) |> box)
99+
| Unknown -> ()
124100

125101
match this.FieldErrorDetails with
126102
| ValueSome details ->
127-
yield KeyValuePair("objectType", details.ObjectDef.Name |> box)
103+
yield KeyValuePair (CustomErrorFields.ObjectType, (string details.ObjectDef) |> box)
128104
match details.FieldDef with
129-
| ValueSome fieldDef ->
130-
yield KeyValuePair("fieldType", (fieldDef.TypeDef :?> NamedDef).Name|> box)
105+
| ValueSome fieldDef -> yield KeyValuePair (CustomErrorFields.FieldType, (string fieldDef.TypeDef) |> box)
131106
| ValueNone -> ()
132107
| ValueNone -> ()
133108

@@ -138,43 +113,12 @@ type internal CoerceVariableErrorWrapper =
138113
| ValueNone -> ()
139114
| _ -> ()
140115
]
141-
|> Dictionary<_,_>
142-
|> ReadOnlyDictionary<_,_>
116+
|> Dictionary<_, _>
143117
:> IReadOnlyDictionary<string, obj>
144118
|> ValueSome
145119

146-
type internal CoerceArgumentError =
147-
{
148-
InnerError : IGQLError
149-
ArgumentName : string
150-
TypeName : string
151-
Path : FieldPath
152-
}
153-
interface IGQLError with
154-
155-
member this.Message = this.InnerError.Message
156-
157-
interface IGQLErrorExtensions with
158-
159-
member this.Extensions =
160-
161-
[
162-
yield KeyValuePair("argumentName", this.ArgumentName |> box)
163-
yield KeyValuePair("typeName", this.TypeName |> box)
164-
165-
match this.Path with
166-
| [] -> ()
167-
| path -> yield KeyValuePair("path", path |> Seq.rev |> box)
168-
169-
match this.InnerError with
170-
| :? IGQLErrorExtensions as ext ->
171-
match ext.Extensions with
172-
| ValueSome extensions -> yield! extensions
173-
| ValueNone -> ()
174-
| _ -> ()
175-
]
176-
|> Dictionary<_,_>
177-
|> ReadOnlyDictionary<_,_>
178-
:> IReadOnlyDictionary<string, obj>
179-
|> ValueSome
120+
interface IInputSourceError with
180121

122+
member this.InputSource
123+
with get () = this.InputSource
124+
and set (value) = this.InputSource <- value

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

Lines changed: 28 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,13 @@ open System.Text.Json
99
open FSharp.Control.Reactive
1010
open FsToolkit.ErrorHandling
1111

12-
open FSharp.Data.GraphQL
1312
open FSharp.Data.GraphQL.Ast
1413
open FSharp.Data.GraphQL.Errors
1514
open FSharp.Data.GraphQL.Extensions
1615
open FSharp.Data.GraphQL.Helpers
1716
open FSharp.Data.GraphQL.Types
1817
open FSharp.Data.GraphQL.Types.Patterns
18+
open FSharp.Data.GraphQL
1919

2020
type Output = IDictionary<string, obj>
2121

@@ -269,8 +269,9 @@ type StreamOutput =
269269
let private raiseErrors errs = AsyncVal.wrap <| Error errs
270270

271271
/// Given an error e, call ParseError in the given context's Schema to convert it into
272-
/// a list of one or more <see herf="IGQLErrors">IGQLErrors</see>, then convert those to a list of <see href="GQLProblemDetails">GQLProblemDetails</see>.
273-
let private resolverError path ctx e = ctx.Schema.ParseError path e |> List.map (GQLProblemDetails.OfFieldError (path |> List.rev))
272+
/// a list of one or more <see herf="IGQLErrors">IGQLErrors</see>, then convert those
273+
/// to a list of <see href="GQLProblemDetails">GQLProblemDetails</see>.
274+
let private resolverError path ctx e = ctx.Schema.ParseError path e |> List.map (GQLProblemDetails.OfFieldExecutionError (path |> List.rev))
274275
// Helper functions for generating more specific <see href="GQLProblemDetails">GQLProblemDetails</see>.
275276
let private nullResolverError name path ctx = resolverError path ctx (GraphQLException <| sprintf "Non-Null field %s resolved as a null!" name)
276277
let private coercionError value tyName path ctx = resolverError path ctx (GraphQLException <| sprintf "Value '%O' could not be coerced to scalar %s" value tyName)
@@ -643,7 +644,8 @@ let private compileInputObject (indef: InputObjectDef) =
643644
indef.Fields
644645
|> Array.iter(fun inputField ->
645646
// TODO: Implement compilation cache to reuse for the same type
646-
inputField.ExecuteInput <- compileByType [inputField.Name |> box] inputField.TypeDef
647+
let inputFieldTypeDef = inputField.TypeDef
648+
inputField.ExecuteInput <- compileByType [ box inputField.Name ] Unknown (inputFieldTypeDef, inputField.TypeDef)
647649
match inputField.TypeDef with
648650
| InputObject inputObjDef -> inputObjDef.ExecuteInput <- inputField.ExecuteInput
649651
| _ -> ()
@@ -661,7 +663,8 @@ let private compileObject (objdef: ObjectDef) (executeFields: FieldDef -> unit)
661663
|> Array.iter (fun arg ->
662664
//let errMsg = $"Object '%s{objdef.Name}': field '%s{fieldDef.Name}': argument '%s{arg.Name}': "
663665
// TODO: Pass arg name
664-
arg.ExecuteInput <- compileByType [fieldDef.Name |> box] arg.TypeDef
666+
let argTypeDef = arg.TypeDef
667+
arg.ExecuteInput <- compileByType [] (Argument arg) (argTypeDef, argTypeDef)
665668
match arg.TypeDef with
666669
| InputObject inputObjDef -> inputObjDef.ExecuteInput <- arg.ExecuteInput
667670
| _ -> ()
@@ -702,7 +705,7 @@ let internal coerceVariables (variables: VarDef list) (vars: ImmutableDictionary
702705
| Named typeDef -> Error [ {
703706
Message = $"Variable '$%s{varDef.Name}' of type '%s{typeDef.Name}!' is not nullable but neither value was provided, nor a default value was specified."
704707
ErrorKind = InputCoercion
705-
Variable = varDef
708+
InputSource = Variable varDef
706709
Path = []
707710
FieldErrorDetails = ValueNone
708711
} :> IGQLError ]
@@ -718,10 +721,22 @@ let internal coerceVariables (variables: VarDef list) (vars: ImmutableDictionary
718721
let! variablesBuilder =
719722
variables
720723
|> List.fold (
721-
fun (acc : Result<ImmutableDictionary<string, obj>.Builder, IGQLError list>) struct(vardef, jsonElement) -> validation {
722-
let! value = coerceVariableValue false [] ValueNone vardef.TypeDef vardef jsonElement
724+
fun (acc : Result<ImmutableDictionary<string, obj>.Builder, IGQLError list>) struct(varDef, jsonElement) -> validation {
725+
let! value =
726+
let varTypeDef = varDef.TypeDef
727+
coerceVariableValue false [] ValueNone (varTypeDef, varTypeDef) varDef jsonElement
728+
|> Result.mapError (
729+
List.map (fun err ->
730+
match err with
731+
| :? IInputSourceError as err ->
732+
match err.InputSource with
733+
| Variable _ -> ()
734+
| _ -> err.InputSource <- Variable varDef
735+
| _ -> ()
736+
err)
737+
)
723738
and! acc = acc
724-
acc.Add(vardef.Name, value)
739+
acc.Add(varDef.Name, value)
725740
return acc
726741
})
727742
(ImmutableDictionary.CreateBuilder<string, obj>() |> Ok)
@@ -733,11 +748,12 @@ let internal coerceVariables (variables: VarDef list) (vars: ImmutableDictionary
733748
let! variablesBuilder =
734749
inlineValues
735750
|> List.fold (
736-
fun (acc : Result<ImmutableDictionary<string, obj>.Builder, IGQLError list>) struct(vardef, defaultValue) -> validation {
737-
let executeInput = compileByType [] vardef.TypeDef
751+
fun (acc : Result<ImmutableDictionary<string, obj>.Builder, IGQLError list>) struct(varDef, defaultValue) -> validation {
752+
let varTypeDef = varDef.TypeDef
753+
let executeInput = compileByType [] (Variable varDef) (varTypeDef, varTypeDef)
738754
let! value = executeInput defaultValue suppliedVaribles
739755
and! acc = acc
740-
acc.Add(vardef.Name, value)
756+
acc.Add (varDef.Name, value)
741757
return acc
742758
})
743759
(variablesBuilder |> Ok)

0 commit comments

Comments
 (0)