diff --git a/src/FSharp.Data.GraphQL.Server.AspNetCore/FSharp.Data.GraphQL.Server.AspNetCore.fsproj b/src/FSharp.Data.GraphQL.Server.AspNetCore/FSharp.Data.GraphQL.Server.AspNetCore.fsproj index 543a96f1c..841184b7c 100644 --- a/src/FSharp.Data.GraphQL.Server.AspNetCore/FSharp.Data.GraphQL.Server.AspNetCore.fsproj +++ b/src/FSharp.Data.GraphQL.Server.AspNetCore/FSharp.Data.GraphQL.Server.AspNetCore.fsproj @@ -13,6 +13,7 @@ + diff --git a/src/FSharp.Data.GraphQL.Server.AspNetCore/GraphQLRequestHandler.fs b/src/FSharp.Data.GraphQL.Server.AspNetCore/GraphQLRequestHandler.fs index ebbe4c138..6e446d498 100644 --- a/src/FSharp.Data.GraphQL.Server.AspNetCore/GraphQLRequestHandler.fs +++ b/src/FSharp.Data.GraphQL.Server.AspNetCore/GraphQLRequestHandler.fs @@ -16,28 +16,34 @@ open FSharp.Data.GraphQL.Server open FSharp.Data.GraphQL.Shared type DefaultGraphQLRequestHandler<'Root> + /// + /// Handles GraphQL requests using a provided root schema. + /// + /// The accessor to the current HTTP context. + /// The options monitor for GraphQL options. + /// The logger to log messages. ( - /// The accessor to the current HTTP context httpContextAccessor : IHttpContextAccessor, - /// The options monitor for GraphQL options options : IOptionsMonitor>, - /// The logger to log messages logger : ILogger> ) = inherit GraphQLRequestHandler<'Root> (httpContextAccessor, options, logger) -/// Provides logic to parse and execute GraphQL request and [] GraphQLRequestHandler<'Root> + /// + /// Provides logic to parse and execute GraphQL requests. + /// + /// The accessor to the current HTTP context. + /// The options monitor for GraphQL options. + /// The logger to log messages. ( - /// The accessor to the current HTTP context httpContextAccessor : IHttpContextAccessor, - /// The options monitor for GraphQL options options : IOptionsMonitor>, - /// The logger to log messages logger : ILogger ) = let ctx = httpContextAccessor.HttpContext + let getInputContext() = (HttpContextRequestExecutionContext ctx) :> IInputExecutionContext let toResponse { DocumentId = documentId; Content = content; Metadata = metadata } = @@ -142,8 +148,8 @@ and [] GraphQLRequestHandler<'Root> let executeIntrospectionQuery (executor : Executor<_>) (ast : Ast.Document voption) : Task = task { let! result = match ast with - | ValueNone -> executor.AsyncExecute IntrospectionQuery.Definition - | ValueSome ast -> executor.AsyncExecute ast + | ValueNone -> executor.AsyncExecute (IntrospectionQuery.Definition, getInputContext) + | ValueSome ast -> executor.AsyncExecute (ast, getInputContext) let response = result |> toResponse return (TypedResults.Ok response) :> IResult @@ -228,7 +234,7 @@ and [] GraphQLRequestHandler<'Root> let! result = Async.StartImmediateAsTask ( - executor.AsyncExecute (content.Ast, root, ?variables = variables, ?operationName = operationName), + executor.AsyncExecute (content.Ast, getInputContext, root, ?variables = variables, ?operationName = operationName), cancellationToken = ctx.RequestAborted ) diff --git a/src/FSharp.Data.GraphQL.Server.AspNetCore/GraphQLWebsocketMiddleware.fs b/src/FSharp.Data.GraphQL.Server.AspNetCore/GraphQLWebsocketMiddleware.fs index 12c521783..6d621e93f 100644 --- a/src/FSharp.Data.GraphQL.Server.AspNetCore/GraphQLWebsocketMiddleware.fs +++ b/src/FSharp.Data.GraphQL.Server.AspNetCore/GraphQLWebsocketMiddleware.fs @@ -45,7 +45,7 @@ type GraphQLWebSocketMiddleware<'Root> | ServerPong p -> { Id = ValueNone; Type = "pong"; Payload = p |> ValueOption.map CustomResponse } | Next (id, payload) -> { Id = ValueSome id; Type = "next"; Payload = ValueSome <| ExecutionResult payload } | Complete id -> { Id = ValueSome id; Type = "complete"; Payload = ValueNone } - | Error (id, errMsgs) -> { Id = ValueSome id; Type = "error"; Payload = ValueSome <| ErrorMessages errMsgs } + | Error (id, errMessages) -> { Id = ValueSome id; Type = "error"; Payload = ValueSome <| ErrorMessages errMessages } return JsonSerializer.Serialize (raw, jsonSerializerOptions) } @@ -89,9 +89,9 @@ type GraphQLWebSocketMiddleware<'Root> && ((segmentResponse = null) || (not segmentResponse.EndOfMessage)) do try - let! r = socket.ReceiveAsync (new ArraySegment (buffer), cancellationToken) + let! r = socket.ReceiveAsync (ArraySegment(buffer), cancellationToken) segmentResponse <- r - completeMessage.AddRange (new ArraySegment (buffer, 0, r.Count)) + completeMessage.AddRange (ArraySegment(buffer, 0, r.Count)) with :? OperationCanceledException -> () @@ -117,7 +117,7 @@ type GraphQLWebSocketMiddleware<'Root> else // TODO: Allocate string only if a debugger is attached let! serializedMessage = message |> serializeServerMessage jsonSerializerOptions - let segment = new ArraySegment (System.Text.Encoding.UTF8.GetBytes (serializedMessage)) + let segment = ArraySegment(System.Text.Encoding.UTF8.GetBytes (serializedMessage)) if not (socket.State = WebSocketState.Open) then logger.LogTrace ($"Ignoring message to be sent via socket, since its state is not '{nameof WebSocketState.Open}', but '{{state}}'", socket.State) else @@ -160,7 +160,7 @@ type GraphQLWebSocketMiddleware<'Root> tryToGracefullyCloseSocket (WebSocketCloseStatus.NormalClosure, "Normal Closure") let handleMessages (cancellationToken : CancellationToken) (httpContext : HttpContext) (socket : WebSocket) : Task = - let subscriptions = new Dictionary () + let subscriptions = Dictionary() // ----------> // Helpers --> // ----------> @@ -168,6 +168,7 @@ type GraphQLWebSocketMiddleware<'Root> let sendMsg = sendMessageViaSocket serializerOptions socket let rcv () = socket |> rcvMsgViaSocket serializerOptions + let getInputContext() = (HttpContextRequestExecutionContext httpContext) :> IInputExecutionContext let sendOutput id (output : SubscriptionExecutionResult) = sendMsg (Next (id, output)) @@ -234,10 +235,10 @@ type GraphQLWebSocketMiddleware<'Root> && socket |> isSocketOpen do let! receivedMessage = rcv () match receivedMessage with - | Result.Error failureMsgs -> + | Result.Error failureMessages -> nameof InvalidMessage |> logMsgReceivedWithOptionalPayload ValueNone - match failureMsgs with + match failureMessages with | InvalidMessage (code, explanation) -> do! socket.CloseAsync (enum code, explanation, CancellationToken.None) | Ok ValueNone -> logger.LogTrace ("WebSocket received empty message! State = '{socketState}'", socket.State) | Ok (ValueSome msg) -> @@ -274,11 +275,11 @@ type GraphQLWebSocketMiddleware<'Root> let variables = query.Variables |> Skippable.toOption let! planExecutionResult = let root = options.RootFactory httpContext - options.SchemaExecutor.AsyncExecute (query.Query, root, ?variables = variables) + options.SchemaExecutor.AsyncExecute (query.Query, getInputContext, root, ?variables = variables) do! planExecutionResult |> applyPlanExecutionResult id socket with ex -> logger.LogError (ex, "Unexpected error during subscription with id '{id}'", id) - do! sendMsg (Error (id, [new Shared.NameValueLookup ([ ("subscription", "Unexpected error during subscription" :> obj) ])])) + do! sendMsg (Error (id, [NameValueLookup([ ("subscription", "Unexpected error during subscription" :> obj) ])])) | ClientComplete id -> "ClientComplete" |> logMsgWithIdReceived id subscriptions @@ -287,7 +288,7 @@ type GraphQLWebSocketMiddleware<'Root> do! socket |> tryToGracefullyCloseSocketWithDefaultBehavior with ex -> logger.LogError (ex, "Cannot handle a message; dropping a websocket connection") - // at this point, only something really weird must have happened. + // At this point, only something really weird must have happened. // In order to avoid faulty state scenarios and unimagined damages, // just close the socket without further ado. do! socket |> tryToGracefullyCloseSocketWithDefaultBehavior @@ -344,7 +345,7 @@ type GraphQLWebSocketMiddleware<'Root> return Result.Error <| "{nameof ConnectionInit} timeout" } - member __.InvokeAsync (ctx : HttpContext) = task { + member _.InvokeAsync (ctx : HttpContext) = task { if not (ctx.Request.Path = endpointUrl) then do! next.Invoke (ctx) else if ctx.WebSockets.IsWebSocketRequest then diff --git a/src/FSharp.Data.GraphQL.Server.AspNetCore/Helpers.fs b/src/FSharp.Data.GraphQL.Server.AspNetCore/Helpers.fs index d281b3f22..285a98973 100644 --- a/src/FSharp.Data.GraphQL.Server.AspNetCore/Helpers.fs +++ b/src/FSharp.Data.GraphQL.Server.AspNetCore/Helpers.fs @@ -3,7 +3,6 @@ namespace FSharp.Data.GraphQL.Server.AspNetCore open System open System.Text - [] module Helpers = @@ -48,4 +47,3 @@ module ReflectionHelpers = | PropertyGet (_, propertyInfo, _) -> propertyInfo.DeclaringType | FieldGet (_, fieldInfo) -> fieldInfo.DeclaringType | _ -> failwith "Expression is no property." - diff --git a/src/FSharp.Data.GraphQL.Server.AspNetCore/HttpContext.fs b/src/FSharp.Data.GraphQL.Server.AspNetCore/HttpContext.fs index 92806132c..3f946cab0 100644 --- a/src/FSharp.Data.GraphQL.Server.AspNetCore/HttpContext.fs +++ b/src/FSharp.Data.GraphQL.Server.AspNetCore/HttpContext.fs @@ -21,7 +21,7 @@ type HttpContext with /// /// Type to deserialize to /// - /// Retruns a Deserialized object or + /// Returns a Deserialized object or /// ProblemDetails as IResult /// if a body could not be deserialized. /// @@ -31,10 +31,23 @@ type HttpContext with let request = ctx.Request try - if not request.Body.CanSeek then - request.EnableBuffering() + let! jsonStream = + task { + if request.HasFormContentType then + let! form = request.ReadFormAsync(ctx.RequestAborted) + match form.TryGetValue("operations") with + | true, values when values.Count > 0 -> + let bytes = System.Text.Encoding.UTF8.GetBytes(values[0]) + return new MemoryStream(bytes) :> Stream + | _ -> + return request.Body + else + if not request.Body.CanSeek then + request.EnableBuffering() + return request.Body + } - return! JsonSerializer.DeserializeAsync<'T>(request.Body, serializerOptions, ctx.RequestAborted) + return! JsonSerializer.DeserializeAsync<'T>(jsonStream, serializerOptions, ctx.RequestAborted) with :? JsonException -> let body = request.Body body.Seek(0, SeekOrigin.Begin) |> ignore diff --git a/src/FSharp.Data.GraphQL.Server.AspNetCore/RequestExecutionContext.fs b/src/FSharp.Data.GraphQL.Server.AspNetCore/RequestExecutionContext.fs new file mode 100644 index 000000000..fa291a56f --- /dev/null +++ b/src/FSharp.Data.GraphQL.Server.AspNetCore/RequestExecutionContext.fs @@ -0,0 +1,18 @@ +namespace FSharp.Data.GraphQL.Server.AspNetCore + +open FSharp.Data.GraphQL +open Microsoft.AspNetCore.Http + +type HttpContextRequestExecutionContext (httpContext : HttpContext) = + + interface IInputExecutionContext with + + member this.GetFile(key) = + if not httpContext.Request.HasFormContentType then + Error "Request does not have form content type" + else + let form = httpContext.Request.Form + match (form.Files |> Seq.vtryFind (fun f -> f.Name = key)) with + | ValueSome file -> Ok (file.OpenReadStream()) + | ValueNone -> Error $"File with key '{key}' not found" + diff --git a/src/FSharp.Data.GraphQL.Server.Middleware/MiddlewareDefinitions.fs b/src/FSharp.Data.GraphQL.Server.Middleware/MiddlewareDefinitions.fs index 9938878d9..dfb82c8c0 100644 --- a/src/FSharp.Data.GraphQL.Server.Middleware/MiddlewareDefinitions.fs +++ b/src/FSharp.Data.GraphQL.Server.Middleware/MiddlewareDefinitions.fs @@ -2,6 +2,7 @@ namespace FSharp.Data.GraphQL.Server.Middleware open System.Collections.Generic open System.Collections.Immutable +open FSharp.Data.GraphQL.Shared open FsToolkit.ErrorHandling open FSharp.Data.GraphQL @@ -11,7 +12,7 @@ open FSharp.Data.GraphQL.Types type internal QueryWeightMiddleware(threshold : float, reportToMetadata : bool) = - let middleware (threshold : float) (ctx : ExecutionContext) (next : ExecutionContext -> AsyncVal) = + let middleware (threshold : float) (inputContext : InputExecutionContextProvider) (ctx : ExecutionContext) (next : ExecutionContext -> AsyncVal) = let measureThreshold (threshold : float) (fields : ExecutionInfo list) = let getWeight f = if f.ParentDef = upcast ctx.ExecutionPlan.RootDef @@ -72,7 +73,7 @@ type internal ObjectListFilterMiddleware<'ObjectType, 'ListType>(reportToMetadat let compileMiddleware (ctx : SchemaCompileContext) (next : SchemaCompileContext -> unit) = let modifyFields (object : ObjectDef<'ObjectType>) (fields : FieldDef<'ObjectType> seq) = let args = [ Define.Input("filter", Nullable ObjectListFilterType) ] - let fields = fields |> Seq.map (fun x -> x.WithArgs(args)) |> List.ofSeq + let fields = fields |> Seq.map _.WithArgs(args) |> List.ofSeq object.WithFields(fields) let typesWithListFields = ctx.TypeMap.GetTypesWithListFields<'ObjectType, 'ListType>() @@ -85,7 +86,7 @@ type internal ObjectListFilterMiddleware<'ObjectType, 'ListType>(reportToMetadat ctx.TypeMap.AddTypes(modifiedTypes, overwrite = true) next ctx - let reportMiddleware (ctx : ExecutionContext) (next : ExecutionContext -> AsyncVal) = + let reportMiddleware (inputContext : InputExecutionContextProvider) (ctx : ExecutionContext) (next : ExecutionContext -> AsyncVal) = let rec collectArgs (path: obj list) (acc : KeyValuePair list) (fields : ExecutionInfo list) = let fieldArgs currentPath field = let filterResults = @@ -93,7 +94,7 @@ type internal ObjectListFilterMiddleware<'ObjectType, 'ListType>(reportToMetadat |> Seq.map (fun x -> match x.Name, x.Value with | "filter", (VariableName variableName) -> Ok (ValueSome (ctx.Variables[variableName] :?> ObjectListFilter)) - | "filter", inlineConstant -> ObjectListFilterType.CoerceInput (InlineConstant inlineConstant) ctx.Variables |> Result.map ValueOption.ofObj + | "filter", inlineConstant -> ObjectListFilterType.CoerceInput inputContext (InlineConstant inlineConstant) ctx.Variables |> Result.map ValueOption.ofObj | _ -> Ok ValueNone) |> Seq.toList match filterResults |> splitSeqErrorsList with diff --git a/src/FSharp.Data.GraphQL.Server.Middleware/SchemaDefinitions.fs b/src/FSharp.Data.GraphQL.Server.Middleware/SchemaDefinitions.fs index 7f4e5d067..e8239cad9 100644 --- a/src/FSharp.Data.GraphQL.Server.Middleware/SchemaDefinitions.fs +++ b/src/FSharp.Data.GraphQL.Server.Middleware/SchemaDefinitions.fs @@ -167,7 +167,7 @@ let ObjectListFilterType : InputCustomDefinition = { Some "The `Filter` scalar type represents a filter on one or more fields of an object in an object list. The filter is represented by a JSON object where the fields are the complemented by specific suffixes to represent a query." CoerceInput = - (fun input variables -> + (fun _ input variables -> match input with | InlineConstant c -> (coerceObjectListFilterInput variables c) diff --git a/src/FSharp.Data.GraphQL.Server/Execution.fs b/src/FSharp.Data.GraphQL.Server/Execution.fs index 7a3122b2d..737ed4b50 100644 --- a/src/FSharp.Data.GraphQL.Server/Execution.fs +++ b/src/FSharp.Data.GraphQL.Server/Execution.fs @@ -25,26 +25,26 @@ let (|RequestError|Direct|Deferred|Stream|) (response : GQLExecutionResult) = | Deferred (data, errors, deferred) -> Deferred (data, errors, deferred) | Stream data -> Stream data -let private collectDefaultArgValue acc (argdef: InputFieldDef) = - match argdef.DefaultValue with - | Some defVal -> Map.add argdef.Name defVal acc +let private collectDefaultArgValue acc (argDef: InputFieldDef) = + match argDef.DefaultValue with + | Some defVal -> Map.add argDef.Name defVal acc | None -> acc -let internal argumentValue variables (argdef: InputFieldDef) (argument: Argument) = - match argdef.ExecuteInput argument.Value variables with +let internal argumentValue inputContext variables (argDef: InputFieldDef) (argument: Argument) = + match argDef.ExecuteInput inputContext argument.Value variables with | Ok null -> - match argdef.DefaultValue with + match argDef.DefaultValue with | Some value -> Ok value | None -> Ok null | result -> result -let private getArgumentValues (argDefs: InputFieldDef []) (args: Argument list) (variables: ImmutableDictionary) : Result, IGQLError list> = +let private getArgumentValues (argDefs: InputFieldDef []) (args: Argument list) (inputContext : InputExecutionContextProvider) (variables: ImmutableDictionary) : Result, IGQLError list> = argDefs |> Array.fold (fun acc argdef -> match List.tryFind (fun (a: Argument) -> a.Name = argdef.Name) args with | Some argument -> validation { let! acc = acc - and! arg = argumentValue variables argdef argument + and! arg = argumentValue inputContext variables argdef argument match arg with | null -> return acc | v -> return Map.add argdef.Name v acc @@ -90,9 +90,9 @@ let private resolveUnionType possibleTypesFn (uniondef: UnionDef) = | Some resolveType -> resolveType | None -> defaultResolveType possibleTypesFn uniondef -let private createFieldContext objdef argDefs ctx (info: ExecutionInfo) (path : FieldPath) = result { +let private createFieldContext objdef inputContext argDefs ctx (info: ExecutionInfo) (path : FieldPath) = result { let fdef = info.Definition - let! args = getArgumentValues argDefs info.Ast.Arguments ctx.Variables + let! args = getArgumentValues argDefs info.Ast.Arguments inputContext ctx.Variables return { ExecutionInfo = info Context = ctx.Context @@ -181,7 +181,7 @@ let collectFields (strategy : ExecutionStrategy) (rs : AsyncVal ResolverResult.mapValue(fun _ -> data) } -let rec private direct (returnDef : OutputDef) (ctx : ResolveFieldContext) (path : FieldPath) (parent : obj) (value : obj) : AsyncVal>> = +let rec private direct (returnDef : OutputDef) (inputContext : InputExecutionContextProvider) (ctx : ResolveFieldContext) (path : FieldPath) (parent : obj) (value : obj) : AsyncVal>> = let name = ctx.ExecutionInfo.Identifier match returnDef with @@ -189,8 +189,8 @@ let rec private direct (returnDef : OutputDef) (ctx : ResolveFieldContext) (path let fields = match ctx.ExecutionInfo.Kind with | SelectFields fields -> fields - | kind -> failwithf "Unexpected value of ctx.ExecutionPlan.Kind: %A" kind - executeObjectFields fields name objDef ctx path value + | kind -> failwithf $"Unexpected value of ctx.ExecutionPlan.Kind: %A{kind}" + executeObjectFields fields name objDef inputContext ctx path value | Scalar scalarDef -> match scalarDef.CoerceOutput (downcast value) with @@ -209,7 +209,7 @@ let rec private direct (returnDef : OutputDef) (ctx : ResolveFieldContext) (path | ResolveCollection innerPlan -> { ctx with ExecutionInfo = { innerPlan with ReturnDef = innerDef } } | kind -> failwithf "Unexpected value of ctx.ExecutionPlan.Kind: %A" kind let resolveItem index item = - executeResolvers innerCtx (box index :: path) value (toOption item |> AsyncVal.wrap) + executeResolvers inputContext innerCtx (box index :: path) value (toOption item |> AsyncVal.wrap) match value with | :? System.Collections.IEnumerable as enumerable -> enumerable @@ -222,7 +222,7 @@ let rec private direct (returnDef : OutputDef) (ctx : ResolveFieldContext) (path | Nullable (Output innerDef) -> let innerCtx = { ctx with ExecutionInfo = { ctx.ExecutionInfo with IsNullable = true; ReturnDef = innerDef } } - executeResolvers innerCtx path parent (toOption value |> AsyncVal.wrap) + executeResolvers inputContext innerCtx path parent (toOption value |> AsyncVal.wrap) |> AsyncVal.map(Result.valueOr (fun errs -> (KeyValuePair(name, null), None, errs)) >> Ok) | Interface iDef -> @@ -232,9 +232,9 @@ let rec private direct (returnDef : OutputDef) (ctx : ResolveFieldContext) (path let typeMap = match ctx.ExecutionInfo.Kind with | ResolveAbstraction typeMap -> typeMap - | kind -> failwithf "Unexpected value of ctx.ExecutionPlan.Kind: %A" kind + | 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 + | Some fields -> executeObjectFields fields name resolvedDef inputContext ctx path value | None -> KeyValuePair(name, obj()) |> ResolverResult.data |> AsyncVal.wrap | Union uDef -> @@ -244,23 +244,22 @@ let rec private direct (returnDef : OutputDef) (ctx : ResolveFieldContext) (path let typeMap = match ctx.ExecutionInfo.Kind with | ResolveAbstraction typeMap -> typeMap - | kind -> failwithf "Unexpected value of ctx.ExecutionPlan.Kind: %A" kind + | 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) + | Some fields -> executeObjectFields fields name resolvedDef inputContext ctx path (uDef.ResolveValue value) | None -> KeyValuePair(name, obj()) |> ResolverResult.data |> AsyncVal.wrap | _ -> failwithf "Unexpected value of returnDef: %O" returnDef -and deferred (ctx : ResolveFieldContext) (path : FieldPath) (parent : obj) (value : obj) = +and deferred (inputContext : InputExecutionContextProvider) (ctx : ResolveFieldContext) (path : FieldPath) (parent : obj) (value : obj) = let info = ctx.ExecutionInfo - let name = info.Identifier let deferred = - executeResolvers ctx path parent (toOption value |> AsyncVal.wrap) + executeResolvers inputContext ctx path parent (toOption value |> AsyncVal.wrap) |> Observable.ofAsyncVal - |> Observable.bind(ResolverResult.mapValue(fun d -> d.Value) >> deferResults path) + |> Observable.bind(ResolverResult.mapValue(_.Value) >> deferResults path) ResolverResult.defered (KeyValuePair (info.Identifier, null)) deferred |> AsyncVal.wrap -and private streamed (options : BufferedStreamOptions) (innerDef : OutputDef) (ctx : ResolveFieldContext) (path : FieldPath) (parent : obj) (value : obj) = +and private streamed (options : BufferedStreamOptions) (innerDef : OutputDef) (inputContext : InputExecutionContextProvider) (ctx : ResolveFieldContext) (path : FieldPath) (parent : obj) (value : obj) = let info = ctx.ExecutionInfo let name = info.Identifier let innerCtx = @@ -296,7 +295,7 @@ and private streamed (options : BufferedStreamOptions) (innerDef : OutputDef) (c |> Observable.bind collectBuffered let resolveItem index item = asyncVal { - let! result = executeResolvers innerCtx (box index :: path) parent (toOption item |> AsyncVal.wrap) + let! result = executeResolvers inputContext innerCtx (box index :: path) parent (toOption item |> AsyncVal.wrap) return (index, result) } @@ -312,7 +311,7 @@ and private streamed (options : BufferedStreamOptions) (innerDef : OutputDef) (c 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) = +and private live (inputContext : InputExecutionContextProvider) (ctx : ResolveFieldContext) (path : FieldPath) (parent : obj) (value : obj) = let info = ctx.ExecutionInfo let name = info.Identifier @@ -325,13 +324,13 @@ and private live (ctx : ResolveFieldContext) (path : FieldPath) (parent : obj) ( | Union uDef -> let resolver = resolveUnionType ctx.Schema.GetPossibleTypes uDef getObjectName (resolver value) - | returnDef -> failwithf "Unexpected value of returnDef: %O" returnDef + | returnDef -> failwithf $"Unexpected value of returnDef: {returnDef}" let typeName = getObjectName info.ParentDef /// So the updatedValue here is actually the fresh parent. let resolveUpdate updatedValue = - executeResolvers ctx path parent (updatedValue |> Some |> AsyncVal.wrap) + executeResolvers inputContext ctx path parent (updatedValue |> Some |> AsyncVal.wrap) |> AsyncVal.map(ResolverResult.mapValue(fun d -> d.Value) >> deferResults path) |> Observable.ofAsyncVal |> Observable.mergeInner @@ -343,12 +342,12 @@ and private live (ctx : ResolveFieldContext) (path : FieldPath) (parent : obj) ( | Some filterFn -> provider.Add (filterFn parent) typeName name |> Observable.bind resolveUpdate | None -> failwithf "No live provider for %s:%s" typeName name - executeResolvers ctx path parent (value |> Some |> AsyncVal.wrap) + executeResolvers inputContext ctx path parent (value |> Some |> AsyncVal.wrap) // TODO: Add tests for `Observable.merge deferred updates` correct order |> AsyncVal.map(Result.map(fun (data, deferred, errs) -> (data, Some <| Option.foldBack Observable.merge deferred updates, errs))) /// Actually execute the resolvers. -and private executeResolvers (ctx : ResolveFieldContext) (path : FieldPath) (parent : obj) (value : AsyncVal) : AsyncVal>> = +and private executeResolvers (inputContext : InputExecutionContextProvider) (ctx : ResolveFieldContext) (path : FieldPath) (parent : obj) (value : AsyncVal) : AsyncVal>> = let info = ctx.ExecutionInfo let name = info.Identifier let returnDef = info.ReturnDef @@ -384,30 +383,30 @@ and private executeResolvers (ctx : ResolveFieldContext) (path : FieldPath) (par match info.Kind, returnDef with | ResolveDeferred innerInfo, _ when innerInfo.IsNullable -> // We can only defer nullable fields - deferred + deferred inputContext |> 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 + streamed mode innerDef inputContext |> resolveWith { ctx with ExecutionInfo = innerInfo } | ResolveStreamed (innerInfo, _), _ -> raiseErrors <| streamListError innerInfo.Identifier (returnDef.ToString()) path ctx | ResolveLive innerInfo, _ -> - live + live inputContext |> resolveWith { ctx with ExecutionInfo = innerInfo } | _ -> - direct returnDef + direct returnDef inputContext |> resolveWith ctx -and executeObjectFields (fields : ExecutionInfo list) (objName : string) (objDef : ObjectDef) (ctx : ResolveFieldContext) (path : FieldPath) (value : obj) : AsyncVal>> = asyncVal { +and executeObjectFields (fields : ExecutionInfo list) (objName : string) (objDef : ObjectDef) (inputContext: InputExecutionContextProvider) (ctx : ResolveFieldContext) (path : FieldPath) (value : obj) : AsyncVal>> = asyncVal { let executeField field = let argDefs = ctx.Context.FieldExecuteMap.GetArgs(objDef.Name, field.Definition.Name) let resolver = ctx.Context.FieldExecuteMap.GetExecute(objDef.Name, field.Definition.Name) let fieldPath = (box field.Identifier :: path) - match createFieldContext objDef argDefs ctx field fieldPath with - | Ok fieldCtx -> executeResolvers fieldCtx fieldPath value (resolveField resolver fieldCtx value) + match createFieldContext objDef inputContext argDefs ctx field fieldPath with + | Ok fieldCtx -> executeResolvers inputContext fieldCtx fieldPath value (resolveField resolver fieldCtx value) | Error errs -> asyncVal { return Error (errs |> List.map GQLProblemDetails.OfError) } let! res = @@ -446,11 +445,11 @@ let private (|String|Other|) (o : obj) = | :? string as s -> String s | _ -> Other -let private executeQueryOrMutation (resultSet: (string * ExecutionInfo) []) (ctx: ExecutionContext) (objdef: ObjectDef) (rootValue : obj) : AsyncVal = +let private executeQueryOrMutation (resultSet: (string * ExecutionInfo) []) (ctx: ExecutionContext) (objDef: ObjectDef) (rootValue : obj) : AsyncVal = let executeRootOperation (name, info) = let fDef = info.Definition let argDefs = ctx.FieldExecuteMap.GetArgs(ctx.ExecutionPlan.RootDef.Name, info.Definition.Name) - match getArgumentValues argDefs info.Ast.Arguments ctx.Variables with + match getArgumentValues argDefs info.Ast.Arguments ctx.GetInputContext ctx.Variables with | Error errs -> asyncVal { return Error (errs |> List.map GQLProblemDetails.OfError) } | Ok args -> let path = [ box info.Identifier ] @@ -458,7 +457,7 @@ let private executeQueryOrMutation (resultSet: (string * ExecutionInfo) []) (ctx { ExecutionInfo = info Context = ctx ReturnType = fDef.TypeDef - ParentType = objdef + ParentType = objDef Schema = ctx.Schema Args = args Variables = ctx.Variables @@ -466,7 +465,7 @@ let private executeQueryOrMutation (resultSet: (string * ExecutionInfo) []) (ctx let execute = ctx.FieldExecuteMap.GetExecute(ctx.ExecutionPlan.RootDef.Name, info.Definition.Name) asyncVal { let! result = - executeResolvers fieldCtx path rootValue (resolveField execute fieldCtx rootValue) + executeResolvers ctx.GetInputContext fieldCtx path rootValue (resolveField execute fieldCtx rootValue) |> AsyncVal.rescue path ctx.Schema.ParseError let result = match result with @@ -487,51 +486,51 @@ let private executeQueryOrMutation (resultSet: (string * ExecutionInfo) []) (ctx | Error errs -> return GQLExecutionResult.RequestError(documentId, errs, ctx.Metadata) } -let private executeSubscription (resultSet: (string * ExecutionInfo) []) (ctx: ExecutionContext) (objdef: SubscriptionObjectDef) value = result { +let private executeSubscription (resultSet: (string * ExecutionInfo) []) (inputContext : InputExecutionContextProvider) (ctx: ExecutionContext) (objDef: SubscriptionObjectDef) value = result { // Subscription queries can only have one root field let nameOrAlias, info = Array.head resultSet - let subdef = info.Definition :?> SubscriptionFieldDef - let! args = getArgumentValues subdef.Args info.Ast.Arguments ctx.Variables - let returnType = subdef.OutputTypeDef + let subDef = info.Definition :?> SubscriptionFieldDef + let! args = getArgumentValues subDef.Args info.Ast.Arguments inputContext ctx.Variables + let returnType = subDef.OutputTypeDef let fieldPath = [ box info.Identifier ] let fieldCtx = { ExecutionInfo = info Context = ctx ReturnType = returnType - ParentType = objdef + ParentType = objDef Schema = ctx.Schema Args = args Variables = ctx.Variables Path = fieldPath |> List.rev } let onValue v = asyncVal { - match! executeResolvers fieldCtx fieldPath value (toOption v |> AsyncVal.wrap) with + match! executeResolvers inputContext fieldCtx fieldPath value (toOption v |> AsyncVal.wrap) with | Ok (data, None, []) -> return SubscriptionResult (NameValueLookup.ofList [nameOrAlias, data.Value]) | Ok (data, None, errs) -> return SubscriptionErrors (NameValueLookup.ofList [nameOrAlias, data.Value], errs) | Ok (_, Some _, _) -> return failwith "Deferred/Streamed/Live are not supported for subscriptions!" | Error errs -> return SubscriptionErrors (null, errs) } return - ctx.Schema.SubscriptionProvider.Add fieldCtx value subdef + ctx.Schema.SubscriptionProvider.Add fieldCtx value subDef |> Observable.bind(onValue >> Observable.ofAsyncVal) } -let private compileInputObject (indef: InputObjectDef) = - indef.Fields +let private compileInputObject (inputDef: InputObjectDef) (inputContext : InputExecutionContextProvider) = + inputDef.Fields |> Array.iter(fun inputField -> // TODO: Implement compilation cache to reuse for the same type let inputFieldTypeDef = inputField.TypeDef - inputField.ExecuteInput <- compileByType [ box inputField.Name ] Unknown (inputFieldTypeDef, inputFieldTypeDef) + inputField.ExecuteInput <- compileByType [ box inputField.Name ] Unknown (inputFieldTypeDef, inputFieldTypeDef) inputContext match inputField.TypeDef with | InputObject inputObjDef -> inputObjDef.ExecuteInput <- inputField.ExecuteInput | _ -> () ) #if DEBUG - if isNull (box indef.ExecuteInput) then - System.Diagnostics.Debug.Fail($"Input object '{indef.Name}' has no ExecuteInput function!") + if isNull (box inputDef.ExecuteInput) then + System.Diagnostics.Debug.Fail($"Input object '{inputDef.Name}' has no ExecuteInput function!") #endif -let private compileObject (objdef: ObjectDef) (executeFields: FieldDef -> unit) = - objdef.Fields +let private compileObject (objDef: ObjectDef) (executeFields: FieldDef -> unit) (inputContext : InputExecutionContextProvider) = + objDef.Fields |> Map.iter (fun _ fieldDef -> executeFields fieldDef fieldDef.Args @@ -539,30 +538,30 @@ let private compileObject (objdef: ObjectDef) (executeFields: FieldDef -> unit) //let errMsg = $"Object '%s{objdef.Name}': field '%s{fieldDef.Name}': argument '%s{arg.Name}': " // TODO: Pass arg name let argTypeDef = arg.TypeDef - arg.ExecuteInput <- compileByType [] (Argument arg) (argTypeDef, argTypeDef) + arg.ExecuteInput <- compileByType [] (Argument arg) (argTypeDef, argTypeDef) inputContext match arg.TypeDef with | InputObject inputObjDef -> inputObjDef.ExecuteInput <- arg.ExecuteInput | _ -> () ) ) -let internal compileSchema (ctx : SchemaCompileContext) = +let internal compileSchema (ctx : SchemaCompileContext) = ctx.Schema.TypeMap.ToSeq() |> Seq.iter (fun (tName, x) -> match x with - | SubscriptionObject subdef -> - compileObject subdef (fun sub -> + | SubscriptionObject subDef -> + compileObject subDef (fun sub -> let filter = match sub with | :? SubscriptionFieldDef as subField -> compileSubscriptionField subField - | _ -> failwith $"Schema error: subscription object '%s{subdef.Name}' does have a field '%s{sub.Name}' that is not a subscription field definition." - ctx.Schema.SubscriptionProvider.Register { Name = sub.Name; Filter = filter }) - | Object objdef -> - compileObject objdef (fun fieldDef -> ctx.FieldExecuteMap.SetExecute(tName, fieldDef)) - | InputObject indef -> compileInputObject indef + | _ -> failwith $"Schema error: subscription object '%s{subDef.Name}' does have a field '%s{sub.Name}' that is not a subscription field definition." + ctx.Schema.SubscriptionProvider.Register { Name = sub.Name; Filter = filter }) ctx.GetInputContext + | Object objDef -> + compileObject objDef (fun fieldDef -> ctx.FieldExecuteMap.SetExecute(tName, fieldDef)) ctx.GetInputContext + | InputObject inputDef -> compileInputObject inputDef ctx.GetInputContext | _ -> ()) -let internal coerceVariables (variables: VarDef list) (vars: ImmutableDictionary) = result { +let internal coerceVariables (variables: VarDef list) (inputContext : InputExecutionContextProvider) (vars: ImmutableDictionary) = result { let variables, inlineValues, nulls = variables |> List.fold @@ -608,7 +607,7 @@ let internal coerceVariables (variables: VarDef list) (vars: ImmutableDictionary VarDef = varDef Input = jsonElement } - coerceVariableValue ctx + coerceVariableValue(ctx, inputContext) |> Result.mapError ( List.map (fun err -> match err with @@ -625,7 +624,7 @@ let internal coerceVariables (variables: VarDef list) (vars: ImmutableDictionary }) (ImmutableDictionary.CreateBuilder() |> Ok) - let suppliedVaribles = variablesBuilder.ToImmutable() + let suppliedVariables = variablesBuilder.ToImmutable() // TODO: consider how to execute inline objects validation having some variables coercion or validation failed // Having variables we can coerce inline values that contain on variables @@ -634,8 +633,8 @@ let internal coerceVariables (variables: VarDef list) (vars: ImmutableDictionary |> List.fold ( fun (acc : Result.Builder, IGQLError list>) struct(varDef, defaultValue) -> validation { let varTypeDef = varDef.TypeDef - let executeInput = compileByType [] (Variable varDef) (varTypeDef, varTypeDef) - let! value = executeInput defaultValue suppliedVaribles + let executeInput = compileByType [] (Variable varDef) (varTypeDef, varTypeDef) inputContext + let! value = executeInput inputContext defaultValue suppliedVariables and! acc = acc acc.Add (varDef.Name, value) return acc @@ -678,7 +677,7 @@ let internal executeOperation (ctx : ExecutionContext) : AsyncVal match ctx.Schema.Subscription with | Some s -> - match executeSubscription resultSet ctx s ctx.RootValue with + match executeSubscription resultSet ctx.GetInputContext ctx s ctx.RootValue with | Ok data -> AsyncVal.wrap(GQLExecutionResult.Stream(ctx.ExecutionPlan.DocumentId, data, ctx.Metadata)) | Error errs -> asyncVal { return GQLExecutionResult.Error(ctx.ExecutionPlan.DocumentId, errs, ctx.Metadata) } diff --git a/src/FSharp.Data.GraphQL.Server/Executor.fs b/src/FSharp.Data.GraphQL.Server/Executor.fs index b332868b0..0d2963275 100644 --- a/src/FSharp.Data.GraphQL.Server/Executor.fs +++ b/src/FSharp.Data.GraphQL.Server/Executor.fs @@ -2,7 +2,6 @@ namespace FSharp.Data.GraphQL open System.Collections.Concurrent open System.Collections.Immutable -open System.Collections.Generic open System.Runtime.InteropServices open System.Text.Json open FsToolkit.ErrorHandling @@ -14,7 +13,7 @@ open FSharp.Data.GraphQL.Validation open FSharp.Data.GraphQL.Parser open FSharp.Data.GraphQL.Planning -/// A function signature that represents a middleware for schema compile phase. +/// A function signature that represents middleware for the schema compile phase. /// It takes two arguments: A schema compile context, containing all the data used for the /// compilation phase, and another function that can be called to pass /// the execution for the next middleware. @@ -37,7 +36,7 @@ type OperationPlanningMiddleware = /// execution phase, and another function that can be called to pass /// the execution for the next middleware. type OperationExecutionMiddleware = - ExecutionContext -> (ExecutionContext -> AsyncVal) -> AsyncVal + InputExecutionContextProvider -> ExecutionContext -> (ExecutionContext -> AsyncVal) -> AsyncVal /// An interface to implement Executor middlewares. /// A middleware can have one to three sub-middlewares, one for each phase of the query execution. @@ -68,7 +67,7 @@ type Executor<'Root>(schema: ISchema<'Root>, middlewares : IExecutorMiddleware s let fieldExecuteMap = FieldExecuteMap(compileField) - // FIXME: for some reason static do or do invocation in module doesn't work + // FIXME: for some reason, static do or do invocation in module doesn't work // for this reason we're compiling executors as part of identifier evaluation let __done = // We don't need to know possible types at this point @@ -92,14 +91,15 @@ type Executor<'Root>(schema: ISchema<'Root>, middlewares : IExecutorMiddleware s go initialCtx middlewaresList do - let compileCtx = { Schema = schema; TypeMap = schema.TypeMap; FieldExecuteMap = fieldExecuteMap } - runMiddlewares (fun x -> x.CompileSchema) compileCtx compileSchema - runMiddlewares (fun x -> x.PostCompileSchema) (upcast schema) ignore + let getInputContext = Unchecked.defaultof + let compileCtx = { Schema = schema; TypeMap = schema.TypeMap; FieldExecuteMap = fieldExecuteMap; GetInputContext = getInputContext } + runMiddlewares _.CompileSchema compileCtx compileSchema + runMiddlewares _.PostCompileSchema (upcast schema) ignore match Validation.Types.validateTypeMap schema.TypeMap with | Success -> () | ValidationError errors -> raise (GQLMessageException (System.String.Join("\n", errors))) - let eval (executionPlan: ExecutionPlan, data: 'Root option, variables: ImmutableDictionary): Async = + let eval (executionPlan: ExecutionPlan, data: 'Root option, variables: ImmutableDictionary, getInputContext : InputExecutionContextProvider): Async = let documentId = executionPlan.DocumentId let prepareOutput res = match res with @@ -111,27 +111,30 @@ type Executor<'Root>(schema: ISchema<'Root>, middlewares : IExecutorMiddleware s try let errors = ConcurrentDictionary>() let root = data |> Option.map box |> Option.toObj - match coerceVariables executionPlan.Variables variables with + match coerceVariables executionPlan.Variables getInputContext variables with | Error errs -> return prepareOutput (GQLExecutionResult.Error (documentId, errs, executionPlan.Metadata)) | Ok variables -> let executionCtx = { Schema = schema RootValue = root ExecutionPlan = executionPlan + GetInputContext = getInputContext Variables = variables Errors = errors FieldExecuteMap = fieldExecuteMap Metadata = executionPlan.Metadata } - let! res = runMiddlewares (fun x -> x.ExecuteOperationAsync) executionCtx executeOperation |> AsyncVal.toAsync + let executorMiddlewareFunc = fun (executorMiddleware : IExecutorMiddleware) -> + executorMiddleware.ExecuteOperationAsync |> Option.map (fun middleware -> middleware(getInputContext)) + let! res = runMiddlewares executorMiddlewareFunc executionCtx executeOperation |> AsyncVal.toAsync return prepareOutput res with | :? GQLMessageException as ex -> return prepareOutput(GQLExecutionResult.Error (documentId, ex, executionPlan.Metadata)) | ex -> return prepareOutput (GQLExecutionResult.ErrorFromException(documentId, ex, executionPlan.Metadata)) } - let execute (executionPlan: ExecutionPlan, data: 'Root option, variables: ImmutableDictionary option) = + let execute (executionPlan: ExecutionPlan, data: 'Root option, variables: ImmutableDictionary option, getInputContext : InputExecutionContextProvider) = let variables = defaultArg variables ImmutableDictionary.Empty - eval (executionPlan, data, variables) + eval (executionPlan, data, variables, getInputContext) let createExecutionPlan (ast: Document, operationName: string option, meta : Metadata) = let documentId = ast.GetHashCode() @@ -187,10 +190,11 @@ type Executor<'Root>(schema: ISchema<'Root>, middlewares : IExecutorMiddleware s /// 'errors' (optional, contains a list of errors that occurred while executing a GraphQL operation). /// /// Execution plan for the operation. + /// Gets input context provider for the operation. /// Optional object provided as a root to all top level field resolvers /// Map of all variable values provided by the client request. - member _.AsyncExecute(executionPlan: ExecutionPlan, ?data: 'Root, ?variables: ImmutableDictionary): Async = - execute (executionPlan, data, variables) + member _.AsyncExecute(executionPlan: ExecutionPlan, getInputContext : InputExecutionContextProvider, ?data: 'Root, ?variables: ImmutableDictionary): Async = + execute (executionPlan, data, variables, getInputContext) /// /// Asynchronously executes parsed GraphQL query AST. Returned value is a readonly dictionary consisting of following top level entries: @@ -199,14 +203,15 @@ type Executor<'Root>(schema: ISchema<'Root>, middlewares : IExecutorMiddleware s /// 'errors' (optional, contains a list of errors that occurred while executing a GraphQL operation). /// /// Parsed GraphQL query string. + /// Gets input context for the operation. /// Optional object provided as a root to all top level field resolvers /// Map of all variable values provided by the client request. /// In case when document consists of many operations, this field describes which of them to execute. /// A plain dictionary of metadata that can be used through execution customizations. - member _.AsyncExecute(ast: Document, ?data: 'Root, ?variables: ImmutableDictionary, ?operationName: string, ?meta : Metadata): Async = + member _.AsyncExecute(ast: Document, getInputContext : InputExecutionContextProvider, ?data: 'Root, ?variables: ImmutableDictionary, ?operationName: string, ?meta : Metadata): Async = let meta = defaultArg meta Metadata.Empty match createExecutionPlan (ast, operationName, meta) with - | Ok executionPlan -> execute (executionPlan, data, variables) + | Ok executionPlan -> execute (executionPlan, data, variables, getInputContext) | Error (documentId, errors) -> async.Return <| GQLExecutionResult.Invalid(documentId, errors, meta) /// @@ -216,15 +221,16 @@ type Executor<'Root>(schema: ISchema<'Root>, middlewares : IExecutorMiddleware s /// 'errors' (optional, contains a list of errors that occurred while executing a GraphQL operation). /// /// GraphQL query string. + /// Gets input context for the operation. /// Optional object provided as a root to all top level field resolvers /// Map of all variable values provided by the client request. /// In case when document consists of many operations, this field describes which of them to execute. /// A plain dictionary of metadata that can be used through execution customizations. - member _.AsyncExecute(queryOrMutation: string, ?data: 'Root, ?variables: ImmutableDictionary, ?operationName: string, ?meta : Metadata): Async = + member _.AsyncExecute(queryOrMutation: string, getInputContext : InputExecutionContextProvider, ?data: 'Root, ?variables: ImmutableDictionary, ?operationName: string, ?meta : Metadata): Async = let meta = defaultArg meta Metadata.Empty let ast = parse queryOrMutation match createExecutionPlan (ast, operationName, meta) with - | Ok executionPlan -> execute (executionPlan, data, variables) + | Ok executionPlan -> execute (executionPlan, data, variables, getInputContext) | Error (documentId, errors) -> async.Return <| GQLExecutionResult.Invalid(documentId, errors, meta) /// Creates an execution plan for provided GraphQL document AST without diff --git a/src/FSharp.Data.GraphQL.Server/Linq.fs b/src/FSharp.Data.GraphQL.Server/Linq.fs index 7efca2daa..4450b8e49 100644 --- a/src/FSharp.Data.GraphQL.Server/Linq.fs +++ b/src/FSharp.Data.GraphQL.Server/Linq.fs @@ -7,6 +7,7 @@ open System.Collections open System.Collections.Generic open System.Linq open System.Linq.Expressions +open FSharp.Data.GraphQL.Shared open FSharp.Reflection open FSharp.Data.GraphQL.Extensions open FSharp.Data.GraphQL.Types @@ -96,10 +97,10 @@ let private mkTrack name src dst = { Name = Some name; ParentType = src; ReturnT /// Takes function with 2 parameters and applies them in reversed order let inline private flip fn a b = fn b a -let inline private argVal vars argDef argOpt = +let inline private argVal inputContext vars argDef argOpt = match argOpt with | Some arg -> - Execution.argumentValue vars argDef arg + Execution.argumentValue inputContext vars argDef arg // TODO: Improve error propagation |> Result.defaultWith (failwithf "%A") |> Some @@ -107,8 +108,8 @@ let inline private argVal vars argDef argOpt = /// Resolves an object representing one of the supported arguments /// given a variables set and GraphQL input data. -let private resolveLinqArg vars (name, argdef, arg) = - argVal vars argdef arg |> Option.map (fun v -> { Arg.Name = name; Value = v }) +let private resolveLinqArg inputContext vars (name, argDef, arg) = + argVal inputContext vars argDef arg |> Option.map (fun v -> { Arg.Name = name; Value = v }) let rec private unwrapType = function @@ -264,14 +265,14 @@ let private applyLast: ArgApplication = fun expression callable -> /// Tries to resolve all supported LINQ args with values /// from a given ExecutionInfo and variables collection -let private linqArgs vars info = +let private linqArgs inputContext vars info = let argDefs = info.Definition.Args if Array.isEmpty argDefs then [] else let args = info.Ast.Arguments argDefs |> Array.map (fun a -> (a.Name, a, args |> List.tryFind (fun x -> x.Name = a.Name))) - |> Array.choose (resolveLinqArg vars) + |> Array.choose (resolveLinqArg inputContext vars) |> Array.toList let rec private track set e = @@ -414,12 +415,12 @@ let rec private infoComposer (root: Tracker) (allTracks: Set) : Set List.map (compose vars)) + IR(info, composed, children |> List.map (compose inputContext vars)) /// Get unrelated tracks from current info and its children (if any) /// Returned set of trackers ALWAYS consists of Direct trackers only @@ -605,15 +606,15 @@ let private defaultArgApplicators: Map = /// and can can cause eager overfetching. /// /// -let rec tracker (vars: ImmutableDictionary) (info: ExecutionInfo) : Tracker = +let rec tracker inputContext (vars: ImmutableDictionary) (info: ExecutionInfo) : Tracker = let ir = getTracks Set.empty info - let composed = compose vars ir + let composed = compose inputContext vars ir let (IR(_, trackers, children)) = composed join trackers children |> Seq.head -let private toLinq info (query: IQueryable<'Source>) variables (argApplicators: Map) : IQueryable<'Source> = +let private toLinq info (query: IQueryable<'Source>) inputContext variables (argApplicators: Map) : IQueryable<'Source> = let parameter = Expression.Parameter (query.GetType()) - let ir = tracker variables info + let ir = tracker inputContext variables info let expr = construct argApplicators ir parameter let compiled = match expr with @@ -650,11 +651,12 @@ type System.Linq.IQueryable<'Source> with /// /// /// Execution info data to be applied on the queryable. + /// Provider for getting input execution context. /// Optional map with client-provided arguments used to resolve argument values. /// Map of applicators used to define LINQ expression mutations based on GraphQL arguments. - member this.Apply(info: ExecutionInfo, ?variables: ImmutableDictionary, ?applicators: Map) : IQueryable<'Source> = - let appl = + member this.Apply(info: ExecutionInfo, inputContext : InputExecutionContextProvider, ?variables: ImmutableDictionary, ?applicators: Map) : IQueryable<'Source> = + let apply = match applicators with | None -> defaultArgApplicators | Some a -> a |> Map.fold (fun acc key value -> Map.add (key.ToLowerInvariant()) value acc) defaultArgApplicators - toLinq info this (defaultArg variables ImmutableDictionary.Empty) appl + toLinq info this inputContext (defaultArg variables ImmutableDictionary.Empty) apply diff --git a/src/FSharp.Data.GraphQL.Server/Values.fs b/src/FSharp.Data.GraphQL.Server/Values.fs index 1e16178d9..7d3153549 100644 --- a/src/FSharp.Data.GraphQL.Server/Values.fs +++ b/src/FSharp.Data.GraphQL.Server/Values.fs @@ -16,13 +16,12 @@ open FSharp.Data.GraphQL.Ast open FSharp.Data.GraphQL.Types open FSharp.Data.GraphQL.Types.Patterns open FSharp.Data.GraphQL.Validation -open FSharp.Data.GraphQL let private wrapOptionalNone (outputType : Type) (inputType : Type) = if inputType.Name <> outputType.Name then if outputType.FullName.StartsWith ReflectionHelper.ValueOptionTypeName then - let _, valuenone, _ = ReflectionHelper.vOptionOfType outputType.GenericTypeArguments[0] - valuenone + let _, valueNone, _ = ReflectionHelper.vOptionOfType outputType.GenericTypeArguments[0] + valueNone elif outputType.IsValueType then Activator.CreateInstance (outputType) else @@ -48,8 +47,8 @@ let normalizeOptional (outputType : Type) value = outputType.FullName.StartsWith ReflectionHelper.ValueOptionTypeName && expectedOutputType.IsAssignableFrom inputType then - let valuesome, _, _ = ReflectionHelper.vOptionOfType expectedOutputType - valuesome value + let valueSome, _, _ = ReflectionHelper.vOptionOfType expectedOutputType + valueSome value else // Use only when option or voption so must not be null let actualInputType = inputType.GenericTypeArguments.FirstOrDefault () @@ -98,16 +97,15 @@ let rec internal compileByType (inputObjectPath : FieldPath) (inputSource : InputSource) (originalInputDef : InputDef, inputDef : InputDef) + (getInputContext : InputExecutionContextProvider) : ExecuteInput = match inputDef with - | Scalar scalardef -> variableOrElse (InlineConstant >> scalardef.CoerceInput) - - | InputCustom customDef -> fun value variables -> customDef.CoerceInput (InlineConstant value) variables - + | Scalar scalarDef -> variableOrElse (InlineConstant >> scalarDef.CoerceInput) + | InputCustom customDef -> fun inputContext value variables -> customDef.CoerceInput inputContext (InlineConstant value) variables | InputObject objDef -> - let objtype = objDef.Type - let ctor = ReflectionHelper.matchConstructor objtype (objDef.Fields |> Array.map (fun x -> x.Name)) + let objType = objDef.Type + let ctor = ReflectionHelper.matchConstructor objType (objDef.Fields |> Array.map (fun x -> x.Name)) let parametersMap = let typeMismatchParameters = HashSet () @@ -137,12 +135,11 @@ let rec internal compileByType | inputDef -> let inputType, paramType = if isParameterSkippable then - inputDef.Type, param.ParameterType.GenericTypeArguments.[0] + inputDef.Type, param.ParameterType.GenericTypeArguments[0] else inputDef.Type, param.ParameterType if ReflectionHelper.isAssignableWithUnwrap inputType paramType then allParameters.Add (struct (ValueSome field, param)) - |> ignore else // TODO: Consider improving by specifying type mismatches typeMismatchParameters.Add param.Name |> ignore @@ -151,7 +148,7 @@ let rec internal compileByType ReflectionHelper.isParameterSkippable param || ReflectionHelper.isParameterOptional param then - allParameters.Add <| struct (ValueNone, param) |> ignore + allParameters.Add <| struct (ValueNone, param) else missingParameters.Add param.Name |> ignore allParameters) @@ -162,28 +159,28 @@ let rec internal compileByType if missingParameters.Any () then let message = let ``params`` = String.Join ("', '", missingParameters) - $"Input object '%s{objDef.Name}' refers to type '%O{objtype}', but mandatory constructor parameters '%s{``params``}' don't match any of the defined GraphQL input fields" + $"Input object '%s{objDef.Name}' refers to type '%O{objType}', but mandatory constructor parameters '%s{``params``}' don't match any of the defined GraphQL input fields" InvalidInputTypeException (message, missingParameters.ToImmutableHashSet ()) if nullableMismatchParameters.Any () then let message = let ``params`` = String.Join ("', '", nullableMismatchParameters) - $"Input object %s{objDef.Name} refers to type '%O{objtype}', but constructor parameters for optional GraphQL fields '%s{``params``}' are not optional" + $"Input object %s{objDef.Name} refers to type '%O{objType}', but constructor parameters for optional GraphQL fields '%s{``params``}' are not optional" InvalidInputTypeException (message, nullableMismatchParameters.ToImmutableHashSet ()) if skippableMismatchParameters.Any () then let message = let ``params`` = String.Join ("', '", skippableMismatchParameters) - $"Input object %s{objDef.Name} refers to type '%O{objtype}', but skippable '%s{``params``}' GraphQL fields and constructor parameters do not match" + $"Input object %s{objDef.Name} refers to type '%O{objType}', but skippable '%s{``params``}' GraphQL fields and constructor parameters do not match" InvalidInputTypeException (message, skippableMismatchParameters.ToImmutableHashSet ()) if typeMismatchParameters.Any () then let message = let ``params`` = String.Join ("', '", typeMismatchParameters) - $"Input object %s{objDef.Name} refers to type '%O{objtype}', but GraphQL fields '%s{``params``}' have different types than constructor parameters" + $"Input object %s{objDef.Name} refers to type '%O{objType}', but GraphQL fields '%s{``params``}' have different types than constructor parameters" InvalidInputTypeException (message, typeMismatchParameters.ToImmutableHashSet ()) ] match exceptions with | [] -> () | [ ex ] -> raise ex - | _ -> raise (AggregateException ($"Invalid input object '%O{objtype}'", exceptions)) + | _ -> raise (AggregateException ($"Invalid input object '%O{objType}'", exceptions)) allParameters @@ -211,7 +208,7 @@ let rec internal compileByType FieldErrorDetails = ValueSome { ObjectDef = objectType; FieldDef = ValueNone } } - fun value variables -> + fun getInputContext value variables -> #if DEBUG let objDef = objDef #endif @@ -227,7 +224,7 @@ let rec internal compileByType | None -> return wrapOptionalNone param.ParameterType field.TypeDef.Type | Some prop -> let! value = - field.ExecuteInput prop variables + field.ExecuteInput getInputContext prop variables |> attachErrorExtensionsIfScalar inputSource inputObjectPath originalInputDef field if field.IsSkippable then let innerType = param.ParameterType.GenericTypeArguments[0] @@ -271,7 +268,7 @@ let rec internal compileByType return (Activator.CreateInstance param.ParameterType) | ValueSome field -> let! value = - field.ExecuteInput (VariableName field.Name) objectFields + field.ExecuteInput getInputContext (VariableName field.Name) objectFields // TODO: Take into account variable name |> attachErrorExtensionsIfScalar inputSource inputObjectPath originalInputDef field if field.IsSkippable then @@ -303,9 +300,9 @@ let rec internal compileByType | _ -> let ty = found.GetType () if - ty = objtype + ty = objType || (ty.FullName.StartsWith "Microsoft.FSharp.Core.FSharpOption`1" - && ty.GetGenericArguments().[0] = objtype) + && ty.GetGenericArguments().[0] = objType) then return found else @@ -324,21 +321,21 @@ let rec internal compileByType match innerDef with | InputObject inputObjDef | Nullable (InputObject inputObjDef) -> - let inner = compileByType inputObjectPath inputSource (inputDef, innerDef) + let inner = compileByType inputObjectPath inputSource (inputDef, innerDef) getInputContext inputObjDef.ExecuteInput <- inner | _ -> () let isArray = inputDef.Type.IsArray // TODO: Improve creation of inner - let inner index = compileByType ((box index) :: inputObjectPath) inputSource (innerDef, innerDef) + let inner index = compileByType ((box index) :: inputObjectPath) inputSource (innerDef, innerDef) getInputContext let cons, nil = ReflectionHelper.listOfType innerDef.Type - fun value variables -> + fun getInputContext value variables -> match value with | ListValue list -> result { let! mappedValues = list - |> Seq.mapi (fun i value -> inner i value variables) + |> Seq.mapi (fun i value -> inner i getInputContext value variables) |> Seq.toList |> splitSeqErrorsList let mappedValues = @@ -351,10 +348,10 @@ let rec internal compileByType else return List.foldBack cons mappedValues nil } - | VariableName variableName -> Ok variables.[variableName] + | VariableName variableName -> Ok variables[variableName] | _ -> result { // try to construct a list from single element - let! single = inner 0 value variables + let! single = inner 0 getInputContext value variables if single = null then return null @@ -365,20 +362,17 @@ let rec internal compileByType } | Nullable (Input innerDef) -> - let inner = compileByType inputObjectPath inputSource (inputDef, innerDef) + let inner = compileByType inputObjectPath inputSource (inputDef, innerDef) getInputContext match innerDef with | InputObject inputObjDef -> inputObjDef.ExecuteInput <- inner | _ -> () - fun value variables -> -#if DEBUG - let innerDef = innerDef -#endif + fun getInputContext value variables -> match value with | NullValue -> Ok null - | _ -> inner value variables + | _ -> inner getInputContext value variables | Enum enumDef -> - fun value variables -> + fun _ value variables -> match value with | VariableName variableName -> match variables.TryGetValue variableName with @@ -403,7 +397,7 @@ let rec internal compileByType } | _ -> Debug.Fail "Unexpected InputDef" - failwithf "Unexpected value of inputDef: %O" inputDef + failwithf $"Unexpected value of inputDef: {inputDef}" type CoerceVariableContext = { IsNullable : bool @@ -423,7 +417,7 @@ type CoerceVariableInputContext = { Input : JsonElement } -let rec internal coerceVariableValue (ctx : CoerceVariableContext) : Result = +let rec internal coerceVariableValue (ctx : CoerceVariableContext, inputContext : InputExecutionContextProvider) : Result = //let { // IsNullable = isNullable @@ -439,7 +433,7 @@ let rec internal coerceVariableValue (ctx : CoerceVariableContext) : Result + | Scalar scalarDef -> if ctx.Input.ValueKind = JsonValueKind.Null then createNullError ctx.OriginalTypeDef else - match scalardef.CoerceInput (InputParameterValue.Variable ctx.Input) with + match scalarDef.CoerceInput (InputParameterValue.Variable ctx.Input) with | Ok null when ctx.IsNullable -> Ok null // TODO: Capture position in the JSON document | Ok null -> createNullError ctx.OriginalTypeDef @@ -497,8 +491,8 @@ let rec internal coerceVariableValue (ctx : CoerceVariableContext) : Result InputDef } - coerceVariableValue ctx' - | Nullable (Input innerdef) -> + coerceVariableValue(ctx', inputContext) + | Nullable (Input innerDef) -> if ctx.Input.ValueKind = JsonValueKind.Null then Ok null else @@ -507,9 +501,9 @@ let rec internal coerceVariableValue (ctx : CoerceVariableContext) : Result let cons, nil = ReflectionHelper.listOfType innerDef.Type @@ -536,7 +530,7 @@ let rec internal coerceVariableValue (ctx : CoerceVariableContext) : Result Seq.toList |> splitSeqErrorsList if areItemsNullable then @@ -557,7 +551,7 @@ let rec internal coerceVariableValue (ctx : CoerceVariableContext) : Result - coerceVariableInputObject { + | InputObject objDef -> + coerceVariableInputObject ({ InputObjectPath = ctx.InputObjectPath OriginalObjectDef = ctx.OriginalTypeDef - ObjectDef = objdef + ObjectDef = objDef VarDef = ctx.VarDef Input = ctx.Input - } - | Enum enumdef -> + }, inputContext) + | Enum enumDef -> match ctx.Input with | _ when ctx.Input.ValueKind = JsonValueKind.Null && ctx.IsNullable -> Ok null | _ when ctx.Input.ValueKind = JsonValueKind.Null -> - createVariableCoercionError $"A variable '$%s{ctx.VarDef.Name}' expected value of type '%s{enumdef.Name}!', but no value was found." + createVariableCoercionError $"A variable '$%s{ctx.VarDef.Name}' expected value of type '%s{enumDef.Name}!', but no value was found." | _ when ctx.Input.ValueKind = JsonValueKind.String -> let value = ctx.Input.GetString () match - enumdef.Options + enumDef.Options |> Array.tryFind (fun o -> o.Name.Equals (value, StringComparison.InvariantCultureIgnoreCase)) with | Some option -> Ok option.Value - | None -> createVariableCoercionError $"A value '%s{value}' is not defined in Enum '%s{enumdef.Name}'." + | None -> createVariableCoercionError $"A value '%s{value}' is not defined in Enum '%s{enumDef.Name}'." | _ -> createVariableCoercionError $"Enum values must be strings but got '%O{ctx.Input.ValueKind}'." - | InputCustom custDef -> + | InputCustom customDef -> if ctx.Input.ValueKind = JsonValueKind.Null then createNullError ctx.OriginalTypeDef else - match custDef.CoerceInput (InputParameterValue.Variable ctx.Input) ImmutableDictionary.Empty with + match customDef.CoerceInput inputContext (InputParameterValue.Variable ctx.Input) ImmutableDictionary.Empty with | Ok null when ctx.IsNullable -> Ok null // TODO: Capture position in the JSON document | Ok null -> createNullError ctx.OriginalTypeDef @@ -619,7 +613,7 @@ let rec internal coerceVariableValue (ctx : CoerceVariableContext) : Result Result.mapError (List.map (mapInputError ctx.VarDef ctx.InputObjectPath ctx.ObjectFieldErrorDetails)) | _ -> failwith $"Variable '$%s{ctx.VarDef.Name}': Only Scalars, Nullables, Lists, and InputObjects are valid type definitions." -and private coerceVariableInputObject (ctx : CoerceVariableInputContext) = +and private coerceVariableInputObject (ctx : CoerceVariableInputContext, getInputContext : InputExecutionContextProvider) = match ctx.Input.ValueKind with | JsonValueKind.Object -> result { let mappedResult = @@ -641,7 +635,7 @@ and private coerceVariableInputObject (ctx : CoerceVariableInputContext) = VarDef = ctx.VarDef Input = value } - coerceVariableValue ctx + coerceVariableValue(ctx, getInputContext) KeyValuePair (field.Name, value) match ctx.Input.TryGetProperty field.Name with | true, value -> coerce value |> ValueSome @@ -660,13 +654,13 @@ and private coerceVariableInputObject (ctx : CoerceVariableInputContext) = seq { KeyValuePair (ctx.VarDef.Name, mapped :> obj) } |> ImmutableDictionary.CreateRange - return! ctx.ObjectDef.ExecuteInput (VariableName ctx.VarDef.Name) variables + return! ctx.ObjectDef.ExecuteInput getInputContext (VariableName ctx.VarDef.Name) variables } | JsonValueKind.Null -> Ok null | valueKind -> Error [ { - InputSource = Variable ctx.VarDef + InputSource = InputSource.Variable ctx.VarDef Message = $"A variable '$%s{ctx.VarDef.Name}' expected to be '%O{JsonValueKind.Object}' but got '%O{valueKind}'." ErrorKind = InputCoercion Path = ctx.InputObjectPath diff --git a/src/FSharp.Data.GraphQL.Shared/Ast.fs b/src/FSharp.Data.GraphQL.Shared/Ast.fs index c79f07119..90b47455c 100644 --- a/src/FSharp.Data.GraphQL.Shared/Ast.fs +++ b/src/FSharp.Data.GraphQL.Shared/Ast.fs @@ -195,7 +195,7 @@ and Directive = { and OperationTypeDefinition = { Type : string; Operation : OperationType } -and SchemaDefintion = { OperationTypes : OperationTypeDefinition } +and SchemaDefinition = { OperationTypes : OperationTypeDefinition } and ObjectTypeDefinition = { Name : string; Interfaces : string[]; Fields : FieldDefinition[] } diff --git a/src/FSharp.Data.GraphQL.Shared/Errors.fs b/src/FSharp.Data.GraphQL.Shared/Errors.fs index d5612f87b..1fdc08357 100644 --- a/src/FSharp.Data.GraphQL.Shared/Errors.fs +++ b/src/FSharp.Data.GraphQL.Shared/Errors.fs @@ -57,6 +57,11 @@ type ErrorKind = /// GraphQL field execution | Execution +module IGQLError = + let create message = { new IGQLError with member _.Message = message } + let createList message = [create message] + let createResultErrorList message = Result.Error (createList message) + #nowarn "0386" /// /// A machine-readable format for specifying errors in GraphQL API responses based on . diff --git a/src/FSharp.Data.GraphQL.Shared/FSharp.Data.GraphQL.Shared.fsproj b/src/FSharp.Data.GraphQL.Shared/FSharp.Data.GraphQL.Shared.fsproj index 75bb65bcd..a116e8ea8 100644 --- a/src/FSharp.Data.GraphQL.Shared/FSharp.Data.GraphQL.Shared.fsproj +++ b/src/FSharp.Data.GraphQL.Shared/FSharp.Data.GraphQL.Shared.fsproj @@ -42,6 +42,7 @@ + diff --git a/src/FSharp.Data.GraphQL.Shared/Helpers/ObjAndStructConversions.fs b/src/FSharp.Data.GraphQL.Shared/Helpers/ObjAndStructConversions.fs index 863c359be..f38328d5c 100644 --- a/src/FSharp.Data.GraphQL.Shared/Helpers/ObjAndStructConversions.fs +++ b/src/FSharp.Data.GraphQL.Shared/Helpers/ObjAndStructConversions.fs @@ -24,7 +24,8 @@ module internal ValueTuple = let fstv struct (a, _) = a let sndv struct (_, b) = b -module internal Seq = +[] +module Seq = let vchoose mapping seq = seq diff --git a/src/FSharp.Data.GraphQL.Shared/InputContext.fs b/src/FSharp.Data.GraphQL.Shared/InputContext.fs new file mode 100644 index 000000000..1ca352568 --- /dev/null +++ b/src/FSharp.Data.GraphQL.Shared/InputContext.fs @@ -0,0 +1,7 @@ +namespace FSharp.Data.GraphQL + +type IInputExecutionContext = + abstract GetFile : string -> Result + +type InputExecutionContextProvider = unit -> IInputExecutionContext + diff --git a/src/FSharp.Data.GraphQL.Shared/Output.fs b/src/FSharp.Data.GraphQL.Shared/Output.fs index c044f139f..de048a55d 100644 --- a/src/FSharp.Data.GraphQL.Shared/Output.fs +++ b/src/FSharp.Data.GraphQL.Shared/Output.fs @@ -1,4 +1,4 @@ -namespace FSharp.Data.GraphQL.Shared +namespace FSharp.Data.GraphQL open System.Collections.Generic open System @@ -140,4 +140,4 @@ type NameValueLookup(keyValues: KeyValuePair []) = module NameValueLookup = /// Create new NameValueLookup from given list of key-value tuples. - let ofList (l: (string * obj) list) = NameValueLookup(l) \ No newline at end of file + let ofList (l: (string * obj) list) = NameValueLookup (l) diff --git a/src/FSharp.Data.GraphQL.Shared/SchemaDefinitions.fs b/src/FSharp.Data.GraphQL.Shared/SchemaDefinitions.fs index 8aa47282b..7e8e9e7ce 100644 --- a/src/FSharp.Data.GraphQL.Shared/SchemaDefinitions.fs +++ b/src/FSharp.Data.GraphQL.Shared/SchemaDefinitions.fs @@ -8,7 +8,6 @@ open System.Collections.Generic open System.Text.Json open FSharp.Data.GraphQL open FSharp.Data.GraphQL.Ast -open FSharp.Data.GraphQL.Extensions open FSharp.Data.GraphQL.Types open FSharp.Data.GraphQL.Validation open FSharp.Quotations @@ -191,6 +190,12 @@ module SchemaDefinitions = | Option o -> Some(o.ToString()) | _ -> Some(x.ToString()) + /// Tries to convert any value to string. + let coerceFileValue (context : IInputExecutionContext) (value : obj) : Result = + match coerceStringValue value with + | Some fileName -> context.GetFile fileName + | None -> Error "Only string value can be used as file name" + /// Tries to convert any value to generic type parameter. let coerceIdValue (x : obj) : string option = match x with @@ -371,7 +376,7 @@ module SchemaDefinitions = /// to take collection of provided value. let ListOf(innerDef : #TypeDef<'Val>) : ListOfDef<'Val, 'Seq> = upcast { ListOfDefinition.OfType = innerDef } - let internal variableOrElse other value (variables : IReadOnlyDictionary) = + let internal variableOrElse other (_ : InputExecutionContextProvider) value (variables : IReadOnlyDictionary) = match value with // TODO: Use FSharp.Collection.Immutable | VariableName variableName -> @@ -473,10 +478,38 @@ module SchemaDefinitions = { Name = "Guid" Description = Some - "The `Guid` scalar type represents a Globaly Unique Identifier value. It's a 128-bit long byte key, that can be serialized to string." + "The `Guid` scalar type represents a Globally Unique Identifier value. It's a 128-bit long byte key, that can be serialized to string." CoerceInput = coerceGuidInput CoerceOutput = coerceGuidValue } + /// Defines an object list filter for use as an argument for filter list of object fields. + let FileType : InputCustomDefinition = { + Name = "FileType" + Description = + Some + "The `File` type represents a file on one or more fields of an object in an object list. The filter is represented by a JSON object where the fields are the complemented by specific suffixes to represent a query." + CoerceInput = + (fun inputContext input variables -> + let getFileStream fileKey = + let inputExecutionContext = inputContext() + let streamResult = inputExecutionContext.GetFile fileKey + match streamResult with + | Ok stream -> Ok stream + | Error errorMessage -> IGQLError.createResultErrorList errorMessage + + match input with + | InlineConstant c -> + match c with + | StringValue strValue -> getFileStream strValue + | VariableName varName -> + Ok (variables[varName] :?> System.IO.Stream) + | _ -> IGQLError.createResultErrorList "Only a string value or a variable with a string value can be used as a file name." + | Variable json -> + match (json |> InputValue.OfJsonElement) with + | StringValue str -> getFileStream str + | _ -> IGQLError.createResultErrorList "Only a variable with a string value can be used as a file name.") + } + /// GraphQL @include directive. let IncludeDirective : DirectiveDef = { Name = "include" diff --git a/src/FSharp.Data.GraphQL.Shared/TypeSystem.fs b/src/FSharp.Data.GraphQL.Shared/TypeSystem.fs index 05d41ed70..11006387b 100644 --- a/src/FSharp.Data.GraphQL.Shared/TypeSystem.fs +++ b/src/FSharp.Data.GraphQL.Shared/TypeSystem.fs @@ -859,7 +859,12 @@ and VarDef = { /// The context used to hold all the information for a schema compiling proccess. -and SchemaCompileContext = { Schema : ISchema; TypeMap : TypeMap; FieldExecuteMap : FieldExecuteMap } +and SchemaCompileContext = { + Schema : ISchema + TypeMap : TypeMap + FieldExecuteMap : FieldExecuteMap + GetInputContext : InputExecutionContextProvider +} /// A planning of an execution phase. /// It is used by the execution process to execute an operation. @@ -893,11 +898,13 @@ and ExecutionContext = { Schema : ISchema /// Boxed value of the top level type, root query/mutation. RootValue : obj - /// Execution plan describing, what fiedls are going to be resolved. + /// Execution plan describing, what fields are going to be resolved. ExecutionPlan : ExecutionPlan - /// Collection of variables provided to execute current operation. + ///Get input execution context + GetInputContext : InputExecutionContextProvider + /// Collection of variables provided to execute a current operation. Variables : ImmutableDictionary - /// Collection of errors that occurred while executing current operation. + /// Collection of errors that occurred while executing a current operation. Errors : ConcurrentDictionary> /// A map of all fields of the query and their respective execution operations. FieldExecuteMap : FieldExecuteMap @@ -1115,7 +1122,20 @@ and [] ScalarDefinition<'Primitive, 'Val> = { and ScalarDefinition<'Val> = ScalarDefinition<'Val, 'Val> -/// A GraphQL representation of single case of the enum type. +and FileDef = + interface + /// Name of the file type. + abstract Name : string + /// Optional scalar type description. + abstract Description : string option + /// A function used to retrieve a .NET object from provided GraphQL query or JsonElement variable. + abstract Coerce : IInputExecutionContext -> InputParameterValue -> Result + inherit TypeDef + inherit NamedDef + inherit InputDef + end + +/// A GraphQL representation for a single value of the enum type. /// Enum value return value is always represented as string. and EnumVal = interface @@ -1129,7 +1149,7 @@ and EnumVal = abstract DeprecationReason : string option end -/// A GraphQL representation of single case of the enum type. +/// A GraphQL representation of a single case of the enum type. /// Enum value return value is always represented as string. and EnumValue<'Val> = { /// Identifier of the enum value. @@ -1693,7 +1713,7 @@ and InputObjectDefinition<'Val> = { override x.ToString () = x.Name + "!" /// Function type used for resolving input object field values. -and ExecuteInput = InputValue -> Variables -> Result +and ExecuteInput = InputExecutionContextProvider -> InputValue -> Variables -> Result /// GraphQL field input definition. Can be used as fields for /// input objects or as arguments for any ordinary field definition. @@ -1766,7 +1786,7 @@ and internal InputCustomDef = /// Optional input field / argument description. abstract Description : string option /// A function used to retrieve a .NET object from provided GraphQL query or JsonElement variable. - abstract CoerceInput : InputParameterValue -> Variables -> Result + abstract CoerceInput : InputExecutionContextProvider -> InputParameterValue -> Variables -> Result inherit TypeDef inherit NamedDef inherit InputDef @@ -1776,7 +1796,7 @@ and internal InputCustomDef = and InputCustomDefinition<'Val> = internal { Name : string Description : string option - CoerceInput : InputParameterValue -> Variables -> Result<'Val, IGQLError list> + CoerceInput : InputExecutionContextProvider -> InputParameterValue -> Variables -> Result<'Val, IGQLError list> } with interface TypeDef with member _.Type = typeof<'Val> @@ -1796,7 +1816,7 @@ and InputCustomDefinition<'Val> = internal { interface InputCustomDef with member x.Name = x.Name member x.Description = x.Description - member x.CoerceInput input variables = x.CoerceInput input variables |> Result.map box + member x.CoerceInput input variables context = x.CoerceInput input variables context |> Result.map box interface NamedDef with member x.Name = x.Name diff --git a/src/FSharp.Data.GraphQL.Shared/Validation.fs b/src/FSharp.Data.GraphQL.Shared/Validation.fs index 1e3951a46..72d841423 100644 --- a/src/FSharp.Data.GraphQL.Shared/Validation.fs +++ b/src/FSharp.Data.GraphQL.Shared/Validation.fs @@ -933,6 +933,7 @@ module Ast = let invalidScalars = [| "Int"; "Float"; "Boolean" |] match tref.Name, tref.Kind with | (Some x, TypeKind.SCALAR) when not (Array.contains x invalidScalars) -> Success + | (Some x, TypeKind.INPUT_OBJECT) when x = FileType.Name -> Success | _ -> canNotCoerce | EnumValue _ -> match tref.Kind with diff --git a/tests/FSharp.Data.GraphQL.Benchmarks/ExecutionBenchmark.fs b/tests/FSharp.Data.GraphQL.Benchmarks/ExecutionBenchmark.fs index a49b71696..04f6bb219 100644 --- a/tests/FSharp.Data.GraphQL.Benchmarks/ExecutionBenchmark.fs +++ b/tests/FSharp.Data.GraphQL.Benchmarks/ExecutionBenchmark.fs @@ -10,6 +10,10 @@ open FSharp.Data.GraphQL.Parser open BenchmarkDotNet.Attributes open FSharp.Data.GraphQL.Benchmarks +type MockInputExecutionContext() = + interface IInputExecutionContext with + member this.GetFile _ = failwith "todo" + [)>] [] type SimpleExecutionBenchmark() = @@ -23,6 +27,7 @@ type SimpleExecutionBenchmark() = let mutable simpleExecutionPlan : ExecutionPlan = Unchecked.defaultof let mutable flatExecutionPlan : ExecutionPlan = Unchecked.defaultof let mutable nestedExecutionPlan : ExecutionPlan = Unchecked.defaultof + let getInputContext = fun () -> MockInputExecutionContext() :> IInputExecutionContext [] member _.Setup() = @@ -38,31 +43,31 @@ type SimpleExecutionBenchmark() = nestedExecutionPlan <- schemaProcessor.CreateExecutionPlanOrFail(nestedAst) [] - member _.BenchmarkSimpleQueryUnparsed() = schemaProcessor.AsyncExecute(QueryStrings.simple) |> Async.RunSynchronously + member _.BenchmarkSimpleQueryUnparsed() = schemaProcessor.AsyncExecute(QueryStrings.simple, getInputContext) |> Async.RunSynchronously [] - member _.BenchmarkSimpleQueryParsed() = schemaProcessor.AsyncExecute(simpleAst) |> Async.RunSynchronously + member _.BenchmarkSimpleQueryParsed() = schemaProcessor.AsyncExecute(simpleAst, getInputContext) |> Async.RunSynchronously [] - member _.BenchmarkSimpleQueryPlanned() = schemaProcessor.AsyncExecute(simpleExecutionPlan) |> Async.RunSynchronously + member _.BenchmarkSimpleQueryPlanned() = schemaProcessor.AsyncExecute(simpleExecutionPlan, getInputContext) |> Async.RunSynchronously [] - member _.BenchmarkFlatQueryUnparsed() = schemaProcessor.AsyncExecute(QueryStrings.flat) |> Async.RunSynchronously + member _.BenchmarkFlatQueryUnparsed() = schemaProcessor.AsyncExecute(QueryStrings.flat, getInputContext) |> Async.RunSynchronously [] - member _.BenchmarkFlatQueryParsed() = schemaProcessor.AsyncExecute(flatAst) |> Async.RunSynchronously + member _.BenchmarkFlatQueryParsed() = schemaProcessor.AsyncExecute(flatAst, getInputContext) |> Async.RunSynchronously [] - member _.BenchmarkFlatQueryPlanned() = schemaProcessor.AsyncExecute(flatExecutionPlan) |> Async.RunSynchronously + member _.BenchmarkFlatQueryPlanned() = schemaProcessor.AsyncExecute(flatExecutionPlan, getInputContext) |> Async.RunSynchronously [] - member _.BenchmarkNestedQueryUnparsed() = schemaProcessor.AsyncExecute(QueryStrings.nested) |> Async.RunSynchronously + member _.BenchmarkNestedQueryUnparsed() = schemaProcessor.AsyncExecute(QueryStrings.nested, getInputContext) |> Async.RunSynchronously [] - member _.BenchmarkNestedQueryParsed() = schemaProcessor.AsyncExecute(nestedAst) |> Async.RunSynchronously + member _.BenchmarkNestedQueryParsed() = schemaProcessor.AsyncExecute(nestedAst, getInputContext) |> Async.RunSynchronously [] - member _.BenchmarkNestedQueryPlanned() = schemaProcessor.AsyncExecute(nestedExecutionPlan) |> Async.RunSynchronously + member _.BenchmarkNestedQueryPlanned() = schemaProcessor.AsyncExecute(nestedExecutionPlan, getInputContext) |> Async.RunSynchronously [] - member _.BenchmarkParallelQueryPlanned() = parallelSchemaProcessor.AsyncExecute({ nestedExecutionPlan with Strategy = Parallel }) |> Async.RunSynchronously + member _.BenchmarkParallelQueryPlanned() = parallelSchemaProcessor.AsyncExecute({ nestedExecutionPlan with Strategy = Parallel }, getInputContext) |> Async.RunSynchronously diff --git a/tests/FSharp.Data.GraphQL.Benchmarks/MiddlewareBenchmark.fs b/tests/FSharp.Data.GraphQL.Benchmarks/MiddlewareBenchmark.fs index c8327e010..404976ba2 100644 --- a/tests/FSharp.Data.GraphQL.Benchmarks/MiddlewareBenchmark.fs +++ b/tests/FSharp.Data.GraphQL.Benchmarks/MiddlewareBenchmark.fs @@ -5,6 +5,8 @@ module FSharp.Data.GraphQL.MiddlewaresBenchmark #nowarn "40" open FSharp.Data.GraphQL +open FSharp.Data.GraphQL.ExecutionBenchmark +open FSharp.Data.GraphQL.Shared open FSharp.Data.GraphQL.Types open FSharp.Data.GraphQL.Parser open BenchmarkDotNet.Attributes @@ -25,6 +27,7 @@ type SimpleExecutionWithMiddlewaresBenchmark() = let mutable nestedExecutionPlan : ExecutionPlan = Unchecked.defaultof let mutable filteredExecutionPlan : ExecutionPlan = Unchecked.defaultof let mutable filteredAst : Ast.Document = Unchecked.defaultof + let getInputContext = fun () -> MockInputExecutionContext() :> IInputExecutionContext [] member _.Setup() = @@ -41,37 +44,37 @@ type SimpleExecutionWithMiddlewaresBenchmark() = filteredExecutionPlan <- schemaProcessor.CreateExecutionPlanOrFail(filteredAst) [] - member _.BenchmarkSimpleQueryUnparsed() = schemaProcessor.AsyncExecute(QueryStrings.simple) |> Async.RunSynchronously + member _.BenchmarkSimpleQueryUnparsed() = schemaProcessor.AsyncExecute(QueryStrings.simple, getInputContext) |> Async.RunSynchronously [] - member _.BenchmarkSimpleQueryParsed() = schemaProcessor.AsyncExecute(simpleAst) |> Async.RunSynchronously + member _.BenchmarkSimpleQueryParsed() = schemaProcessor.AsyncExecute(simpleAst, getInputContext) |> Async.RunSynchronously [] - member _.BenchmarkSimpleQueryPlanned() = schemaProcessor.AsyncExecute(simpleExecutionPlan) |> Async.RunSynchronously + member _.BenchmarkSimpleQueryPlanned() = schemaProcessor.AsyncExecute(simpleExecutionPlan, getInputContext) |> Async.RunSynchronously [] - member _.BenchmarkFlatQueryUnparsed() = schemaProcessor.AsyncExecute(QueryStrings.flat) |> Async.RunSynchronously + member _.BenchmarkFlatQueryUnparsed() = schemaProcessor.AsyncExecute(QueryStrings.flat, getInputContext) |> Async.RunSynchronously [] - member _.BenchmarkFlatQueryParsed() = schemaProcessor.AsyncExecute(flatAst) |> Async.RunSynchronously + member _.BenchmarkFlatQueryParsed() = schemaProcessor.AsyncExecute(flatAst, getInputContext) |> Async.RunSynchronously [] - member _.BenchmarkFlatQueryPlanned() = schemaProcessor.AsyncExecute(flatExecutionPlan) |> Async.RunSynchronously + member _.BenchmarkFlatQueryPlanned() = schemaProcessor.AsyncExecute(flatExecutionPlan, getInputContext) |> Async.RunSynchronously [] - member _.BenchmarkNestedQueryUnparsed() = schemaProcessor.AsyncExecute(QueryStrings.nested) |> Async.RunSynchronously + member _.BenchmarkNestedQueryUnparsed() = schemaProcessor.AsyncExecute(QueryStrings.nested, getInputContext) |> Async.RunSynchronously [] - member _.BenchmarkNestedQueryParsed() = schemaProcessor.AsyncExecute(nestedAst) |> Async.RunSynchronously + member _.BenchmarkNestedQueryParsed() = schemaProcessor.AsyncExecute(nestedAst, getInputContext) |> Async.RunSynchronously [] - member _.BenchmarkNestedQueryPlanned() = schemaProcessor.AsyncExecute(nestedExecutionPlan) |> Async.RunSynchronously + member _.BenchmarkNestedQueryPlanned() = schemaProcessor.AsyncExecute(nestedExecutionPlan, getInputContext) |> Async.RunSynchronously [] - member _.BenchmarkFilteredQueryUnparsed() = schemaProcessor.AsyncExecute(QueryStrings.filtered) |> Async.RunSynchronously + member _.BenchmarkFilteredQueryUnparsed() = schemaProcessor.AsyncExecute(QueryStrings.filtered, getInputContext) |> Async.RunSynchronously [] - member _.BenchmarkFilteredQueryParsed() = schemaProcessor.AsyncExecute(filteredAst) |> Async.RunSynchronously + member _.BenchmarkFilteredQueryParsed() = schemaProcessor.AsyncExecute(filteredAst, getInputContext) |> Async.RunSynchronously [] - member _.BenchmarkFilteredQueryPlanned() = schemaProcessor.AsyncExecute(filteredExecutionPlan) |> Async.RunSynchronously + member _.BenchmarkFilteredQueryPlanned() = schemaProcessor.AsyncExecute(filteredExecutionPlan, getInputContext) |> Async.RunSynchronously diff --git a/tests/FSharp.Data.GraphQL.IntegrationTests.Server/Schema.fs b/tests/FSharp.Data.GraphQL.IntegrationTests.Server/Schema.fs index b94cd567d..b3f314c66 100644 --- a/tests/FSharp.Data.GraphQL.IntegrationTests.Server/Schema.fs +++ b/tests/FSharp.Data.GraphQL.IntegrationTests.Server/Schema.fs @@ -19,19 +19,25 @@ type InputField = Int : int StringOption : string option IntOption : int option - Uri : System.Uri - Guid : System.Guid - GuidOption : System.Guid option } + Uri : Uri + Guid : Guid + GuidOption : Guid option } type Input = { Single : InputField option List : InputField list option } +type InputFile = { File : System.IO.Stream} + type UploadedFile = { Name : string ContentType : string ContentAsText : string } +type UploadedContentFile = + { Name : string + ContentAsText : string } + type UploadRequest = { Single : File Multiple : File list @@ -130,56 +136,78 @@ module Schema = args = [ Define.Input("input", Nullable InputType, description = "The input to be echoed as an output.") ], resolve = fun ctx _ -> ctx.TryArg("input")) ]) + let InputFileObject = Define.InputObject( + name = "InputFile", + fields = + [ + Define.Input("file", FileType) + ]) + + // let MutationType = + // let contentAsText (stream : System.IO.Stream) = + // use reader = new System.IO.StreamReader(stream, Encoding.UTF8) + // reader.ReadToEnd() + // let mapUploadToOutput (file : File) = + // { Name = file.Name; ContentType = file.ContentType; ContentAsText = contentAsText file.Content } + // let mapUploadRequestToOutput (request : UploadRequest) = + // { Single = mapUploadToOutput request.Single + // Multiple = request.Multiple |> List.map mapUploadToOutput + // NullableMultiple = request.NullableMultiple |> Option.map (List.map mapUploadToOutput) + // NullableMultipleNullable = request.NullableMultipleNullable |> Option.map (List.map (Option.map mapUploadToOutput)) } + // Define.Object( + // name = "Mutation", + // fields = + // [ Define.Field( + // name = "singleUpload", + // typedef = UploadedFileType, + // description = "Uploads a single file to the server and get it back.", + // args = [ Define.Input("file", Upload, description = "The file to be uploaded.") ], + // resolve = fun ctx _ -> mapUploadToOutput (ctx.Arg("file"))) + // Define.Field( + // name = "nullableSingleUpload", + // typedef = StructNullable UploadedFileType, + // description = "Uploads (maybe) a single file to the server and get it back (maybe).", + // args = [ Define.Input("file", Nullable Upload, description = "The file to be uploaded.") ], + // resolve = fun ctx _ -> ctx.TryArg("file") |> ValueOption.flatten |> ValueOption.map mapUploadToOutput) + // Define.Field( + // name = "multipleUpload", + // typedef = ListOf UploadedFileType, + // description = "Uploads a list of files to the server and get them back.", + // args = [ Define.Input("files", ListOf Upload, description = "The files to upload.") ], + // resolve = fun ctx _ -> ctx.Arg("files") |> Seq.map mapUploadToOutput) + // Define.Field( + // name = "nullableMultipleUpload", + // typedef = StructNullable (ListOf UploadedFileType), + // description = "Uploads (maybe) a list of files to the server and get them back (maybe).", + // args = [ Define.Input("files", Nullable (ListOf Upload), description = "The files to upload.") ], + // resolve = fun ctx _ -> ctx.TryArg("files") |> ValueOption.flatten |> ValueOption.map (Seq.map mapUploadToOutput)) + // Define.Field( + // name = "nullableMultipleNullableUpload", + // typedef = StructNullable (ListOf (Nullable UploadedFileType)), + // description = "Uploads (maybe) a list of files (maybe) to the server and get them back (maybe).", + // args = [ Define.Input("files", Nullable (ListOf (Nullable Upload)), description = "The files to upload.") ], + // resolve = fun ctx _ -> ctx.TryArg("files") |> ValueOption.flatten |> ValueOption.map (Seq.map (Option.map mapUploadToOutput))) + // Define.Field( + // name = "uploadRequest", + // typedef = UploadResponseType, + // description = "Upload several files in different forms.", + // args = [ Define.Input("request", UploadRequestType, description = "The request for uploading several files in different forms.") ], + // resolve = fun ctx _ -> mapUploadRequestToOutput (ctx.Arg("request"))) ]) let MutationType = - let contentAsText (stream : System.IO.Stream) = - use reader = new System.IO.StreamReader(stream, Encoding.UTF8) + let getFileContent (ctx : ResolveFieldContext) argName = + let stream = ctx.Arg argName + use reader = new System.IO.StreamReader(stream, Encoding.UTF8, true) reader.ReadToEnd() - let mapUploadToOutput (file : File) = - { Name = file.Name; ContentType = file.ContentType; ContentAsText = contentAsText file.Content } - let mapUploadRequestToOutput (request : UploadRequest) = - { Single = mapUploadToOutput request.Single - Multiple = request.Multiple |> List.map mapUploadToOutput - NullableMultiple = request.NullableMultiple |> Option.map (List.map mapUploadToOutput) - NullableMultipleNullable = request.NullableMultipleNullable |> Option.map (List.map (Option.map mapUploadToOutput)) } - Define.Object( - name = "Mutation", + + Define.Object ( + name = "MutationType", fields = - [ Define.Field( - name = "singleUpload", - typedef = UploadedFileType, - description = "Uploads a single file to the server and get it back.", - args = [ Define.Input("file", Upload, description = "The file to be uploaded.") ], - resolve = fun ctx _ -> mapUploadToOutput (ctx.Arg("file"))) - Define.Field( - name = "nullableSingleUpload", - typedef = StructNullable UploadedFileType, - description = "Uploads (maybe) a single file to the server and get it back (maybe).", - args = [ Define.Input("file", Nullable Upload, description = "The file to be uploaded.") ], - resolve = fun ctx _ -> ctx.TryArg("file") |> ValueOption.flatten |> ValueOption.map mapUploadToOutput) - Define.Field( - name = "multipleUpload", - typedef = ListOf UploadedFileType, - description = "Uploads a list of files to the server and get them back.", - args = [ Define.Input("files", ListOf Upload, description = "The files to upload.") ], - resolve = fun ctx _ -> ctx.Arg("files") |> Seq.map mapUploadToOutput) - Define.Field( - name = "nullableMultipleUpload", - typedef = StructNullable (ListOf UploadedFileType), - description = "Uploads (maybe) a list of files to the server and get them back (maybe).", - args = [ Define.Input("files", Nullable (ListOf Upload), description = "The files to upload.") ], - resolve = fun ctx _ -> ctx.TryArg("files") |> ValueOption.flatten |> ValueOption.map (Seq.map mapUploadToOutput)) - Define.Field( - name = "nullableMultipleNullableUpload", - typedef = StructNullable (ListOf (Nullable UploadedFileType)), - description = "Uploads (maybe) a list of files (maybe) to the server and get them back (maybe).", - args = [ Define.Input("files", Nullable (ListOf (Nullable Upload)), description = "The files to upload.") ], - resolve = fun ctx _ -> ctx.TryArg("files") |> ValueOption.flatten |> ValueOption.map (Seq.map (Option.map mapUploadToOutput))) - Define.Field( - name = "uploadRequest", - typedef = UploadResponseType, - description = "Upload several files in different forms.", - args = [ Define.Input("request", UploadRequestType, description = "The request for uploading several files in different forms.") ], - resolve = fun ctx _ -> mapUploadRequestToOutput (ctx.Arg("request"))) ]) + [ Define.Field ("uploadFile", StringType, "", [ Define.Input ("input", FileType) ], + (fun ctx _ -> getFileContent ctx "input")); + Define.Field ("uploadFileComplex", StringType, "", [ Define.Input ("input", InputFileObject) ], + (fun ctx _ -> getFileContent ctx "input")) + ] + ) let schema : ISchema = upcast Schema(QueryType, MutationType) diff --git a/tests/FSharp.Data.GraphQL.IntegrationTests/LocalProviderTests.fs b/tests/FSharp.Data.GraphQL.IntegrationTests/LocalProviderTests.fs index 89e9201ce..3479825ab 100644 --- a/tests/FSharp.Data.GraphQL.IntegrationTests/LocalProviderTests.fs +++ b/tests/FSharp.Data.GraphQL.IntegrationTests/LocalProviderTests.fs @@ -9,6 +9,7 @@ let [] ServerUrl = "http://localhost:8085" let [] EmptyGuidAsString = "00000000-0000-0000-0000-000000000000" type Provider = GraphQLProvider +// type FileProvider = GraphQLProvider let context = Provider.GetContext(ServerUrl) @@ -251,6 +252,13 @@ module SingleOptionalUploadOperation = result.Data.Value.NullableSingleUpload.Value.ContentAsText |> equals file.Content result.Data.Value.NullableSingleUpload.Value.ContentType |> equals file.ContentType) + +// [] +// let ``Should be able to execute a upload by passing a file with new approach``() = +// let file = { Name = "file.txt"; ContentType = "text/plain"; Content = "Sample text file contents" } +// let result = SingleOptionalUploadOperation.fileOperation.Run(file.MakeUpload()) +// |> SingleOptionalUploadOperation.validateResult (Some file) + [] let ``Should be able to execute a single optional upload by passing a file``() = let file = { Name = "file.txt"; ContentType = "text/plain"; Content = "Sample text file contents" } diff --git a/tests/FSharp.Data.GraphQL.IntegrationTests/introspection.json b/tests/FSharp.Data.GraphQL.IntegrationTests/introspection.json index fed38ddc5..b397d2f41 100644 --- a/tests/FSharp.Data.GraphQL.IntegrationTests/introspection.json +++ b/tests/FSharp.Data.GraphQL.IntegrationTests/introspection.json @@ -1,5 +1,5 @@ { - "documentId": -837497709, + "documentId": 1466593774, "data": { "__schema": { "queryType": { @@ -1438,7 +1438,7 @@ "name": "filter", "description": null, "type": { - "kind": "SCALAR", + "kind": "INPUT_OBJECT", "name": "ObjectListFilter", "ofType": null }, @@ -1551,6 +1551,22 @@ }, "isDeprecated": false, "deprecationReason": null + }, + { + "name": "satelitesCount", + "description": "The number of satelites of the planet.", + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null } ], "inputFields": null, @@ -1605,6 +1621,51 @@ "name": "Mutation", "description": null, "fields": [ + { + "name": "patchPlanet", + "description": null, + "args": [ + { + "name": "id", + "description": null, + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + }, + "defaultValue": null + }, + { + "name": "planet", + "description": null, + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "INPUT_OBJECT", + "name": "InputPatchPlanet", + "ofType": null + } + }, + "defaultValue": null + } + ], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "Planet", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, { "name": "setMoon", "description": "Defines if a planet is actually a moon or not.", @@ -1653,11 +1714,52 @@ "possibleTypes": null }, { - "kind": "SCALAR", + "kind": "INPUT_OBJECT", + "name": "InputPatchPlanet", + "description": "A planet in the Star Wars universe.", + "fields": null, + "inputFields": [ + { + "name": "name", + "description": null, + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "defaultValue": null + }, + { + "name": "satelitesCount", + "description": null, + "type": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + }, + "defaultValue": null + }, + { + "name": "isMoon", + "description": null, + "type": { + "kind": "SCALAR", + "name": "Boolean", + "ofType": null + }, + "defaultValue": null + } + ], + "interfaces": null, + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "INPUT_OBJECT", "name": "ObjectListFilter", "description": "The `Filter` scalar type represents a filter on one or more fields of an object in an object list. The filter is represented by a JSON object where the fields are the complemented by specific suffixes to represent a query.", "fields": null, - "inputFields": null, + "inputFields": [], "interfaces": null, "enumValues": null, "possibleTypes": null diff --git a/tests/FSharp.Data.GraphQL.Tests/AbstractionTests.fs b/tests/FSharp.Data.GraphQL.Tests/AbstractionTests.fs index 98297ab33..1eb3fe22f 100644 --- a/tests/FSharp.Data.GraphQL.Tests/AbstractionTests.fs +++ b/tests/FSharp.Data.GraphQL.Tests/AbstractionTests.fs @@ -101,7 +101,7 @@ let ``Execute handles execution of abstract types: isTypeOf is used to resolve r } }""" - let result = sync <| schemaWithInterface.Value.AsyncExecute (parse query) + let result = sync <| schemaWithInterface.Value.AsyncExecute (parse query, getMockInputContext) let expected = NameValueLookup.ofList @@ -126,7 +126,7 @@ let ``Execute handles execution of abstract types: not specified Interface types } }""" - let result = sync <| schemaWithInterface.Value.AsyncExecute (parse query) + let result = sync <| schemaWithInterface.Value.AsyncExecute (parse query, getMockInputContext) let expected = NameValueLookup.ofList [ "name", "Odie" :> obj; "woofs", upcast true ] @@ -146,7 +146,7 @@ let ``Execute handles execution of abstract types: not specified Interface types } }""" - let result = sync <| schemaWithInterface.Value.AsyncExecute (parse query) + let result = sync <| schemaWithInterface.Value.AsyncExecute (parse query, getMockInputContext) let expected = NameValueLookup.ofList [ "name", "Garfield" :> obj; "meows", upcast false ] @@ -173,7 +173,7 @@ let ``Execute handles execution of abstract types: absent field resolution produ } }""" - let result = sync <| schemaWithInterface.Value.AsyncExecute (parse query) + let result = sync <| schemaWithInterface.Value.AsyncExecute (parse query, getMockInputContext) ensureRequestError result <| fun [ dogError; catError ] -> dogError |> ensureValidationError "Field 'unknownField1' is not defined in schema type 'Dog'." [ "pets"; "unknownField1" ] catError |> ensureValidationError "Field 'unknownField2' is not defined in schema type 'Cat'." [ "pets"; "unknownField2" ] @@ -195,7 +195,7 @@ let ``Execute handles execution of abstract types: absent type resolution produc } }""" - let result = sync <| schemaWithInterface.Value.AsyncExecute (parse query) + let result = sync <| schemaWithInterface.Value.AsyncExecute (parse query, getMockInputContext) ensureRequestError result <| fun [ catError; dogError ] -> 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" ] @@ -215,7 +215,7 @@ let ``Execute handles execution of abstract types: absent type resolution produc } }""" - let result = sync <| schemaWithInterface.Value.AsyncExecute (parse query) + let result = sync <| schemaWithInterface.Value.AsyncExecute (parse query, getMockInputContext) 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" ] @@ -272,7 +272,7 @@ let ``Execute handles execution of abstract types: isTypeOf is used to resolve r } }""" - let result = sync <| schemaWithUnion.Value.AsyncExecute (parse query) + let result = sync <| schemaWithUnion.Value.AsyncExecute (parse query, getMockInputContext) let expected = NameValueLookup.ofList @@ -297,7 +297,7 @@ let ``Execute handles execution of abstract types: not specified Union types mus } }""" - let result = sync <| schemaWithUnion.Value.AsyncExecute (parse query) + let result = sync <| schemaWithUnion.Value.AsyncExecute (parse query, getMockInputContext) let expected = NameValueLookup.ofList [ "name", "Odie" :> obj; "woofs", upcast true ] @@ -317,7 +317,7 @@ let ``Execute handles execution of abstract types: not specified Union types mus } }""" - let result = sync <| schemaWithUnion.Value.AsyncExecute (parse query) + let result = sync <| schemaWithUnion.Value.AsyncExecute (parse query, getMockInputContext) let expected = NameValueLookup.ofList [ "name", "Garfield" :> obj; "meows", upcast false ] @@ -344,7 +344,7 @@ let ``Execute handles execution of abstract types: absent field resolution produ } }""" - let result = sync <| schemaWithInterface.Value.AsyncExecute (parse query) + let result = sync <| schemaWithInterface.Value.AsyncExecute (parse query, getMockInputContext) ensureRequestError result <| fun [ dogError; catError ] -> dogError |> ensureValidationError "Field 'unknownField1' is not defined in schema type 'Dog'." [ "pets"; "unknownField1" ] catError |> ensureValidationError "Field 'unknownField2' is not defined in schema type 'Cat'." [ "pets"; "unknownField2" ] @@ -366,7 +366,7 @@ let ``Execute handles execution of abstract types: absent type resolution produc } }""" - let result = sync <| schemaWithInterface.Value.AsyncExecute (parse query) + let result = sync <| schemaWithInterface.Value.AsyncExecute (parse query, getMockInputContext) ensureRequestError result <| fun [ dogError; catError ] -> dogError |> ensureValidationError "Field 'unknownField1' is not defined in schema type 'Dog'." [ "pets"; "unknownField1" ] catError |> ensureValidationError "Inline fragment has type condition 'UnknownCat', but that type does not exist in the schema." [ "pets" ] diff --git a/tests/FSharp.Data.GraphQL.Tests/DeferredTests.fs b/tests/FSharp.Data.GraphQL.Tests/DeferredTests.fs index 8c45c458a..3ce28c0d7 100644 --- a/tests/FSharp.Data.GraphQL.Tests/DeferredTests.fs +++ b/tests/FSharp.Data.GraphQL.Tests/DeferredTests.fs @@ -286,7 +286,7 @@ let ``Resolver error`` () = } } }""" - let result = query |> executor.AsyncExecute |> sync + let result = executor.AsyncExecute(query, getMockInputContext) |> sync ensureDeferred result <| fun data errors deferred -> empty errors data |> equals (upcast expectedDirect) @@ -321,7 +321,7 @@ let ``Resolver list error`` () = } } }""" - let result = query |> executor.AsyncExecute |> sync + let result = executor.AsyncExecute(query, getMockInputContext) |> sync ensureDeferred result <| fun data errors deferred -> empty errors data |> equals (upcast expectedDirect) @@ -354,7 +354,7 @@ let ``Nullable error`` () = } } }""" - let result = query |> executor.AsyncExecute |> sync + let result = executor.AsyncExecute(query, getMockInputContext) |> sync ensureDeferred result <| fun data errors deferred -> empty errors data |> equals (upcast expectedDirect) @@ -386,7 +386,7 @@ let ``Single Root object field - Defer and Stream`` () = } } }""" - let result = query |> executor.AsyncExecute |> sync + let result = executor.AsyncExecute(query, getMockInputContext) |> sync ensureDeferred result <| fun data errors deferred -> empty errors data |> equals (upcast expectedDirect) @@ -423,7 +423,7 @@ let ``Single Root object list field - Defer`` () = } } }""" - let result = query |> executor.AsyncExecute |> sync + let result = executor.AsyncExecute(query, getMockInputContext) |> sync ensureDeferred result <| fun data errors deferred -> empty errors data |> equals (upcast expectedDirect) @@ -465,7 +465,7 @@ let ``Single Root object list field - Stream`` () = } } }""" - let result = query |> executor.AsyncExecute |> sync + let result = executor.AsyncExecute(query, getMockInputContext) |> sync ensureDeferred result <| fun data errors deferred -> empty errors data |> equals (upcast expectedDirect) @@ -497,7 +497,7 @@ let ``Interface field - Defer`` () = } } }""" - let result = query |> executor.AsyncExecute |> sync + let result = executor.AsyncExecute(query, getMockInputContext) |> sync ensureDeferred result <| fun data errors deferred -> empty errors data |> equals (upcast expectedDirect) @@ -532,7 +532,7 @@ let ``Interface list field - Defer`` () = } } }""" - let result = query |> executor.AsyncExecute |> sync + let result = executor.AsyncExecute(query, getMockInputContext) |> sync ensureDeferred result <| fun data errors deferred -> empty errors data |> equals (upcast expectedDirect) @@ -572,7 +572,7 @@ let ``Each live result should be sent as soon as it is computed`` () = use mre1 = new ManualResetEvent(false) use mre2 = new ManualResetEvent(false) resetLiveData() - let result = query |> executor.AsyncExecute |> sync + let result = executor.AsyncExecute(query, getMockInputContext) |> sync ensureDeferred result <| fun data errors deferred -> empty errors data |> equals (upcast expectedDirect) @@ -611,7 +611,7 @@ let ``Live Query`` () = } }""" resetLiveData() - let result = query |> executor.AsyncExecute |> sync + let result = executor.AsyncExecute(query, getMockInputContext) |> sync ensureDeferred result <| fun data errors deferred -> empty errors data |> equals (upcast expectedDirect) @@ -653,7 +653,7 @@ let ``Parallel Defer`` () = } } }""" - let result = query |> executor.AsyncExecute |> sync + let result = executor.AsyncExecute(query, getMockInputContext) |> sync ensureDeferred result <| fun data errors deferred -> empty errors data |> equals (upcast expectedDirect) @@ -705,7 +705,7 @@ let ``Parallel Stream`` () = } } }""" - let result = query |> executor.AsyncExecute |> sync + let result = executor.AsyncExecute(query, getMockInputContext) |> sync ensureDeferred result <| fun data errors deferred -> empty errors data |> equals (upcast expectedDirect) @@ -742,7 +742,7 @@ let ``Inner Object List Defer`` () = } } }""" - let result = query |> executor.AsyncExecute |> sync + let result = executor.AsyncExecute(query, getMockInputContext) |> sync ensureDeferred result <| fun data errors deferred -> empty errors data |> equals (upcast expectedDirect) @@ -775,7 +775,7 @@ let ``Inner Object List Stream`` () = } } }""" - let result = query |> executor.AsyncExecute |> sync + let result = executor.AsyncExecute(query, getMockInputContext) |> sync ensureDeferred result <| fun data errors deferred -> empty errors data |> equals (upcast expectedDirect) @@ -823,7 +823,7 @@ let ``Nested Inner Object List Defer`` () = } } }""" - let result = query |> executor.AsyncExecute |> sync + let result = executor.AsyncExecute(query, getMockInputContext) |> sync ensureDeferred result <| fun data errors deferred -> empty errors data |> equals (upcast expectedDirect) @@ -880,7 +880,7 @@ let ``Nested Inner Object List Stream`` () = } } }""" - let result = query |> executor.AsyncExecute |> sync + let result = executor.AsyncExecute(query, getMockInputContext) |> sync ensureDeferred result <| fun data errors deferred -> empty errors data |> equals (upcast expectedDirect) @@ -909,7 +909,7 @@ let ``Simple Defer and Stream`` () = b } }""" - let result = query |> executor.AsyncExecute |> sync + let result = executor.AsyncExecute(query, getMockInputContext) |> sync ensureDeferred result <| fun data errors deferred -> empty errors data |> equals (upcast expectedDirect) @@ -955,7 +955,7 @@ let ``List Defer``() = } } }""" - let result = query |> executor.AsyncExecute |> sync + let result = executor.AsyncExecute(query, getMockInputContext) |> sync ensureDeferred result <| fun data errors deferred -> empty errors data |> equals (upcast expectedDirect) @@ -982,7 +982,7 @@ let ``List Fragment Defer and Stream - Exclusive``() = ] ] let expectedDeferred = DeferredResult ("Union A", [ "testData"; "list"; 0; "a" ]) - let query = sprintf """{ + let query = """{ testData { a list { @@ -997,7 +997,7 @@ let ``List Fragment Defer and Stream - Exclusive``() = } } }""" - let result = query |> executor.AsyncExecute |> sync + let result = executor.AsyncExecute(query, getMockInputContext) |> sync ensureDeferred result <| fun data errors deferred -> empty errors data |> equals (upcast expectedDirect) @@ -1024,7 +1024,7 @@ let ``List Fragment Defer and Stream - Common``() = ] ] let expectedDeferred = DeferredResult ("2", [ "testData"; "list"; 0; "id" ]) - let query = sprintf """{ + let query = """{ testData { a list { @@ -1039,7 +1039,7 @@ let ``List Fragment Defer and Stream - Common``() = } } }""" - let result = query |> executor.AsyncExecute |> sync + let result = executor.AsyncExecute(query, getMockInputContext) |> sync ensureDeferred result <| fun data errors deferred -> empty errors data |> equals (upcast expectedDirect) @@ -1083,7 +1083,7 @@ let ``List inside root - Stream``() = } } }""" - let result = query |> executor.AsyncExecute |> sync + let result = executor.AsyncExecute(query, getMockInputContext) |> sync ensureDeferred result <| fun data errors deferred -> empty errors data |> equals (upcast expectedDirect) @@ -1137,7 +1137,7 @@ let ``List Stream``() = } } }""" - let result = query |> executor.AsyncExecute |> sync + let result = executor.AsyncExecute(query, getMockInputContext) |> sync ensureDeferred result <| fun data errors deferred -> empty errors data |> equals (upcast expectedDirect) @@ -1188,7 +1188,7 @@ let ``Should buffer stream list correctly by timing information``() = |> parse use mre1 = new ManualResetEvent(false) use mre2 = new ManualResetEvent(false) - let result = query |> executor.AsyncExecute |> sync + let result = executor.AsyncExecute(query, getMockInputContext) |> sync ensureDeferred result <| fun data errors deferred -> empty errors data |> equals (upcast expectedDirect) @@ -1249,7 +1249,7 @@ let ``Should buffer stream list correctly by count information``() = }""" use mre1 = new ManualResetEvent(false) use mre2 = new ManualResetEvent(false) - let result = query |> executor.AsyncExecute |> sync + let result = executor.AsyncExecute(query, getMockInputContext) |> sync ensureDeferred result <| fun data errors deferred -> empty errors data |> equals (upcast expectedDirect) @@ -1290,7 +1290,7 @@ let ``Union Defer`` () = NameValueLookup.ofList [ "id", upcast "1"; "a", upcast "Union A" ], [ "testData"; "union" ] ) - let query = sprintf """{ + let query = """{ testData { a b @@ -1306,7 +1306,7 @@ let ``Union Defer`` () = } } }""" - let result = query |> executor.AsyncExecute |> sync + let result = executor.AsyncExecute(query, getMockInputContext) |> sync ensureDeferred result <| fun data errors deferred -> empty errors data |> equals (upcast expectedDirect) @@ -1336,7 +1336,7 @@ let ``Each deferred result should be sent as soon as it is computed``() = }""" use mre1 = new ManualResetEvent(false) use mre2 = new ManualResetEvent(false) - let result = query |> executor.AsyncExecute |> sync + let result = executor.AsyncExecute(query, getMockInputContext) |> sync ensureDeferred result <| fun data errors deferred -> empty errors data |> equals (upcast expectedDirect) @@ -1345,7 +1345,7 @@ let ``Each deferred result should be sent as soon as it is computed``() = elif Seq.length sub.Received = 2 then mre2.Set() |> ignore) // The second result is a delayed async field, which is set to compute the value for 5 seconds. // The first result should come almost instantly, as it is not a delayed computed field. - // Therefore, let's assume that if it does not come in at least 3 seconds, test has failed. + // Therefore, let's assume that if it does not come in at least 3 seconds, the test has failed. if TimeSpan.FromSeconds(float (ms 3)) |> mre1.WaitOne |> not then fail "Timeout while waiting for first deferred result" if TimeSpan.FromSeconds(float (ms 10)) |> mre2.WaitOne |> not @@ -1383,7 +1383,7 @@ let ``Each deferred result of a list should be sent as soon as it is computed`` }""" use mre1 = new ManualResetEvent(false) use mre2 = new ManualResetEvent(false) - let result = query |> executor.AsyncExecute |> sync + let result = executor.AsyncExecute(query, getMockInputContext) |> sync ensureDeferred result <| fun data errors deferred -> empty errors data |> equals (upcast expectedDirect) @@ -1392,7 +1392,7 @@ let ``Each deferred result of a list should be sent as soon as it is computed`` elif Seq.length sub.Received = 2 then mre2.Set() |> ignore) // The first result is a delayed async field, which is set to compute the value for 5 seconds. // The second result should come first, almost instantly, as it is not a delayed computed field. - // Therefore, let's assume that if it does not come in at least 4 seconds, test has failed. + // Therefore, let's assume that if it does not come in at least 4 seconds, the test has failed. if TimeSpan.FromSeconds(float (ms 4)) |> mre1.WaitOne |> not then fail "Timeout while waiting for first deferred result" if TimeSpan.FromSeconds(float (ms 10)) |> mre2.WaitOne |> not @@ -1425,7 +1425,7 @@ let ``Each streamed result should be sent as soon as it is computed - async seq` }""" use mre1 = new ManualResetEvent(false) use mre2 = new ManualResetEvent(false) - let result = query |> executor.AsyncExecute |> sync + let result = executor.AsyncExecute(query, getMockInputContext) |> sync ensureDeferred result <| fun data errors deferred -> empty errors data |> equals (upcast expectedDirect) diff --git a/tests/FSharp.Data.GraphQL.Tests/DirectivesTests.fs b/tests/FSharp.Data.GraphQL.Tests/DirectivesTests.fs index a46cc2856..222cc0696 100644 --- a/tests/FSharp.Data.GraphQL.Tests/DirectivesTests.fs +++ b/tests/FSharp.Data.GraphQL.Tests/DirectivesTests.fs @@ -17,7 +17,7 @@ let schema = Schema(Define.Object("TestType", [ Define.AutoField("a", StringType); Define.AutoField("b", StringType) ])) :> Schema let private execAndCompare query expected = - let result = sync <| Executor(schema).AsyncExecute(parse query, data) + let result = sync <| Executor(schema).AsyncExecute(parse query, getMockInputContext, data) ensureDirect result <| fun data errors -> empty errors data |> equals (upcast expected) diff --git a/tests/FSharp.Data.GraphQL.Tests/ExecutionTests.fs b/tests/FSharp.Data.GraphQL.Tests/ExecutionTests.fs index a1820de10..07bc58271 100644 --- a/tests/FSharp.Data.GraphQL.Tests/ExecutionTests.fs +++ b/tests/FSharp.Data.GraphQL.Tests/ExecutionTests.fs @@ -143,7 +143,7 @@ let ``Execution handles basic tasks: executes arbitrary code`` () = let schema = Schema(DataType) let schemaProcessor = Executor(schema) let params' = JsonDocument.Parse("""{"size":100}""").RootElement.Deserialize>(serializerOptions) - let result = sync <| schemaProcessor.AsyncExecute(ast, data, variables = params', operationName = "Example") + let result = sync <| schemaProcessor.AsyncExecute(ast, getMockInputContext, data, variables = params', operationName = "Example") ensureDirect result <| fun data errors -> empty errors data |> equals (upcast expected) @@ -191,7 +191,7 @@ let ``Execution handles basic tasks: merges parallel fragments`` () = ] "c", upcast "Cherry" ] - let result = sync <| schemaProcessor.AsyncExecute(ast, obj()) + let result = sync <| schemaProcessor.AsyncExecute(ast, getMockInputContext, obj()) ensureDirect result <| fun data errors -> empty errors data |> equals (upcast expected) @@ -201,8 +201,8 @@ let ``Execution handles basic tasks: threads root value context correctly`` () = let query = "query Example { a }" let data = { Thing = "" } let Thing = Define.Object("Type", [ Define.Field("a", StringType, fun _ value -> value.Thing <- "thing"; value.Thing) ]) - let result = sync <| Executor(Schema(Thing)).AsyncExecute(parse query, data) - ensureDirect result <| fun data errors -> empty errors + let result = sync <| Executor(Schema(Thing)).AsyncExecute(parse query, getMockInputContext, data) + ensureDirect result <| fun _ errors -> empty errors equals "thing" data.Thing type TestTarget = @@ -223,8 +223,8 @@ let ``Execution handles basic tasks: correctly threads arguments`` () = value.Str <- ctx.TryArg("stringArg") value.Str) ]) - let result = sync <| Executor(Schema(Type)).AsyncExecute(parse query, data) - ensureDirect result <| fun data errors -> empty errors + let result = sync <| Executor(Schema(Type)).AsyncExecute(parse query, getMockInputContext,data) + ensureDirect result <| fun _ errors -> empty errors equals (ValueSome 123) data.Num equals (ValueSome "foo") data.Str @@ -242,8 +242,8 @@ let ``Execution handles basic tasks: correctly handles null arguments`` () = value.Str <- ctx.TryArg("stringArg") value.Str) ]) - let result = sync <| Executor(Schema(Type)).AsyncExecute(parse query, data) - ensureDirect result <| fun data errors -> empty errors + let result = sync <| Executor(Schema(Type)).AsyncExecute(parse query, getMockInputContext, data) + ensureDirect result <| fun _ errors -> empty errors equals ValueNone data.Num equals ValueNone data.Str @@ -272,8 +272,8 @@ let ``Execution handles basic tasks: correctly handles discriminated union argum value.Num <- ValueSome 123 value.Str | _ -> ValueNone) ]) - let result = sync <| Executor(Schema(Type)).AsyncExecute(parse query, data) - ensureDirect result <| fun data errors -> empty errors + let result = sync <| Executor(Schema(Type)).AsyncExecute(parse query, getMockInputContext, data) + ensureDirect result <| fun _ errors -> empty errors equals (ValueSome 123) data.Num equals (ValueSome "foo") data.Str @@ -300,8 +300,8 @@ let ``Execution handles basic tasks: correctly handles Enum arguments`` () = value.Num <- ValueSome 123 value.Str | _ -> ValueNone) ]) - let result = sync <| Executor(Schema(Type)).AsyncExecute(parse query, data) - ensureDirect result <| fun data errors -> empty errors + let result = sync <| Executor(Schema(Type)).AsyncExecute(parse query, getMockInputContext, data) + ensureDirect result <| fun _ errors -> empty errors equals (ValueSome 123) data.Num equals (ValueSome "foo") data.Str @@ -313,7 +313,7 @@ let ``Execution handles basic tasks: uses the inline operation if no operation n "Type", [ Define.Field("a", StringType, fun _ x -> x.A) ])) - let result = sync <| Executor(schema).AsyncExecute(parse "{ a }", { A = "b" }) + let result = sync <| Executor(schema).AsyncExecute(parse "{ a }", getMockInputContext, { A = "b" }) ensureDirect result <| fun data errors -> empty errors data |> equals (upcast NameValueLookup.ofList ["a", "b" :> obj]) @@ -325,7 +325,7 @@ let ``Execution handles basic tasks: uses the only operation if no operation nam "Type", [ Define.Field("a", StringType, fun _ x -> x.A) ])) - let result = sync <| Executor(schema).AsyncExecute(parse "query Example { a }", { A = "b" }) + let result = sync <| Executor(schema).AsyncExecute(parse "query Example { a }", getMockInputContext, { A = "b" }) ensureDirect result <| fun data errors -> empty errors data |> equals (upcast NameValueLookup.ofList ["a", "b" :> obj]) @@ -338,7 +338,7 @@ let ``Execution handles basic tasks: uses the named operation if operation name Define.Field("a", StringType, fun _ x -> x.A) ])) let query = "query Example { first: a } query OtherExample { second: a }" - let result = sync <| Executor(schema).AsyncExecute(parse query, { A = "b" }, operationName = "OtherExample") + let result = sync <| Executor(schema).AsyncExecute(parse query, getMockInputContext, { A = "b" }, operationName = "OtherExample") ensureDirect result <| fun data errors -> empty errors data |> equals (upcast NameValueLookup.ofList ["second", "b" :> obj]) @@ -350,7 +350,7 @@ let ``Execution handles basic tasks: list of scalars`` () = "Type", [ Define.Field("strings", ListOf StringType, fun _ _ -> ["foo"; "bar"; "baz"]) ])) - let result = sync <| Executor(schema).AsyncExecute("query Example { strings }") + let result = sync <| Executor(schema).AsyncExecute("query Example { strings }", getMockInputContext) ensureDirect result <| fun data errors -> empty errors data |> equals (upcast NameValueLookup.ofList ["strings", box [ box "foo"; upcast "bar"; upcast "baz" ]]) @@ -366,7 +366,7 @@ let ``Execution when querying the same field twice will return it`` () = Define.Field("b", IntType, fun _ x -> x.B) ])) let query = "query Example { a, b, a }" - let result = sync <| Executor(schema).AsyncExecute(query, { A = "aa"; B = 2 }); + let result = sync <| Executor(schema).AsyncExecute(query, getMockInputContext, { A = "aa"; B = 2 }); let expected = NameValueLookup.ofList [ "a", upcast "aa" @@ -383,8 +383,8 @@ let ``Execution when querying returns unique document id with response`` () = Define.Field("a", StringType, fun _ x -> x.A) Define.Field("b", IntType, fun _ x -> x.B) ])) - let result1 = sync <| Executor(schema).AsyncExecute("query Example { a, b, a }", { A = "aa"; B = 2 }) - let result2 = sync <| Executor(schema).AsyncExecute("query Example { a, b, a }", { A = "aa"; B = 2 }) + let result1 = sync <| Executor(schema).AsyncExecute("query Example { a, b, a }", getMockInputContext, { A = "aa"; B = 2 }) + let result2 = sync <| Executor(schema).AsyncExecute("query Example { a, b, a }", getMockInputContext, { A = "aa"; B = 2 }) result1.DocumentId |> notEquals Unchecked.defaultof result1.DocumentId |> equals result2.DocumentId match result1,result2 with @@ -434,7 +434,7 @@ let ``Execution handles errors: properly propagates errors`` () = ] let result = let variables = { Inner = { Kaboom = null }; InnerPartialSuccess = { Kaboom = "Yes, Rico, Kaboom" } } - sync <| Executor(schema).AsyncExecute("query Example { inner { kaboom } partialSuccess { kaboom } }", variables) + sync <| Executor(schema).AsyncExecute("query Example { inner { kaboom } partialSuccess { kaboom } }", getMockInputContext, variables) ensureDirect result <| fun data errors -> result.DocumentId |> notEquals Unchecked.defaultof data |> equals (upcast expectedData) @@ -448,7 +448,7 @@ let ``Execution handles errors: exceptions`` () = Define.Field("a", StringType, fun _ _ -> failwith "Resolver Error!") ])) let expectedError = GQLProblemDetails.CreateWithKind ("Resolver Error!", Execution, [ box "a" ]) - let result = sync <| Executor(schema).AsyncExecute("query Test { a }", ()) + let result = sync <| Executor(schema).AsyncExecute("query Test { a }", getMockInputContext, ()) ensureRequestError result <| fun [ error ] -> error |> equals expectedError [] @@ -472,7 +472,7 @@ let ``Execution handles errors: nullable list fields`` () = GQLProblemDetails.CreateWithKind ("Resolver Error!", Execution, [ box "list"; 0; "error" ]) GQLProblemDetails.CreateWithKind ("Resolver Error!", Execution, [ box "list"; 1; "error" ]) ] - let result = sync <| Executor(schema).AsyncExecute("query Test { list { error } }", ()) + let result = sync <| Executor(schema).AsyncExecute("query Test { list { error } }", getMockInputContext, ()) ensureDirect result <| fun data errors -> result.DocumentId |> notEquals Unchecked.defaultof data |> equals (upcast expectedData) @@ -480,12 +480,12 @@ let ``Execution handles errors: nullable list fields`` () = [] -let ``Execution handles errors: additional error added when exception is rised in a nullable field resolver`` () = +let ``Execution handles errors: additional error added when exception is raised in a nullable field resolver`` () = let InnerNullableExceptionObjType = // executeResolvers/resolveWith, case 1 let resolveWithException (ctx : ResolveFieldContext) (_ : InnerNullableTest) : string option = ctx.AddError { new IGQLError with member _.Message = "Non-critical error" } - raise (System.Exception "Unexpected error") + raise (Exception "Unexpected error") Define.Object( "InnerNullableException", [ Define.Field("kaboom", Nullable StringType, resolve = resolveWithException) @@ -508,7 +508,7 @@ let ``Execution handles errors: additional error added when exception is rised i ] let result = let variables = { Inner = { Kaboom = null }; InnerPartialSuccess = { Kaboom = "Yes, Rico, Kaboom" } } - sync <| Executor(schema).AsyncExecute("query Example { inner { kaboom } }", variables) + sync <| Executor(schema).AsyncExecute("query Example { inner { kaboom } }", getMockInputContext, variables) ensureDirect result <| fun data errors -> result.DocumentId |> notEquals Unchecked.defaultof data |> equals (upcast expectedData) @@ -542,7 +542,7 @@ let ``Execution handles errors: additional error added when None returned from a ] let result = let variables = { Inner = { Kaboom = null }; InnerPartialSuccess = { Kaboom = "Yes, Rico, Kaboom" } } - sync <| Executor(schema).AsyncExecute("query Example { inner { kaboom } }", variables) + sync <| Executor(schema).AsyncExecute("query Example { inner { kaboom } }", getMockInputContext, variables) ensureDirect result <| fun data errors -> result.DocumentId |> notEquals Unchecked.defaultof data |> equals (upcast expectedData) @@ -554,7 +554,7 @@ let ``Execution handles errors: additional error added when exception is rised i // executeResolvers/resolveWith, case 3 let resolveWithException (ctx : ResolveFieldContext) (_ : InnerNullableTest) : string = ctx.AddError { new IGQLError with member _.Message = "Non-critical error" } - raise (System.Exception "Fatal error") + raise (Exception "Fatal error") Define.Object( "InnerNonNullableException", [ Define.Field("kaboom", StringType, resolve = resolveWithException) @@ -571,7 +571,7 @@ let ``Execution handles errors: additional error added when exception is rised i ] let result = let variables = { Inner = { Kaboom = "Yes, Rico, Kaboom" }; InnerPartialSuccess = { Kaboom = "Yes, Rico, Kaboom" } } - sync <| Executor(schema).AsyncExecute("query Example { inner { kaboom } }", variables) + sync <| Executor(schema).AsyncExecute("query Example { inner { kaboom } }", getMockInputContext, variables) ensureRequestError result <| fun errors -> result.DocumentId |> notEquals Unchecked.defaultof errors |> equals expectedErrors @@ -599,7 +599,7 @@ let ``Execution handles errors: additional error added and when null returned fr ] let result = let variables = { Inner = { Kaboom = "Yes, Rico, Kaboom" }; InnerPartialSuccess = { Kaboom = "Yes, Rico, Kaboom" } } - sync <| Executor(schema).AsyncExecute("query Example { inner { kaboom } }", variables) + sync <| Executor(schema).AsyncExecute("query Example { inner { kaboom } }", getMockInputContext, variables) ensureRequestError result <| fun errors -> result.DocumentId |> notEquals Unchecked.defaultof errors |> equals expectedErrors diff --git a/tests/FSharp.Data.GraphQL.Tests/ExecutorMiddlewareTests.fs b/tests/FSharp.Data.GraphQL.Tests/ExecutorMiddlewareTests.fs index 6611603b4..0039c3cbc 100644 --- a/tests/FSharp.Data.GraphQL.Tests/ExecutorMiddlewareTests.fs +++ b/tests/FSharp.Data.GraphQL.Tests/ExecutorMiddlewareTests.fs @@ -55,10 +55,10 @@ let compileMiddleware (ctx : SchemaCompileContext) (next : SchemaCompileContext // After the schema has been compiled, update the input fields to flip every boolean input let postCompileMiddleware (schema : ISchema) (next : ISchema -> unit) = - let flipBools execute value vars = + let flipBools ctx execute value vars = match value with - | BooleanValue b -> execute (BooleanValue (not b)) vars - | _ -> execute value vars + | BooleanValue b -> ctx execute (BooleanValue (not b)) vars + | _ -> ctx execute value vars schema.TypeMap.ToSeq() |> Seq.iter(fun (n, def) -> match def with @@ -79,7 +79,7 @@ let planningMiddleware (ctx : PlanningContext) (next : PlanningContext -> Execut { result with Metadata = metadata } // On the execution phase, we remove the evaluation of the c field -let executionMiddleware (ctx : ExecutionContext) (next : ExecutionContext -> AsyncVal) = +let executionMiddleware (inputContext : InputExecutionContextProvider) (ctx : ExecutionContext) (next : ExecutionContext -> AsyncVal) = let chooserS set = set |> List.choose (fun x -> match x with Field f when f.Name <> "c" -> Some x | _ -> None) let chooserK kind = @@ -108,7 +108,7 @@ let executor = Executor(schema, [ middleware ]) [] let ``Executor middleware: change fields and measure planning time`` () = - let result = sync <| executor.AsyncExecute(ast) + let result = sync <| executor.AsyncExecute(ast, getMockInputContext) let expected = NameValueLookup.ofList [ "testData", diff --git a/tests/FSharp.Data.GraphQL.Tests/FSharp.Data.GraphQL.Tests.fsproj b/tests/FSharp.Data.GraphQL.Tests/FSharp.Data.GraphQL.Tests.fsproj index 0b0390c21..a640b5388 100644 --- a/tests/FSharp.Data.GraphQL.Tests/FSharp.Data.GraphQL.Tests.fsproj +++ b/tests/FSharp.Data.GraphQL.Tests/FSharp.Data.GraphQL.Tests.fsproj @@ -70,6 +70,7 @@ + diff --git a/tests/FSharp.Data.GraphQL.Tests/FileTests.fs b/tests/FSharp.Data.GraphQL.Tests/FileTests.fs new file mode 100644 index 000000000..42dfc93d3 --- /dev/null +++ b/tests/FSharp.Data.GraphQL.Tests/FileTests.fs @@ -0,0 +1,120 @@ +module FSharp.Data.GraphQL.Tests.FileTests + +open System.Collections.Immutable +open System.IO +open System.Text +open System.Text.Json +open FSharp.Data.GraphQL +open FSharp.Data.GraphQL.Parser +open FSharp.Data.GraphQL.Types +open Xunit + +type Root = { File : Stream } + +let QueryType = + Define.Object ( + name = "QueryType", + fields = + [ Define.Field ("dummy", StringType, fun _ _ -> "dummy")] + ) + +type Input = { + File : Stream + File2 : Stream option +} + +let InputObject = Define.InputObject( + name = "Input", + fields = + [ + Define.Input("file", FileType) + Define.Input("file2", Nullable FileType) + ]) + +let MutationType = + Define.Object ( + name = "MutationType", + fields = + [ Define.Field ("uploadFile", StringType, "", [ Define.Input ("input", FileType) ], + (fun ctx () -> + let stream = ctx.Arg "input" + use reader = new StreamReader(stream, Encoding.UTF8, true) + reader.ReadToEnd() + )); + Define.Field ("uploadFileComplex", StringType, "", [ Define.Input ("input", InputObject) ], + (fun ctx () -> + let input = ctx.Arg "input" + let reader = new StreamReader(input.File, Encoding.UTF8, true) + let fileContent = reader.ReadToEnd() + let file2Content = match input.File2 with + | Some file2 -> + let reader2 = new StreamReader(file2, Encoding.UTF8, true) + reader2.ReadToEnd() + | None -> "" + fileContent + file2Content + )) + ] + ) +let schema = Schema (QueryType, MutationType) +let executor = Executor (schema, []) +let execute (query : string) = executor.AsyncExecute (query, getMockInputContext) |> sync +let executeWithVariables ( query : string, variables : ImmutableDictionary) = + executor.AsyncExecute (ast = parse query, getInputContext = getMockInputContext, variables = variables) |> sync + +let mutationWithVariable = """mutation uploadFile ($file : FileType!) { + uploadFile (input : $file) +} +""" + +let mutationWithConstant = """mutation uploadFile () { + uploadFile (input : "fileKey") +}""" + +let mutationComplexObject = """mutation uploadFile () { + uploadFileComplex (input : { file : "fileKey" }) +}""" + +let mutationComplexObjectWithTwoFiles = """mutation uploadFile () { + uploadFileComplex (input : {file : "fileKey", file2: "fileKey2" }) +}""" + +[] +let ``File type: Must upload file as input scalar using inline string as a file name`` () = + let expected = NameValueLookup.ofList [ "uploadFile", MockInputContext.mockFileText ] + let result = execute mutationWithConstant + ensureDirect result <| fun data errors -> + empty errors + data |> equals (upcast expected) + () + +[] +let ``File type: Must upload a file as input scalar using a variable`` () = + let expected = NameValueLookup.ofList [ "uploadFile", MockInputContext.mockFileText ] + let jsonVariable = "\"fileKey\"" |> JsonDocument.Parse |> _.RootElement + let variables = ImmutableDictionary.Empty.Add ("file", jsonVariable) + let result = executeWithVariables (mutationWithVariable, variables) + ensureDirect result <| fun data errors -> + empty errors + data |> equals (upcast expected) + () + +[] +let ``File type: Must upload a file as input object field using inline string a file name`` () = + let expected = NameValueLookup.ofList [ "uploadFileComplex", MockInputContext.mockFileText ] + let result = execute mutationComplexObject + ensureDirect result <| fun data errors -> + empty errors + data |> equals (upcast expected) + () + +[] +let ``File type: Must upload two files as input object field using inline string a file name`` () = + let expectedContent = MockInputContext.mockFileText + MockInputContext.mockFileText2 + let expected = NameValueLookup.ofList [ + "uploadFileComplex", expectedContent + ] + let result = execute mutationComplexObjectWithTwoFiles + ensureDirect result <| fun data errors -> + empty errors + data |> equals (upcast expected) + () diff --git a/tests/FSharp.Data.GraphQL.Tests/Helpers and Extensions/NameValueLookupTests.fs b/tests/FSharp.Data.GraphQL.Tests/Helpers and Extensions/NameValueLookupTests.fs index 339419dfb..ed8c9bd20 100644 --- a/tests/FSharp.Data.GraphQL.Tests/Helpers and Extensions/NameValueLookupTests.fs +++ b/tests/FSharp.Data.GraphQL.Tests/Helpers and Extensions/NameValueLookupTests.fs @@ -2,7 +2,7 @@ open Helpers open Xunit -open FSharp.Data.GraphQL.Shared +open FSharp.Data.GraphQL [] let ``Lookups containing different lists as inner items should not be equal`` () = @@ -25,4 +25,4 @@ let ``Lookups containing different lists as inner items should not be equal`` () ] ] ] - lookup1 |> notEquals lookup2 \ No newline at end of file + lookup1 |> notEquals lookup2 diff --git a/tests/FSharp.Data.GraphQL.Tests/Helpers.fs b/tests/FSharp.Data.GraphQL.Tests/Helpers.fs index a814938be..12bad6429 100644 --- a/tests/FSharp.Data.GraphQL.Tests/Helpers.fs +++ b/tests/FSharp.Data.GraphQL.Tests/Helpers.fs @@ -5,12 +5,13 @@ module internal Helpers open System open System.Collections.Generic +open System.IO open System.Linq +open System.Text open System.Text.Json.Serialization open System.Threading -open System.Threading.Tasks -open Xunit open FSharp.Data.GraphQL +open Xunit let serializerOptions = Shared.Json.getWSSerializerOptions Seq.empty @@ -180,3 +181,38 @@ type ExecutorExtensions = match executor.CreateExecutionPlan(queryOrMutation, ?operationName = operationName, ?meta = meta) with | Ok executionPlan -> executionPlan | Error _ -> fail "invalid query"; Unchecked.defaultof<_> + + +module MockInputContext = + + let mockFileKey = "fileKey" + let mockFileKey2 = "fileKey2" + let mockFileText = "fileText" + let mockFileText2 = "fileText2" + + type MockInputExecutionContext () = + + member _.FileKey = mockFileKey + member _.FileKey2 = mockFileKey2 + member _.FileText = mockFileText + member _.FileText2 = mockFileText2 + member context.Stream = + let bytes = Encoding.UTF8.GetBytes context.FileText + new MemoryStream (bytes) :> Stream + + member context.Stream2 = + let bytes = Encoding.UTF8.GetBytes context.FileText2 + new MemoryStream (bytes) :> Stream + + interface IInputExecutionContext with + member context.GetFile key = + if (key = context.FileKey) then + Ok context.Stream + else if (key = context.FileKey2) then + Ok context.Stream2 + else + failwith $"only file {context.FileKey} and file {context.FileKey2} exist" + + let mockInputContextInstance = MockInputExecutionContext() + +let getMockInputContext = fun () -> MockInputContext.mockInputContextInstance :> IInputExecutionContext diff --git a/tests/FSharp.Data.GraphQL.Tests/IntrospectionTests.fs b/tests/FSharp.Data.GraphQL.Tests/IntrospectionTests.fs index a3cea4422..fa33e3147 100644 --- a/tests/FSharp.Data.GraphQL.Tests/IntrospectionTests.fs +++ b/tests/FSharp.Data.GraphQL.Tests/IntrospectionTests.fs @@ -50,7 +50,7 @@ let ``Input field must be marked as nullable when defaultValue is provided`` () ], fun _ _ -> "Only value") ]) let schema = Schema(root) - let result = sync <| Executor(schema).AsyncExecute(inputFieldQuery) + let result = sync <| Executor(schema).AsyncExecute(inputFieldQuery, getMockInputContext) let expected = NameValueLookup.ofList [ "__type", upcast NameValueLookup.ofList [ "fields", upcast [ @@ -90,7 +90,7 @@ let ``Input field must be marked as non-nullable when defaultValue is not provid ], fun _ _ -> "Only value") ]) let schema = Schema(root) - let result = sync <| Executor(schema).AsyncExecute(inputFieldQuery) + let result = sync <| Executor(schema).AsyncExecute(inputFieldQuery, getMockInputContext) let expected = NameValueLookup.ofList [ "__type", upcast NameValueLookup.ofList [ "fields", upcast [ @@ -122,7 +122,7 @@ let ``Input field must be marked as nullable when its type is nullable`` () = ], fun _ _ -> "Only value") ]) let schema = Schema(root) - let result = sync <| Executor(schema).AsyncExecute(inputFieldQuery) + let result = sync <| Executor(schema).AsyncExecute(inputFieldQuery, getMockInputContext) let expected = NameValueLookup.ofList [ "__type", upcast NameValueLookup.ofList [ "fields", upcast [ @@ -154,7 +154,7 @@ let ``Input field must be marked as nullable when its type is nullable and have ], fun _ _ -> "Only value") ]) let schema = Schema(root) - let result = sync <| Executor(schema).AsyncExecute(inputFieldQuery) + let result = sync <| Executor(schema).AsyncExecute(inputFieldQuery, getMockInputContext) let expected = NameValueLookup.ofList [ "__type", upcast NameValueLookup.ofList [ "fields", upcast [ @@ -282,7 +282,7 @@ let ``Introspection schema must be serializable back and forth using json`` () = } } }""" - let result = Executor(schema).AsyncExecute(query) |> sync + let result = Executor(schema).AsyncExecute(query, getMockInputContext) |> sync ensureDirect result <| fun data errors -> empty errors let additionalConverters = Seq.empty //seq { NameValueLookupConverter() :> JsonConverter } @@ -317,7 +317,7 @@ let ``Core type definitions are considered nullable`` () = } } } }""" - let result = sync <| Executor(schema).AsyncExecute(query) + let result = sync <| Executor(schema).AsyncExecute(query, getMockInputContext) let expected = NameValueLookup.ofList [ "__type", upcast NameValueLookup.ofList [ @@ -347,7 +347,7 @@ let ``Introspection works with query and mutation sharing same generic param`` ( Define.Object("Mutation", [ Define.Field("addUser", user, "Adds an user", [ Define.Input("input", userInput) ], fun _ u -> u |> List.head)]) let schema = Schema(query, mutation) - Executor(schema).AsyncExecute(IntrospectionQuery.Definition) |> sync |> ignore + Executor(schema).AsyncExecute(IntrospectionQuery.Definition, getMockInputContext) |> sync |> ignore [] let ``Default field type definitions are considered non-null`` () = @@ -374,7 +374,7 @@ let ``Default field type definitions are considered non-null`` () = } } } }""" - let result = sync <| Executor(schema).AsyncExecute(query) + let result = sync <| Executor(schema).AsyncExecute(query, getMockInputContext) let expected = NameValueLookup.ofList [ "__type", upcast NameValueLookup.ofList [ @@ -417,7 +417,7 @@ let ``Nullabe field type definitions are considered nullable`` () = } } } }""" - let result = sync <| Executor(schema).AsyncExecute(query) + let result = sync <| Executor(schema).AsyncExecute(query, getMockInputContext) let expected = NameValueLookup.ofList [ "__type", upcast NameValueLookup.ofList [ @@ -457,7 +457,7 @@ let ``StructNullabe field type definitions are considered nullable`` () = } } } }""" - let result = sync <| Executor(schema).AsyncExecute(query) + let result = sync <| Executor(schema).AsyncExecute(query, getMockInputContext) let expected = NameValueLookup.ofList [ "__type", upcast NameValueLookup.ofList [ @@ -499,7 +499,7 @@ let ``Default field args type definitions are considered non-null`` () = } } } }""" - let result = sync <| Executor(schema).AsyncExecute(query) + let result = sync <| Executor(schema).AsyncExecute(query, getMockInputContext) let expected = NameValueLookup.ofList [ "__type", upcast NameValueLookup.ofList [ @@ -546,7 +546,7 @@ let ``Nullable field args type definitions are considered nullable`` () = } } } }""" - let result = sync <| Executor(schema).AsyncExecute(query) + let result = sync <| Executor(schema).AsyncExecute(query, getMockInputContext) let expected = NameValueLookup.ofList [ "__type", upcast NameValueLookup.ofList [ @@ -590,7 +590,7 @@ let ``StructNullable field args type definitions are considered nullable`` () = } } } }""" - let result = sync <| Executor(schema).AsyncExecute(query) + let result = sync <| Executor(schema).AsyncExecute(query, getMockInputContext) let expected = NameValueLookup.ofList [ "__type", upcast NameValueLookup.ofList [ @@ -612,7 +612,7 @@ let ``Introspection executes an introspection query`` () = let root = Define.Object("QueryRoot", [ Define.Field("onlyField", StringType) ]) let schema = Schema(root) let (Patterns.Object raw) = root - let result = sync <| Executor(schema).AsyncExecute(parse IntrospectionQuery.Definition, raw) + let result = sync <| Executor(schema).AsyncExecute(parse IntrospectionQuery.Definition, getMockInputContext, raw) let expected = NameValueLookup.ofList [ "__schema", upcast NameValueLookup.ofList [ diff --git a/tests/FSharp.Data.GraphQL.Tests/MiddlewareTests.fs b/tests/FSharp.Data.GraphQL.Tests/MiddlewareTests.fs index 19b86c642..8859057e8 100644 --- a/tests/FSharp.Data.GraphQL.Tests/MiddlewareTests.fs +++ b/tests/FSharp.Data.GraphQL.Tests/MiddlewareTests.fs @@ -202,15 +202,15 @@ let getExecutor (expectedFilter : ObjectListFilter voption) = let executor = getExecutor (ValueNone) -let execute (query : Document) = executor.AsyncExecute (query) |> sync +let execute (query : Document) = executor.AsyncExecute (query, getMockInputContext) |> sync let executeWithVariables (query : Document, variables : ImmutableDictionary) = - executor.AsyncExecute (ast = query, variables = variables) + executor.AsyncExecute (ast = query, getInputContext = getMockInputContext, variables = variables) |> sync let executeAndVerifyFilter (query : Document, variables : ImmutableDictionary, filterToVerify : ObjectListFilter) = let ex = getExecutor (ValueSome filterToVerify) - ex.AsyncExecute (ast = query, variables = variables) |> sync + ex.AsyncExecute (ast = query, getInputContext = getMockInputContext, variables = variables) |> sync let expectedThresholdErrors : GQLProblemDetails list = [ GQLProblemDetails.Create ("Query complexity exceeds maximum threshold. Please reduce query complexity and try again.") diff --git a/tests/FSharp.Data.GraphQL.Tests/MutationTests.fs b/tests/FSharp.Data.GraphQL.Tests/MutationTests.fs index 5d1ac5ef5..1f1f16a23 100644 --- a/tests/FSharp.Data.GraphQL.Tests/MutationTests.fs +++ b/tests/FSharp.Data.GraphQL.Tests/MutationTests.fs @@ -24,9 +24,9 @@ type Root = x.NumberHolder.Number <- num return x.NumberHolder } - member x.ChangeFail(num): NumberHolder option = + member x.ChangeFail _: NumberHolder option = failwith "Cannot change number" - member x.AsyncChangeFail(num): Async = + member x.AsyncChangeFail _: Async = async { return failwith "Cannot change number" } @@ -64,7 +64,7 @@ let ``Execute handles mutation execution ordering: evaluates mutations serially` } }""" - let mutationResult = sync <| Executor(schema).AsyncExecute(parse query, {NumberHolder = {Number = 6}}) + let mutationResult = sync <| Executor(schema).AsyncExecute(parse query, getMockInputContext, {NumberHolder = {Number = 6}}) let expected = NameValueLookup.ofList [ "first", upcast NameValueLookup.ofList [ "theNumber", 1 :> obj] @@ -103,7 +103,7 @@ let ``Execute handles mutation execution ordering: evaluates mutations correctly }""" let data = {NumberHolder = {Number = 6}} - let mutationResult = sync <| Executor(schema).AsyncExecute(parse query, data) + let mutationResult = sync <| Executor(schema).AsyncExecute(parse query, getMockInputContext, data) let expected = NameValueLookup.ofList [ "first", upcast NameValueLookup.ofList [ "theNumber", 1 :> obj] diff --git a/tests/FSharp.Data.GraphQL.Tests/PropertyTrackerTests.fs b/tests/FSharp.Data.GraphQL.Tests/PropertyTrackerTests.fs index 46f48f0b2..e0914e9a4 100644 --- a/tests/FSharp.Data.GraphQL.Tests/PropertyTrackerTests.fs +++ b/tests/FSharp.Data.GraphQL.Tests/PropertyTrackerTests.fs @@ -134,7 +134,7 @@ let ``Property tracker can track indirect properties`` () = Tracker.Direct (track "LastName" typeof typeof, []) ] ) - let actual = tracker ImmutableDictionary.Empty info + let actual = tracker getMockInputContext ImmutableDictionary.Empty info actual |> equals expected [] @@ -175,5 +175,5 @@ let ``Property tracker can correctly jump over properties not being part of the ] ) - let actual = tracker ImmutableDictionary.Empty info + let actual = tracker getMockInputContext ImmutableDictionary.Empty info actual |> equals expected diff --git a/tests/FSharp.Data.GraphQL.Tests/Relay/ConnectionTests.fs b/tests/FSharp.Data.GraphQL.Tests/Relay/ConnectionTests.fs index 39c344723..769e57513 100644 --- a/tests/FSharp.Data.GraphQL.Tests/Relay/ConnectionTests.fs +++ b/tests/FSharp.Data.GraphQL.Tests/Relay/ConnectionTests.fs @@ -145,7 +145,7 @@ let ``Connection definition includes connection and edge fields for simple cases } } }""" - let result = sync <| Executor(schema).AsyncExecute (query) + let result = sync <| Executor(schema).AsyncExecute (query, getMockInputContext) let expected = NameValueLookup.ofList [ "strings", upcast NameValueLookup.ofList [ @@ -186,7 +186,7 @@ let ``Connection definition includes connection and edge fields for complex case } } }""" - let result = sync <| Executor(schema).AsyncExecute (query) + let result = sync <| Executor(schema).AsyncExecute (query, getMockInputContext) let expected = NameValueLookup.ofList [ "people", upcast NameValueLookup.ofList [ diff --git a/tests/FSharp.Data.GraphQL.Tests/Relay/CursorTests.fs b/tests/FSharp.Data.GraphQL.Tests/Relay/CursorTests.fs index 74172dac6..4071e057e 100644 --- a/tests/FSharp.Data.GraphQL.Tests/Relay/CursorTests.fs +++ b/tests/FSharp.Data.GraphQL.Tests/Relay/CursorTests.fs @@ -95,7 +95,7 @@ let ``Relay cursor works for types with nested fileds`` () = } }""" - let result = sync <| schemaProcessor.AsyncExecute (parse query) + let result = sync <| schemaProcessor.AsyncExecute (parse query, getMockInputContext) match result with | Direct (_, errors) -> empty errors diff --git a/tests/FSharp.Data.GraphQL.Tests/Relay/NodeTests.fs b/tests/FSharp.Data.GraphQL.Tests/Relay/NodeTests.fs index 540866ae9..aad296f5c 100644 --- a/tests/FSharp.Data.GraphQL.Tests/Relay/NodeTests.fs +++ b/tests/FSharp.Data.GraphQL.Tests/Relay/NodeTests.fs @@ -58,7 +58,7 @@ let schema = let execAndValidateNode (query : string) expectedDirect expectedDeferred = - let result = sync <| Executor(schema).AsyncExecute (query) + let result = sync <| Executor(schema).AsyncExecute (query, getMockInputContext) match expectedDeferred with | Some expectedDeferred -> ensureDeferred result diff --git a/tests/FSharp.Data.GraphQL.Tests/ResolveTests.fs b/tests/FSharp.Data.GraphQL.Tests/ResolveTests.fs index b1dfbf576..5d9c5975e 100644 --- a/tests/FSharp.Data.GraphQL.Tests/ResolveTests.fs +++ b/tests/FSharp.Data.GraphQL.Tests/ResolveTests.fs @@ -24,7 +24,7 @@ let ``Execute uses default resolve to accesses properties`` () = let schema = testSchema [ Define.AutoField ("test", StringType) ] let expected = NameValueLookup.ofList [ "test", "testValue" :> obj ] - let result = sync <| Executor(schema).AsyncExecute (parse "{ test }", { Test = "testValue" }) + let result = sync <| Executor(schema).AsyncExecute (parse "{ test }", getMockInputContext, { Test = "testValue" }) ensureDirect result <| fun data errors -> empty errors data |> equals (upcast expected) @@ -37,7 +37,7 @@ let ``Execute uses provided resolve function to accesses properties`` () = ] let expected = NameValueLookup.ofList [ "test", "testValueString" :> obj ] - let result = sync <| Executor(schema) .AsyncExecute (parse "{ test(a: \"String\") }", { Test = "testValue" }) + let result = sync <| Executor(schema) .AsyncExecute (parse "{ test(a: \"String\") }", getMockInputContext, { Test = "testValue" }) ensureDirect result <| fun data errors -> empty errors data |> equals (upcast expected) @@ -73,11 +73,11 @@ let private fruitType = let ``Execute resolves enums to their names`` () = let schema = testSchema [ - Define.Field ("fruits", ListOf fruitType, "", [], resolve = (fun ctx d -> [ Apple; Banana; Cherry; DragonFruit ])) + Define.Field ("fruits", ListOf fruitType, "", [], resolve = (fun _ _ -> [ Apple; Banana; Cherry; DragonFruit ])) ] let expected = NameValueLookup.ofList [ "fruits", [ "APPLE"; "BANANA"; "CHERRY"; "DRAGON_FRUIT" ] :> obj ] - let result = sync <| Executor(schema).AsyncExecute (parse "{ fruits() }") + let result = sync <| Executor(schema).AsyncExecute (parse "{ fruits() }", getMockInputContext) ensureDirect result <| fun data errors -> empty errors @@ -100,7 +100,7 @@ let ``Execute resolves enums arguments from their names`` () = ] let expected = NameValueLookup.ofList [ "foo", box "You asked for dragon fruit" ] - let result = sync <| Executor(schema).AsyncExecute (parse "{ foo(fruit: DRAGON_FRUIT) }") + let result = sync <| Executor(schema).AsyncExecute (parse "{ foo(fruit: DRAGON_FRUIT) }", getMockInputContext) ensureDirect result <| fun data errors -> empty errors data |> equals (upcast expected) diff --git a/tests/FSharp.Data.GraphQL.Tests/SchemaTests.fs b/tests/FSharp.Data.GraphQL.Tests/SchemaTests.fs index bf04b8322..4b732b789 100644 --- a/tests/FSharp.Data.GraphQL.Tests/SchemaTests.fs +++ b/tests/FSharp.Data.GraphQL.Tests/SchemaTests.fs @@ -69,7 +69,7 @@ let ``Schema config must be able to override default error handling`` () = } } """ - let result = sync <| Executor(schema).AsyncExecute query + let result = sync <| Executor(schema).AsyncExecute(query, getMockInputContext) let expected = NameValueLookup.ofList [ "test", box <| NameValueLookup.ofList [ "failing1", null; "passing", box "ok"; "failing2", null ] ] let expectedErrors = [ diff --git a/tests/FSharp.Data.GraphQL.Tests/SelectLinqTests.fs b/tests/FSharp.Data.GraphQL.Tests/SelectLinqTests.fs index 02a8b9ad8..dcad59739 100644 --- a/tests/FSharp.Data.GraphQL.Tests/SelectLinqTests.fs +++ b/tests/FSharp.Data.GraphQL.Tests/SelectLinqTests.fs @@ -56,7 +56,7 @@ let internal undefined<'t> = Unchecked.defaultof<'t> let resolveRoot ctx () = let info = ctx.ExecutionInfo let queryable = data.AsQueryable() - let result = queryable.Apply(info) |> Seq.toList + let result = queryable.Apply(info, getMockInputContext) |> Seq.toList result let linqArgs = @@ -83,7 +83,7 @@ let schema = fun ctx () -> let info = ctx.ExecutionInfo let queryable = data.AsQueryable () - let result = queryable.Apply (info) |> Seq.toList + let result = queryable.Apply (info, getMockInputContext) |> Seq.toList result ) ] @@ -102,7 +102,7 @@ let ``LINQ interpreter works with auto-fields``() = } """ let info = plan.["people"] - let people = data.AsQueryable().Apply(info) |> Seq.toList + let people = data.AsQueryable().Apply(info, getMockInputContext) |> Seq.toList List.length people |> equals 3 let result = List.head people result.FirstName |> equals "Ben" @@ -120,7 +120,7 @@ let ``LINQ interpreter works with fields with defined resolvers``() = } """ let info = plan.["people"] - let people = data.AsQueryable().Apply(info) |> Seq.toList + let people = data.AsQueryable().Apply(info, getMockInputContext) |> Seq.toList List.length people |> equals 3 let result = List.head people result.FirstName |> equals undefined @@ -138,7 +138,7 @@ let ``LINQ interpreter works with fields referring to nested property resolver`` } """ let info = plan.["people"] - let people = data.AsQueryable().Apply(info) |> Seq.toList + let people = data.AsQueryable().Apply(info, getMockInputContext) |> Seq.toList List.length people |> equals 3 let result = List.head people result.FirstName |> equals undefined @@ -156,7 +156,7 @@ let ``LINQ interpreter works with nested collections``() = } """ let info = plan.["people"] - let people = data.AsQueryable().Apply(info) |> Seq.toList + let people = data.AsQueryable().Apply(info, getMockInputContext) |> Seq.toList List.length people |> equals 3 let result = List.head people result.FirstName |> equals undefined @@ -175,7 +175,7 @@ let ``LINQ interpreter works with nested property getters in resolve function``( } """ let info = plan.["people"] - let people = data.AsQueryable().Apply(info) |> Seq.toList + let people = data.AsQueryable().Apply(info, getMockInputContext) |> Seq.toList List.length people |> equals 3 let result = List.head people result.FirstName |> equals undefined @@ -194,7 +194,7 @@ let ``LINQ interpreter resolves multiple properties from complex resolvers``() = } """ let info = plan.["people"] - let people = data.AsQueryable().Apply(info) |> Seq.toList + let people = data.AsQueryable().Apply(info, getMockInputContext) |> Seq.toList List.length people |> equals 3 let result = List.head people // both FirstName and LastName should be resolved, because @@ -215,7 +215,7 @@ let ``LINQ interpreter works with id arg``() = } """ let info = plan.["people"] - let people = data.AsQueryable().Apply(info) |> Seq.toList + let people = data.AsQueryable().Apply(info, getMockInputContext) |> Seq.toList List.length people |> equals 1 let result = List.head people result.ID |> equals 2 @@ -235,7 +235,7 @@ let ``LINQ interpreter works with skip arg``() = } """ let info = plan.["people"] - let people = data.AsQueryable().Apply(info) |> Seq.toList + let people = data.AsQueryable().Apply(info, getMockInputContext) |> Seq.toList List.length people |> equals 1 let result = List.head people result.ID |> equals 7 @@ -255,7 +255,7 @@ let ``LINQ interpreter works with take arg``() = } """ let info = plan.["people"] - let people = data.AsQueryable().Apply(info) |> Seq.toList + let people = data.AsQueryable().Apply(info, getMockInputContext) |> Seq.toList List.length people |> equals 2 let result = people |> List.map (fun p -> (p.ID, p.FirstName)) result |> equals [ (4, "Ben") @@ -272,7 +272,7 @@ let ``LINQ interpreter works with orderBy arg``() = } """ let info = plan.["people"] - let people = data.AsQueryable().Apply(info) |> Seq.toList + let people = data.AsQueryable().Apply(info, getMockInputContext) |> Seq.toList List.length people |> equals 3 let result = people |> List.map (fun p -> (p.ID, p.FirstName)) result |> equals [ (4, "Ben") @@ -290,7 +290,7 @@ let ``LINQ interpreter works with orderByDesc arg``() = } """ let info = plan.["people"] - let people = data.AsQueryable().Apply(info) |> Seq.toList + let people = data.AsQueryable().Apply(info, getMockInputContext) |> Seq.toList List.length people |> equals 3 let result = people |> List.map (fun p -> (p.ID, p.FirstName)) result |> equals [ (2, "Jonathan") diff --git a/tests/FSharp.Data.GraphQL.Tests/SubscriptionTests.fs b/tests/FSharp.Data.GraphQL.Tests/SubscriptionTests.fs index 398d2ddb4..d3d2ada11 100644 --- a/tests/FSharp.Data.GraphQL.Tests/SubscriptionTests.fs +++ b/tests/FSharp.Data.GraphQL.Tests/SubscriptionTests.fs @@ -115,7 +115,7 @@ let ``Can subscribe to sync field and get results``() = data } }""" - let result = executor.AsyncExecute(query) |> sync + let result = executor.AsyncExecute(query, getMockInputContext) |> sync match result with | Stream data -> use sub = Observer.create data @@ -142,7 +142,7 @@ let ``Can subscribe to tagged sync field and get results with expected tag``() = data } }""" - let result = executor.AsyncExecute(query) |> sync + let result = executor.AsyncExecute(query, getMockInputContext) |> sync match result with | Stream data -> use sub = Observer.create data @@ -162,7 +162,7 @@ let ``Can subscribe to tagged sync field and do not get results with unexpected data } }""" - let result = executor.AsyncExecute(query) |> sync + let result = executor.AsyncExecute(query, getMockInputContext) |> sync match result with | Stream data -> use sub = Observer.create data @@ -184,7 +184,7 @@ let ``Can subscribe to async field and get results``() = data } }""" - let result = executor.AsyncExecute(query) |> sync + let result = executor.AsyncExecute(query, getMockInputContext) |> sync match result with | Stream data -> use sub = Observer.create data @@ -211,7 +211,7 @@ let ``Can subscribe to tagged async field and get results with expected tag``() data } }""" - let result = executor.AsyncExecute(query) |> sync + let result = executor.AsyncExecute(query, getMockInputContext) |> sync match result with | Stream data -> use sub = Observer.create data @@ -231,7 +231,7 @@ let ``Can subscribe to tagged async field and do not get results with unexpected data } }""" - let result = executor.AsyncExecute(query) |> sync + let result = executor.AsyncExecute(query, getMockInputContext) |> sync match result with | Stream data -> use sub = Observer.create data diff --git a/tests/FSharp.Data.GraphQL.Tests/UnionInterfaceTests.fs b/tests/FSharp.Data.GraphQL.Tests/UnionInterfaceTests.fs index e1c344142..291a8bf40 100644 --- a/tests/FSharp.Data.GraphQL.Tests/UnionInterfaceTests.fs +++ b/tests/FSharp.Data.GraphQL.Tests/UnionInterfaceTests.fs @@ -113,7 +113,7 @@ let ``Execute can introspect on union and intersection types`` () = inputFields { name } } }""" - let result = sync <| Executor(schema).AsyncExecute(ast) + let result = sync <| Executor(schema).AsyncExecute(ast, getMockInputContext) let expected = NameValueLookup.ofList [ "Named", upcast NameValueLookup.ofList [ @@ -155,7 +155,7 @@ let ``Executes union types`` () = meows } }""" - let result = sync <| Executor(schema).AsyncExecute(ast, john) + let result = sync <| Executor(schema).AsyncExecute(ast, getMockInputContext, john) let expected = NameValueLookup.ofList [ "__typename", box "Person" @@ -191,7 +191,7 @@ let ``Executes union types with inline fragments`` () = } } }""" - let result = sync <| Executor(schema).AsyncExecute(ast, john) + let result = sync <| Executor(schema).AsyncExecute(ast, getMockInputContext, john) let expected = NameValueLookup.ofList [ "__typename", box "Person" @@ -222,7 +222,7 @@ let ``Executes interface types`` () = meows } }""" - let result = sync <| Executor(schema).AsyncExecute(ast, john) + let result = sync <| Executor(schema).AsyncExecute(ast, getMockInputContext, john) let expected = NameValueLookup.ofList [ "__typename", box "Person" @@ -256,7 +256,7 @@ let ``Executes interface types with inline fragments`` () = } } }""" - let result = sync <| Executor(schema).AsyncExecute(ast, john) + let result = sync <| Executor(schema).AsyncExecute(ast, getMockInputContext, john) let expected = NameValueLookup.ofList [ "__typename", box "Person" @@ -304,7 +304,7 @@ let ``Execute allows fragment conditions to be abstract types`` () = meows } }""" - let result = sync <| Executor(schema).AsyncExecute(ast, john) + let result = sync <| Executor(schema).AsyncExecute(ast, getMockInputContext, john) let expected = NameValueLookup.ofList [ "__typename", box "Person" diff --git a/tests/FSharp.Data.GraphQL.Tests/Variables and Inputs/InputComplexTests.fs b/tests/FSharp.Data.GraphQL.Tests/Variables and Inputs/InputComplexTests.fs index af3625804..954ace7ec 100644 --- a/tests/FSharp.Data.GraphQL.Tests/Variables and Inputs/InputComplexTests.fs +++ b/tests/FSharp.Data.GraphQL.Tests/Variables and Inputs/InputComplexTests.fs @@ -73,7 +73,7 @@ let schema = Schema (TestType) let ``Execute handles objects and nullability using inline structs with complex input`` () = let ast = parse """{ fieldWithObjectInput(input: {mand: "baz", opt: "foo", optSeq: ["bar"], optArr: ["baf"]}) }""" - let result = sync <| Executor(schema).AsyncExecute (ast) + let result = sync <| Executor(schema).AsyncExecute (ast, getMockInputContext) let expected = NameValueLookup.ofList @@ -88,7 +88,7 @@ let ``Execute handles objects and nullability using inline structs with complex [] let ``Execute handles objects and nullability using inline structs and properly parses single value to list`` () = let ast = parse """{ fieldWithObjectInput(input: {mand:"baz", opt: "foo", optSeq: "bar"}) }""" - let result = sync <| Executor(schema).AsyncExecute (ast) + let result = sync <| Executor(schema).AsyncExecute (ast, getMockInputContext) let expected = NameValueLookup.ofList [ "fieldWithObjectInput", upcast """{"mand":"baz", "opt":"foo", "optSeq":["bar"], "glCode":null, "optArr":null}""" ] ensureDirect result <| fun data errors -> @@ -98,7 +98,7 @@ let ``Execute handles objects and nullability using inline structs and properly [] let ``Execute handles objects and nullability using inline structs and properly coerces complex scalar types`` () = let ast = parse """{ fieldWithObjectInput(input: {mand: "foo", glCode: "SerializedValue"}) }""" - let result = sync <| Executor(schema).AsyncExecute (ast) + let result = sync <| Executor(schema).AsyncExecute (ast, getMockInputContext) let expected = NameValueLookup.ofList [ "fieldWithObjectInput", @@ -127,7 +127,7 @@ let ``Execute handles variables with complex inputs`` () = }""" let params' = paramsWithValueInput testInputObject - let result = sync <| Executor(schema).AsyncExecute (ast, variables = params') + let result = sync <| Executor(schema).AsyncExecute (ast, getMockInputContext, variables = params') let expected = NameValueLookup.ofList [ "fieldWithObjectInput", upcast testInputObject ] ensureDirect result <| fun data errors -> empty errors @@ -141,7 +141,7 @@ let ``Execute handles variables with default value when no value was provided`` fieldWithObjectInput(input: $input) }""" - let result = sync <| Executor(schema).AsyncExecute (ast) + let result = sync <| Executor(schema).AsyncExecute (ast, getMockInputContext) let expected = NameValueLookup.ofList [ "fieldWithObjectInput", upcast testInputObject ] ensureDirect result <| fun data errors -> empty errors @@ -157,7 +157,7 @@ let ``Execute handles variables and errors on null for nested non-nulls`` () = let testInputObject = """{"mand":null, "opt":"foo", "optSeq":["bar"], "voptSeq":["bar"]}""" let params' = paramsWithValueInput testInputObject - let result = sync <| Executor(schema).AsyncExecute (ast, variables = params') + let result = sync <| Executor(schema).AsyncExecute (ast, getMockInputContext, variables = params') ensureRequestError result <| fun [ error ] -> let message = "Non-nullable field 'mand' expected value of type 'String!', but got 'null'." error |> ensureInputObjectFieldCoercionError (Variable "input") message [] "TestInputObject" "String!" @@ -172,7 +172,7 @@ let ``Execute handles variables and errors on incorrect type`` () = let testInputObject = "\"foo bar\"" let params' = paramsWithValueInput testInputObject - let result = sync <| Executor(schema).AsyncExecute (ast, variables = params') + let result = sync <| Executor(schema).AsyncExecute (ast, getMockInputContext, variables = params') ensureRequestError result <| fun [ error ] -> let message = $"A variable '$input' expected to be '%O{JsonValueKind.Object}' but got '%O{JsonValueKind.String}'." error |> ensureInputCoercionError (Variable "input") message "TestInputObject" @@ -187,7 +187,7 @@ let ``Execute handles variables and errors on omission of nested non-nulls`` () let testInputObject = """{"opt":"foo","optSeq":["bar"]}""" let params' = paramsWithValueInput testInputObject - let result = sync <| Executor(schema).AsyncExecute (ast, variables = params') + let result = sync <| Executor(schema).AsyncExecute (ast, getMockInputContext, variables = params') ensureRequestError result <| fun [ error ] -> let message = "Non-nullable field 'mand' expected value of type 'String!', but got 'null'." error |> ensureInputObjectFieldCoercionError (Variable "input") message [] "TestInputObject" "String!" @@ -202,7 +202,7 @@ let ``Execute handles list inputs and nullability and does not allow invalid typ // as that kind of an error inside of opt query is guaranteed to fail in every call, we're gonna to fail noisy here let testInputList = "[\"A\",\"B\"]" let params' = paramsWithValueInput testInputList - let result = sync <| Executor(schema).AsyncExecute (ast, variables = params') + let result = sync <| Executor(schema).AsyncExecute (ast, getMockInputContext, variables = params') ensureRequestError result <| fun [ error ] -> let message = $"A variable '$input' expected to be '%O{JsonValueKind.Object}' but got '%O{JsonValueKind.Array}'." error |> ensureInputCoercionError (Variable "input") message "TestInputObject!" @@ -220,7 +220,7 @@ let ``Execute handles list inputs and nullability and does not allow unknown typ // as that kind of an error inside of opt query is guaranteed to fail in every call, we're gonna to fail noisy here let testInputValue = "\"whoknows\"" let params' = paramsWithValueInput testInputValue - let result = sync <| Executor(schema).AsyncExecute (ast, variables = params') + let result = sync <| Executor(schema).AsyncExecute (ast, getMockInputContext, variables = params') let expectedError = let message = "A variable '$input' in operation 'q' has a type that is not an input type defined by the schema (UnknownType!)." GQLProblemDetails.CreateWithKind (message, Validation) diff --git a/tests/FSharp.Data.GraphQL.Tests/Variables and Inputs/InputEnumTests.fs b/tests/FSharp.Data.GraphQL.Tests/Variables and Inputs/InputEnumTests.fs index 3ee801295..f8e8966ff 100644 --- a/tests/FSharp.Data.GraphQL.Tests/Variables and Inputs/InputEnumTests.fs +++ b/tests/FSharp.Data.GraphQL.Tests/Variables and Inputs/InputEnumTests.fs @@ -56,7 +56,7 @@ let ``Execute handles enum input as variable`` () = let testInputValue = "\"Foo\"" let params' = paramsWithEnumInput testInputValue - let result = sync <| Executor(schema).AsyncExecute (ast, variables = params') + let result = sync <| Executor(schema).AsyncExecute (ast, getMockInputContext, variables = params') let expected = NameValueLookup.ofList [ "fieldWithEnumInput", upcast "\"Foo\"" ] ensureDirect result <| fun data errors -> empty errors @@ -72,7 +72,7 @@ let ``Execute handles nullable null enum input as variable`` () = let testInputValue = "null" let params' = paramsWithEnumInput testInputValue - let result = sync <| Executor(schema).AsyncExecute (ast, variables = params') + let result = sync <| Executor(schema).AsyncExecute (ast, getMockInputContext, variables = params') let expected = NameValueLookup.ofList [ "fieldWithNullableEnumInput", upcast testInputValue ] ensureDirect result <| fun data errors -> empty errors @@ -88,7 +88,7 @@ let ``Execute handles union enum input as variable`` () = let testInputValue = "\"Bar\"" let params' = paramsWithEnumInput testInputValue - let result = sync <| Executor(schema).AsyncExecute (ast, variables = params') + let result = sync <| Executor(schema).AsyncExecute (ast, getMockInputContext, variables = params') let expected = NameValueLookup.ofList [ "fieldWithEnumInput", upcast "\"Bar\"" ] ensureDirect result <| fun data errors -> empty errors @@ -104,7 +104,7 @@ let ``Execute handles Some union enum input as variable`` () = let testInputValue = "\"Bar\"" let params' = paramsWithEnumInput testInputValue - let result = sync <| Executor(schema).AsyncExecute (ast, variables = params') + let result = sync <| Executor(schema).AsyncExecute (ast, getMockInputContext, variables = params') let expected = NameValueLookup.ofList [ "fieldWithNullableEnumInput", upcast "\"Bar\"" ] ensureDirect result <| fun data errors -> empty errors @@ -120,7 +120,7 @@ let ``Execute handles None enum input as variable`` () = let testInputValue = "null" let params' = paramsWithEnumInput testInputValue - let result = sync <| Executor(schema).AsyncExecute (ast, variables = params') + let result = sync <| Executor(schema).AsyncExecute (ast, getMockInputContext, variables = params') let expected = NameValueLookup.ofList [ "fieldWithNullableEnumInput", upcast testInputValue ] ensureDirect result <| fun data errors -> empty errors diff --git a/tests/FSharp.Data.GraphQL.Tests/Variables and Inputs/InputListTests.fs b/tests/FSharp.Data.GraphQL.Tests/Variables and Inputs/InputListTests.fs index 06d393196..8c28d22e6 100644 --- a/tests/FSharp.Data.GraphQL.Tests/Variables and Inputs/InputListTests.fs +++ b/tests/FSharp.Data.GraphQL.Tests/Variables and Inputs/InputListTests.fs @@ -52,7 +52,7 @@ let ``Execute handles list inputs and nullability and allows lists to be null`` let testInputValue = "null" let params' = paramsWithValueInput testInputValue - let result = sync <| Executor(schema).AsyncExecute (ast, variables = params') + let result = sync <| Executor(schema).AsyncExecute (ast, getMockInputContext, variables = params') let expected = NameValueLookup.ofList [ "list", upcast testInputValue ] ensureDirect result <| fun data errors -> empty errors @@ -68,7 +68,7 @@ let ``Execute handles list inputs and nullability and allows lists to contain va let testInputList = "[\"A\"]" let params' = paramsWithValueInput testInputList - let result = sync <| Executor(schema).AsyncExecute (ast, variables = params') + let result = sync <| Executor(schema).AsyncExecute (ast, getMockInputContext, variables = params') let expected = NameValueLookup.ofList [ "list", upcast testInputList ] ensureDirect result <| fun data errors -> empty errors @@ -84,7 +84,7 @@ let ``Execute handles list inputs and nullability and allows lists to contain nu let testInputList = "[\"A\",null,\"B\"]" let params' = paramsWithValueInput testInputList - let result = sync <| Executor(schema).AsyncExecute (ast, variables = params') + let result = sync <| Executor(schema).AsyncExecute (ast, getMockInputContext, variables = params') let expected = NameValueLookup.ofList [ "list", upcast testInputList ] ensureDirect result <| fun data errors -> empty errors @@ -100,7 +100,7 @@ let ``Execute handles list inputs and nullability and does not allow non-null li let testInputList = "null" let params' = paramsWithValueInput testInputList - let result = sync <| Executor(schema).AsyncExecute (ast, variables = params') + let result = sync <| Executor(schema).AsyncExecute (ast, getMockInputContext, variables = params') ensureRequestError result <| fun [ error ] -> let message = "Non-nullable variable '$input' expected value of type '[String]!', but got 'null'." error |> ensureInputCoercionError (Variable "input") message "[String]!" @@ -115,7 +115,7 @@ let ``Execute handles list inputs and nullability and allows non-null lists to c let testInputList = "[\"A\"]" let params' = paramsWithValueInput testInputList - let result = sync <| Executor(schema).AsyncExecute (ast, variables = params') + let result = sync <| Executor(schema).AsyncExecute (ast, getMockInputContext, variables = params') let expected = NameValueLookup.ofList [ "nnList", upcast testInputList ] ensureDirect result <| fun data errors -> empty errors @@ -131,7 +131,7 @@ let ``Execute handles list inputs and nullability and allows non-null lists to c let testInputList = "[\"A\",null,\"B\"]" let params' = paramsWithValueInput testInputList - let result = sync <| Executor(schema).AsyncExecute (ast, variables = params') + let result = sync <| Executor(schema).AsyncExecute (ast, getMockInputContext, variables = params') let expected = NameValueLookup.ofList [ "nnList", upcast testInputList ] ensureDirect result <| fun data errors -> empty errors @@ -147,7 +147,7 @@ let ``Execute handles list inputs and nullability and allows lists of non-nulls let testInputList = "null" let params' = paramsWithValueInput testInputList - let result = sync <| Executor(schema).AsyncExecute (ast, variables = params') + let result = sync <| Executor(schema).AsyncExecute (ast, getMockInputContext, variables = params') let expected = NameValueLookup.ofList [ "listNN", upcast testInputList ] ensureDirect result <| fun data errors -> empty errors @@ -163,7 +163,7 @@ let ``Execute handles list inputs and nullability and allows lists of non-nulls let testInputList = "[\"A\"]" let params' = paramsWithValueInput testInputList - let result = sync <| Executor(schema).AsyncExecute (ast, variables = params') + let result = sync <| Executor(schema).AsyncExecute (ast, getMockInputContext, variables = params') let expected = NameValueLookup.ofList [ "listNN", upcast testInputList ] ensureDirect result <| fun data errors -> empty errors @@ -179,7 +179,7 @@ let ``Execute handles list inputs and nullability and does not allow lists of no let testInputList = "[\"A\",null,\"B\"]" let params' = paramsWithValueInput testInputList - let result = sync <| Executor(schema).AsyncExecute (ast, variables = params') + let result = sync <| Executor(schema).AsyncExecute (ast, getMockInputContext, variables = params') ensureRequestError result <| fun [ error ] -> let message = "Non-nullable variable '$input' expected value of type '[String!]', but got 'null'." error |> ensureInputCoercionError (Variable "input") message "[String!]" @@ -194,7 +194,7 @@ let ``Execute handles list inputs and nullability and does not allow non-null li let testInputList = "null" let params' = paramsWithValueInput testInputList - let result = sync <| Executor(schema).AsyncExecute (ast, variables = params') + let result = sync <| Executor(schema).AsyncExecute (ast, getMockInputContext, variables = params') ensureRequestError result <| fun [ error ] -> let message = "Non-nullable variable '$input' expected value of type '[String!]!', but got 'null'." error |> ensureInputCoercionError (Variable "input") message "[String!]!" @@ -209,7 +209,7 @@ let ``Execute handles list inputs and nullability and does not allow non-null li let testInputList = "[\"A\"]" let params' = paramsWithValueInput testInputList - let result = sync <| Executor(schema).AsyncExecute (ast, variables = params') + let result = sync <| Executor(schema).AsyncExecute (ast, getMockInputContext, variables = params') let expected = NameValueLookup.ofList [ "nnListNN", upcast testInputList ] ensureDirect result <| fun data errors -> empty errors @@ -225,7 +225,7 @@ let ``Execute handles list inputs and nullability and does not allow non-null li let testInputList = "[\"A\",null,\"B\"]" let params' = paramsWithValueInput testInputList - let result = sync <| Executor(schema).AsyncExecute (ast, variables = params') + let result = sync <| Executor(schema).AsyncExecute (ast, getMockInputContext, variables = params') ensureRequestError result <| fun [ error ] -> let message = "Non-nullable variable '$input' expected value of type '[String!]!', but got 'null'." error |> ensureInputCoercionError (Variable "input") message "[String!]!" diff --git a/tests/FSharp.Data.GraphQL.Tests/Variables and Inputs/InputNestedTests.fs b/tests/FSharp.Data.GraphQL.Tests/Variables and Inputs/InputNestedTests.fs index 714c02901..69fc2b825 100644 --- a/tests/FSharp.Data.GraphQL.Tests/Variables and Inputs/InputNestedTests.fs +++ b/tests/FSharp.Data.GraphQL.Tests/Variables and Inputs/InputNestedTests.fs @@ -105,7 +105,7 @@ let schema = Schema (TestType) let ``Execute handles nested input objects and nullability using inline structs and properly coerces complex scalar types`` () = let ast = parse """{ fieldWithNestedInputObject(input: {n:"optSeq", no:{mand:"mand"}, nvo:{mand:"mand"}, nl: []})}""" - let result = sync <| Executor(schema).AsyncExecute (ast) + let result = sync <| Executor(schema).AsyncExecute (ast, getMockInputContext) let expected = NameValueLookup.ofList [ "fieldWithNestedInputObject", @@ -122,7 +122,7 @@ let ``Execute handles nested input objects and nullability using inline structs let ``Execute handles nested input objects and nullability using inline structs and properly coerces complex scalar types with empty lists`` () = let ast = parse """{ fieldWithNestedInputObject(input: {n:"optSeq", no:{mand:"mand"}, nvo:{mand:"mand"}, nl:[], nlo: [], nlvo: []})}""" - let result = sync <| Executor(schema).AsyncExecute (ast) + let result = sync <| Executor(schema).AsyncExecute (ast, getMockInputContext) let expected = NameValueLookup.ofList [ "fieldWithNestedInputObject", @@ -139,7 +139,7 @@ let ``Execute handles nested input objects and nullability using inline structs let ``Execute handles nested input objects and nullability using inline structs and properly coerces complex scalar types with lists`` () = let ast = parse """{ fieldWithNestedInputObject(input: {n:"optSeq", nl:[{mand:"mand"}], nlo: [{mand:"mand"}], nlvo: [{mand:"mand"}]})}""" - let result = sync <| Executor(schema).AsyncExecute (ast) + let result = sync <| Executor(schema).AsyncExecute (ast, getMockInputContext) let expected = NameValueLookup.ofList [ "fieldWithNestedInputObject", @@ -156,7 +156,7 @@ let ``Execute handles nested input objects and nullability using inline structs let ``Execute handles recursive input objects and nullability using inline structs and properly coerces complex scalar types`` () = let ast = parse """{ fieldWithRecursiveInputObject(input: {r:"optSeq", ro:{r:"mand"}, rvo:{r:"mand"}})}""" - let result = sync <| Executor(schema).AsyncExecute (ast) + let result = sync <| Executor(schema).AsyncExecute (ast, getMockInputContext) let expected = NameValueLookup.ofList [ "fieldWithRecursiveInputObject", diff --git a/tests/FSharp.Data.GraphQL.Tests/Variables and Inputs/InputNullableStringTests.fs b/tests/FSharp.Data.GraphQL.Tests/Variables and Inputs/InputNullableStringTests.fs index d9781696c..08039ad8f 100644 --- a/tests/FSharp.Data.GraphQL.Tests/Variables and Inputs/InputNullableStringTests.fs +++ b/tests/FSharp.Data.GraphQL.Tests/Variables and Inputs/InputNullableStringTests.fs @@ -50,7 +50,7 @@ let paramsWithValueInput input = [] let ``Execute handles variables and allows nullable inputs to be omitted`` () = let ast = parse """{ fieldWithNullableStringInput }""" - let result = sync <| Executor(schema).AsyncExecute (ast) + let result = sync <| Executor(schema).AsyncExecute (ast, getMockInputContext) let expected = NameValueLookup.ofList [ "fieldWithNullableStringInput", upcast "null" ] ensureDirect result <| fun data errors -> empty errors @@ -64,7 +64,7 @@ let ``Execute handles variables and allows nullable inputs to be omitted in a va fieldWithNullableStringInput(input: $value) }""" - let result = sync <| Executor(schema).AsyncExecute (ast) + let result = sync <| Executor(schema).AsyncExecute (ast, getMockInputContext) let expected = NameValueLookup.ofList [ "fieldWithNullableStringInput", upcast "null" ] ensureDirect result <| fun data errors -> empty errors @@ -81,7 +81,7 @@ let ``Execute handles variables and allows nullable inputs to be set to null in let testInputValue = "null" let params' = paramsWithValueInput testInputValue - let result = sync <| Executor(schema).AsyncExecute (ast, variables = params') + let result = sync <| Executor(schema).AsyncExecute (ast, getMockInputContext, variables = params') let expected = NameValueLookup.ofList [ "fieldWithNullableStringInput", upcast testInputValue ] ensureDirect result <| fun data errors -> empty errors @@ -103,7 +103,7 @@ let ``Execute handles variables and allows nullable inputs to be set to a value let testInputValue = "\"a\"" let params' = paramsWithValueInput testInputValue - let result = sync <| Executor(schema).AsyncExecute (ast, variables = params') + let result = sync <| Executor(schema).AsyncExecute (ast, getMockInputContext, variables = params') let expected = NameValueLookup.ofList [ "fieldWithNullableStringInput", upcast testInputValue ] ensureDirect result <| fun data errors -> empty errors @@ -112,7 +112,7 @@ let ``Execute handles variables and allows nullable inputs to be set to a value [] let ``Execute handles variables and allows nullable inputs to be set to a value directly`` () = let ast = parse """{ fieldWithNullableStringInput(input: "a") }""" - let result = sync <| Executor(schema).AsyncExecute (ast) + let result = sync <| Executor(schema).AsyncExecute (ast, getMockInputContext) let expected = NameValueLookup.ofList [ "fieldWithNullableStringInput", upcast "\"a\"" ] ensureDirect result <| fun data errors -> empty errors @@ -133,7 +133,7 @@ let ``Execute handles non-nullable scalars and does not allow non-nullable input let testInputValue = "null" let params' = paramsWithValueInput testInputValue - let result = sync <| Executor(schema).AsyncExecute (ast, variables = params') + let result = sync <| Executor(schema).AsyncExecute (ast, getMockInputContext, variables = params') ensureRequestError result <| fun [ error ] -> error |> ensureInputCoercionError (Variable "value") "Non-nullable variable '$value' expected value of type 'String!', but got 'null'." "String!" @@ -153,7 +153,7 @@ let ``Execute handles non-nullable scalars and allows non-nullable inputs to be let testInputValue = "\"a\"" let params' = paramsWithValueInput testInputValue - let result = sync <| Executor(schema).AsyncExecute (ast, variables = params') + let result = sync <| Executor(schema).AsyncExecute (ast, getMockInputContext, variables = params') let expected = NameValueLookup.ofList [ "fieldWithNonNullableStringInput", upcast testInputValue ] ensureDirect result <| fun data errors -> empty errors @@ -162,7 +162,7 @@ let ``Execute handles non-nullable scalars and allows non-nullable inputs to be [] let ``Execute handles non-nullable scalars and allows non-nullable inputs to be set to a value directly`` () = let ast = parse """{ fieldWithNonNullableStringInput(input: "a") }""" - let result = sync <| Executor(schema).AsyncExecute (ast) + let result = sync <| Executor(schema).AsyncExecute (ast, getMockInputContext) let expected = NameValueLookup.ofList [ "fieldWithNonNullableStringInput", upcast "\"a\"" ] ensureDirect result <| fun data errors -> empty errors @@ -171,7 +171,7 @@ let ``Execute handles non-nullable scalars and allows non-nullable inputs to be [] let ``Execute uses argument default value when no argument was provided`` () = let ast = parse """{ fieldWithDefaultArgumentValue }""" - let result = sync <| Executor(schema).AsyncExecute (ast) + let result = sync <| Executor(schema).AsyncExecute (ast, getMockInputContext) let expected = NameValueLookup.ofList [ "fieldWithDefaultArgumentValue", upcast "\"hello world\"" ] ensureDirect result <| fun data errors -> empty errors @@ -192,7 +192,7 @@ let ``Execute uses argument default value when nullable variable provided`` () = let testInputValue = "\"hello world\"" let params' = paramsWithOptionalInput testInputValue - let result = sync <| Executor(schema).AsyncExecute (ast, variables = params') + let result = sync <| Executor(schema).AsyncExecute (ast, getMockInputContext, variables = params') let expected = NameValueLookup.ofList [ "fieldWithDefaultArgumentValue", upcast testInputValue ] ensureDirect result <| fun data errors -> empty errors diff --git a/tests/FSharp.Data.GraphQL.Tests/Variables and Inputs/InputObjectValidatorTests.fs b/tests/FSharp.Data.GraphQL.Tests/Variables and Inputs/InputObjectValidatorTests.fs index 293f07574..ee1fcd188 100644 --- a/tests/FSharp.Data.GraphQL.Tests/Variables and Inputs/InputObjectValidatorTests.fs +++ b/tests/FSharp.Data.GraphQL.Tests/Variables and Inputs/InputObjectValidatorTests.fs @@ -126,7 +126,7 @@ let ``Execute handles validation of valid inline input records with all fields`` recordNested: { homeAddress: { country: "US", zipCode: "12345", city: "Miami" }, workAddress: { country: "US", zipCode: "67890", city: "Miami" } } ) }""" - let result = sync <| schema.AsyncExecute(parse query) + let result = sync <| schema.AsyncExecute(parse query, getMockInputContext) ensureDirect result <| fun data errors -> empty errors [] @@ -138,7 +138,7 @@ let ``Execute handles validation of invalid inline input records with all fields recordNested: { homeAddress: { country: "US", zipCode: "12345", city: "Miami" }, workAddress: { country: "US", zipCode: "67890", city: "Miami" }, mailingAddress: { country: "US", zipCode: "12345", city: "Miami" } } ) }""" - let result = sync <| schema.AsyncExecute(parse query) + let result = sync <| schema.AsyncExecute(parse query, getMockInputContext) match result with | RequestError [ zipCodeError ; addressError ] -> zipCodeError |> ensureInputObjectValidationError (Argument "record") "ZipCode must be 5 characters for US" [] "InputRecord!" @@ -175,7 +175,7 @@ let ``Execute handles validation of valid input records from variables with all """{ "country": "US", "zipCode": "67890", "city": "Miami" }""", """null""" ) |> paramsWithValues - let result = sync <| schema.AsyncExecute(parse query, variables = params') + let result = sync <| schema.AsyncExecute(parse query, getMockInputContext, variables = params') //let expected = NameValueLookup.ofList [ "recordInputs", upcast testInputObject ] ensureDirect result <| fun data errors -> empty errors @@ -197,7 +197,7 @@ let ``Execute handles validation of invalid input records from variables with al """{ "country": "US", "zipCode": "67890", "city": "Miami" }""", """{ "country": "US", "zipCode": "12345", "city": "Miami" }""" ) |> paramsWithValues - let result = sync <| schema.AsyncExecute(parse query, variables = params') + let result = sync <| schema.AsyncExecute(parse query, getMockInputContext, variables = params') //let expected = NameValueLookup.ofList [ "recordInputs", upcast testInputObject ] ensureRequestError result <| fun [ zipCodeError ; addressError ] -> zipCodeError |> ensureInputObjectValidationError (Variable "record") "ZipCode must be 5 characters for US" [ box "mailingAddress" ] "InputRecord" @@ -227,7 +227,7 @@ let ``Execute handles validation of valid input records from variables with all """{ "country": "US", "zipCode": "67890", "city": "Miami" }""", """null""" ) |> paramsWithValues - let result = sync <| schema.AsyncExecute(parse query, variables = params') + let result = sync <| schema.AsyncExecute(parse query, getMockInputContext, variables = params') //let expected = NameValueLookup.ofList [ "recordInputs", upcast testInputObject ] ensureDirect result <| fun data errors -> empty errors @@ -248,10 +248,10 @@ let ``Execute handles validation of invalid input records from variables with al """{ "country": "US", "zipCode": "12345", "city": "Miami" }""", """{ "country": "US", "zipCode": "67890", "city": "Miami" }""" ) |> paramsWithValues - let result = sync <| schema.AsyncExecute(parse query, variables = params') + let result = sync <| schema.AsyncExecute(parse query, getMockInputContext, variables = params') //let expected = NameValueLookup.ofList [ "recordInputs", upcast testInputObject ] ensureRequestError result <| fun [ error ] -> error |> ensureInputObjectValidationError (Variable "record1") "ZipCode must be 5 characters for US" [ box "mailingAddress" ] "InputRecord" - // Because all variables are coerced together validation of the inline object that contains variables do not happen + // Because all variables are coerced together, validation of the inline object that contains variables do not happen // as total variables coercion failed //hasError "Object 'Query': field 'recordInputs': argument 'recordNested': HomeAddress and MailingAddress must be different" errors diff --git a/tests/FSharp.Data.GraphQL.Tests/Variables and Inputs/InputRecordListTests.fs b/tests/FSharp.Data.GraphQL.Tests/Variables and Inputs/InputRecordListTests.fs index 149005f0b..d9d3ca57f 100644 --- a/tests/FSharp.Data.GraphQL.Tests/Variables and Inputs/InputRecordListTests.fs +++ b/tests/FSharp.Data.GraphQL.Tests/Variables and Inputs/InputRecordListTests.fs @@ -64,7 +64,7 @@ let ``Execute handles creation of inline empty input records list`` () = recordsNested: [] ) }""" - let result = sync <| (schema AllInclude).AsyncExecute(parse query) + let result = sync <| (schema AllInclude).AsyncExecute(parse query, getMockInputContext) ensureDirect result <| fun data errors -> empty errors [] @@ -83,7 +83,7 @@ let ``Execute handles creation of inline input records list with all fields`` () }] ) }""" - let result = sync <| (schema AllInclude).AsyncExecute(parse query) + let result = sync <| (schema AllInclude).AsyncExecute(parse query, getMockInputContext) ensureDirect result <| fun data errors -> empty errors [] @@ -96,7 +96,7 @@ let ``Execute handles creation of inline input records list with optional null f recordsNested: [{ a: { a: "a", b: "b", c: "c" }, b: null, c: null, s: null, l: [] }] ) }""" - let result = sync <| (schema Nothing).AsyncExecute(parse query) + let result = sync <| (schema Nothing).AsyncExecute(parse query, getMockInputContext) ensureDirect result <| fun data errors -> empty errors [] @@ -108,7 +108,7 @@ let ``Execute handles creation of inline input records list with mandatory only recordsNested: [{ a: { a: "a", b: "b", c: "c" }, l: [{ a: "a", b: "b", c: "c" }] }] ) }""" - let result = sync <| (schema Nothing).AsyncExecute(parse query) + let result = sync <| (schema Nothing).AsyncExecute(parse query, getMockInputContext) ensureDirect result <| fun data errors -> empty errors let variablesWithAllInputs (record, optRecord, skippable) = @@ -142,7 +142,7 @@ let ``Execute handles creation of input records list from variables with all fie let testInputObject = """{"a":"a","b":"b","c":"c"}""" let params' = variablesWithAllInputs(testInputObject, testInputObject, testInputObject) |> paramsWithValues - let result = sync <| (schema AllInclude).AsyncExecute(parse query, variables = params') + let result = sync <| (schema AllInclude).AsyncExecute(parse query, getMockInputContext, variables = params') //let expected = NameValueLookup.ofList [ "recordInputs", upcast testInputObject ] ensureDirect result <| fun data errors -> empty errors @@ -165,7 +165,7 @@ let ``Execute handles creation of input records list from variables with optiona let testInputObject = """{"a":"a","b":"b","c":"c"}""" let testInputSkippable = """{ "a": null, "b": null, "c": null }""" let params' = variablesWithAllInputs(testInputObject, "null", testInputSkippable) |> paramsWithValues - let result = sync <| (schema SkipAndIncludeNull).AsyncExecute(parse query, variables = params') + let result = sync <| (schema SkipAndIncludeNull).AsyncExecute(parse query, getMockInputContext, variables = params') ensureDirect result <| fun data errors -> empty errors [] @@ -187,5 +187,5 @@ let ``Execute handles creation of input records from variables with mandatory on }""" let testInputObject = """{"a":"a","b":"b","c":"c"}""" let params' = variablesWithAllInputs testInputObject |> paramsWithValues - let result = sync <| (schema AllSkip).AsyncExecute(parse query, variables = params') + let result = sync <| (schema AllSkip).AsyncExecute(parse query, getMockInputContext, variables = params') ensureDirect result <| fun data errors -> empty errors diff --git a/tests/FSharp.Data.GraphQL.Tests/Variables and Inputs/InputRecordTests.fs b/tests/FSharp.Data.GraphQL.Tests/Variables and Inputs/InputRecordTests.fs index fc19e33d8..1c71d3619 100644 --- a/tests/FSharp.Data.GraphQL.Tests/Variables and Inputs/InputRecordTests.fs +++ b/tests/FSharp.Data.GraphQL.Tests/Variables and Inputs/InputRecordTests.fs @@ -169,7 +169,7 @@ let ``Execute handles creation of inline input records with all fields`` () = } ) }""" - let result = sync <| (schema AllInclude).AsyncExecute(parse query) + let result = sync <| (schema AllInclude).AsyncExecute(parse query, getMockInputContext) ensureDirect result <| fun data errors -> empty errors [] @@ -182,7 +182,7 @@ let ``Execute handles creation of inline input records with optional null fields recordNested: { a: { a: "a", b: "b", c: "c" }, b: null, c: null, s: null, l: [] } ) }""" - let result = sync <| (schema Nothing).AsyncExecute(parse query) + let result = sync <| (schema Nothing).AsyncExecute(parse query, getMockInputContext) ensureDirect result <| fun data errors -> empty errors [] @@ -194,7 +194,7 @@ let ``Execute handles creation of inline input records with mandatory only field recordNested: { a: { a: "a", b: "b", c: "c" }, l: [{ a: "a", b: "b", c: "c" }] } ) }""" - let result = sync <| (schema Nothing).AsyncExecute(parse query) + let result = sync <| (schema Nothing).AsyncExecute(parse query, getMockInputContext) ensureDirect result <| fun data errors -> empty errors let variablesWithAllInputs (record, optRecord, skippable) = @@ -225,7 +225,7 @@ let ``Execute handles creation of input records from variables with all fields`` let testInputObject = """{"a":"a","b":"b","c":"c"}""" let params' = variablesWithAllInputs(testInputObject, testInputObject, testInputObject) |> paramsWithValues - let result = sync <| (schema AllInclude).AsyncExecute(parse query, variables = params') + let result = sync <| (schema AllInclude).AsyncExecute(parse query, getMockInputContext, variables = params') //let expected = NameValueLookup.ofList [ "recordInputs", upcast testInputObject ] ensureDirect result <| fun data errors -> empty errors @@ -244,7 +244,7 @@ let ``Execute handles creation of input records from variables with optional nul let testInputObject = """{"a":"a","b":"b","c":"c"}""" let testInputSkippable = """{ "a": null, "b": null, "c": null }""" let params' = variablesWithAllInputs(testInputObject, "null", testInputSkippable) |> paramsWithValues - let result = sync <| (schema SkipAndIncludeNull).AsyncExecute(parse query, variables = params') + let result = sync <| (schema SkipAndIncludeNull).AsyncExecute(parse query, getMockInputContext, variables = params') ensureDirect result <| fun data errors -> empty errors [] @@ -258,5 +258,5 @@ let ``Execute handles creation of input records from variables with mandatory on }""" let testInputObject = """{"a":"a","b":"b","c":"c"}""" let params' = variablesWithAllInputs(testInputObject, "null", "{}") |> paramsWithValues - let result = sync <| (schema AllSkip).AsyncExecute(parse query, variables = params') + let result = sync <| (schema AllSkip).AsyncExecute(parse query, getMockInputContext, variables = params') ensureDirect result <| fun data errors -> empty errors diff --git a/tests/FSharp.Data.GraphQL.Tests/Variables and Inputs/InputScalarAndAutoFieldScalarTests.fs b/tests/FSharp.Data.GraphQL.Tests/Variables and Inputs/InputScalarAndAutoFieldScalarTests.fs index 186202a50..7a2a50e3e 100644 --- a/tests/FSharp.Data.GraphQL.Tests/Variables and Inputs/InputScalarAndAutoFieldScalarTests.fs +++ b/tests/FSharp.Data.GraphQL.Tests/Variables and Inputs/InputScalarAndAutoFieldScalarTests.fs @@ -184,7 +184,7 @@ let ``Execute handles nullable auto-fields in input and output record fields coe } }""" - let result = sync <| schema.Value.AsyncExecute (parse query) + let result = sync <| schema.Value.AsyncExecute (parse query, getMockInputContext) let expected = NameValueLookup.ofList [ "record", upcast NameValueLookup.ofList [ "a", "a" :> obj; "b", "b"; "c", "c"] ] @@ -203,7 +203,7 @@ let ``Execute handles nullable auto-fields in input and output object fields coe } }""" - let result = sync <| schema.Value.AsyncExecute (parse query) + let result = sync <| schema.Value.AsyncExecute (parse query, getMockInputContext) let expected = NameValueLookup.ofList [ "record", upcast NameValueLookup.ofList [ "a", "a" :> obj; "b", "b"; "c", "c" ] ] diff --git a/tests/FSharp.Data.GraphQL.Tests/Variables and Inputs/OptionalsNormalizationTests.fs b/tests/FSharp.Data.GraphQL.Tests/Variables and Inputs/OptionalsNormalizationTests.fs index 28e70d52d..29f859e8e 100644 --- a/tests/FSharp.Data.GraphQL.Tests/Variables and Inputs/OptionalsNormalizationTests.fs +++ b/tests/FSharp.Data.GraphQL.Tests/Variables and Inputs/OptionalsNormalizationTests.fs @@ -220,7 +220,7 @@ let ``Execute handles validation of valid inline input records with all fields`` structOptional: { zipCode: "12345", city: "Miami" } ) }""" - let result = sync <| schema.AsyncExecute(parse query) + let result = sync <| schema.AsyncExecute(parse query, getMockInputContext) ensureDirect result <| fun data errors -> empty errors [] @@ -238,5 +238,5 @@ let ``Execute handles validation of valid inline input records with mandatory-on struct: { zipCode: "12345", city: "Miami" }, ) }""" - let result = sync <| schema.AsyncExecute(parse query) + let result = sync <| schema.AsyncExecute(parse query, getMockInputContext) ensureDirect result <| fun data errors -> empty errors diff --git a/tests/FSharp.Data.GraphQL.Tests/Variables and Inputs/SkippablesNormalizationTests.fs b/tests/FSharp.Data.GraphQL.Tests/Variables and Inputs/SkippablesNormalizationTests.fs index b15c36d84..ce31b5c5e 100644 --- a/tests/FSharp.Data.GraphQL.Tests/Variables and Inputs/SkippablesNormalizationTests.fs +++ b/tests/FSharp.Data.GraphQL.Tests/Variables and Inputs/SkippablesNormalizationTests.fs @@ -295,7 +295,7 @@ let ``Execute handles validation of valid inline input records with all fields`` structOptional: { zipCode: "12345", city: "Miami" } ) }""" - let result = sync <| (schema Nothing).AsyncExecute(parse query) + let result = sync <| (schema Nothing).AsyncExecute(parse query, getMockInputContext) ensureDirect result <| fun data errors -> empty errors [] @@ -313,7 +313,7 @@ let ``Execute handles validation of valid inline input records with mandatory-on struct: { zipCode: "12345", city: "Miami" }, ) }""" - let result = sync <| (schema Nothing).AsyncExecute(parse query) + let result = sync <| (schema Nothing).AsyncExecute(parse query, getMockInputContext) ensureDirect result <| fun data errors -> empty errors [] @@ -334,8 +334,8 @@ let ``Execute handles validation of valid inline input records with null mandato structOptional: { zipCode: null, city: null } ) }""" - let result = sync <| (schema Skip).AsyncExecute(parse query) - ensureDirect result <| fun data errors -> empty errors + let result = sync <| (schema Skip).AsyncExecute(parse query, getMockInputContext) + ensureDirect result <| fun _ errors -> empty errors [] let ``Execute handles validation of valid inline input records with null optional field`` () = @@ -355,5 +355,5 @@ let ``Execute handles validation of valid inline input records with null optiona structOptional: { line2: null } ) }""" - let result = sync <| (schema SkipAndIncludeNull).AsyncExecute(parse query) - ensureDirect result <| fun data errors -> empty errors + let result = sync <| (schema SkipAndIncludeNull).AsyncExecute(parse query, getMockInputContext) + ensureDirect result <| fun _ errors -> empty errors