Skip to content

Commit

Permalink
feat: initial project
Browse files Browse the repository at this point in the history
  • Loading branch information
hodfords-phuong-vo-be committed Sep 24, 2024
0 parents commit df3a21b
Show file tree
Hide file tree
Showing 36 changed files with 13,030 additions and 0 deletions.
3 changes: 3 additions & 0 deletions .commitlintrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"extends": ["@commitlint/config-conventional"]
}
19 changes: 19 additions & 0 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
name: Publish Package to npmjs
on:
push:
branches:
- main
jobs:
lint:
uses: hodfords-solutions/actions/.github/workflows/lint.yaml@main
build:
uses: hodfords-solutions/actions/.github/workflows/publish.yaml@main
with:
build_path: dist/lib
secrets:
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
update-docs:
uses: hodfords-solutions/actions/.github/workflows/update-doc.yaml@main
needs: build
secrets:
DOC_SSH_PRIVATE_KEY: ${{ secrets.DOC_SSH_PRIVATE_KEY }}
36 changes: 36 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# compiled output
/dist
/node_modules

# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
lerna-debug.log*

# OS
.DS_Store

# Tests
/coverage
/.nyc_output

# IDEs and editors
/.idea
.project
.classpath
.c9/
*.launch
.settings/
*.sublime-workspace

# IDE - VSCode
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json

.env
1 change: 1 addition & 0 deletions .husky/commit-msg
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
npx --no -- commitlint --edit "${1}"
3 changes: 3 additions & 0 deletions .husky/pre-commit
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
changedFiles="$(git diff --name-only --cached)"
npm run cspell --no-must-find-files ${changedFiles}
npm run lint-staged
3 changes: 3 additions & 0 deletions .lintstagedrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"lib/**/*.ts": ["cspell", "eslint --fix --max-warnings 0"]
}
3 changes: 3 additions & 0 deletions .npmrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
//registry.npmjs.org/:_authToken=${NODE_AUTH_TOKEN}
registry=https://registry.npmjs.org/
always-auth=true
12 changes: 12 additions & 0 deletions .prettierrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"singleQuote": true,
"printWidth": 120,
"proseWrap": "always",
"tabWidth": 4,
"useTabs": false,
"trailingComma": "none",
"bracketSpacing": true,
"bracketSameLine": false,
"semi": true,
"endOfLine": "auto"
}
91 changes: 91 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
<p align="center">
<a href="http://opensource.hodfords.uk" target="blank"><img src="https://opensource.hodfords.uk/img/logo.svg" width="320" alt="Hodfords Logo" /></a>
</p>

<p align="center"> <b>nestjs-validation</b> enhances validation in your NestJS projects by providing a customized `ValidationPipe` that returns custom error messages. This library simplifies error handling by offering localized and user-friendly responses

## Installation 🤖

Install the `nestjs-validation` package with:

```bash
npm install @hodfords/nestjs-validation --save
```

## Usage 🚀

First, create an instance of ValidationPipe with the desired configuration:

```typescript
import { ValidationPipe } from '@diginexhk/nestjs-validation-helper';
import { ValidateException } from '@diginexhk/nestjs-exception';

export const validateConfig = new ValidationPipe({
whitelist: true,
stopAtFirstError: true,
forbidUnknownValues: false,
exceptionFactory: (errors): ValidateException => new ValidateException(errors)
});
```

Next, set the validation configuration globally in your bootstrap function:

```typescript
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.useGlobalPipes(validateConfig);
await app.listen(3000);
}
```

### Customize Validation Error

The original error message provides basic information but lacks detail. With **nestjs-validation**, you can enhance these errors by adding meaningful context, such as the field’s property name, value, and target object.

**Original Validation Error**

```javascript
ValidationError {
target: AppDto { stringValue: undefined },
value: undefined,
property: 'stringValue',
children: [],
constraints: { isString: 'stringValue must be a string' }
}
```

**Customized Validation Error**

```javascript
ValidationError {
target: AppDto { stringValue: undefined },
value: undefined,
property: 'stringValue',
children: [],
constraints: {
isString: {
message: '$property must be a string',
detail: { property: 'stringValue', target: 'AppDto', value: undefined }
}
}
}
```

### Exception

When combined with [nestjs-exception](https://www.npmjs.com/package/@hodfords/nestjs-exception), errors are translated into localized messages:

```json
{
"message": "Validate Exception",
"errors": {
"stringValue": {
"messages": ["String Value phải là một chuỗi kí tự"]
}
}
}
```

## License 📝

This project is licensed under the MIT License
22 changes: 22 additions & 0 deletions cspell.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
{
"version": "0.2",
"language": "en",
"words": [
"nestjs",
"diginexhk",
"metadatas",
"dtos",
"metatype",
"postbuild",
"hodfords",
"commitlint",
"classpath",
"dotenv",
"pids",
"diginex",
"typeorm",
"npmjs"
],
"flagWords": ["hte"],
"ignorePaths": ["node_modules", "test", "*.spec.ts", "cspell.json", "dist"]
}
1 change: 1 addition & 0 deletions eslint.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
module.exports = require('@hodfords/nestjs-eslint-config');
1 change: 1 addition & 0 deletions lib/constants/transformer.constant.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export const TRANSFORMER_EXCLUDE_KEY = 'transformer:exclude';
6 changes: 6 additions & 0 deletions lib/dtos/parent.dto.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { IsOptional } from 'class-validator';

export abstract class ParentDto {
@IsOptional()
protected parentDto: any;
}
8 changes: 8 additions & 0 deletions lib/dtos/request.dto.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { IsOptional } from 'class-validator';
import { ParentDto } from './parent.dto';
import { RequestDtoType } from '../types/request-dto.type';

export abstract class RequestDto extends ParentDto {
@IsOptional()
protected requestDto?: RequestDtoType;
}
3 changes: 3 additions & 0 deletions lib/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export * from './dtos/parent.dto';
export * from './dtos/request.dto';
export * from './pipes/validation.pipe';
40 changes: 40 additions & 0 deletions lib/overrides/class-validation.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import rewire from 'rewire';
import { MetadataStorageWrapper } from './metadata.storage';
import { ValidationUtils } from './validation.util';
import { MetadataStorage } from 'class-validator';

export function getRewireModule(): any {
// Issues: https://github.com/jhnns/rewire/issues/144
// Ref: https://github.com/jhnns/rewire/pull/149/files
const rewireRef = rewire('rewire');
const moduleEnvRef = rewire('rewire/lib/moduleEnv.js');

const jsExtension = moduleEnvRef.__get__('jsExtension');
moduleEnvRef.__set__('jsExtension', () => {
return function (module: any, filename: any) {
jsExtension(module, filename);
};
});
rewireRef.__set__('moduleEnv', moduleEnvRef);

return rewireRef;
}

export function getOverrideModule(metadataStorage?: MetadataStorage): any {
const rewireRef = getRewireModule();
const moduleRef = rewireRef('class-validator');

// Reverse validator executor
const storage = new MetadataStorageWrapper();
Object.assign(storage, metadataStorage ?? globalThis.classValidatorMetadataStorage);
moduleRef.__set__('MetadataStorage_1.getMetadataStorage', () => storage);

// Override custom key
const validationExecutorRef = rewireRef('class-validator/cjs/validation/ValidationExecutor.js');
const validatorRef = rewireRef('class-validator/cjs/validation/Validator.js');
validationExecutorRef.__set__('ValidationUtils_1.ValidationUtils', ValidationUtils);
validatorRef.__set__('ValidationExecutor_1', validationExecutorRef);
moduleRef.__set__('Validator_1', validatorRef);

return moduleRef;
}
19 changes: 19 additions & 0 deletions lib/overrides/metadata.storage.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { MetadataStorage } from 'class-validator';
import { ValidationMetadata } from 'class-validator/types/metadata/ValidationMetadata';

// Ref: https://github.com/typestack/class-validator/blob/develop/src/metadata/MetadataStorage.ts

export class MetadataStorageWrapper extends MetadataStorage {
getTargetValidationMetadatas(
/* eslint-disable @typescript-eslint/no-unsafe-function-type */
targetConstructor: Function,
targetSchema: string,
always: boolean,
strictGroups: boolean,
groups?: string[]
): ValidationMetadata[] {
return super
.getTargetValidationMetadatas(targetConstructor, targetSchema, always, strictGroups, groups)
.reverse();
}
}
32 changes: 32 additions & 0 deletions lib/overrides/validation.util.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { ValidationArguments } from 'class-validator';
import { ValidationMessage } from '../types/validation-message.type';

// Ref: https://github.com/typestack/class-validator/blob/develop/src/validation/ValidationUtils.ts

export function constraintToString(constraint: unknown): string {
if (Array.isArray(constraint)) {
return constraint.join(', ');
}
return `${constraint}`;
}

export class ValidationUtils {
static replaceMessageSpecialTokens(
message: string | ((args: ValidationArguments) => string),
validationArguments: ValidationArguments
): ValidationMessage {
const detail: ValidationMessage['detail'] = {
property: validationArguments.property,
target: validationArguments.targetName,
value: validationArguments.value
};

if (validationArguments.constraints instanceof Array) {
validationArguments.constraints.forEach((constraint, index) => {
detail[`constraint${index + 1}`] = constraintToString(constraint);
});
}

return { message, detail };
}
}
Loading

0 comments on commit df3a21b

Please sign in to comment.