diff --git a/lib/apollo-exception.filter.ts b/lib/apollo-exception.filter.ts new file mode 100644 index 0000000000..357e503e42 --- /dev/null +++ b/lib/apollo-exception.filter.ts @@ -0,0 +1,39 @@ +import { Catch, ArgumentsHost, HttpException } from '@nestjs/common'; +import { + ApolloError, + AuthenticationError, + ForbiddenError, +} from 'apollo-server-errors'; +import { GqlExceptionFilter } from './interfaces'; +import { GqlContextType } from './services'; + +const apolloPredefinedExceptions: Record = { + 401: AuthenticationError, + 403: ForbiddenError, +}; + +@Catch(HttpException) +export class ApolloExceptionFilter implements GqlExceptionFilter { + catch(exception: HttpException, host: ArgumentsHost) { + if (host.getType() !== 'graphql') { + throw exception; + } + + let error: ApolloError; + if (exception.getStatus() in apolloPredefinedExceptions) { + error = new apolloPredefinedExceptions[exception.getStatus()]( + exception.message, + ); + } else { + error = new ApolloError( + exception.message, + exception.getStatus().toString(), + ); + } + + error.stack = exception.stack; + error.extensions['response'] = exception.getResponse(); + + throw error; + } +} diff --git a/lib/index.ts b/lib/index.ts index d22e5e9c83..69b65807d6 100644 --- a/lib/index.ts +++ b/lib/index.ts @@ -2,6 +2,7 @@ export * from './decorators'; export * from './federation'; export * from './graphql-ast.explorer'; export * from './graphql-definitions.factory'; +export * from './apollo-exception.filter'; export * from './graphql-schema.host'; export * from './graphql-types.loader'; export * from './graphql.factory'; diff --git a/tests/e2e/guards-filters.spec.ts b/tests/e2e/guards-filters.spec.ts index 7a1051a824..37fc3ff20c 100644 --- a/tests/e2e/guards-filters.spec.ts +++ b/tests/e2e/guards-filters.spec.ts @@ -1,6 +1,7 @@ import { INestApplication } from '@nestjs/common'; import { Test } from '@nestjs/testing'; import * as request from 'supertest'; +import { ApolloExceptionFilter } from '../../lib'; import { ApplicationModule } from '../code-first/app.module'; describe('GraphQL - Guards', () => { @@ -12,6 +13,7 @@ describe('GraphQL - Guards', () => { }).compile(); app = module.createNestApplication(); + app.useGlobalFilters(new ApolloExceptionFilter()); await app.init(); }); @@ -26,7 +28,7 @@ describe('GraphQL - Guards', () => { .expect(200, { errors: [ { - message: 'Unauthorized error', + message: 'Unauthorized', locations: [ { line: 2, @@ -35,7 +37,11 @@ describe('GraphQL - Guards', () => { ], path: ['recipe'], extensions: { - code: 'INTERNAL_SERVER_ERROR', + code: 'UNAUTHENTICATED', + response: { + statusCode: 401, + message: 'Unauthorized', + }, }, }, ], diff --git a/tests/e2e/pipes.spec.ts b/tests/e2e/pipes.spec.ts index 7d86f1e967..0f2511f76b 100644 --- a/tests/e2e/pipes.spec.ts +++ b/tests/e2e/pipes.spec.ts @@ -1,6 +1,7 @@ import { INestApplication, ValidationPipe } from '@nestjs/common'; import { Test } from '@nestjs/testing'; import * as request from 'supertest'; +import { ApolloExceptionFilter } from '../../lib'; import { ApplicationModule } from '../code-first/app.module'; describe('GraphQL - Pipes', () => { @@ -12,6 +13,7 @@ describe('GraphQL - Pipes', () => { }).compile(); app = module.createNestApplication(); + app.useGlobalFilters(new ApolloExceptionFilter()); app.useGlobalPipes(new ValidationPipe()); await app.init(); }); @@ -30,17 +32,13 @@ describe('GraphQL - Pipes', () => { errors: [ { extensions: { - code: 'INTERNAL_SERVER_ERROR', - exception: { - message: 'Bad Request Exception', - response: { - error: 'Bad Request', - message: [ - 'description must be longer than or equal to 30 characters', - ], - statusCode: 400, - }, - status: 400, + code: '400', + response: { + error: 'Bad Request', + message: [ + 'description must be longer than or equal to 30 characters', + ], + statusCode: 400, }, }, locations: [