Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
83 changes: 74 additions & 9 deletions Sources/GraphQLHummingbird/HTTP/GraphQLHandler+HTTP.swift
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,16 @@ extension GraphQLHandler {
variables: variables
)

let documentAST: Document
let operationType: OperationType
do {
operationType = try graphQLRequest.operationType()
documentAST = try parse(
source: Source(body: graphQLRequest.query, name: graphQLRequest.operationName ?? "")
)
operationType = try Self.operationTypeOf(
document: documentAST,
named: graphQLRequest.operationName
)
} catch {
// Indicates a request parsing error
throw HTTPError(.badRequest, message: error.localizedDescription)
Expand All @@ -60,7 +67,9 @@ extension GraphQLHandler {
)
let graphQLContext = try await computeContext(graphQLContextComputationInputs)
let result = await execute(
graphQLRequest: graphQLRequest,
documentAST: documentAST,
operationName: graphQLRequest.operationName,
variables: graphQLRequest.variables,
context: graphQLContext,
additionalValidationRules: config.additionalValidationRules
)
Expand Down Expand Up @@ -100,6 +109,17 @@ extension GraphQLHandler {
throw HTTPError(.unsupportedMediaType)
}

let documentAST: Document
do {
documentAST = try parse(
source: Source(body: graphQLRequest.query, name: graphQLRequest.operationName ?? "")
)
} catch let error as GraphQLError {
// Indicates a request parsing error
let result = GraphQLResult(data: nil, errors: [error])
return try encodeResponse(result: result, request: request, context: context)
}

let graphQLContextComputationInputs = GraphQLContextComputationInputs<
Context, WebSocketInitResult
>(
Expand All @@ -110,32 +130,55 @@ extension GraphQLHandler {
)
let graphQLContext = try await computeContext(graphQLContextComputationInputs)
let result = await execute(
graphQLRequest: graphQLRequest,
documentAST: documentAST,
operationName: graphQLRequest.operationName,
variables: graphQLRequest.variables,
context: graphQLContext,
additionalValidationRules: config.additionalValidationRules
)
return try encodeResponse(result: result, request: request, context: context)
}

private func execute(
graphQLRequest: GraphQLRequest,
documentAST: Document,
operationName: String?,
variables: [String: Map],
context: GraphQLContext,
additionalValidationRules: [@Sendable (ValidationContext) -> Visitor]
) async -> GraphQLResult {
// https://github.com/graphql/graphql-over-http/blob/main/spec/GraphQLOverHTTP.md#validation

// Validate schema
do {
let schemaValidationErrors = try validateSchema(schema: schema)
guard schemaValidationErrors.isEmpty else {
return GraphQLResult(errors: schemaValidationErrors)
}
} catch {
return GraphQLResult(errors: [GraphQLError(error)])
}

// Validate request
let validationRules = GraphQL.specifiedRules + additionalValidationRules
let validationErrors = validate(
schema: schema,
ast: documentAST,
rules: validationRules
)
guard validationErrors.isEmpty else {
return GraphQLResult(errors: validationErrors)
}

// https://github.com/graphql/graphql-over-http/blob/main/spec/GraphQLOverHTTP.md#execution
let result: GraphQLResult
do {
result = try await graphql(
result = try await GraphQL.execute(
schema: schema,
request: graphQLRequest.query,
documentAST: documentAST,
rootValue: rootValue,
context: context,
variableValues: graphQLRequest.variables,
operationName: graphQLRequest.operationName,
validationRules: validationRules
variableValues: variables,
operationName: operationName
)
} catch let error as GraphQLError {
// This indicates a request parsing error
Expand Down Expand Up @@ -205,4 +248,26 @@ extension GraphQLHandler {
MediaType(from: segment.trimmingCharacters(in: .whitespaces))
}
}

private static func operationTypeOf(
document: Document,
named operationName: String? = nil
) throws -> OperationType {
let operationDefinitions = document.definitions.filter { operation in
operation.kind == .operationDefinition
}.compactMap { operation in
operation as? OperationDefinition
}

// We don't have to validate the document and check for multiple/missing operations here
// That will be done upon execution.
let operationMatch = operationDefinitions.first { operation in
if let operationName {
return operationName == operation.name?.value
} else {
return true
}
}
return operationMatch?.operation ?? .query
}
}
Loading