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: 71 additions & 12 deletions Sources/GraphQLVapor/HTTP/GraphQLHandler+HTTP.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,24 +5,32 @@ extension GraphQLHandler {
/// https://github.com/graphql/graphql-over-http/blob/main/spec/GraphQLOverHTTP.md#get
func handleGet(request: Request) async throws -> Response {
let graphQLRequest = try request.query.decode(GraphQLRequest.self)

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 Abort(.badRequest, reason: error.localizedDescription)
}
guard operationType != .mutation else {
throw Abort(.methodNotAllowed, reason: "Mutations using GET are disallowed")
}

let graphQLContextComputationInputs = GraphQLContextComputationInputs<WebSocketInitResult>(
vaporRequest: request,
graphQLRequest: graphQLRequest,
websocketInitResult: nil
)
let context = try await computeContext(graphQLContextComputationInputs)
let result = await execute(
graphQLRequest: graphQLRequest,
documentAST: documentAST,
operationName: graphQLRequest.operationName,
variables: graphQLRequest.variables,
context: context,
additionalValidationRules: config.additionalValidationRules
)
Expand All @@ -35,43 +43,75 @@ extension GraphQLHandler {
throw Abort(.unsupportedMediaType, reason: "Missing `Content-Type` header")
}
let graphQLRequest = try request.content.decode(GraphQLRequest.self)

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, headers: request.headers)
}

let graphQLContextComputationInputs = GraphQLContextComputationInputs<WebSocketInitResult>(
vaporRequest: request,
graphQLRequest: graphQLRequest,
websocketInitResult: nil
)
let context = try await computeContext(graphQLContextComputationInputs)
let result = await execute(
graphQLRequest: graphQLRequest,
documentAST: documentAST,
operationName: graphQLRequest.operationName,
variables: graphQLRequest.variables,
context: context,
additionalValidationRules: config.additionalValidationRules
)
return try encodeResponse(result: result, headers: request.headers)
}

private func execute(
graphQLRequest: GraphQLRequest,
documentAST: Document,
operationName: String?,
variables: [String: Map],
context: Context,
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)])
}

let validationRules = GraphQL.specifiedRules + additionalValidationRules
// Validate request
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
return GraphQLResult(data: nil, errors: [error])
} catch {
return GraphQLResult(
data: nil,
Expand Down Expand Up @@ -122,4 +162,23 @@ extension GraphQLHandler {
try response.content.encode(result, as: selectedMediaType)
return response
}

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