Skip to content

Commit b3ef84e

Browse files
fix: application/graphql-response+json status codes
Previously these would always return 200 success, but must return 400's for spec compliance. Also improves result error formatting
1 parent 68cdf98 commit b3ef84e

2 files changed

Lines changed: 49 additions & 9 deletions

File tree

Sources/GraphQLHummingbird/Coding/GraphQLJSONEncoder+encode.swift

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,24 @@ extension GraphQLJSONEncoder: @retroactive ResponseEncoder {
77
/// - value: Value to encode
88
/// - request: Request used to generate response
99
/// - context: Request context
10-
public func encode(_ value: some Encodable, from _: Request, context _: some RequestContext) throws -> Response {
10+
public func encode(_ value: some Encodable, from request: Request, context: some RequestContext) throws -> Response {
11+
try encode(value, status: .ok, from: request, context: context)
12+
}
13+
}
14+
15+
extension GraphQLJSONEncoder {
16+
/// Extend GraphQLJSONEncoder to support generating a ``HummingbirdCore/Response``. Sets body and header values. Similar to the
17+
/// `ResponseEncoder`-required version, except it allows setting the reponse status as well.
18+
/// - Parameters:
19+
/// - value: Value to encode
20+
/// - status: The status of the response
21+
/// - request: Request used to generate response
22+
/// - context: Request context
23+
func encode(_ value: some Encodable, status: HTTPResponse.Status, from _: Request, context _: some RequestContext) throws -> Response {
1124
let data = try encode(value)
1225
let buffer = ByteBuffer(bytes: data)
1326
return Response(
14-
status: .ok,
27+
status: status,
1528
headers: [
1629
.contentType: "\(MediaType.applicationJsonGraphQL); charset=utf-8",
1730
.contentLength: data.count.description,
@@ -20,3 +33,20 @@ extension GraphQLJSONEncoder: @retroactive ResponseEncoder {
2033
)
2134
}
2235
}
36+
37+
extension GraphQLJSONEncoder {
38+
/// Overload for GraphQLJSONEncoder to support generating a ``HummingbirdCore/Response`` from a GraphQLResult. Sets body, headers, and status, according to [GraphQL-over-HTTP spec](https://github.com/graphql/graphql-over-http/blob/main/spec/GraphQLOverHTTP.md#applicationgraphql-responsejson).
39+
/// - Parameters:
40+
/// - value: GraphQLResult to encode
41+
/// - request: Request used to generate response
42+
/// - context: Request context
43+
func encode(_ value: GraphQLResult, from request: Request, context: some RequestContext) throws -> Response {
44+
var status = HTTPResponse.Status.ok
45+
// We must return `bad request` with the content if there were failures preventing a partial result
46+
// https://github.com/graphql/graphql-over-http/blob/main/spec/GraphQLOverHTTP.md#applicationgraphql-responsejson
47+
if value.data == nil {
48+
status = .badRequest
49+
}
50+
return try encode(value, status: status, from: request, context: context)
51+
}
52+
}

Sources/GraphQLHummingbird/HTTP/GraphQLHandler+HTTP.swift

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ extension GraphQLHandler {
2525
throw HTTPError(.methodNotAllowed, message: "Mutations using GET are disallowed")
2626
}
2727
let graphqlContext = try await computeContext(request, context)
28-
let result = try await execute(
28+
let result = await execute(
2929
graphQLRequest: graphQLRequest,
3030
context: graphqlContext,
3131
additionalValidationRules: config.additionalValidationRules
@@ -43,15 +43,23 @@ extension GraphQLHandler {
4343
let graphQLRequest: GraphQLRequest
4444
switch mediaType {
4545
case .applicationJson, .applicationJsonGraphQL:
46-
graphQLRequest = try await JSONDecoder().decode(GraphQLRequest.self, from: request, context: context)
46+
do {
47+
graphQLRequest = try await JSONDecoder().decode(GraphQLRequest.self, from: request, context: context)
48+
} catch {
49+
throw HTTPError(.badRequest, message: error.localizedDescription)
50+
}
4751
case .applicationUrlEncoded:
48-
graphQLRequest = try await URLEncodedFormDecoder().decode(GraphQLRequest.self, from: request, context: context)
52+
do {
53+
graphQLRequest = try await URLEncodedFormDecoder().decode(GraphQLRequest.self, from: request, context: context)
54+
} catch {
55+
throw HTTPError(.badRequest, message: error.localizedDescription)
56+
}
4957
default:
5058
throw HTTPError(.unsupportedMediaType)
5159
}
5260

5361
let graphqlContext = try await computeContext(request, context)
54-
let result = try await execute(
62+
let result = await execute(
5563
graphQLRequest: graphQLRequest,
5664
context: graphqlContext,
5765
additionalValidationRules: config.additionalValidationRules
@@ -63,7 +71,7 @@ extension GraphQLHandler {
6371
graphQLRequest: GraphQLRequest,
6472
context: GraphQLContext,
6573
additionalValidationRules: [@Sendable (ValidationContext) -> Visitor]
66-
) async throws -> GraphQLResult {
74+
) async -> GraphQLResult {
6775
// https://github.com/graphql/graphql-over-http/blob/main/spec/GraphQLOverHTTP.md#validation
6876
let validationRules = GraphQL.specifiedRules + additionalValidationRules
6977

@@ -79,9 +87,11 @@ extension GraphQLHandler {
7987
operationName: graphQLRequest.operationName,
8088
validationRules: validationRules
8189
)
82-
} catch {
90+
} catch let error as GraphQLError {
8391
// This indicates a request parsing error
84-
throw HTTPError(.badRequest, message: error.localizedDescription)
92+
return GraphQLResult(data: nil, errors: [error])
93+
} catch {
94+
return GraphQLResult(data: nil, errors: [GraphQLError(message: error.localizedDescription)])
8595
}
8696
return result
8797
}

0 commit comments

Comments
 (0)