Skip to content

Commit

Permalink
Formatted GraphQLRequestHandler
Browse files Browse the repository at this point in the history
  • Loading branch information
xperiandri committed Feb 22, 2025
1 parent 9dc0d9a commit 95950ef
Showing 1 changed file with 68 additions and 81 deletions.
149 changes: 68 additions & 81 deletions src/FSharp.Data.GraphQL.Server.AspNetCore/GraphQLRequestHandler.fs
Original file line number Diff line number Diff line change
Expand Up @@ -15,42 +15,46 @@ open FsToolkit.ErrorHandling
open FSharp.Data.GraphQL.Server
open FSharp.Data.GraphQL.Shared

type DefaultGraphQLRequestHandler<'Root> (
httpContextAccessor : IHttpContextAccessor,
options : IOptionsMonitor<GraphQLOptions<'Root>>,
logger : ILogger<DefaultGraphQLRequestHandler<'Root>>
) =
type DefaultGraphQLRequestHandler<'Root>
(
/// The accessor to the current HTTP context
httpContextAccessor : IHttpContextAccessor,
/// The options monitor for GraphQL options
options : IOptionsMonitor<GraphQLOptions<'Root>>,
/// The logger to log messages
logger : ILogger<DefaultGraphQLRequestHandler<'Root>>
) =
inherit GraphQLRequestHandler<'Root> (httpContextAccessor, options, logger)

/// Provides logic to parse and execute GraphQL request
and [<AbstractClass>] GraphQLRequestHandler<'Root> (
httpContextAccessor : IHttpContextAccessor,
options : IOptionsMonitor<GraphQLOptions<'Root>>,
logger : ILogger
) =
and [<AbstractClass>] GraphQLRequestHandler<'Root>
(
/// The accessor to the current HTTP context
httpContextAccessor : IHttpContextAccessor,
/// The options monitor for GraphQL options
options : IOptionsMonitor<GraphQLOptions<'Root>>,
/// The logger to log messages
logger : ILogger
) =

let ctx = httpContextAccessor.HttpContext

let toResponse { DocumentId = documentId; Content = content; Metadata = metadata } =

let serializeIndented value =
let jsonSerializerOptions = options.Get(GraphQLOptions.IndentedOptionsName).SerializerOptions
JsonSerializer.Serialize(value, jsonSerializerOptions)
JsonSerializer.Serialize (value, jsonSerializerOptions)

match content with
| Direct(data, errs) ->
logger.LogDebug(
$"Produced direct GraphQL response with documentId = '{{documentId}}' and metadata:\n{{metadata}}",
documentId,
metadata
)
| Direct (data, errs) ->
logger.LogDebug ($"Produced direct GraphQL response with documentId = '{{documentId}}' and metadata:\n{{metadata}}", documentId, metadata)

if logger.IsEnabled LogLevel.Trace then
logger.LogTrace($"GraphQL response data:\n:{{data}}", serializeIndented data)
logger.LogTrace ($"GraphQL response data:\n:{{data}}", serializeIndented data)

GQLResponse.Direct(documentId, data, errs)
| Deferred(data, errs, deferred) ->
logger.LogDebug(
GQLResponse.Direct (documentId, data, errs)
| Deferred (data, errs, deferred) ->
logger.LogDebug (
$"Produced deferred GraphQL response with documentId = '{{documentId}}' and metadata:\n{{metadata}}",
documentId,
metadata
Expand All @@ -59,97 +63,83 @@ and [<AbstractClass>] GraphQLRequestHandler<'Root> (
if logger.IsEnabled LogLevel.Debug then
deferred
|> Observable.add (function
| DeferredResult(data, path) ->
logger.LogDebug(
"Produced GraphQL deferred result for path: {path}",
path |> Seq.map string |> Seq.toArray |> Path.Join
)
| DeferredResult (data, path) ->
logger.LogDebug ("Produced GraphQL deferred result for path: {path}", path |> Seq.map string |> Seq.toArray |> Path.Join)

if logger.IsEnabled LogLevel.Trace then
logger.LogTrace(
$"GraphQL deferred data:\n{{data}}",
serializeIndented data
)
| DeferredErrors(null, errors, path) ->
logger.LogDebug(
"Produced GraphQL deferred errors for path: {path}",
path |> Seq.map string |> Seq.toArray |> Path.Join
)
logger.LogTrace ($"GraphQL deferred data:\n{{data}}", serializeIndented data)
| DeferredErrors (null, errors, path) ->
logger.LogDebug ("Produced GraphQL deferred errors for path: {path}", path |> Seq.map string |> Seq.toArray |> Path.Join)

if logger.IsEnabled LogLevel.Trace then
logger.LogTrace($"GraphQL deferred errors:\n{{errors}}", errors)
| DeferredErrors(data, errors, path) ->
logger.LogDebug(
logger.LogTrace ($"GraphQL deferred errors:\n{{errors}}", errors)
| DeferredErrors (data, errors, path) ->
logger.LogDebug (
"Produced GraphQL deferred result with errors for path: {path}",
path |> Seq.map string |> Seq.toArray |> Path.Join
)

if logger.IsEnabled LogLevel.Trace then
logger.LogTrace(
logger.LogTrace (
$"GraphQL deferred errors:\n{{errors}}\nGraphQL deferred data:\n{{data}}",
errors,
serializeIndented data
))

GQLResponse.Direct(documentId, data, errs)
GQLResponse.Direct (documentId, data, errs)

| Stream stream ->
logger.LogDebug(
$"Produced stream GraphQL response with documentId = '{{documentId}}' and metadata:\n{{metadata}}",
documentId,
metadata
)
logger.LogDebug ($"Produced stream GraphQL response with documentId = '{{documentId}}' and metadata:\n{{metadata}}", documentId, metadata)

if logger.IsEnabled LogLevel.Debug then
stream
|> Observable.add (function
| SubscriptionResult data ->
logger.LogDebug("Produced GraphQL subscription result")
logger.LogDebug ("Produced GraphQL subscription result")

if logger.IsEnabled LogLevel.Trace then
logger.LogTrace(
$"GraphQL subscription data:\n{{data}}",
serializeIndented data
)
| SubscriptionErrors(null, errors) ->
logger.LogDebug("Produced GraphQL subscription errors")
logger.LogTrace ($"GraphQL subscription data:\n{{data}}", serializeIndented data)
| SubscriptionErrors (null, errors) ->
logger.LogDebug ("Produced GraphQL subscription errors")

if logger.IsEnabled LogLevel.Trace then
logger.LogTrace($"GraphQL subscription errors:\n{{errors}}", errors)
| SubscriptionErrors(data, errors) ->
logger.LogDebug("Produced GraphQL subscription result with errors")
logger.LogTrace ($"GraphQL subscription errors:\n{{errors}}", errors)
| SubscriptionErrors (data, errors) ->
logger.LogDebug ("Produced GraphQL subscription result with errors")

if logger.IsEnabled LogLevel.Trace then
logger.LogTrace(
logger.LogTrace (
$"GraphQL subscription errors:\n{{errors}}\nGraphQL deferred data:\n{{data}}",
errors,
serializeIndented data
))

GQLResponse.Stream documentId

| RequestError errs ->
logger.LogWarning(
logger.LogWarning (
$"Produced request error GraphQL response with documentId = '{{documentId}}' and metadata:\n{{metadata}}",
documentId,
metadata
)

GQLResponse.RequestError(documentId, errs)
GQLResponse.RequestError (documentId, errs)

/// Checks if the request contains a body
let checkIfHasBody (request: HttpRequest) = task {
let checkIfHasBody (request : HttpRequest) = task {
if request.Body.CanSeek then
return (request.Body.Length > 0L)
else
request.EnableBuffering()
request.EnableBuffering ()
let body = request.Body
let buffer = Array.zeroCreate 1
let! bytesRead = body.ReadAsync(buffer, 0, 1)
body.Seek(0, SeekOrigin.Begin) |> ignore
let! bytesRead = body.ReadAsync (buffer, 0, 1)
body.Seek (0, SeekOrigin.Begin) |> ignore
return bytesRead > 0
}

/// Execute default or custom introspection query
let executeIntrospectionQuery (executor: Executor<_>) (ast: Ast.Document voption) : Task<IResult> = task {
let executeIntrospectionQuery (executor : Executor<_>) (ast : Ast.Document voption) : Task<IResult> = task {
let! result =
match ast with
| ValueNone -> executor.AsyncExecute IntrospectionQuery.Definition
Expand All @@ -166,21 +156,19 @@ and [<AbstractClass>] GraphQLRequestHandler<'Root> (
/// <returns>Result of check of <see cref="OperationType"/></returns>
let checkOperationType () = taskResult {

let checkAnonymousFieldsOnly (ctx: HttpContext) = taskResult {
let! gqlRequest = ctx.TryBindJsonAsync<GQLRequestContent>(GQLRequestContent.expectedJSON)
let checkAnonymousFieldsOnly (ctx : HttpContext) = taskResult {
let! gqlRequest = ctx.TryBindJsonAsync<GQLRequestContent> (GQLRequestContent.expectedJSON)
let! ast = Parser.parseOrIResult ctx.Request.Path.Value gqlRequest.Query
let operationName = gqlRequest.OperationName |> Skippable.toValueOption

let createParsedContent() = {
let createParsedContent () = {
Query = gqlRequest.Query
Ast = ast
OperationName = gqlRequest.OperationName
Variables = gqlRequest.Variables
}
if ast.IsEmpty then
logger.LogTrace(
"Request is not GET, but 'query' field is an empty string. Must be an introspection query"
)
logger.LogTrace ("Request is not GET, but 'query' field is an empty string. Must be an introspection query")
return IntrospectionQuery <| ValueNone
else
match Ast.tryFindOperationByName operationName ast with
Expand All @@ -195,53 +183,52 @@ and [<AbstractClass>] GraphQLRequestHandler<'Root> (
let hasNonMetaFields =
Ast.containsFieldsBeyond
Ast.metaTypeFields
(fun x ->
logger.LogTrace($"Operation Selection in Field with name: {{fieldName}}", x.Name))
(fun field -> logger.LogTrace ($"Operation Selection in Field with name: {{fieldName}}", field.Name))
(fun _ -> logger.LogTrace "Operation Selection is non-Field type")
op

if hasNonMetaFields then
return createParsedContent() |> OperationQuery
return createParsedContent () |> OperationQuery
else
return IntrospectionQuery <| ValueSome ast
}

let request = ctx.Request

if HttpMethods.Get = request.Method then
logger.LogTrace("Request is GET. Must be an introspection query")
logger.LogTrace ("Request is GET. Must be an introspection query")
return IntrospectionQuery <| ValueNone
else
let! hasBody = checkIfHasBody request

if not hasBody then
logger.LogTrace("Request is not GET, but has no body. Must be an introspection query")
logger.LogTrace ("Request is not GET, but has no body. Must be an introspection query")
return IntrospectionQuery <| ValueNone
else
return! checkAnonymousFieldsOnly ctx
}

abstract ExecuteOperation<'Root> : executor:Executor<'Root> * content:ParsedGQLQueryRequestContent -> Task<IResult>
abstract ExecuteOperation<'Root> : executor : Executor<'Root> * content : ParsedGQLQueryRequestContent -> Task<IResult>

/// Execute the operation for given request
default _.ExecuteOperation<'Root> (executor: Executor<'Root>, content) = task {
default _.ExecuteOperation<'Root> (executor : Executor<'Root>, content) = task {

let operationName = content.OperationName |> Skippable.filter (not << isNull) |> Skippable.toOption
let variables = content.Variables |> Skippable.filter (not << isNull) |> Skippable.toOption

operationName
|> Option.iter (fun on -> logger.LogTrace("GraphQL operation name: '{operationName}'", on))
|> Option.iter (fun on -> logger.LogTrace ("GraphQL operation name: '{operationName}'", on))

logger.LogTrace($"Executing GraphQL query:\n{{query}}", content.Query)
logger.LogTrace ($"Executing GraphQL query:\n{{query}}", content.Query)

variables
|> Option.iter (fun v -> logger.LogTrace($"GraphQL variables:\n{{variables}}", v))
|> Option.iter (fun v -> logger.LogTrace ($"GraphQL variables:\n{{variables}}", v))

let root = options.CurrentValue.RootFactory ctx

let! result =
Async.StartImmediateAsTask(
executor.AsyncExecute(content.Ast, root, ?variables = variables, ?operationName = operationName),
Async.StartImmediateAsTask (
executor.AsyncExecute (content.Ast, root, ?variables = variables, ?operationName = operationName),
cancellationToken = ctx.RequestAborted
)

Expand Down

0 comments on commit 95950ef

Please sign in to comment.