Skip to content
This repository was archived by the owner on Jan 6, 2025. It is now read-only.

Commit bce8a1d

Browse files
committed
feat: Change the way it transforms the errors
The errors now are returned differently, change the code where you perceive the errors, accordingly.
2 parents 3b617b3 + b54f78a commit bce8a1d

File tree

7 files changed

+197
-13
lines changed

7 files changed

+197
-13
lines changed

README.md

Lines changed: 24 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ The default validation pipe is great, but error it returns is just an array of e
1010
{
1111
"statusCode": 400,
1212
"error": "Bad Request",
13-
"message": ["email must be an email", "phone cannot be empty"]
13+
"message": ["email must be an email", "phone should not be empty"]
1414
}
1515
```
1616

@@ -20,10 +20,25 @@ This package changes the `message` to be an object with field names as keys:
2020
{
2121
"statusCode": 400,
2222
"error": "Bad Request",
23-
"message": {
24-
"email": ["email must be an email"],
25-
"phone": ["phone cannot be empty"]
26-
}
23+
"message": [
24+
{
25+
"field": ["email"],
26+
"errors": ["email must be an email"]
27+
},
28+
{
29+
"field": ["phone"],
30+
"errors": ["phone should not be empty"]
31+
}
32+
]
33+
}
34+
```
35+
36+
It also works for [nested values](https://github.com/typestack/class-validator#validating-nested-objects):
37+
38+
```json
39+
{
40+
"field": ["nestedObject", "name"],
41+
"errors": ["name should not be empty"]
2742
}
2843
```
2944

@@ -43,10 +58,10 @@ On NPM:
4358
npm install @exonest/better-validation-pipe
4459
```
4560

46-
## Motivation
61+
## Usage
4762

48-
This behavior is achievable by passing a custom `exceptionFactory` to the original pipe, but I found myself writing the same exception factory for each one of my projects, so I made this small package.
63+
Just use it as you would normally use [Nest's built-in validation pipe](https://docs.nestjs.com/techniques/validation#using-the-built-in-validationpipe). You can also pass options to it, just like you would with the built-in one.
4964

50-
## Inspiration
65+
## Motivation
5166

52-
This is the same way [Laravel](https://laravel.com/docs/master/validation) returns errors; as an object that contains arrays of errors (strings).
67+
This behavior is achievable by passing a custom `exceptionFactory` to the original pipe, but I found myself writing the same exception factory for each one of my projects, so I made this package to do the job.

package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,8 @@
7272
},
7373
"dependencies": {
7474
"@nestjs/common": "^8.2.0",
75+
"class-transformer": "^0.4.0",
76+
"class-validator": "^0.13.1",
7577
"reflect-metadata": "^0.1.13"
7678
}
7779
}

src/error-transformer.spec.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import { transformErrors } from './error-transformer';
2+
import { exampleErrors } from './example-errors';
3+
4+
describe('Error transformer', () => {
5+
it('returns an array of errors with relavant fields', async () => {
6+
const transformedErrors = transformErrors(exampleErrors);
7+
8+
expect(Array.isArray(transformedErrors)).toBe(true);
9+
expect(Array.isArray(transformedErrors[0].errors)).toBe(true);
10+
expect(typeof transformedErrors[0].errors[0]).toBe('string');
11+
expect(Array.isArray(transformedErrors[0].field)).toBe(true);
12+
expect(typeof transformedErrors[0].field[0]).toBe('string');
13+
});
14+
});

src/error-transformer.ts

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import { ValidationError } from '@nestjs/common';
2+
3+
export interface TransformedError {
4+
field: string[];
5+
errors: string[];
6+
}
7+
8+
export const transformErrors = (
9+
validationErrors: ValidationError[],
10+
nest: string[] = []
11+
) => {
12+
return validationErrors.reduce((accumulator: TransformedError[], current) => {
13+
accumulator.push(...transformError(current, nest));
14+
return accumulator;
15+
}, []);
16+
};
17+
18+
const transformError = (
19+
validationError: ValidationError,
20+
nest: string[]
21+
): TransformedError[] => {
22+
const op = [];
23+
24+
if (validationError.constraints) {
25+
op.push({
26+
field: [...nest, validationError.property],
27+
errors: Object.values(validationError.constraints || []),
28+
});
29+
}
30+
31+
if (validationError.children?.length) {
32+
validationError.children.forEach(child => {
33+
op.push(...transformError(child, [...nest, validationError.property]));
34+
});
35+
}
36+
37+
return op;
38+
};

src/example-errors.ts

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
import { ValidationError } from '@nestjs/common';
2+
3+
export const exampleErrors: ValidationError[] = [
4+
{
5+
target: {
6+
description: '',
7+
email: '',
8+
handle: '',
9+
nestedObject: { name: '', phone: '' },
10+
title: '',
11+
},
12+
value: '',
13+
property: 'title',
14+
children: [],
15+
constraints: {
16+
minLength: 'title must be longer than or equal to 10 characters',
17+
},
18+
},
19+
{
20+
target: {
21+
description: '',
22+
email: '',
23+
handle: '',
24+
nestedObject: { name: '', phone: '' },
25+
title: '',
26+
},
27+
value: '',
28+
property: 'description',
29+
children: [],
30+
constraints: { isNotEmpty: 'description should not be empty' },
31+
},
32+
{
33+
target: {
34+
description: '',
35+
email: '',
36+
handle: '',
37+
nestedObject: { name: '', phone: '' },
38+
title: '',
39+
},
40+
value: '',
41+
property: 'handle',
42+
children: [],
43+
constraints: { isNotEmpty: 'handle should not be empty' },
44+
},
45+
{
46+
target: {
47+
description: '',
48+
email: '',
49+
handle: '',
50+
nestedObject: { name: '', phone: '' },
51+
title: '',
52+
},
53+
value: '',
54+
property: 'email',
55+
children: [],
56+
constraints: { isEmail: 'email must be an email' },
57+
},
58+
{
59+
target: {
60+
description: '',
61+
email: '',
62+
handle: '',
63+
nestedObject: { name: '', phone: '' },
64+
title: '',
65+
},
66+
value: { name: '', phone: '' },
67+
property: 'nestedObject',
68+
children: [
69+
{
70+
target: { name: '', phone: '' },
71+
value: '',
72+
property: 'name',
73+
children: [],
74+
constraints: { isNotEmpty: 'name should not be empty' },
75+
},
76+
{
77+
target: { name: '', phone: '' },
78+
value: '',
79+
property: 'phone',
80+
children: [],
81+
constraints: {
82+
isPhoneNumber: 'phone must be a valid phone number',
83+
isNotEmpty: 'phone should not be empty',
84+
},
85+
},
86+
],
87+
},
88+
];

src/validation.pipe.ts

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,17 +3,15 @@ import {
33
ValidationPipe as OriginalValidationPipe,
44
} from '@nestjs/common';
55
import { HttpErrorByCode } from '@nestjs/common/utils/http-error-by-code.util';
6+
import { transformErrors } from './error-transformer';
67

78
export class ValidationPipe extends OriginalValidationPipe {
89
public createExceptionFactory() {
910
return (validationErrors: ValidationError[] = []) => {
1011
if (this.isDetailedOutputDisabled) {
1112
return new HttpErrorByCode[this.errorHttpStatusCode]();
1213
}
13-
const errors: Record<string, unknown> = {};
14-
validationErrors.forEach(error => {
15-
errors[error.property] = Object.values(error.constraints || {});
16-
});
14+
const errors = transformErrors(validationErrors);
1715
return new HttpErrorByCode[this.errorHttpStatusCode](errors);
1816
};
1917
}

yarn.lock

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1611,6 +1611,11 @@
16111611
resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-1.0.1.tgz#0a851d3bd96498fa25c33ab7278ed3bd65f06c3e"
16121612
integrity sha512-l42BggppR6zLmpfU6fq9HEa2oGPEI8yrSPL3GITjfRInppYFahObbIQOQK3UGxEnyQpltZLaPe75046NOZQikw==
16131613

1614+
"@types/validator@^13.1.3":
1615+
version "13.6.6"
1616+
resolved "https://registry.yarnpkg.com/@types/validator/-/validator-13.6.6.tgz#6e6e2d086148db5ae14851614971b715670cbd52"
1617+
integrity sha512-+qogUELb4gMhrMjSh/seKmGVvN+uQLfyqJAqYRWqVHsvBsUO2xDBCL8CJ/ZSukbd8vXaoYbpIssAmfLEzzBHEw==
1618+
16141619
"@types/webpack@^5.0.0":
16151620
version "5.28.0"
16161621
resolved "https://registry.yarnpkg.com/@types/webpack/-/webpack-5.28.0.tgz#78dde06212f038d77e54116cfe69e88ae9ed2c03"
@@ -2593,6 +2598,11 @@ ci-job-number@^1.2.2:
25932598
resolved "https://registry.yarnpkg.com/ci-job-number/-/ci-job-number-1.2.2.tgz#f4e5918fcaeeda95b604f214be7d7d4a961fe0c0"
25942599
integrity sha512-CLOGsVDrVamzv8sXJGaILUVI6dsuAkouJP/n6t+OxLPeeA4DDby7zn9SB6EUpa1H7oIKoE+rMmkW80zYsFfUjA==
25952600

2601+
class-transformer@^0.4.0:
2602+
version "0.4.0"
2603+
resolved "https://registry.yarnpkg.com/class-transformer/-/class-transformer-0.4.0.tgz#b52144117b423c516afb44cc1c76dbad31c2165b"
2604+
integrity sha512-ETWD/H2TbWbKEi7m9N4Km5+cw1hNcqJSxlSYhsLsNjQzWWiZIYA1zafxpK9PwVfaZ6AqR5rrjPVUBGESm5tQUA==
2605+
25962606
class-utils@^0.3.5:
25972607
version "0.3.6"
25982608
resolved "https://registry.yarnpkg.com/class-utils/-/class-utils-0.3.6.tgz#f93369ae8b9a7ce02fd41faad0ca83033190c463"
@@ -2603,6 +2613,15 @@ class-utils@^0.3.5:
26032613
isobject "^3.0.0"
26042614
static-extend "^0.1.1"
26052615

2616+
class-validator@^0.13.1:
2617+
version "0.13.1"
2618+
resolved "https://registry.yarnpkg.com/class-validator/-/class-validator-0.13.1.tgz#381b2001ee6b9e05afd133671fbdf760da7dec67"
2619+
integrity sha512-zWIeYFhUitvAHBwNhDdCRK09hWx+P0HUwFE8US8/CxFpMVzkUK8RJl7yOIE+BVu2lxyPNgeOaFv78tLE47jBIg==
2620+
dependencies:
2621+
"@types/validator" "^13.1.3"
2622+
libphonenumber-js "^1.9.7"
2623+
validator "^13.5.2"
2624+
26062625
clean-stack@^2.0.0:
26072626
version "2.2.0"
26082627
resolved "https://registry.yarnpkg.com/clean-stack/-/clean-stack-2.2.0.tgz#ee8472dbb129e727b31e8a10a427dee9dfe4008b"
@@ -5588,6 +5607,11 @@ levn@^0.3.0, levn@~0.3.0:
55885607
prelude-ls "~1.1.2"
55895608
type-check "~0.3.2"
55905609

5610+
libphonenumber-js@^1.9.7:
5611+
version "1.9.42"
5612+
resolved "https://registry.yarnpkg.com/libphonenumber-js/-/libphonenumber-js-1.9.42.tgz#41f41d540f89b6e3fd36de7120ddb57b3a468c77"
5613+
integrity sha512-UBtU0ylpZPKPT8NLIyQJWj/DToMFxmo3Fm5m6qDc0LATvf0SY0qUhaurCEvukAB9Fo+Ia2Anjzqwoupaa64fXg==
5614+
55915615
lilconfig@^2.0.3:
55925616
version "2.0.4"
55935617
resolved "https://registry.yarnpkg.com/lilconfig/-/lilconfig-2.0.4.tgz#f4507d043d7058b380b6a8f5cb7bcd4b34cee082"
@@ -8579,6 +8603,11 @@ validate-npm-package-name@^3.0.0:
85798603
dependencies:
85808604
builtins "^1.0.3"
85818605

8606+
validator@^13.5.2:
8607+
version "13.7.0"
8608+
resolved "https://registry.yarnpkg.com/validator/-/validator-13.7.0.tgz#4f9658ba13ba8f3d82ee881d3516489ea85c0857"
8609+
integrity sha512-nYXQLCBkpJ8X6ltALua9dRrZDHVYxjJ1wgskNt1lH9fzGjs3tgojGSCBjmEPwkWS1y29+DrizMTW19Pr9uB2nw==
8610+
85828611
vendors@^1.0.3:
85838612
version "1.0.4"
85848613
resolved "https://registry.yarnpkg.com/vendors/-/vendors-1.0.4.tgz#e2b800a53e7a29b93506c3cf41100d16c4c4ad8e"

0 commit comments

Comments
 (0)