BREAKING CHANGE: Fix graphql errors (#12775)
We were using a global ValidationPipe in main.ts. This is an issue as @Controllers should return HttpExecption and @Resolvers should return GraphqlErrors Removing the global pipe and creating a ResolverValidationPipe able to generate GraphqlError. We also need to handle the exception in a filter to avoid nest to think it's unhandled and make it flow to logs Next step: - it would be nice to have both @UsePipes(ResolverValidationPipe) + @UseFilters(GraphqlValidationExceptionFilter) come together. This should be possible if we create a @GraphQLResolver annotation
This commit is contained in:
@@ -1,11 +1,12 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
|
||||
import { useGraphQLErrorHandlerHook } from 'src/engine/core-modules/graphql/hooks/use-graphql-error-handler.hook';
|
||||
import { ExceptionHandlerModule } from 'src/engine/core-modules/exception-handler/exception-handler.module';
|
||||
import { useGraphQLErrorHandlerHook } from 'src/engine/core-modules/graphql/hooks/use-graphql-error-handler.hook';
|
||||
import { ResolverValidationPipe } from 'src/engine/core-modules/graphql/pipes/resolver-validation.pipe';
|
||||
|
||||
@Module({
|
||||
imports: [ExceptionHandlerModule],
|
||||
exports: [useGraphQLErrorHandlerHook],
|
||||
providers: [],
|
||||
exports: [useGraphQLErrorHandlerHook, ResolverValidationPipe],
|
||||
providers: [ResolverValidationPipe],
|
||||
})
|
||||
export class EngineGraphQLModule {}
|
||||
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
import { ArgumentsHost, Catch, ExceptionFilter } from '@nestjs/common';
|
||||
|
||||
import { GraphQLError } from 'graphql';
|
||||
|
||||
/**
|
||||
* In NestJS, if an exception is not handled, it will shown in the logs
|
||||
* This filter is used to prevent NestJS from auto-logging GraphQL errors
|
||||
* and leave it to the GraphQL layer to handle the error.
|
||||
*/
|
||||
@Catch(GraphQLError)
|
||||
export class PreventNestToAutoLogGraphqlErrorsFilter
|
||||
implements ExceptionFilter
|
||||
{
|
||||
catch(exception: GraphQLError, _host: ArgumentsHost) {
|
||||
return exception;
|
||||
}
|
||||
}
|
||||
+58
@@ -0,0 +1,58 @@
|
||||
import {
|
||||
ArgumentMetadata,
|
||||
Injectable,
|
||||
PipeTransform,
|
||||
Type,
|
||||
} from '@nestjs/common';
|
||||
|
||||
import { plainToInstance } from 'class-transformer';
|
||||
import { ValidationError, validate } from 'class-validator';
|
||||
|
||||
import { UserInputError } from 'src/engine/core-modules/graphql/utils/graphql-errors.util';
|
||||
|
||||
@Injectable()
|
||||
export class ResolverValidationPipe implements PipeTransform {
|
||||
async transform(value: unknown, metadata: ArgumentMetadata) {
|
||||
const { metatype } = metadata;
|
||||
|
||||
if (!metatype || !this.toValidate(metatype)) {
|
||||
return value;
|
||||
}
|
||||
|
||||
const object = plainToInstance(metatype, value);
|
||||
|
||||
try {
|
||||
const errors = await validate(object);
|
||||
|
||||
if (errors.length > 0) {
|
||||
const errorMessage = this.formatErrorMessage(errors);
|
||||
|
||||
throw new UserInputError(errorMessage);
|
||||
}
|
||||
} catch (error) {
|
||||
// If the element is not a class, we can't validate it
|
||||
return value;
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
private toValidate(metatype: Type<any>): boolean {
|
||||
const types: unknown[] = [String, Boolean, Number, Array, Object];
|
||||
|
||||
return !types.includes(metatype);
|
||||
}
|
||||
|
||||
private formatErrorMessage(errors: ValidationError[]): string {
|
||||
const messages = errors.flatMap((error) => {
|
||||
if (error.constraints) {
|
||||
return Object.values(error.constraints);
|
||||
}
|
||||
|
||||
return [];
|
||||
});
|
||||
|
||||
return messages.join(', ');
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user