@webilix/nestjs-helper
Version:
Helper library for NestJS
205 lines (159 loc) • 8.45 kB
text/typescript
import { Injectable, PipeTransform, ArgumentMetadata, HttpException, HttpStatus } from '@nestjs/common';
import { Helper } from '@webilix/helper-library';
import { Errors } from '../errors';
import { FormatsEnum } from '../formats';
import { Condition } from './validator.type';
import { IDateCondition, INumberCondition, IStringCondition } from './validator.interface';
export class ValidatorPipe implements PipeTransform {
private errors: string[] = [];
private setError(error: string): void {
this.errors.push(error);
}
transform(values: { [key: string]: any }, meta: ArgumentMetadata) {
this.errors = [];
const conditions = Reflect.getMetadata('validator', meta.metatype || {});
if (conditions) {
values = this.updateValues(conditions, values);
this.validate(conditions, values);
}
if (this.errors.length !== 0) throw new HttpException(this.errors, HttpStatus.BAD_REQUEST);
return values;
}
private updateValues(conditions: any, values: { [key: string]: any }): { [key: string]: any } {
const update = (condition: Condition, value: any): any => {
if (Helper.IS.empty(value)) return value;
switch (condition.type) {
case 'BOOLEAN':
case 'NUMBER':
return value;
case 'DATE':
if (!Helper.IS.STRING.jsonDate(value)) return value;
value = Helper.STRING.changeNumbers(value, 'EN');
if (!condition.omitConvert) value = new Date(value);
return value;
case 'STRING':
if (!Helper.IS.string(value)) return value;
if (!condition.omitTrim) value = value.trim();
if (condition.format) value = Helper.STRING.changeNumbers(value, 'EN');
else if (condition.changeNumbers) value = Helper.STRING.changeNumbers(value, condition.changeNumbers);
return value;
case 'OBJECT':
const properties: string[] = Object.keys(condition.properties);
properties.forEach((property: string) => {
if (Helper.IS.empty(value[property])) return;
value[property] = update(condition.properties[property], value[property]);
});
return value;
}
};
Object.keys(conditions).forEach((key: string) => {
const condition: Condition = conditions[key];
if (condition.array) {
if (Helper.IS.array(values[key]))
values[key].map((_: any, index: number) => update(condition, values[key][index]));
} else values[key] = update(condition, values[key]);
});
return values;
}
private validate(conditions: any, values: { [key: string]: any }) {
Object.keys(conditions).forEach((key: string) => {
const condition: Condition = conditions[key];
if (condition.array) this.validateArray(condition, values[key]);
else this.validateValue(condition, values[key]);
});
}
private validateType(condition: Condition, value: any): boolean {
if (Helper.IS.empty(value)) return true;
switch (condition.type) {
case 'BOOLEAN':
return Helper.IS.boolean(value);
case 'DATE':
return Helper.IS.date(value) || Helper.IS.STRING.jsonDate(value);
case 'NUMBER':
return Helper.IS.number(value);
case 'OBJECT':
return Helper.IS.object(value);
case 'STRING':
return Helper.IS.string(value);
}
}
private validateArray(condition: Condition, value: any[]): void {
if (!condition.array) return;
const title: string = condition.title;
// UNDEFINED
if (value === undefined) return this.setError(Errors.undefined(title));
// NULLABLE
const isEmpty: boolean = Helper.IS.empty(value);
if (!condition.nullable && isEmpty) return this.setError(Errors.empty(title));
if (isEmpty) return;
// TYPE
if (!Helper.IS.array(value)) return this.setError(Errors.invalid(title));
// COUNT && UNIQUE
if (condition.array !== true) {
if (condition.array.minCount && value.length < condition.array.minCount)
return this.setError(Errors.minCount(title, condition.array.minCount));
if (condition.array.maxCount && value.length > condition.array.maxCount)
return this.setError(Errors.maxCount(title, condition.array.maxCount));
if (
condition.array.unique &&
!Helper.IS.ARRAY.unique(value, condition.array.unique === true ? undefined : condition.array.unique)
)
return this.setError(Errors.unique(title));
}
// Change nullable option for array values
condition = { ...condition, nullable: false };
value.forEach((_: any, index: number) => this.validateValue(condition, value[index], index + 1));
}
private validateValue(condition: Condition, value: any, index?: number, parent?: string): void {
const title: string =
(parent ? `${parent}: ` : '') + condition.title + (index ? ` ${Helper.NUMBER.getTitle(index)}` : '');
// UNDEFINED
if (value === undefined) return this.setError(Errors.undefined(title));
// NULLABLE
const isEmpty: boolean = Helper.IS.empty(value);
if (!condition.nullable && isEmpty) return this.setError(Errors.empty(title));
if (isEmpty) return;
// TYPE
if (!this.validateType(condition, value)) return this.setError(Errors.invalid(title));
// CONDITIONS
switch (condition.type) {
case 'BOOLEAN':
return;
case 'DATE':
return this.validateDate(condition, title, new Date(value));
case 'NUMBER':
return this.validateNumber(condition, title, +value);
case 'STRING':
return this.validateString(condition, title, value.toString());
case 'OBJECT':
const properties: string[] = Object.keys(condition.properties);
properties.forEach((property: string) =>
this.validateValue(condition.properties[property], value[property], undefined, title),
);
return;
}
}
private validateDate(condition: IDateCondition, title: string, value: Date): void {
if (condition.minDate && value.getTime() < condition.minDate.getTime())
return this.setError(Errors.minDate(title, condition.minDate));
if (condition.maxDate && value.getTime() > condition.maxDate.getTime())
return this.setError(Errors.maxDate(title, condition.maxDate));
}
private validateNumber(condition: INumberCondition, title: string, value: number): void {
if (condition.minimum && value < condition.minimum) return this.setError(Errors.minimum(title, condition.minimum));
if (condition.maximum && value > condition.maximum) return this.setError(Errors.maximum(title, condition.maximum));
if (condition.enum && !condition.enum.includes(value)) return this.setError(Errors.invalid(title));
}
private validateString(condition: IStringCondition, title: string, value: string): void {
if (condition.format && !FormatsEnum[condition.format].validate(value)) return this.setError(Errors.invalid(title));
if (condition.enum && !condition.enum.includes(value)) return this.setError(Errors.invalid(title));
if (condition.length && value.length !== condition.length)
return this.setError(Errors.eqLength(title, condition.length));
if (condition.minLength && value.length < condition.minLength)
return this.setError(Errors.minLength(title, condition.minLength));
if (condition.maxLength && value.length > condition.maxLength)
return this.setError(Errors.maxLength(title, condition.maxLength));
if (condition.pattern && !condition.pattern.test(value)) return this.setError(Errors.invalid(title));
}
}