diff --git a/src/FSharp.Data.GraphQL.Server/Executor.fs b/src/FSharp.Data.GraphQL.Server/Executor.fs index 11c21267..f9592230 100644 --- a/src/FSharp.Data.GraphQL.Server/Executor.fs +++ b/src/FSharp.Data.GraphQL.Server/Executor.fs @@ -175,6 +175,8 @@ type Executor<'Root>(schema: ISchema<'Root>, middlewares : IExecutorMiddleware s new(schema) = Executor(schema, middlewares = Seq.empty) + abstract member AsyncExecute: ExecutionPlan * 'Root option * ImmutableDictionary option -> Async + /// /// Asynchronously executes a provided execution plan. In case of repetitive queries, execution plan may be preprocessed /// and cached using `documentId` as an identifier. @@ -186,7 +188,7 @@ type Executor<'Root>(schema: ISchema<'Root>, middlewares : IExecutorMiddleware s /// Execution plan 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 = + default _.AsyncExecute(executionPlan: ExecutionPlan, ?data: 'Root, ?variables: ImmutableDictionary): Async = execute (executionPlan, data, variables) /// @@ -200,10 +202,10 @@ type Executor<'Root>(schema: ISchema<'Root>, middlewares : IExecutorMiddleware s /// 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 this.AsyncExecute(ast: Document, ?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 -> this.AsyncExecute (executionPlan, data, variables) | Error (documentId, errors) -> async.Return <| GQLExecutionResult.Invalid(documentId, errors, meta) /// @@ -217,11 +219,11 @@ type Executor<'Root>(schema: ISchema<'Root>, middlewares : IExecutorMiddleware s /// 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 this.AsyncExecute(queryOrMutation: string, ?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 -> this.AsyncExecute (executionPlan, data, variables) | 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/Values.fs b/src/FSharp.Data.GraphQL.Server/Values.fs index b589f91b..c4ea6bfb 100644 --- a/src/FSharp.Data.GraphQL.Server/Values.fs +++ b/src/FSharp.Data.GraphQL.Server/Values.fs @@ -105,10 +105,35 @@ let rec internal compileByType | InputObject objDef -> let objtype = objDef.Type - let ctor = ReflectionHelper.matchConstructor objtype (objDef.Fields |> Array.map (fun x -> x.Name)) + let (constructor : obj[] -> obj), (parameterInfos : Reflection.ParameterInfo[]) = + if typeof>.IsAssignableFrom(objtype) then + let parameterInfos = [| + for f in objDef.Fields -> + { new Reflection.ParameterInfo() with + member _.Name = f.Name + member _.ParameterType = f.TypeDef.Type + member _.Attributes = + match f.TypeDef with + | Nullable _ -> Reflection.ParameterAttributes.Optional + | _ -> Reflection.ParameterAttributes.None + } + |] + let constructor (args:obj[]) = + let o = Activator.CreateInstance(objtype) + let dict = o :?> IDictionary + for fld,arg in Seq.zip objDef.Fields args do + match arg, fld.TypeDef with + | null, Nullable _ -> () // skip populating Nullable fields with nulls + | _, _ -> dict.Add(fld.Name, arg) + box o + constructor, parameterInfos + else + let ctor = ReflectionHelper.matchConstructor objtype (objDef.Fields |> Array.map (fun x -> x.Name)) + ctor.Invoke, ctor.GetParameters() + let struct (mapper, nullableMismatchParameters, missingParameters) = - ctor.GetParameters () + parameterInfos |> Array.fold (fun struct (all : ResizeArray<_>, areNullable : HashSet<_>, missing : HashSet<_>) param -> match @@ -180,6 +205,19 @@ let rec internal compileByType | None -> Ok <| wrapOptionalNone param.ParameterType field.TypeDef.Type + | Some input when isNull (box field.ExecuteInput) -> + // hack around the case where field.ExecuteInput is null + let rec extract = function + | NullValue -> null + | IntValue i -> box i + | FloatValue f -> box f + | BooleanValue b -> box b + | StringValue s -> box s + | EnumValue e -> box e + | ListValue l -> box (l |> List.map extract) + | ObjectValue o -> o |> Map.map (fun k v -> extract v) |> box + | VariableName v -> failwithf "Todo: extract variable" + extract input |> Ok | Some prop -> field.ExecuteInput prop variables |> Result.map (normalizeOptional param.ParameterType) @@ -188,7 +226,7 @@ let rec internal compileByType let! args = argResults |> splitSeqErrorsList - let instance = ctor.Invoke args + let instance = constructor args do! objDef.Validator instance |> ValidationResult.mapErrors (fun err -> @@ -201,7 +239,6 @@ let rec internal compileByType | true, found -> match found with | :? IReadOnlyDictionary as objectFields -> - let argResults = mapper |> Seq.map (fun struct (field, param) -> result { @@ -217,7 +254,7 @@ let rec internal compileByType let! args = argResults |> splitSeqErrorsList - let instance = ctor.Invoke args + let instance = constructor args do! objDef.Validator instance |> ValidationResult.mapErrors (fun err ->