Skip to content

Commit

Permalink
Merge 655ef0c into e9300a5
Browse files Browse the repository at this point in the history
  • Loading branch information
rubiin committed Oct 5, 2023
2 parents e9300a5 + 655ef0c commit e91ff14
Show file tree
Hide file tree
Showing 7 changed files with 151 additions and 21 deletions.
36 changes: 27 additions & 9 deletions src/filters/i18n-validation-exception.filter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,22 +32,22 @@ 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),
});
.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 +87,22 @@ 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:
this.options.errorHttpStatusCode === undefined
? exc.getStatus()
: this.options.errorHttpStatusCode,
message: exc.getResponse(),
errors,
};
}
}
}
5 changes: 1 addition & 4 deletions src/i18n.context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,7 @@ export class I18nContext<K = Record<string, unknown>>
return this;
}

constructor(
readonly lang: string,
readonly service: I18nService<K>,
) {}
constructor(readonly lang: string, readonly service: I18nService<K>) {}

public translate<P extends Path<K> = any, R = PathValue<K, P>>(
key: P,
Expand Down
8 changes: 7 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,9 @@ export interface I18nValidationExceptionFilterDetailedErrorsOption
export interface I18nValidationExceptionFilterErrorFormatterOption
extends I18nValidationExceptionFilterCommonErrorsOption {
errorFormatter?: (errors: ValidationError[]) => object;
responseBodyFormatter?: (
host: ArgumentsHost,
exc: I18nValidationException,
formattedErrors: object,
) => Record<string, unknown>;
}
6 changes: 1 addition & 5 deletions src/middlewares/i18n.middleware.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,11 +92,7 @@ export class I18nMiddleware implements NestMiddleware {
class MiddlewareHttpContext
implements ExecutionContext, ArgumentsHost, HttpArgumentsHost
{
constructor(
private req: any,
private res: any,
private next: any,
) {}
constructor(private req: any, private res: any, private next: any) {}

getClass<T = any>(): Type<T> {
throw ExecutionContextMethodNotImplemented;
Expand Down
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
14 changes: 13 additions & 1 deletion 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 { ArgumentsHost, 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 = (host: ArgumentsHost, 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 e91ff14

Please sign in to comment.