Skip to content

Commit

Permalink
feat: use extensions.code to specify errors
Browse files Browse the repository at this point in the history
  • Loading branch information
ardatan committed Jan 16, 2025
1 parent 9a475ba commit cb866e0
Show file tree
Hide file tree
Showing 17 changed files with 89 additions and 32 deletions.
5 changes: 5 additions & 0 deletions .changeset/smooth-spoons-greet.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'graphql-yoga': minor
---

`extensions.code` usage for specific errors
27 changes: 15 additions & 12 deletions examples/graphql-armor/__integration-tests__/graphql-armor.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,18 +23,21 @@ describe('graphql-auth example integration', () => {
const response = await yoga.fetch(`http://yoga/graphql?query=query{books{titlee}}`);
const body = await response.json();
expect(body.errors).toMatchInlineSnapshot(`
[
{
"locations": [
{
"column": 13,
"line": 1,
},
],
"message": "Cannot query field "titlee" on type "Book". [Suggestion hidden]",
},
]
`);
[
{
"extensions": {
"code": "GRAPHQL_VALIDATION_FAILED",
},
"locations": [
{
"column": 13,
"line": 1,
},
],
"message": "Cannot query field "titlee" on type "Book". [Suggestion hidden]",
},
]
`);
expect(body.data).toBeFalsy();
});
});
3 changes: 3 additions & 0 deletions packages/graphql-yoga/__tests__/500.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,9 @@ describe('Handle non GraphQL Errors as 500 when error masking is disabled', () =
errors: [
{
message: 'Oops!',
extensions: {
code: 'INTERNAL_SERVER_ERROR',
},
},
],
});
Expand Down
18 changes: 18 additions & 0 deletions packages/graphql-yoga/__tests__/batching.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,9 @@ describe('Batching', () => {
errors: [
{
message: 'Batching is limited to 10 operations per request.',
extensions: {
code: 'BAD_REQUEST',
},
},
],
});
Expand Down Expand Up @@ -132,6 +135,9 @@ describe('Batching', () => {
errors: [
{
message: 'Batching is not supported.',
extensions: {
code: 'BAD_REQUEST',
},
},
],
});
Expand Down Expand Up @@ -169,6 +175,9 @@ describe('Batching', () => {
errors: [
{
message: 'Batching is limited to 2 operations per request.',
extensions: {
code: 'BAD_REQUEST',
},
},
],
});
Expand Down Expand Up @@ -202,6 +211,9 @@ describe('Batching', () => {
errors: [
{
message: 'Batching is not supported.',
extensions: {
code: 'BAD_REQUEST',
},
},
],
});
Expand Down Expand Up @@ -237,6 +249,9 @@ describe('Batching', () => {
errors: [
{
message: 'Batching is not supported.',
extensions: {
code: 'BAD_REQUEST',
},
},
],
});
Expand Down Expand Up @@ -283,6 +298,9 @@ describe('Batching', () => {
errors: [
{
message: 'Batching is limited to 10 operations per request.',
extensions: {
code: 'BAD_REQUEST',
},
},
],
});
Expand Down
3 changes: 3 additions & 0 deletions packages/graphql-yoga/__tests__/error-masking.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -744,6 +744,9 @@ describe('error masking', () => {
},
],
message: 'Unexpected error.',
extensions: {
code: 'INTERNAL_SERVER_ERROR',
},
path: ['root'],
},
],
Expand Down
4 changes: 2 additions & 2 deletions packages/graphql-yoga/__tests__/graphql-over-http.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ describe('GraphQL over HTTP', () => {

const text = await result.text();
expect(text).toMatchInlineSnapshot(
`"{"errors":[{"message":"Unexpected error.","locations":[{"line":1,"column":3}],"path":["hi"]}],"data":{"hi":null,"foo":"hi"}}"`,
`"{"errors":[{"message":"Unexpected error.","locations":[{"line":1,"column":3}],"path":["hi"],"extensions":{"code":"INTERNAL_SERVER_ERROR"}}],"data":{"hi":null,"foo":"hi"}}"`,
);

expect(result.headers.get('content-type')).toEqual(
Expand Down Expand Up @@ -77,7 +77,7 @@ describe('GraphQL over HTTP', () => {

const text = await result.text();
expect(text).toMatchInlineSnapshot(
`"{"errors":[{"message":"Unexpected error.","locations":[{"line":1,"column":3}],"path":["hi"]}],"data":null}"`,
`"{"errors":[{"message":"Unexpected error.","locations":[{"line":1,"column":3}],"path":["hi"],"extensions":{"code":"INTERNAL_SERVER_ERROR"}}],"data":null}"`,
);

expect(result.headers.get('content-type')).toEqual(
Expand Down
2 changes: 1 addition & 1 deletion packages/graphql-yoga/__tests__/graphql-sse.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,7 @@ data:
":
event: next
data: {"errors":[{"message":"Cannot query field \\"nope\\" on type \\"Query\\".","locations":[{"line":1,"column":2}]}]}
data: {"errors":[{"message":"Cannot query field \\"nope\\" on type \\"Query\\".","locations":[{"line":1,"column":2}],"extensions":{"code":"GRAPHQL_VALIDATION_FAILED"}}]}
event: complete
data:
Expand Down
2 changes: 1 addition & 1 deletion packages/graphql-yoga/__tests__/logging.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ describe('logging', () => {
});

expect(await response.text()).toMatchInlineSnapshot(
`"{"errors":[{"message":"Unexpected error.","locations":[{"line":1,"column":2}],"path":["hi"]}],"data":{"hi":null}}"`,
`"{"errors":[{"message":"Unexpected error.","locations":[{"line":1,"column":2}],"path":["hi"],"extensions":{"code":"INTERNAL_SERVER_ERROR"}}],"data":{"hi":null}}"`,
);

expect(logger.error).toHaveBeenCalledTimes(1);
Expand Down
2 changes: 1 addition & 1 deletion packages/graphql-yoga/__tests__/subscriptions.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -283,7 +283,7 @@ event: next
data: {"data":{"hi":"hi"}}
event: next
data: {"errors":[{"message":"Unexpected error.","locations":[{"line":2,"column":11}]}]}
data: {"errors":[{"message":"Unexpected error.","locations":[{"line":2,"column":11}],"extensions":{"code":"INTERNAL_SERVER_ERROR"}}]}
event: complete
data:
Expand Down
2 changes: 1 addition & 1 deletion packages/graphql-yoga/__tests__/validation-cache.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,7 @@ describe('validation cache', () => {
});
expect(response.status).toEqual(200);
expect(await response.text()).toMatchInlineSnapshot(
`"{"errors":[{"message":"Cannot query field \\"foo\\" on type \\"Query\\".","locations":[{"line":4,"column":9}]}]}"`,
`"{"errors":[{"message":"Cannot query field \\"foo\\" on type \\"Query\\".","locations":[{"line":4,"column":9}],"extensions":{"code":"GRAPHQL_VALIDATION_FAILED"}}]}"`,
);

expect(validateFn).toHaveBeenCalledTimes(2);
Expand Down
2 changes: 2 additions & 0 deletions packages/graphql-yoga/src/error.ts
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ export function handleError(
errors.add(
createGraphQLError(error, {
extensions: {
code: 'INTERNAL_SERVER_ERROR',
unexpected: true,
},
}),
Expand All @@ -96,6 +97,7 @@ export function handleError(
errors.add(
createGraphQLError(error.toString(), {
extensions: {
code: 'INTERNAL_SERVER_ERROR',
unexpected: true,
},
}),
Expand Down
3 changes: 3 additions & 0 deletions packages/graphql-yoga/src/plugins/request-parser/post-json.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ export async function parsePOSTJsonRequest(request: Request): Promise<GraphQLPar
spec: true,
status: 400,
},
code: 'BAD_REQUEST',
};
if (err instanceof Error) {
extensions['originalError'] = {
Expand All @@ -39,6 +40,7 @@ export async function parsePOSTJsonRequest(request: Request): Promise<GraphQLPar
http: {
status: 400,
},
code: 'BAD_REQUEST',
},
});
}
Expand All @@ -52,6 +54,7 @@ export async function parsePOSTJsonRequest(request: Request): Promise<GraphQLPar
http: {
status: 400,
},
code: 'BAD_REQUEST',
},
},
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ export function assertInvalidParams(
spec: true,
status: 400,
},
code: 'BAD_REQUEST',
},
});
}
Expand All @@ -31,6 +32,7 @@ export function assertInvalidParams(
http: {
status: 400,
},
code: 'BAD_REQUEST',
},
});
}
Expand All @@ -52,6 +54,7 @@ export function checkGraphQLQueryParams(
Allow: 'GET, POST',
},
},
code: 'BAD_REQUEST',
},
},
);
Expand All @@ -69,6 +72,7 @@ export function checkGraphQLQueryParams(
Allow: 'GET, POST',
},
},
code: 'BAD_REQUEST',
},
});
}
Expand All @@ -83,6 +87,7 @@ export function checkGraphQLQueryParams(
Allow: 'GET, POST',
},
},
code: 'BAD_REQUEST',
},
});
}
Expand All @@ -99,6 +104,7 @@ export function checkGraphQLQueryParams(
Allow: 'GET, POST',
},
},
code: 'BAD_REQUEST',
},
},
);
Expand All @@ -116,6 +122,7 @@ export function checkGraphQLQueryParams(
Allow: 'GET, POST',
},
},
code: 'BAD_REQUEST',
},
},
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,12 @@ export function useHTTPValidationError<
return ({ valid, result }) => {
if (!valid) {
for (const error of result) {
error.extensions.http = {
...error.extensions.http,
spec: error.extensions.http?.spec ?? true,
status: error.extensions.http?.status ?? 400,
};
error.extensions ||= {};
error.extensions.code ||= 'GRAPHQL_VALIDATION_FAILED';
error.extensions.http ||= {};
error.extensions.http.spec =
error.extensions.http.spec == null ? true : error.extensions.http.spec;
error.extensions.http.status ||= 400;
}
}
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ export function useLimitBatching(limit?: number): Plugin {
http: {
status: 400,
},
code: 'BAD_REQUEST',
},
});
}
Expand All @@ -22,6 +23,7 @@ export function useLimitBatching(limit?: number): Plugin {
http: {
status: 413,
},
code: 'BAD_REQUEST',
},
});
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,10 @@
import { DocumentNode, getOperationAST, GraphQLError, OperationDefinitionNode } from 'graphql';
import {
DocumentNode,
getOperationAST,
GraphQLError,
GraphQLHTTPErrorExtensions,
OperationDefinitionNode,
} from 'graphql';
import { Maybe } from '@envelop/core';
import { createGraphQLError } from '@graphql-tools/utils';
import type { YogaInitialContext } from '../../types.js';
Expand All @@ -16,6 +22,7 @@ export function assertMutationViaGet(
if (!operation) {
throw createGraphQLError('Could not determine what operation to execute.', {
extensions: {
code: 'OPERATION_RESOLUTION_FAILURE',
http: {
status: 400,
},
Expand Down Expand Up @@ -59,10 +66,12 @@ export function usePreventMutationViaGET(): Plugin<YogaInitialContext> {

if (result instanceof Error) {
if (result instanceof GraphQLError) {
result.extensions['http'] = {
spec: true,
status: 400,
};
// @ts-expect-error - We are modifying the extensions on purpose
const extensions: Record<string, unknown> = (result.extensions ||= {});
extensions['code'] ||= 'GRAPHQL_PARSE_FAILED';
const httpExtensions: GraphQLHTTPErrorExtensions = (extensions['http'] ||= {});
httpExtensions.spec ||= true;
httpExtensions.status ||= 400;
}
throw result;
}
Expand Down
9 changes: 5 additions & 4 deletions packages/graphql-yoga/src/utils/mask-error.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,10 @@ export const maskError: MaskError = (
return error;
}
// Original error should be removed
const extensions: GraphQLErrorExtensions = {
...error.extensions,
unexpected: true,
};
// @ts-expect-error - we are modifying the error
const extensions: GraphQLErrorExtensions = (error.extensions ||= {});
extensions['code'] ||= 'INTERNAL_SERVER_ERROR';
extensions.unexpected = true;
if (isDev) {
extensions['originalError'] = {
message: error.originalError.message,
Expand All @@ -37,6 +37,7 @@ export const maskError: MaskError = (

return createGraphQLError(message, {
extensions: {
code: 'INTERNAL_SERVER_ERROR',
unexpected: true,
originalError: isDev
? error instanceof Error
Expand Down

0 comments on commit cb866e0

Please sign in to comment.