Skip to content

Commit

Permalink
feat: added ability to pass an optional custom response body formatter
Browse files Browse the repository at this point in the history
  • Loading branch information
rubiin committed Oct 5, 2023
1 parent 52a5232 commit a46f7f9
Show file tree
Hide file tree
Showing 5 changed files with 142 additions and 12 deletions.
34 changes: 24 additions & 10 deletions src/filters/i18n-validation-exception.filter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,22 +32,19 @@ export class I18nValidationExceptionFilter implements ExceptionFilter {
lang: i18n.lang,
});

const normalizedErrors = this.normalizeValidationErrors(
errors,
);

switch (host.getType() as string) {
case 'http':
const response = host.switchToHttp().getResponse();
const responseBody = this.buildResponseBody(host, exception, normalizedErrors);
response
.status(this.options.errorHttpStatusCode || exception.getStatus())
.send({
statusCode:
this.options.errorHttpStatusCode || exception.getStatus(),
message: exception.getResponse(),
errors: this.normalizeValidationErrors(errors),
});
.status(this.options.errorHttpStatusCode || exception.getStatus()).send(responseBody);
break;
case 'graphql':
exception.errors = this.normalizeValidationErrors(
errors,
) as I18nValidationError[];
exception.errors = normalizedErrors as I18nValidationError[];
return exception;
}
}
Expand Down Expand Up @@ -87,4 +84,21 @@ export class I18nValidationExceptionFilter implements ExceptionFilter {
.flatten()
.toArray();
}

protected buildResponseBody(
host: ArgumentsHost,
exc: I18nValidationException,
errors: string[] | I18nValidationError[] | object,
) {

if('responseBodyFormatter' in this.options) {
return this.options.responseBodyFormatter(host, exc, errors);
} else {
return {
statusCode: exc.getStatus(),
message: exc.getResponse(),
errors,
};
}
}
}
5 changes: 4 additions & 1 deletion src/interfaces/i18n-validation-exception-filter.interface.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { HttpStatus, ValidationError } from '@nestjs/common';
import { ArgumentsHost, HttpStatus, ValidationError } from '@nestjs/common';
import {I18nValidationException} from "./i18n-validation-error.interface";

interface I18nValidationExceptionFilterCommonErrorsOption {
errorHttpStatusCode?: HttpStatus | number;
Expand All @@ -12,4 +13,6 @@ export interface I18nValidationExceptionFilterDetailedErrorsOption
export interface I18nValidationExceptionFilterErrorFormatterOption
extends I18nValidationExceptionFilterCommonErrorsOption {
errorFormatter?: (errors: ValidationError[]) => object;
responseBodyFormatter?: (host: ArgumentsHost, exc: I18nValidationException, formattedErrors: object) => Record<string, unknown>;

}
13 changes: 12 additions & 1 deletion tests/app/controllers/hello.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,10 @@ import {
} from '../../../src';
import { I18nTranslations } from '../../generated/i18n.generated';
import { CreateUserDto } from '../dto/create-user.dto';
import { exampleErrorFormatter } from '../examples/example.functions';
import { TestException, TestExceptionFilter } from '../filter/test.filter';
import { TestGuard } from '../guards/test.guard';
import { Hero, HeroById } from '../interfaces/hero.interface';
import {exampleErrorFormatter, exampleResponseBodyFormatter} from '../examples/example.functions';

@Controller('hello')
@UseFilters(new TestExceptionFilter())
Expand Down Expand Up @@ -187,6 +187,17 @@ export class HelloController {
return 'This action adds a new user';
}

@Post('/validation-custom-response-body-formatter')
@UseFilters(
new I18nValidationExceptionFilter({
responseBodyFormatter: exampleResponseBodyFormatter,
errorFormatter: exampleErrorFormatter,
}),
)
validationResponseBodyFormatter(@Body() createUserDto: CreateUserDto): any {
return 'This action adds a new user';
}

@Post('/custom-validation')
customValidation(@I18n() i18n: I18nContext<I18nTranslations>): any {
let createUserDto = new CreateUserDto();
Expand Down
12 changes: 12 additions & 0 deletions tests/app/examples/example.functions.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import { ValidationError } from '@nestjs/common';
import { mapChildrenToValidationErrors } from '../../../src/utils/format';
import {I18nValidationException} from "../../../src";


export const exampleErrorFormatter = (errors: ValidationError[]): object => {
const errorMessages = {};
Expand All @@ -15,3 +17,13 @@ export const exampleErrorFormatter = (errors: ValidationError[]): object => {

return errorMessages;
};


export const exampleResponseBodyFormatter = (exc: I18nValidationException, formattedErrors: object) => {
return {
type: 'static',
status: exc.getStatus(),
message: exc.getResponse(),
data: formattedErrors,
};
}
90 changes: 90 additions & 0 deletions tests/i18n-dto.e2e.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -256,6 +256,96 @@ describe('i18n module e2e dto', () => {
});
});


it(`should translate validation messages if a custom response body formatter specified`, async () => {
await request(app.getHttpServer())
.post('/hello/validation-custom-response-body-formatter')
.send({
email: '',
password: '',
extra: { subscribeToEmail: '', min: 1, max: 100 },
})
.set('Accept', 'application/json')
.expect(400)
.expect((res) => {
expect(res.body).toMatchObject({
status: 400,
type: 'static',
message: 'Bad Request',
data: {
email: ['email is invalid', 'email cannot be empty'],
password: ['password cannot be empty'],
subscribeToEmail: ['extra.subscribeToEmail is not a boolean'],
min: [
'extra.min with value: "1" needs to be at least 5, ow and COOL',
],
max: [
'extra.max with value: "100" needs to be less than 10, ow and SUPER',
],
},
});
});

await request(app.getHttpServer())
.post('/hello/validation-custom-response-body-formatter')
.send({
test: '',
email: '',
password: '',
extra: { subscribeToEmail: '', min: 1, max: 100 },
})
.set('Accept', 'application/json')
.expect(400)
.expect((res) => {
expect(res.body).toMatchObject({
status: 400,
type: 'static',
message: 'Bad Request',
data: {
test: ['property test should not exist'],
email: ['email is invalid', 'email cannot be empty'],
password: ['password cannot be empty'],
subscribeToEmail: ['extra.subscribeToEmail is not a boolean'],
min: [
'extra.min with value: "1" needs to be at least 5, ow and COOL',
],
max: [
'extra.max with value: "100" needs to be less than 10, ow and SUPER',
],
},
});
});

return request(app.getHttpServer())
.post('/hello/validation-custom-response-body-formatter?l=nl')
.send({
email: '',
password: '',
extra: { subscribeToEmail: '', min: 1, max: 100 },
})
.set('Accept', 'application/json')
.expect(400)
.expect((res) => {
expect(res.body).toMatchObject({
status: 400,
type: 'static',
message: 'Bad Request',
data: {
email: ['email is ongeldig', 'e-mail adres mag niet leeg zijn'],
password: ['wachtwoord mag niet leeg zijn'],
subscribeToEmail: ['extra.subscribeToEmail is geen boolean'],
min: [
'extra.min met waarde: "1" moet hoger zijn dan 5, ow en COOL',
],
max: [
'extra.max met waarde: "100" moet lager zijn dan 10, ow en SUPER',
],
},
});
});
});


it(`should translate validation messages with detailed error`, async () => {
await request(app.getHttpServer())
.post('/hello/validation')
Expand Down

0 comments on commit a46f7f9

Please sign in to comment.