From dd2d83cd17fe13216539df8d01cae43124a0d4bd Mon Sep 17 00:00:00 2001 From: Andrii Chebukin Date: Wed, 18 Oct 2023 02:21:19 +0400 Subject: [PATCH] Moved all GraphQL exceptions to the Server project --- .../FSharp.Data.GraphQL.Server.Relay.fsproj | 4 +-- .../ErrorMessages.fs | 6 +++- src/FSharp.Data.GraphQL.Server/Exceptions.fs | 32 +++++++++++++++++-- src/FSharp.Data.GraphQL.Server/Execution.fs | 18 +++++------ src/FSharp.Data.GraphQL.Server/Executor.fs | 4 +-- .../FSharp.Data.GraphQL.Server.fsproj | 1 + src/FSharp.Data.GraphQL.Server/Schema.fs | 2 +- src/FSharp.Data.GraphQL.Server/TypeSystem.fs | 17 ++++++++++ src/FSharp.Data.GraphQL.Shared/Exceptions.fs | 17 ---------- .../FSharp.Data.GraphQL.Shared.fsproj | 1 - .../SchemaDefinitions.fs | 2 -- src/FSharp.Data.GraphQL.Shared/TypeSystem.fs | 7 ---- 12 files changed, 66 insertions(+), 45 deletions(-) create mode 100644 src/FSharp.Data.GraphQL.Server/TypeSystem.fs delete mode 100644 src/FSharp.Data.GraphQL.Shared/Exceptions.fs diff --git a/src/FSharp.Data.GraphQL.Server.Relay/FSharp.Data.GraphQL.Server.Relay.fsproj b/src/FSharp.Data.GraphQL.Server.Relay/FSharp.Data.GraphQL.Server.Relay.fsproj index 5f2aa039e..d4faba8a4 100644 --- a/src/FSharp.Data.GraphQL.Server.Relay/FSharp.Data.GraphQL.Server.Relay.fsproj +++ b/src/FSharp.Data.GraphQL.Server.Relay/FSharp.Data.GraphQL.Server.Relay.fsproj @@ -19,8 +19,8 @@ - - + + diff --git a/src/FSharp.Data.GraphQL.Server/ErrorMessages.fs b/src/FSharp.Data.GraphQL.Server/ErrorMessages.fs index 92540ad27..8d59797f6 100644 --- a/src/FSharp.Data.GraphQL.Server/ErrorMessages.fs +++ b/src/FSharp.Data.GraphQL.Server/ErrorMessages.fs @@ -1,5 +1,9 @@ // The MIT License (MIT) -module FSharp.Data.GraphQL.ErrorMessagess +module FSharp.Data.GraphQL.ErrorMessages + +open System let variableNotFound variableName = $"A variable '$%s{variableName}' was not provided" + +let expectedEnumerableValue indetifier ``type`` = $"Expected to have enumerable value in field '%s{indetifier}' but got '%O{(``type``:Type)}'" diff --git a/src/FSharp.Data.GraphQL.Server/Exceptions.fs b/src/FSharp.Data.GraphQL.Server/Exceptions.fs index 223e6e275..28ed75f94 100644 --- a/src/FSharp.Data.GraphQL.Server/Exceptions.fs +++ b/src/FSharp.Data.GraphQL.Server/Exceptions.fs @@ -5,11 +5,37 @@ namespace FSharp.Data.GraphQL open System open System.Collections.Immutable +open System.Collections.Generic +open System.Runtime.InteropServices + +[] +type GraphQLException = + inherit Exception + + new () = { inherit Exception () } + new (msg) = { inherit Exception (msg) } + new (info : Runtime.Serialization.SerializationInfo, context : Runtime.Serialization.StreamingContext) = { inherit Exception (info, context) } + +[] +type GQLMessageExceptionBase (errorKind, msg, [] extensions) = + inherit GraphQLException (msg) + interface IGQLError with + member _.Message = msg + interface IGQLErrorExtensions with + member _.Extensions = + match extensions with + | null -> Dictionary 1 + | _ -> extensions + |> GQLProblemDetails.SetErrorKind errorKind + |> ValueSome + +type GQLMessageException (msg) = + inherit GQLMessageExceptionBase (Execution, msg) type InvalidInputTypeException (msg, unmatchedOptionalFields) = - inherit Exception(msg) + inherit GQLMessageExceptionBase (InputCoercion, msg) member _.UnmatchedOptionalFields : string ImmutableHashSet = unmatchedOptionalFields -type MalformedGQLQueryException(msg) = - inherit GraphQLException(msg) +type MalformedGQLQueryException (msg) = + inherit GQLMessageExceptionBase (Validation, msg) diff --git a/src/FSharp.Data.GraphQL.Server/Execution.fs b/src/FSharp.Data.GraphQL.Server/Execution.fs index c2820b536..4e5ca4dd5 100644 --- a/src/FSharp.Data.GraphQL.Server/Execution.fs +++ b/src/FSharp.Data.GraphQL.Server/Execution.fs @@ -273,12 +273,12 @@ let private raiseErrors errs = AsyncVal.wrap <| Error errs /// to a list of GQLProblemDetails. let private resolverError path ctx e = ctx.Schema.ParseError path e |> List.map (GQLProblemDetails.OfFieldExecutionError (path |> List.rev)) // Helper functions for generating more specific GQLProblemDetails. -let private nullResolverError name path ctx = resolverError path ctx (GraphQLException <| sprintf "Non-Null field %s resolved as a null!" name) -let private coercionError value tyName path ctx = resolverError path ctx (GraphQLException <| sprintf "Value '%O' could not be coerced to scalar %s" value tyName) -let private interfaceImplError ifaceName tyName path ctx = resolverError path ctx (GraphQLException <| sprintf "GraphQL Interface '%s' is not implemented by the type '%s'" ifaceName tyName) -let private unionImplError unionName tyName path ctx = resolverError path ctx (GraphQLException (sprintf "GraphQL Union '%s' is not implemented by the type '%s'" unionName tyName)) -let private deferredNullableError name tyName path ctx = resolverError path ctx (GraphQLException (sprintf "Deferred field %s of type '%s' must be nullable" name tyName)) -let private streamListError name tyName path ctx = resolverError path ctx (GraphQLException (sprintf "Streamed field %s of type '%s' must be list" name tyName)) +let private nullResolverError name path ctx = resolverError path ctx (GQLMessageException <| sprintf "Non-Null field %s resolved as a null!" name) +let private coercionError value tyName path ctx = resolverError path ctx (GQLMessageException <| sprintf "Value '%O' could not be coerced to scalar %s" value tyName) +let private interfaceImplError ifaceName tyName path ctx = resolverError path ctx (GQLMessageException <| sprintf "GraphQL Interface '%s' is not implemented by the type '%s'" ifaceName tyName) +let private unionImplError unionName tyName path ctx = resolverError path ctx (GQLMessageException (sprintf "GraphQL Union '%s' is not implemented by the type '%s'" unionName tyName)) +let private deferredNullableError name tyName path ctx = resolverError path ctx (GQLMessageException (sprintf "Deferred field %s of type '%s' must be nullable" name tyName)) +let private streamListError name tyName path ctx = resolverError path ctx (GQLMessageException (sprintf "Streamed field %s of type '%s' must be list" name tyName)) let private resolved name v : AsyncVal>> = AsyncVal.wrap <| Ok(KeyValuePair(name, box v), None, []) @@ -353,7 +353,7 @@ let rec private direct (returnDef : OutputDef) (ctx : ResolveFieldContext) (path |> Array.mapi resolveItem |> collectFields Parallel |> AsyncVal.map(ResolverResult.mapValue(fun items -> KeyValuePair(name, items |> Array.map(fun d -> d.Value) |> box))) - | _ -> raise <| GraphQLException (sprintf "Expected to have enumerable value in field '%s' but got '%O'" ctx.ExecutionInfo.Identifier (value.GetType())) + | _ -> raise <| GQLMessageException (ErrorMessages.expectedEnumerableValue ctx.ExecutionInfo.Identifier (value.GetType())) | Nullable (Output innerDef) -> let innerCtx = { ctx with ExecutionInfo = { ctx.ExecutionInfo with IsNullable = true; ReturnDef = innerDef } } @@ -445,7 +445,7 @@ and private streamed (options : BufferedStreamOptions) (innerDef : OutputDef) (c |> Observable.ofAsyncValSeq |> buffer AsyncVal.wrap <| Ok(KeyValuePair(info.Identifier, box [||]), Some stream, []) - | _ -> raise <| GraphQLException (sprintf "Expected to have enumerable value in field '%s' but got '%O'" ctx.ExecutionInfo.Identifier (value.GetType())) + | _ -> raise <| GQLMessageException (ErrorMessages.expectedEnumerableValue ctx.ExecutionInfo.Identifier (value.GetType())) and private live (ctx : ResolveFieldContext) (path : FieldPath) (parent : obj) (value : obj) = let info = ctx.ExecutionInfo @@ -549,7 +549,7 @@ let internal compileSubscriptionField (subfield: SubscriptionFieldDef) = match subfield.Resolve with | Resolve.BoxedFilterExpr(_, _, _, filter) -> fun ctx a b -> filter ctx a b |> AsyncVal.wrap |> AsyncVal.toAsync | Resolve.BoxedAsyncFilterExpr(_, _, _, filter) -> filter - | _ -> raise <| GraphQLException ("Invalid filter expression for subscription field!") + | _ -> raise <| GQLMessageException ("Invalid filter expression for subscription field!") let internal compileField (fieldDef: FieldDef) : ExecuteField = match fieldDef.Resolve with diff --git a/src/FSharp.Data.GraphQL.Server/Executor.fs b/src/FSharp.Data.GraphQL.Server/Executor.fs index 022d4c5be..e20684df9 100644 --- a/src/FSharp.Data.GraphQL.Server/Executor.fs +++ b/src/FSharp.Data.GraphQL.Server/Executor.fs @@ -96,7 +96,7 @@ type Executor<'Root>(schema: ISchema<'Root>, middlewares : IExecutorMiddleware s runMiddlewares (fun x -> x.PostCompileSchema) (upcast schema) ignore match Validation.Types.validateTypeMap schema.TypeMap with | Success -> () - | ValidationError errors -> raise (GraphQLException (System.String.Join("\n", errors))) + | ValidationError errors -> raise (GQLMessageException (System.String.Join("\n", errors))) let eval (executionPlan: ExecutionPlan, data: 'Root option, variables: ImmutableDictionary): Async = let documentId = executionPlan.DocumentId @@ -124,7 +124,7 @@ type Executor<'Root>(schema: ISchema<'Root>, middlewares : IExecutorMiddleware s let! res = runMiddlewares (fun x -> x.ExecuteOperationAsync) executionCtx executeOperation |> AsyncVal.toAsync return prepareOutput res with - | :? GraphQLException as ex -> return prepareOutput(GQLExecutionResult.Error (documentId, ex, executionPlan.Metadata)) + | :? GQLMessageException as ex -> return prepareOutput(GQLExecutionResult.Error (documentId, ex, executionPlan.Metadata)) | ex -> return prepareOutput (GQLExecutionResult.Error(documentId, ex.ToString(), executionPlan.Metadata)) // TODO: Handle better } diff --git a/src/FSharp.Data.GraphQL.Server/FSharp.Data.GraphQL.Server.fsproj b/src/FSharp.Data.GraphQL.Server/FSharp.Data.GraphQL.Server.fsproj index 431a2178d..484695ff1 100644 --- a/src/FSharp.Data.GraphQL.Server/FSharp.Data.GraphQL.Server.fsproj +++ b/src/FSharp.Data.GraphQL.Server/FSharp.Data.GraphQL.Server.fsproj @@ -37,6 +37,7 @@ + diff --git a/src/FSharp.Data.GraphQL.Server/Schema.fs b/src/FSharp.Data.GraphQL.Server/Schema.fs index 814788ba1..ba44fdceb 100644 --- a/src/FSharp.Data.GraphQL.Server/Schema.fs +++ b/src/FSharp.Data.GraphQL.Server/Schema.fs @@ -132,7 +132,7 @@ type SchemaConfig = ParseError = fun path ex -> match ex with - | :? GraphQLException as ex -> [ex] + | :? GQLMessageException as ex -> [ex] | ex -> [{ new IGQLError with member _.Message = ex.Message }] SubscriptionProvider = SchemaConfig.DefaultSubscriptionProvider() LiveFieldSubscriptionProvider = SchemaConfig.DefaultLiveFieldSubscriptionProvider() diff --git a/src/FSharp.Data.GraphQL.Server/TypeSystem.fs b/src/FSharp.Data.GraphQL.Server/TypeSystem.fs new file mode 100644 index 000000000..750634d59 --- /dev/null +++ b/src/FSharp.Data.GraphQL.Server/TypeSystem.fs @@ -0,0 +1,17 @@ +// The MIT License (MIT) +// Copyright (c) 2016 Bazinga Technologies Inc +[] +module FSharp.Data.GraphQL.Types.ResolveFieldContextExtensions + +open FSharp.Data.GraphQL +open FSharp.Data.GraphQL.Extensions + +type ResolveFieldContext with + + /// Returns an argument by provided name. If argument was not found a GraphQL exception will be thrown. + /// When argument with the name not found in the Args. + member x.Arg(name : string) : 't = + match Map.tryFind name x.Args with + | Some found -> downcast found + | None -> raise (GQLMessageException $"Argument '%s{name}' was not provided within context of a field '%s{x.ExecutionInfo.Identifier}'. Check if it was supplied within GraphQL query.") + diff --git a/src/FSharp.Data.GraphQL.Shared/Exceptions.fs b/src/FSharp.Data.GraphQL.Shared/Exceptions.fs deleted file mode 100644 index 46643a2b0..000000000 --- a/src/FSharp.Data.GraphQL.Shared/Exceptions.fs +++ /dev/null @@ -1,17 +0,0 @@ -// The MIT License (MIT) -// Copyright (c) 2016 Bazinga Technologies Inc - -namespace FSharp.Data.GraphQL - -open System -open System.Collections.Generic - -type GraphQLException(msg) = - inherit Exception(msg) - interface IGQLError with - member _.Message = msg - interface IGQLErrorExtensions with - member _.Extensions = - Dictionary 1 - |> GQLProblemDetails.SetErrorKind Execution - |> ValueSome 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 a84ed4c3b..f1b33dc2d 100644 --- a/src/FSharp.Data.GraphQL.Shared/FSharp.Data.GraphQL.Shared.fsproj +++ b/src/FSharp.Data.GraphQL.Shared/FSharp.Data.GraphQL.Shared.fsproj @@ -49,7 +49,6 @@ - diff --git a/src/FSharp.Data.GraphQL.Shared/SchemaDefinitions.fs b/src/FSharp.Data.GraphQL.Shared/SchemaDefinitions.fs index 2d98b742b..131247d29 100644 --- a/src/FSharp.Data.GraphQL.Shared/SchemaDefinitions.fs +++ b/src/FSharp.Data.GraphQL.Shared/SchemaDefinitions.fs @@ -519,8 +519,6 @@ module SchemaDefinitions = DirectiveLocation.FIELD ||| DirectiveLocation.FRAGMENT_SPREAD ||| DirectiveLocation.INLINE_FRAGMENT ||| DirectiveLocation.FRAGMENT_DEFINITION Args = [||] } - let internal matchParameters (methodInfo : MethodInfo) (ctx : ResolveFieldContext) = - methodInfo.GetParameters() |> Array.map (fun param -> ctx.Arg(param.Name)) let inline internal strip (fn : 'In -> 'Out) : obj -> obj = fun i -> upcast fn (i :?> 'In) /// Common space for all definition helper methods. diff --git a/src/FSharp.Data.GraphQL.Shared/TypeSystem.fs b/src/FSharp.Data.GraphQL.Shared/TypeSystem.fs index a247c74ae..1b3dea8d5 100644 --- a/src/FSharp.Data.GraphQL.Shared/TypeSystem.fs +++ b/src/FSharp.Data.GraphQL.Shared/TypeSystem.fs @@ -893,13 +893,6 @@ and ResolveFieldContext = | Some o -> Some(o :?> 't) // TODO: Use Convert.ChangeType | None -> None - /// Returns an argument by provided name. If argument was not found a GraphQL exception will be thrown. - /// When argument with the name not found in the Args. - member x.Arg(name : string) : 't = - match Map.tryFind name x.Args with - | Some found -> downcast found - | None -> raise (GraphQLException $"Argument '%s{name}' was not provided within context of a field '%s{x.ExecutionInfo.Identifier}'. Check if it was supplied within GraphQL query.") - /// Function type for the compiled field executor. and ExecuteField = ResolveFieldContext -> obj -> AsyncVal