diff --git a/packages/backend/server/src/__tests__/nestjs/error-handler.spec.ts b/packages/backend/server/src/__tests__/nestjs/error-handler.spec.ts index b19ce6e0e3..6b46add42d 100644 --- a/packages/backend/server/src/__tests__/nestjs/error-handler.spec.ts +++ b/packages/backend/server/src/__tests__/nestjs/error-handler.spec.ts @@ -1,3 +1,5 @@ +import assert from 'node:assert/strict'; + import { applyDecorators, Body, @@ -130,7 +132,12 @@ function gql(app: INestApplication, query: string) { return request(app.getHttpServer()) .post('/graphql') .send({ query }) - .expect(200); + .expect(res => { + assert( + res.status === 200 || res.status === 400, + 'GraphQL query should return 200 or 400' + ); + }); } test.before(async ({ context }) => { @@ -199,6 +206,15 @@ test('should be able to handle validation error in graphql query', async t => { t.true(t.context.logger.error.notCalled); }); +test('should be able to handle graphql input error', async t => { + const res = await gql(t.context.app, `mutation { validate(email: 123) }`); + const err = res.body.errors[0]; + t.is(err.message, 'String cannot represent a non string value: 123'); + t.is(err.extensions.status, HttpStatus.BAD_REQUEST); + t.is(err.extensions.name, 'GRAPHQL_BAD_REQUEST'); + t.true(t.context.logger.error.notCalled); +}); + test('should be able to respond request', async t => { const res = await request(t.context.app.getHttpServer()) .get('/ok') diff --git a/packages/backend/server/src/base/graphql/index.ts b/packages/backend/server/src/base/graphql/index.ts index 7e7b4fad61..cba86da4fd 100644 --- a/packages/backend/server/src/base/graphql/index.ts +++ b/packages/backend/server/src/base/graphql/index.ts @@ -13,6 +13,7 @@ import { GraphQLError } from 'graphql'; import { Config } from '../config'; import { UserFriendlyError } from '../error'; +import { isGraphQLBadRequest, mapAnyError } from '../nestjs/exception'; import { GQLLoggerPlugin } from './logger-plugin'; export type GraphqlContext = { @@ -65,6 +66,15 @@ export type GraphqlContext = { formattedError.extensions = error.originalError.toJSON(); formattedError.extensions.stacktrace = error.originalError.stack; return formattedError; + } else if ( + error instanceof GraphQLError && + isGraphQLBadRequest(error) + ) { + const err = mapAnyError(error); + // @ts-expect-error allow assign + formattedError.extensions = err.toJSON(); + formattedError.extensions.stacktrace = err.stack; + return formattedError; } else { // @ts-expect-error allow assign formattedError.message = 'Internal Server Error'; diff --git a/packages/backend/server/src/base/nestjs/exception.ts b/packages/backend/server/src/base/nestjs/exception.ts index 1d9606b173..488c669565 100644 --- a/packages/backend/server/src/base/nestjs/exception.ts +++ b/packages/backend/server/src/base/nestjs/exception.ts @@ -26,7 +26,7 @@ import { import { metrics } from '../metrics'; import { getRequestIdFromHost } from '../utils'; -function isGraphQLBadRequest(error: any): error is GraphQLError { +export function isGraphQLBadRequest(error: GraphQLError) { // https://www.apollographql.com/docs/apollo-server/data/errors const code = error.extensions?.code; return ( @@ -39,14 +39,13 @@ function isGraphQLBadRequest(error: any): error is GraphQLError { export function mapAnyError(error: any): UserFriendlyError { if (error instanceof GraphQLError) { - const err = error; if (isGraphQLBadRequest(error)) { return new GraphqlBadRequest({ - code: err.extensions.code as string, - message: err.message, + code: error.extensions.code as string, + message: error.message, }); } - error = err.originalError ?? error; + error = error.originalError ?? error; } if (error instanceof UserFriendlyError) { return error;