@dismissible/nestjs-validation 0.0.2-canary.8976e84.0 → 0.0.2-canary.a611bd3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -5
- package/package.json +14 -5
- package/src/index.d.ts +4 -0
- package/src/index.js +8 -0
- package/src/index.js.map +1 -0
- package/src/transform-boolean.decorator.d.ts +17 -0
- package/src/transform-boolean.decorator.js +32 -0
- package/src/transform-boolean.decorator.js.map +1 -0
- package/src/transform-comma-separated.decorator.d.ts +10 -0
- package/src/transform-comma-separated.decorator.js +17 -0
- package/src/transform-comma-separated.decorator.js.map +1 -0
- package/src/validation.module.d.ts +2 -0
- package/src/validation.module.js +16 -0
- package/src/validation.module.js.map +1 -0
- package/src/validation.service.d.ts +7 -0
- package/src/validation.service.js +52 -0
- package/src/validation.service.js.map +1 -0
- package/jest.config.ts +0 -10
- package/project.json +0 -34
- package/src/index.ts +0 -2
- package/src/validation.module.ts +0 -8
- package/src/validation.service.spec.ts +0 -171
- package/src/validation.service.ts +0 -57
- package/tsconfig.json +0 -13
- package/tsconfig.lib.json +0 -14
package/README.md
CHANGED
|
@@ -161,11 +161,11 @@ A NestJS module that provides `ValidationService` as a singleton.
|
|
|
161
161
|
|
|
162
162
|
## Features
|
|
163
163
|
|
|
164
|
-
-
|
|
165
|
-
-
|
|
166
|
-
-
|
|
167
|
-
-
|
|
168
|
-
-
|
|
164
|
+
- Automatic transformation using `class-transformer`
|
|
165
|
+
- Validation using `class-validator` decorators
|
|
166
|
+
- Nested validation error extraction
|
|
167
|
+
- Formatted error messages
|
|
168
|
+
- Type-safe DTOs with TypeScript generics
|
|
169
169
|
|
|
170
170
|
## Example: Using in a Controller
|
|
171
171
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dismissible/nestjs-validation",
|
|
3
|
-
"version": "0.0.2-canary.
|
|
3
|
+
"version": "0.0.2-canary.a611bd3.0",
|
|
4
4
|
"description": "Validation service module for NestJS applications using class-validator and class-transformer",
|
|
5
5
|
"main": "./src/index.js",
|
|
6
6
|
"types": "./src/index.d.ts",
|
|
@@ -11,11 +11,16 @@
|
|
|
11
11
|
"types": "./src/index.d.ts"
|
|
12
12
|
}
|
|
13
13
|
},
|
|
14
|
+
"files": [
|
|
15
|
+
"src",
|
|
16
|
+
"README.md"
|
|
17
|
+
],
|
|
14
18
|
"dependencies": {},
|
|
15
19
|
"peerDependencies": {
|
|
16
|
-
"@nestjs/common": "^11.0.0",
|
|
17
|
-
"class-validator": "
|
|
18
|
-
"class-transformer": "
|
|
20
|
+
"@nestjs/common": "10.0.0 || ^11.0.0",
|
|
21
|
+
"class-validator": "0.14.0",
|
|
22
|
+
"class-transformer": "0.5.0",
|
|
23
|
+
"reflect-metadata": "0.2.2"
|
|
19
24
|
},
|
|
20
25
|
"peerDependenciesMeta": {
|
|
21
26
|
"@nestjs/common": {
|
|
@@ -26,6 +31,9 @@
|
|
|
26
31
|
},
|
|
27
32
|
"class-transformer": {
|
|
28
33
|
"optional": false
|
|
34
|
+
},
|
|
35
|
+
"reflect-metadata": {
|
|
36
|
+
"optional": false
|
|
29
37
|
}
|
|
30
38
|
},
|
|
31
39
|
"keywords": [
|
|
@@ -43,5 +51,6 @@
|
|
|
43
51
|
},
|
|
44
52
|
"publishConfig": {
|
|
45
53
|
"access": "public"
|
|
46
|
-
}
|
|
54
|
+
},
|
|
55
|
+
"type": "commonjs"
|
|
47
56
|
}
|
package/src/index.d.ts
ADDED
package/src/index.js
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const tslib_1 = require("tslib");
|
|
4
|
+
tslib_1.__exportStar(require("./validation.service"), exports);
|
|
5
|
+
tslib_1.__exportStar(require("./validation.module"), exports);
|
|
6
|
+
tslib_1.__exportStar(require("./transform-boolean.decorator"), exports);
|
|
7
|
+
tslib_1.__exportStar(require("./transform-comma-separated.decorator"), exports);
|
|
8
|
+
//# sourceMappingURL=index.js.map
|
package/src/index.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../libs/validation/src/index.ts"],"names":[],"mappings":";;;AAAA,+DAAqC;AACrC,8DAAoC;AACpC,wEAA8C;AAC9C,gFAAsD"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Transforms string values to boolean, preserving existing boolean values.
|
|
3
|
+
* Useful for environment variable configuration where boolean values may be passed as strings.
|
|
4
|
+
*
|
|
5
|
+
* @param defaultValue - Optional default value to return if the value is not a boolean or string.
|
|
6
|
+
* If not provided, the original value is returned unchanged.
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* ```typescript
|
|
10
|
+
* class Config {
|
|
11
|
+
* @IsBoolean()
|
|
12
|
+
* @TransformBoolean()
|
|
13
|
+
* enabled!: boolean;
|
|
14
|
+
* }
|
|
15
|
+
* ```
|
|
16
|
+
*/
|
|
17
|
+
export declare function TransformBoolean(defaultValue?: boolean): PropertyDecorator;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.TransformBoolean = TransformBoolean;
|
|
4
|
+
const class_transformer_1 = require("class-transformer");
|
|
5
|
+
/**
|
|
6
|
+
* Transforms string values to boolean, preserving existing boolean values.
|
|
7
|
+
* Useful for environment variable configuration where boolean values may be passed as strings.
|
|
8
|
+
*
|
|
9
|
+
* @param defaultValue - Optional default value to return if the value is not a boolean or string.
|
|
10
|
+
* If not provided, the original value is returned unchanged.
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* ```typescript
|
|
14
|
+
* class Config {
|
|
15
|
+
* @IsBoolean()
|
|
16
|
+
* @TransformBoolean()
|
|
17
|
+
* enabled!: boolean;
|
|
18
|
+
* }
|
|
19
|
+
* ```
|
|
20
|
+
*/
|
|
21
|
+
function TransformBoolean(defaultValue) {
|
|
22
|
+
return (0, class_transformer_1.Transform)(({ value }) => {
|
|
23
|
+
if (typeof value === 'boolean') {
|
|
24
|
+
return value;
|
|
25
|
+
}
|
|
26
|
+
if (typeof value === 'string') {
|
|
27
|
+
return value.toLowerCase() === 'true';
|
|
28
|
+
}
|
|
29
|
+
return defaultValue !== undefined ? defaultValue : value;
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
//# sourceMappingURL=transform-boolean.decorator.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"transform-boolean.decorator.js","sourceRoot":"","sources":["../../../../libs/validation/src/transform-boolean.decorator.ts"],"names":[],"mappings":";;AAkBA,4CAUC;AA5BD,yDAA8C;AAE9C;;;;;;;;;;;;;;;GAeG;AACH,SAAgB,gBAAgB,CAAC,YAAsB;IACrD,OAAO,IAAA,6BAAS,EAAC,CAAC,EAAE,KAAK,EAAE,EAAE,EAAE;QAC7B,IAAI,OAAO,KAAK,KAAK,SAAS,EAAE,CAAC;YAC/B,OAAO,KAAK,CAAC;QACf,CAAC;QACD,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;YAC9B,OAAO,KAAK,CAAC,WAAW,EAAE,KAAK,MAAM,CAAC;QACxC,CAAC;QACD,OAAO,YAAY,KAAK,SAAS,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,KAAK,CAAC;IAC3D,CAAC,CAAC,CAAC;AACL,CAAC"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Transforms a comma-separated string into an array of trimmed strings.
|
|
3
|
+
* If the value is already an array, it is returned as-is.
|
|
4
|
+
*
|
|
5
|
+
* @example
|
|
6
|
+
* // Input: "GET,POST,DELETE" → Output: ["GET", "POST", "DELETE"]
|
|
7
|
+
* // Input: "a , b , c" → Output: ["a", "b", "c"]
|
|
8
|
+
* // Input: ["a", "b"] → Output: ["a", "b"]
|
|
9
|
+
*/
|
|
10
|
+
export declare function TransformCommaSeparated(): PropertyDecorator;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.TransformCommaSeparated = TransformCommaSeparated;
|
|
4
|
+
const class_transformer_1 = require("class-transformer");
|
|
5
|
+
/**
|
|
6
|
+
* Transforms a comma-separated string into an array of trimmed strings.
|
|
7
|
+
* If the value is already an array, it is returned as-is.
|
|
8
|
+
*
|
|
9
|
+
* @example
|
|
10
|
+
* // Input: "GET,POST,DELETE" → Output: ["GET", "POST", "DELETE"]
|
|
11
|
+
* // Input: "a , b , c" → Output: ["a", "b", "c"]
|
|
12
|
+
* // Input: ["a", "b"] → Output: ["a", "b"]
|
|
13
|
+
*/
|
|
14
|
+
function TransformCommaSeparated() {
|
|
15
|
+
return (0, class_transformer_1.Transform)(({ value }) => typeof value === 'string' ? value.split(',').map((s) => s.trim()) : value);
|
|
16
|
+
}
|
|
17
|
+
//# sourceMappingURL=transform-comma-separated.decorator.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"transform-comma-separated.decorator.js","sourceRoot":"","sources":["../../../../libs/validation/src/transform-comma-separated.decorator.ts"],"names":[],"mappings":";;AAWA,0DAIC;AAfD,yDAA8C;AAE9C;;;;;;;;GAQG;AACH,SAAgB,uBAAuB;IACrC,OAAO,IAAA,6BAAS,EAAC,CAAC,EAAE,KAAK,EAAE,EAAE,EAAE,CAC7B,OAAO,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC,KAAK,CAC1E,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ValidationModule = void 0;
|
|
4
|
+
const tslib_1 = require("tslib");
|
|
5
|
+
const common_1 = require("@nestjs/common");
|
|
6
|
+
const validation_service_1 = require("./validation.service");
|
|
7
|
+
let ValidationModule = class ValidationModule {
|
|
8
|
+
};
|
|
9
|
+
exports.ValidationModule = ValidationModule;
|
|
10
|
+
exports.ValidationModule = ValidationModule = tslib_1.__decorate([
|
|
11
|
+
(0, common_1.Module)({
|
|
12
|
+
providers: [validation_service_1.ValidationService],
|
|
13
|
+
exports: [validation_service_1.ValidationService],
|
|
14
|
+
})
|
|
15
|
+
], ValidationModule);
|
|
16
|
+
//# sourceMappingURL=validation.module.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"validation.module.js","sourceRoot":"","sources":["../../../../libs/validation/src/validation.module.ts"],"names":[],"mappings":";;;;AAAA,2CAAwC;AACxC,6DAAyD;AAMlD,IAAM,gBAAgB,GAAtB,MAAM,gBAAgB;CAAG,CAAA;AAAnB,4CAAgB;2BAAhB,gBAAgB;IAJ5B,IAAA,eAAM,EAAC;QACN,SAAS,EAAE,CAAC,sCAAiB,CAAC;QAC9B,OAAO,EAAE,CAAC,sCAAiB,CAAC;KAC7B,CAAC;GACW,gBAAgB,CAAG"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { ClassConstructor } from 'class-transformer';
|
|
2
|
+
export declare class ValidationService {
|
|
3
|
+
validateDto<T extends object>(dtoClass: ClassConstructor<T>, data: unknown): Promise<T>;
|
|
4
|
+
validateInstance<T extends object>(instance: T): Promise<void>;
|
|
5
|
+
private formatValidationErrors;
|
|
6
|
+
private extractErrorMessage;
|
|
7
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ValidationService = void 0;
|
|
4
|
+
const tslib_1 = require("tslib");
|
|
5
|
+
const common_1 = require("@nestjs/common");
|
|
6
|
+
const class_validator_1 = require("class-validator");
|
|
7
|
+
const class_transformer_1 = require("class-transformer");
|
|
8
|
+
let ValidationService = class ValidationService {
|
|
9
|
+
async validateDto(dtoClass, data) {
|
|
10
|
+
if (data === null || data === undefined) {
|
|
11
|
+
throw new common_1.BadRequestException('Data cannot be null or undefined');
|
|
12
|
+
}
|
|
13
|
+
const dtoInstance = (0, class_transformer_1.plainToInstance)(dtoClass, data);
|
|
14
|
+
const validationErrors = await (0, class_validator_1.validate)(dtoInstance);
|
|
15
|
+
if (validationErrors.length > 0) {
|
|
16
|
+
const errorMessages = this.formatValidationErrors(validationErrors);
|
|
17
|
+
throw new common_1.BadRequestException(errorMessages);
|
|
18
|
+
}
|
|
19
|
+
return dtoInstance;
|
|
20
|
+
}
|
|
21
|
+
async validateInstance(instance) {
|
|
22
|
+
const validationErrors = await (0, class_validator_1.validate)(instance);
|
|
23
|
+
if (validationErrors.length > 0) {
|
|
24
|
+
const errorMessages = this.formatValidationErrors(validationErrors);
|
|
25
|
+
throw new common_1.BadRequestException(errorMessages);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
formatValidationErrors(errors) {
|
|
29
|
+
return errors
|
|
30
|
+
.map((error) => this.extractErrorMessage(error))
|
|
31
|
+
.filter((message) => message.length > 0)
|
|
32
|
+
.join('; ');
|
|
33
|
+
}
|
|
34
|
+
extractErrorMessage(error) {
|
|
35
|
+
const messages = [];
|
|
36
|
+
if (error.constraints) {
|
|
37
|
+
messages.push(...Object.values(error.constraints));
|
|
38
|
+
}
|
|
39
|
+
if (error.children && error.children.length > 0) {
|
|
40
|
+
const childMessages = error.children
|
|
41
|
+
.map((child) => this.extractErrorMessage(child))
|
|
42
|
+
.filter((message) => message.length > 0);
|
|
43
|
+
messages.push(...childMessages);
|
|
44
|
+
}
|
|
45
|
+
return messages.join(', ');
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
exports.ValidationService = ValidationService;
|
|
49
|
+
exports.ValidationService = ValidationService = tslib_1.__decorate([
|
|
50
|
+
(0, common_1.Injectable)()
|
|
51
|
+
], ValidationService);
|
|
52
|
+
//# sourceMappingURL=validation.service.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"validation.service.js","sourceRoot":"","sources":["../../../../libs/validation/src/validation.service.ts"],"names":[],"mappings":";;;;AAAA,2CAAiE;AACjE,qDAA4D;AAC5D,yDAAsE;AAG/D,IAAM,iBAAiB,GAAvB,MAAM,iBAAiB;IAC5B,KAAK,CAAC,WAAW,CAAmB,QAA6B,EAAE,IAAa;QAC9E,IAAI,IAAI,KAAK,IAAI,IAAI,IAAI,KAAK,SAAS,EAAE,CAAC;YACxC,MAAM,IAAI,4BAAmB,CAAC,kCAAkC,CAAC,CAAC;QACpE,CAAC;QAED,MAAM,WAAW,GAAG,IAAA,mCAAe,EAAC,QAAQ,EAAE,IAAI,CAAC,CAAC;QACpD,MAAM,gBAAgB,GAAG,MAAM,IAAA,0BAAQ,EAAC,WAAqB,CAAC,CAAC;QAE/D,IAAI,gBAAgB,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YAChC,MAAM,aAAa,GAAG,IAAI,CAAC,sBAAsB,CAAC,gBAAgB,CAAC,CAAC;YACpE,MAAM,IAAI,4BAAmB,CAAC,aAAa,CAAC,CAAC;QAC/C,CAAC;QAED,OAAO,WAAW,CAAC;IACrB,CAAC;IAED,KAAK,CAAC,gBAAgB,CAAmB,QAAW;QAClD,MAAM,gBAAgB,GAAG,MAAM,IAAA,0BAAQ,EAAC,QAAkB,CAAC,CAAC;QAE5D,IAAI,gBAAgB,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YAChC,MAAM,aAAa,GAAG,IAAI,CAAC,sBAAsB,CAAC,gBAAgB,CAAC,CAAC;YACpE,MAAM,IAAI,4BAAmB,CAAC,aAAa,CAAC,CAAC;QAC/C,CAAC;IACH,CAAC;IAEO,sBAAsB,CAAC,MAAyB;QACtD,OAAO,MAAM;aACV,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,IAAI,CAAC,mBAAmB,CAAC,KAAK,CAAC,CAAC;aAC/C,MAAM,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,CAAC,MAAM,GAAG,CAAC,CAAC;aACvC,IAAI,CAAC,IAAI,CAAC,CAAC;IAChB,CAAC;IAEO,mBAAmB,CAAC,KAAsB;QAChD,MAAM,QAAQ,GAAa,EAAE,CAAC;QAE9B,IAAI,KAAK,CAAC,WAAW,EAAE,CAAC;YACtB,QAAQ,CAAC,IAAI,CAAC,GAAG,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,WAAW,CAAC,CAAC,CAAC;QACrD,CAAC;QAED,IAAI,KAAK,CAAC,QAAQ,IAAI,KAAK,CAAC,QAAQ,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YAChD,MAAM,aAAa,GAAG,KAAK,CAAC,QAAQ;iBACjC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,IAAI,CAAC,mBAAmB,CAAC,KAAK,CAAC,CAAC;iBAC/C,MAAM,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;YAC3C,QAAQ,CAAC,IAAI,CAAC,GAAG,aAAa,CAAC,CAAC;QAClC,CAAC;QAED,OAAO,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAC7B,CAAC;CACF,CAAA;AAjDY,8CAAiB;4BAAjB,iBAAiB;IAD7B,IAAA,mBAAU,GAAE;GACA,iBAAiB,CAiD7B"}
|
package/jest.config.ts
DELETED
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
export default {
|
|
2
|
-
displayName: 'validation',
|
|
3
|
-
preset: '../../jest.preset.js',
|
|
4
|
-
testEnvironment: 'node',
|
|
5
|
-
transform: {
|
|
6
|
-
'^.+\\.[tj]s$': ['ts-jest', { tsconfig: '<rootDir>/tsconfig.json' }],
|
|
7
|
-
},
|
|
8
|
-
moduleFileExtensions: ['ts', 'js', 'html'],
|
|
9
|
-
coverageDirectory: '../../coverage/libs/validation',
|
|
10
|
-
};
|
package/project.json
DELETED
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "validation",
|
|
3
|
-
"$schema": "../../node_modules/nx/schemas/project-schema.json",
|
|
4
|
-
"sourceRoot": "libs/validation/src",
|
|
5
|
-
"projectType": "library",
|
|
6
|
-
"tags": [],
|
|
7
|
-
"targets": {
|
|
8
|
-
"build": {
|
|
9
|
-
"executor": "@nx/js:tsc",
|
|
10
|
-
"outputs": ["{options.outputPath}"],
|
|
11
|
-
"options": {
|
|
12
|
-
"outputPath": "dist/libs/validation",
|
|
13
|
-
"main": "libs/validation/src/index.ts",
|
|
14
|
-
"tsConfig": "libs/validation/tsconfig.lib.json",
|
|
15
|
-
"assets": ["libs/validation/package.json", "libs/validation/README.md"],
|
|
16
|
-
"generatePackageJson": true
|
|
17
|
-
}
|
|
18
|
-
},
|
|
19
|
-
"lint": {
|
|
20
|
-
"executor": "@nx/eslint:lint",
|
|
21
|
-
"outputs": ["{options.outputFile}"],
|
|
22
|
-
"options": {
|
|
23
|
-
"lintFilePatterns": ["libs/validation/**/*.ts"]
|
|
24
|
-
}
|
|
25
|
-
},
|
|
26
|
-
"npm-publish": {
|
|
27
|
-
"executor": "nx:run-commands",
|
|
28
|
-
"options": {
|
|
29
|
-
"command": "npm publish --access public",
|
|
30
|
-
"cwd": "dist/libs/validation"
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
}
|
package/src/index.ts
DELETED
package/src/validation.module.ts
DELETED
|
@@ -1,171 +0,0 @@
|
|
|
1
|
-
import { BadRequestException } from '@nestjs/common';
|
|
2
|
-
import { ValidationService } from './validation.service';
|
|
3
|
-
import { IsString, IsOptional, Length, IsEmail } from 'class-validator';
|
|
4
|
-
|
|
5
|
-
class TestDto {
|
|
6
|
-
@IsString()
|
|
7
|
-
@Length(1, 10)
|
|
8
|
-
name: string;
|
|
9
|
-
|
|
10
|
-
@IsEmail()
|
|
11
|
-
email: string;
|
|
12
|
-
|
|
13
|
-
@IsOptional()
|
|
14
|
-
@IsString()
|
|
15
|
-
description?: string;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
describe('ValidationService', () => {
|
|
19
|
-
let service: ValidationService;
|
|
20
|
-
|
|
21
|
-
beforeEach(() => {
|
|
22
|
-
service = new ValidationService();
|
|
23
|
-
});
|
|
24
|
-
|
|
25
|
-
describe('validateDto', () => {
|
|
26
|
-
it('should validate a valid DTO successfully', async () => {
|
|
27
|
-
const data = {
|
|
28
|
-
name: 'test',
|
|
29
|
-
email: 'test@example.com',
|
|
30
|
-
description: 'optional field',
|
|
31
|
-
};
|
|
32
|
-
|
|
33
|
-
const result = await service.validateDto(TestDto, data);
|
|
34
|
-
|
|
35
|
-
expect(result).toBeInstanceOf(TestDto);
|
|
36
|
-
expect(result.name).toBe('test');
|
|
37
|
-
expect(result.email).toBe('test@example.com');
|
|
38
|
-
expect(result.description).toBe('optional field');
|
|
39
|
-
});
|
|
40
|
-
|
|
41
|
-
it('should validate a valid DTO with optional fields missing', async () => {
|
|
42
|
-
const data = {
|
|
43
|
-
name: 'test',
|
|
44
|
-
email: 'test@example.com',
|
|
45
|
-
};
|
|
46
|
-
|
|
47
|
-
const result = await service.validateDto(TestDto, data);
|
|
48
|
-
|
|
49
|
-
expect(result).toBeInstanceOf(TestDto);
|
|
50
|
-
expect(result.name).toBe('test');
|
|
51
|
-
expect(result.email).toBe('test@example.com');
|
|
52
|
-
expect(result.description).toBeUndefined();
|
|
53
|
-
});
|
|
54
|
-
|
|
55
|
-
it('should throw BadRequestException for invalid string length', async () => {
|
|
56
|
-
const data = {
|
|
57
|
-
name: 'this name is too long',
|
|
58
|
-
email: 'test@example.com',
|
|
59
|
-
};
|
|
60
|
-
|
|
61
|
-
await expect(service.validateDto(TestDto, data)).rejects.toThrow(BadRequestException);
|
|
62
|
-
});
|
|
63
|
-
|
|
64
|
-
it('should throw BadRequestException for invalid email', async () => {
|
|
65
|
-
const data = {
|
|
66
|
-
name: 'test',
|
|
67
|
-
email: 'invalid-email',
|
|
68
|
-
};
|
|
69
|
-
|
|
70
|
-
await expect(service.validateDto(TestDto, data)).rejects.toThrow(BadRequestException);
|
|
71
|
-
});
|
|
72
|
-
|
|
73
|
-
it('should throw BadRequestException for missing required fields', async () => {
|
|
74
|
-
const data = {
|
|
75
|
-
name: 'test',
|
|
76
|
-
};
|
|
77
|
-
|
|
78
|
-
await expect(service.validateDto(TestDto, data)).rejects.toThrow(BadRequestException);
|
|
79
|
-
});
|
|
80
|
-
|
|
81
|
-
it('should combine multiple validation errors', async () => {
|
|
82
|
-
const data = {
|
|
83
|
-
name: 'this name is way too long for validation',
|
|
84
|
-
email: 'invalid-email',
|
|
85
|
-
};
|
|
86
|
-
|
|
87
|
-
try {
|
|
88
|
-
await service.validateDto(TestDto, data);
|
|
89
|
-
fail('Should have thrown BadRequestException');
|
|
90
|
-
} catch (error) {
|
|
91
|
-
expect(error).toBeInstanceOf(BadRequestException);
|
|
92
|
-
const message = (error as BadRequestException).message;
|
|
93
|
-
expect(message).toContain('name must be shorter than or equal to 10 characters');
|
|
94
|
-
expect(message).toContain('email must be an email');
|
|
95
|
-
}
|
|
96
|
-
});
|
|
97
|
-
|
|
98
|
-
it('should handle empty data', async () => {
|
|
99
|
-
const data = {};
|
|
100
|
-
|
|
101
|
-
await expect(service.validateDto(TestDto, data)).rejects.toThrow(BadRequestException);
|
|
102
|
-
});
|
|
103
|
-
|
|
104
|
-
it('should handle null data', async () => {
|
|
105
|
-
const data = null;
|
|
106
|
-
|
|
107
|
-
await expect(service.validateDto(TestDto, data)).rejects.toThrow(BadRequestException);
|
|
108
|
-
});
|
|
109
|
-
});
|
|
110
|
-
|
|
111
|
-
describe('validateInstance', () => {
|
|
112
|
-
it('should validate a valid instance successfully', async () => {
|
|
113
|
-
const instance = new TestDto();
|
|
114
|
-
instance.name = 'test';
|
|
115
|
-
instance.email = 'test@example.com';
|
|
116
|
-
|
|
117
|
-
await expect(service.validateInstance(instance)).resolves.toBeUndefined();
|
|
118
|
-
});
|
|
119
|
-
|
|
120
|
-
it('should throw BadRequestException for invalid instance', async () => {
|
|
121
|
-
const instance = new TestDto();
|
|
122
|
-
instance.name = 'this name is too long';
|
|
123
|
-
instance.email = 'invalid-email';
|
|
124
|
-
|
|
125
|
-
await expect(service.validateInstance(instance)).rejects.toThrow(BadRequestException);
|
|
126
|
-
});
|
|
127
|
-
|
|
128
|
-
it('should handle instance with missing required fields', async () => {
|
|
129
|
-
const instance = new TestDto();
|
|
130
|
-
instance.name = 'test';
|
|
131
|
-
// Missing email
|
|
132
|
-
|
|
133
|
-
await expect(service.validateInstance(instance)).rejects.toThrow(BadRequestException);
|
|
134
|
-
});
|
|
135
|
-
});
|
|
136
|
-
|
|
137
|
-
describe('error message formatting', () => {
|
|
138
|
-
it('should format single error correctly', async () => {
|
|
139
|
-
const data = {
|
|
140
|
-
name: 'test',
|
|
141
|
-
email: 'invalid-email',
|
|
142
|
-
};
|
|
143
|
-
|
|
144
|
-
try {
|
|
145
|
-
await service.validateDto(TestDto, data);
|
|
146
|
-
fail('Should have thrown BadRequestException');
|
|
147
|
-
} catch (error) {
|
|
148
|
-
expect(error).toBeInstanceOf(BadRequestException);
|
|
149
|
-
expect(error.message).toBe('email must be an email');
|
|
150
|
-
}
|
|
151
|
-
});
|
|
152
|
-
|
|
153
|
-
it('should format multiple errors with semicolon separator', async () => {
|
|
154
|
-
const data = {
|
|
155
|
-
name: '',
|
|
156
|
-
email: 'invalid',
|
|
157
|
-
};
|
|
158
|
-
|
|
159
|
-
try {
|
|
160
|
-
await service.validateDto(TestDto, data);
|
|
161
|
-
fail('Should have thrown BadRequestException');
|
|
162
|
-
} catch (error) {
|
|
163
|
-
expect(error).toBeInstanceOf(BadRequestException);
|
|
164
|
-
const message = (error as BadRequestException).message;
|
|
165
|
-
expect(message).toContain(';');
|
|
166
|
-
expect(message).toContain('name must be longer than or equal to 1 characters');
|
|
167
|
-
expect(message).toContain('email must be an email');
|
|
168
|
-
}
|
|
169
|
-
});
|
|
170
|
-
});
|
|
171
|
-
});
|
|
@@ -1,57 +0,0 @@
|
|
|
1
|
-
import { Injectable, BadRequestException } from '@nestjs/common';
|
|
2
|
-
import { validate, ValidationError } from 'class-validator';
|
|
3
|
-
import { plainToInstance, ClassConstructor } from 'class-transformer';
|
|
4
|
-
|
|
5
|
-
@Injectable()
|
|
6
|
-
export class ValidationService {
|
|
7
|
-
async validateDto<T extends object>(dtoClass: ClassConstructor<T>, data: unknown): Promise<T> {
|
|
8
|
-
if (data === null || data === undefined) {
|
|
9
|
-
throw new BadRequestException('Data cannot be null or undefined');
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
const dtoInstance = plainToInstance(dtoClass, data);
|
|
13
|
-
const validationErrors = await validate(dtoInstance as object);
|
|
14
|
-
|
|
15
|
-
if (validationErrors.length > 0) {
|
|
16
|
-
const errorMessages = this.formatValidationErrors(validationErrors);
|
|
17
|
-
throw new BadRequestException(errorMessages);
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
return dtoInstance;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
async validateInstance<T extends object>(instance: T): Promise<void> {
|
|
24
|
-
const validationErrors = await validate(instance as object);
|
|
25
|
-
|
|
26
|
-
if (validationErrors.length > 0) {
|
|
27
|
-
const errorMessages = this.formatValidationErrors(validationErrors);
|
|
28
|
-
throw new BadRequestException(errorMessages);
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
private formatValidationErrors(errors: ValidationError[]): string {
|
|
33
|
-
return errors
|
|
34
|
-
.map((error) => this.extractErrorMessage(error))
|
|
35
|
-
.filter((message) => message.length > 0)
|
|
36
|
-
.join('; ');
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
private extractErrorMessage(error: ValidationError): string {
|
|
40
|
-
const messages: string[] = [];
|
|
41
|
-
|
|
42
|
-
// Add constraint messages
|
|
43
|
-
if (error.constraints) {
|
|
44
|
-
messages.push(...Object.values(error.constraints));
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
// Recursively extract messages from nested errors
|
|
48
|
-
if (error.children && error.children.length > 0) {
|
|
49
|
-
const childMessages = error.children
|
|
50
|
-
.map((child) => this.extractErrorMessage(child))
|
|
51
|
-
.filter((message) => message.length > 0);
|
|
52
|
-
messages.push(...childMessages);
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
return messages.join(', ');
|
|
56
|
-
}
|
|
57
|
-
}
|
package/tsconfig.json
DELETED
package/tsconfig.lib.json
DELETED
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"extends": "./tsconfig.json",
|
|
3
|
-
"compilerOptions": {
|
|
4
|
-
"outDir": "../../dist/out-tsc",
|
|
5
|
-
"declaration": true,
|
|
6
|
-
"module": "commonjs",
|
|
7
|
-
"types": ["node"],
|
|
8
|
-
"emitDecoratorMetadata": true,
|
|
9
|
-
"experimentalDecorators": true,
|
|
10
|
-
"target": "ES2021"
|
|
11
|
-
},
|
|
12
|
-
"exclude": ["node_modules", "**/*.spec.ts", "**/*.test.ts"],
|
|
13
|
-
"include": ["src/**/*.ts"]
|
|
14
|
-
}
|