🚀 Big News: Socket Acquires Coana to Bring Reachability Analysis to Every Appsec Team.Learn more
Socket
DemoInstallSign in
Socket

nestjs-crud-microservice-validation

Package Overview
Dependencies
Maintainers
1
Versions
17
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

nestjs-crud-microservice-validation - npm Package Compare versions

Comparing version

to
1.1.0

.eslintignore

196

lib/index.d.ts

@@ -1,190 +0,8 @@

import "reflect-metadata";
import { MetadataStorage, ValidationArguments, ValidationError } from 'class-validator';
import { ConstraintMetadata } from 'class-validator/types/metadata/ConstraintMetadata';
import { ValidationMetadata } from 'class-validator/types/metadata/ValidationMetadata';
export declare class NestJSMicroserviceValidationException extends Error {
readonly messages: string[];
constructor(messages: string[]);
}
export declare const RMQ_TRANSPORT_DTO_TYPE = "RMQ_TRANSPORT_DTO_TYPE";
export declare const RmqTransportDtoType: (type: any) => ParameterDecorator;
export declare enum CrudRequestTypes {
CREATE = "create",
READ = "read",
UPDATE = "update",
DELETE = "delete"
}
/**
* Save metadata for each property of Descriptor
*
* @param descriptor - descriptor of original method
*/
export declare const savePropertyMetadata: (descriptor: any) => Map<string, any>;
/**
* Assign metadata of Original Method to new Descriptor
*
* @param descriptor - descriptor of original method
* @param metadataFieldValues - metadata fields list of original method
*/
export declare const assignOldMetadataToNewDescriptor: (descriptor: PropertyDescriptor | undefined, metadataFieldValues: Map<string, any>) => void;
/**
* Detect RequestType of Original Method
*
* @param methodName - name of method route
*/
export declare const detectMethodGroup: (methodName: string) => CrudRequestTypes | undefined;
/**
* Parse each CRUD Request Filter param
*
* @param requestFilter - CRUD Request Filter params
*/
export declare const parseCrudRequestFilter: (requestFilter: any) => any;
/**
* Parse of Search Params Operators Recursively
*
* @param property - property with search operators and conditions
* @param parsedList - previously parsed list of params
* @param propertyName - name of property
*/
export declare const recursiveSearchPropertiesParse: (property: any, parsedList: any[], propertyName: string) => void;
/**
* Parse of Search Params Recursively
*
* @param properties - object containing search param properties
* @param propertyKeys - keys of properties object
* @param parsedList - previously parsed list of params
*/
export declare const recursiveSearchParse: (properties: any, propertyKeys: any, parsedList: any) => void;
/**
* Parse CRUD Request Search param
*
* @param search - CRUD Request Search param
*/
export declare const parseCrudRequestSearch: (search: any) => any[];
/**
* Generate whiteList ValidationError
*
* @param target - DTO used for validation
* @param propertyName - name of property
* @param value - value of property
*/
export declare const generateWhitelistError: (target: any, propertyName: string, value: any) => ValidationError;
/**
*
* Get dto instance metadata
*
* @param dtoInstance - instance of dto class
* @param metadataStorage - validation metadata storage
*/
export declare const receiveTargetMetadata: (dtoInstance: any, metadataStorage: MetadataStorage) => any;
/**
* Receive properties keys of DTO
*
* @param dtoInstance - instance of DTO used to validate arguments
* @param group? - CRUD Request method type
*/
export declare const getDtoPropertiesFromInstance: <T>(dtoInstance: T, group?: null | CrudRequestTypes | undefined) => any;
/**
* Create DTO instance and patch all parameters values
*
* @param Dto - DTO used to validate arguments
* @param value - DTO object properties values without validation metadata
* @param validationErrors - list of previously detected errors
* @param isCustomDto - check if custom dto is passed
* @param group - CRUD Request method type
*/
export declare const createDtoInstance: (Dto: any, value: any, validationErrors: ValidationError[], isCustomDto?: boolean | undefined, group?: CrudRequestTypes | undefined) => any;
/**
* Validate DTO using Validation DTO or Custom DTO
*
* @param dto - DTO object properties values without validation metadata
* @param validationDto - DTO used to validate arguments
* @param validationErrors - list of previously detected errors
* @param group - CRUD Request method type
* @param customDtoClass - Custom DTO used to validate arguments
*/
export declare const validateDto: (dto: any, validationDto: any, validationErrors: ValidationError[], group?: CrudRequestTypes | undefined, customDtoClass?: any) => Promise<void>;
/**
* Receives constraint type by metadata info
*
* @param metadata - validation metadata of property
* @param customValidatorMetadata - constraint metadata
*/
export declare const getConstraintType: (metadata: ValidationMetadata, customValidatorMetadata?: ConstraintMetadata | undefined) => string;
/**
* Replaces special tokens in default error message with real values
*
* @param message - default text error message with special tokens
* @param validationArguments - arguments used for validation
*/
export declare const replaceMessageSpecialTokens: (message: string | ((args: ValidationArguments) => string), validationArguments: ValidationArguments) => string;
/**
* Manually create validation error
*
* @param object - target object
* @param value - value of property
* @param propertyName - name of property
* @param type - constraint type
* @param message - text message of error
*/
export declare const generateConstraintError: (object: any, value: any, propertyName: string, type: string, message: string) => ValidationError;
/**
* Parse string dot notation to object
*
* @param target - results object
* @param property - string dot notation of object
*/
export declare const parseDotNotationToObject: (target: any, property: string) => any;
/**
* Validate single CRUD request parameter
*
* @param Dto - DTO used to validate arguments
* @param value - object that represents current parameter with value
* @param validationErrors - list of previously detected errors
* @param isCustomDto - check if custom dto is passed
* @param group - CRUD Request method type
*/
export declare const validateRequestParam: (Dto: any, value: any, validationErrors: ValidationError[], isCustomDto?: boolean | undefined, group?: CrudRequestTypes | undefined) => Promise<void>;
/**
* Validate each CRUD Request param
*
* @param params - CRUD Request params
* @param validationDto - DTO used to validate arguments
* @param validationErrors - list of previously detected errors
* @param group - CRUD Request method type
*/
export declare const validateEachRequestParam: (params: any, validationDto: any, validationErrors: ValidationError[], group?: CrudRequestTypes | undefined) => Promise<void>;
/**
* Validate CRUD Request params
*
* @param request - CRUD Request params
* @param validationDto - DTO used to validate arguments
* @param validationErrors - list of previously detected errors
* @param group - CRUD Request method type
*/
export declare const validateCrudRequest: (request: any, validationDto: any, validationErrors: ValidationError[], group?: CrudRequestTypes | undefined) => Promise<void>;
/**
* Parse single ValidationError constraints to string
*
* @param parsedErrors - list of previously parsed errors
* @param validationError - single validation error object
* @param parent - parent error
*/
export declare const parseValidationError: (parsedErrors: string[], validationError: ValidationError, parent?: string | undefined) => void;
/**
* Validation of original method arguments
*
* @param argumentsList - list of method arguments
* @param validationDto - DTO used to validate arguments
* @param methodGroup - CRUD Request method type
* @param customDtoClass - Custom DTO used to validate arguments
*/
export declare const validateMethodArguments: (argumentsList: any[], validationDto: any, methodGroup?: CrudRequestTypes | undefined, customDtoClass?: any) => Promise<any>;
/**
* Micro-service data validation decorator
*
* @param validationDto - DTO used to validate arguments
* @constructor - constructor of micro-service validation decorator
*/
export declare const MicroserviceValidation: (validationDto: any) => (target: any) => void;
import 'reflect-metadata';
export { CrudRequestTypes } from './common/constants';
export * from './decorators/microservice-validation.decorator';
export * from './decorators/rmq-transport-dto-type.decorator';
export * from './decorators/related-dto.decorator';
export { NestJSMicroserviceValidationException } from './validation/validation-errors';
export { createDtoInstance } from './validation/validate-dto/create-dto-instance';
//# sourceMappingURL=index.d.ts.map
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __exportStar = (this && this.__exportStar) || function(m, exports) {
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.MicroserviceValidation = exports.validateMethodArguments = exports.parseValidationError = exports.validateCrudRequest = exports.validateEachRequestParam = exports.validateRequestParam = exports.parseDotNotationToObject = exports.generateConstraintError = exports.replaceMessageSpecialTokens = exports.getConstraintType = exports.validateDto = exports.createDtoInstance = exports.getDtoPropertiesFromInstance = exports.receiveTargetMetadata = exports.generateWhitelistError = exports.parseCrudRequestSearch = exports.recursiveSearchParse = exports.recursiveSearchPropertiesParse = exports.parseCrudRequestFilter = exports.detectMethodGroup = exports.assignOldMetadataToNewDescriptor = exports.savePropertyMetadata = exports.CrudRequestTypes = exports.RmqTransportDtoType = exports.RMQ_TRANSPORT_DTO_TYPE = exports.NestJSMicroserviceValidationException = void 0;
exports.createDtoInstance = exports.NestJSMicroserviceValidationException = exports.CrudRequestTypes = void 0;
require("reflect-metadata");
const class_validator_1 = require("class-validator");
class NestJSMicroserviceValidationException extends Error {
constructor(messages) {
super(messages.join(', '));
this.messages = messages;
}
}
exports.NestJSMicroserviceValidationException = NestJSMicroserviceValidationException;
exports.RMQ_TRANSPORT_DTO_TYPE = 'RMQ_TRANSPORT_DTO_TYPE';
exports.RmqTransportDtoType = (type) => (target, propertyKey) => {
Reflect.defineMetadata(exports.RMQ_TRANSPORT_DTO_TYPE, type, target, propertyKey);
};
var CrudRequestTypes;
(function (CrudRequestTypes) {
CrudRequestTypes["CREATE"] = "create";
CrudRequestTypes["READ"] = "read";
CrudRequestTypes["UPDATE"] = "update";
CrudRequestTypes["DELETE"] = "delete";
})(CrudRequestTypes = exports.CrudRequestTypes || (exports.CrudRequestTypes = {}));
/**
* Save metadata for each property of Descriptor
*
* @param descriptor - descriptor of original method
*/
exports.savePropertyMetadata = (descriptor) => {
const metadataFieldValues = new Map();
const fields = Reflect.getMetadataKeys(descriptor.value) || [];
for (let i = 0; i < fields.length; i += 1) {
metadataFieldValues.set(fields[i], Reflect.getMetadata(fields[i], descriptor.value));
}
return metadataFieldValues;
};
/**
* Assign metadata of Original Method to new Descriptor
*
* @param descriptor - descriptor of original method
* @param metadataFieldValues - metadata fields list of original method
*/
exports.assignOldMetadataToNewDescriptor = (descriptor, metadataFieldValues) => {
if (descriptor) {
for (const [key, value] of metadataFieldValues) {
Reflect.defineMetadata(key, value, descriptor.value);
}
}
};
/**
* Detect RequestType of Original Method
*
* @param methodName - name of method route
*/
exports.detectMethodGroup = (methodName) => {
switch (methodName) {
case 'getMany':
case 'getOne':
return CrudRequestTypes.READ;
case 'createOne':
case 'createMany':
return CrudRequestTypes.CREATE;
case 'updateOne':
case 'replaceOne':
return CrudRequestTypes.UPDATE;
case 'deleteOne':
return CrudRequestTypes.DELETE;
default:
return undefined;
}
};
/**
* Parse each CRUD Request Filter param
*
* @param requestFilter - CRUD Request Filter params
*/
exports.parseCrudRequestFilter = (requestFilter) => {
const result = [];
const requestFilterKeys = Object.keys(requestFilter);
for (let i = 0; i < requestFilterKeys.length; i += 1) {
const item = requestFilter[requestFilterKeys[i]];
const itemKeys = Object.keys(item);
if (Array.isArray(item.value)) {
for (let k = 0; k < itemKeys.length; k += 1) {
result.push({ [item.field]: item[itemKeys[i]] });
}
}
else {
result.push({ [item.field]: item.value });
}
}
return result;
};
/**
* Parse of Search Params Operators Recursively
*
* @param property - property with search operators and conditions
* @param parsedList - previously parsed list of params
* @param propertyName - name of property
*/
exports.recursiveSearchPropertiesParse = (property, parsedList, propertyName) => {
const propertyKeys = Object.keys(property);
for (let i = 0; i < propertyKeys.length; i += 1) {
const condition = property[propertyKeys[i]];
if (!Array.isArray(condition) && typeof condition === 'object' && condition !== null) {
exports.recursiveSearchPropertiesParse(condition, parsedList, propertyName);
}
else if (propertyKeys[i].includes('isnull') || propertyKeys[i].includes('notnull')) {
parsedList.push({ [propertyName]: null });
}
else if (Array.isArray(condition) && condition !== null) {
parsedList.push(...condition.map(item => ({ [propertyName]: item })));
}
else {
parsedList.push({ [propertyName]: condition });
}
}
};
/**
* Parse of Search Params Recursively
*
* @param properties - object containing search param properties
* @param propertyKeys - keys of properties object
* @param parsedList - previously parsed list of params
*/
exports.recursiveSearchParse = (properties, propertyKeys, parsedList) => {
for (let i = 0; i < propertyKeys.length; i += 1) {
const property = properties[propertyKeys[i]];
const objectKeys = Object.keys(property);
for (let k = 0; k < objectKeys.length; k += 1) {
if (!objectKeys[k].startsWith('$')) {
if (typeof property === 'object' && property !== null) {
exports.recursiveSearchPropertiesParse({ [objectKeys[k]]: property[objectKeys[k]] }, parsedList, objectKeys[k]);
}
}
else {
exports.recursiveSearchParse(property[objectKeys[k]], Object.keys(property[objectKeys[k]]), parsedList);
}
}
}
};
/**
* Parse CRUD Request Search param
*
* @param search - CRUD Request Search param
*/
exports.parseCrudRequestSearch = (search) => {
const parsedList = [];
if (Object.prototype.hasOwnProperty.call(search, '$and')) {
const properties = search.$and.filter((item) => item && JSON.stringify(item) !== '{}');
const propertyKeys = Object.keys(properties);
exports.recursiveSearchParse(properties, propertyKeys, parsedList);
}
return parsedList;
};
/**
* Generate whiteList ValidationError
*
* @param target - DTO used for validation
* @param propertyName - name of property
* @param value - value of property
*/
exports.generateWhitelistError = (target, propertyName, value) => {
const validationError = new class_validator_1.ValidationError();
validationError.target = target;
validationError.value = value;
validationError.property = propertyName;
validationError.constraints = {
[class_validator_1.ValidationTypes.WHITELIST]: `property ${propertyName} should not exist`,
};
validationError.children = [];
return validationError;
};
/**
*
* Get dto instance metadata
*
* @param dtoInstance - instance of dto class
* @param metadataStorage - validation metadata storage
*/
exports.receiveTargetMetadata = (dtoInstance, metadataStorage) => {
const targetMetadata = [];
let instancePrototype = Object.getPrototypeOf(dtoInstance).constructor;
while (Object.prototype.hasOwnProperty.call(instancePrototype, 'name') &&
instancePrototype.name.length) {
targetMetadata.push(...metadataStorage.getTargetValidationMetadatas(instancePrototype, ''));
if (!Object.getPrototypeOf(instancePrototype).name ||
Object.getPrototypeOf(instancePrototype).name === 'Function' ||
Object.getPrototypeOf(instancePrototype).name === instancePrototype.name) {
break;
}
instancePrototype = Object.getPrototypeOf(instancePrototype);
}
return targetMetadata;
};
/**
* Receive properties keys of DTO
*
* @param dtoInstance - instance of DTO used to validate arguments
* @param group? - CRUD Request method type
*/
exports.getDtoPropertiesFromInstance = (dtoInstance, group) => {
const metadataStorage = class_validator_1.getMetadataStorage();
const targetMetadata = exports.receiveTargetMetadata(dtoInstance, metadataStorage);
const groupedMetadata = metadataStorage.groupByPropertyName(targetMetadata);
const properties = Object.getOwnPropertyNames(groupedMetadata);
const metadataKeys = {};
if (group) {
properties.forEach(key => {
if (groupedMetadata[key].some(item => { var _a; return item.always || !((_a = item.groups) === null || _a === void 0 ? void 0 : _a.length) || item.groups.indexOf(group) !== -1; })) {
// @ts-ignore
metadataKeys[key] = groupedMetadata[key];
}
});
}
return group ? metadataKeys : groupedMetadata;
};
/**
* Create DTO instance and patch all parameters values
*
* @param Dto - DTO used to validate arguments
* @param value - DTO object properties values without validation metadata
* @param validationErrors - list of previously detected errors
* @param isCustomDto - check if custom dto is passed
* @param group - CRUD Request method type
*/
exports.createDtoInstance = (Dto, value, validationErrors, isCustomDto, group) => {
const instance = new Dto();
const metaKeys = exports.getDtoPropertiesFromInstance(instance, isCustomDto ? null : group);
const keys = Object.getOwnPropertyNames(metaKeys);
const valueKeys = Object.keys(value);
for (let i = 0; i < valueKeys.length; i += 1) {
if (Object.prototype.hasOwnProperty.call(value, valueKeys[i]) &&
keys.indexOf(valueKeys[i]) !== -1) {
const nestedValidationParam = metaKeys[valueKeys[i]].find((item) => item.type === class_validator_1.ValidationTypes.NESTED_VALIDATION);
if (nestedValidationParam && nestedValidationParam.context) {
if (nestedValidationParam.each && value[valueKeys[i]].length) {
const properties = value[valueKeys[i]];
const propertiesKeys = Object.keys(properties);
const propertyInstances = [];
for (let p = 0; p < propertiesKeys.length; p += 1) {
propertyInstances.push(exports.createDtoInstance(nestedValidationParam.context, properties[p], validationErrors, true));
}
instance[valueKeys[i]] = propertyInstances;
}
else {
instance[valueKeys[i]] = exports.createDtoInstance(nestedValidationParam.context, value[valueKeys[i]], validationErrors, true);
}
}
else {
instance[valueKeys[i]] = value[valueKeys[i]];
}
}
else {
validationErrors.push(exports.generateWhitelistError(instance, valueKeys[i], value[valueKeys[i]]));
}
}
return instance;
};
/**
* Validate DTO using Validation DTO or Custom DTO
*
* @param dto - DTO object properties values without validation metadata
* @param validationDto - DTO used to validate arguments
* @param validationErrors - list of previously detected errors
* @param group - CRUD Request method type
* @param customDtoClass - Custom DTO used to validate arguments
*/
exports.validateDto = async (dto, validationDto, validationErrors, group, customDtoClass) => {
const isCustomDto = customDtoClass && customDtoClass.name && customDtoClass.name.includes('Dto');
const dtoObject = exports.createDtoInstance(isCustomDto ? customDtoClass : validationDto, dto, validationErrors, isCustomDto, group);
validationErrors.push(...(await class_validator_1.validate(dtoObject, {
groups: group && !isCustomDto ? [group] : [],
})));
};
/**
* Receives constraint type by metadata info
*
* @param metadata - validation metadata of property
* @param customValidatorMetadata - constraint metadata
*/
exports.getConstraintType = (metadata, customValidatorMetadata) => {
return customValidatorMetadata && customValidatorMetadata.name
? customValidatorMetadata.name
: metadata.type;
};
/**
* Replaces special tokens in default error message with real values
*
* @param message - default text error message with special tokens
* @param validationArguments - arguments used for validation
*/
exports.replaceMessageSpecialTokens = (message, validationArguments) => {
let messageString = '';
if (message instanceof Function) {
messageString = message(validationArguments);
}
else if (typeof message === 'string') {
messageString = message;
}
if (messageString) {
validationArguments.constraints.forEach((constraint, index) => {
messageString = messageString.replace(new RegExp(`\\$constraint${index + 1}`, 'g'), constraint);
});
}
if (messageString &&
validationArguments.value !== undefined &&
validationArguments.value !== null &&
typeof validationArguments.value === 'string')
messageString = messageString.replace(/\$value/g, validationArguments.value);
if (messageString)
messageString = messageString.replace(/\$property/g, validationArguments.property);
if (messageString)
messageString = messageString.replace(/\$target/g, validationArguments.targetName);
return messageString;
};
/**
* Manually create validation error
*
* @param object - target object
* @param value - value of property
* @param propertyName - name of property
* @param type - constraint type
* @param message - text message of error
*/
exports.generateConstraintError = (object, value, propertyName, type, message) => {
const validationError = new class_validator_1.ValidationError();
validationError.property = propertyName;
validationError.children = [];
validationError.constraints = {};
validationError.target = object;
validationError.value = value;
validationError.constraints[type] = message;
return validationError;
};
/**
* Parse string dot notation to object
*
* @param target - results object
* @param property - string dot notation of object
*/
exports.parseDotNotationToObject = (target, property) => {
if (property.indexOf('.') !== -1) {
const hierarchy = property.split('.');
const field = hierarchy[0];
const least = hierarchy.slice(1).join('.');
const res = { [field]: least };
res[field] = exports.parseDotNotationToObject(res[field], least);
return res;
}
return property;
};
/**
* Validate single CRUD request parameter
*
* @param Dto - DTO used to validate arguments
* @param value - object that represents current parameter with value
* @param validationErrors - list of previously detected errors
* @param isCustomDto - check if custom dto is passed
* @param group - CRUD Request method type
*/
exports.validateRequestParam = async (Dto, value, validationErrors, isCustomDto, group) => {
const instance = new Dto();
const metadataStorage = class_validator_1.getMetadataStorage();
const metaKeys = exports.getDtoPropertiesFromInstance(instance, isCustomDto ? null : group);
const valueKeys = Object.keys(value);
for (let i = 0, key = valueKeys[i]; i < valueKeys.length; i += 1, key = valueKeys[i]) {
if (key !== undefined && key !== null) {
if (key.indexOf('.') !== -1) {
const hierarchy = key.split('.');
if ((hierarchy === null || hierarchy === void 0 ? void 0 : hierarchy.length) > 1) {
const resultObject = exports.parseDotNotationToObject({}, `${key}.${value[key]}`);
delete value[key];
const shiftResult = hierarchy.shift();
if (shiftResult !== undefined) {
key = shiftResult;
value[key] = resultObject[key];
}
}
}
if (Object.prototype.hasOwnProperty.call(metaKeys, key) &&
metaKeys[key] &&
metaKeys[key].length) {
const filteredMetakeys = metaKeys[key].filter((item) => {
var _a;
return (!!item.constraintCls && ((_a = item.groups) === null || _a === void 0 ? void 0 : _a.indexOf(group)) !== -1) ||
item.type === class_validator_1.ValidationTypes.NESTED_VALIDATION;
});
const metaObjectKeys = Object.keys(filteredMetakeys);
/* eslint-disable no-await-in-loop */
for (let t = 0, metakey = metaObjectKeys[t], metadata = filteredMetakeys[metakey]; t < metaObjectKeys.length; t += 1, metakey = metaObjectKeys[t], metadata = filteredMetakeys[metakey]) {
const constraint = metadataStorage.getTargetValidatorConstraints(metadata.constraintCls);
if (metadata.type === class_validator_1.ValidationTypes.NESTED_VALIDATION &&
!!metadata.context &&
typeof value[key] === 'object' &&
value[key] !== null &&
!Array.isArray(value[key])) {
await exports.validateRequestParam(metadata.context, value[key], validationErrors, true);
}
for (let i = 0; i < constraint.length; i += 1) {
const custom = constraint[i];
const validationArguments = {
targetName: instance.constructor ? instance.constructor.name : undefined,
property: metadata.propertyName,
object: instance,
value,
constraints: metadata.constraints,
};
const result = custom.instance.validate(value[key], validationArguments);
if (!result && custom && custom.instance && custom.instance.defaultMessage) {
let message = custom.instance.defaultMessage(validationArguments);
message = exports.replaceMessageSpecialTokens(message, validationArguments);
const type = exports.getConstraintType(metadata, custom);
validationErrors.push(exports.generateConstraintError(value, value[key], key, type, message));
}
}
}
/* eslint-enable no-await-in-loop */
}
else {
validationErrors.push(exports.generateWhitelistError(value, key, value[key]));
}
}
}
};
/**
* Validate each CRUD Request param
*
* @param params - CRUD Request params
* @param validationDto - DTO used to validate arguments
* @param validationErrors - list of previously detected errors
* @param group - CRUD Request method type
*/
exports.validateEachRequestParam = async (params, validationDto, validationErrors, group) => {
/* eslint-disable no-await-in-loop */
const keys = Object.keys(params);
for (let i = 0; i < keys.length; i += 1) {
await exports.validateRequestParam(validationDto, params[keys[i]], validationErrors, false, group);
}
/* eslint-enable no-await-in-loop */
};
/**
* Validate CRUD Request params
*
* @param request - CRUD Request params
* @param validationDto - DTO used to validate arguments
* @param validationErrors - list of previously detected errors
* @param group - CRUD Request method type
*/
exports.validateCrudRequest = async (request, validationDto, validationErrors, group) => {
const crudRequestFilters = exports.parseCrudRequestFilter(request.parsed.filter);
await exports.validateEachRequestParam(crudRequestFilters, validationDto, validationErrors, group);
const crudRequestParamsFilters = exports.parseCrudRequestFilter(request.parsed.paramsFilter);
await exports.validateEachRequestParam(crudRequestParamsFilters, validationDto, validationErrors, group);
const crudRequestParamsOrFilter = exports.parseCrudRequestFilter(request.parsed.or);
await exports.validateEachRequestParam(crudRequestParamsOrFilter, validationDto, validationErrors, group);
const crudRequestSearch = exports.parseCrudRequestSearch(request.parsed.search);
await exports.validateEachRequestParam(crudRequestSearch, validationDto, validationErrors, group);
};
/**
* Parse single ValidationError constraints to string
*
* @param parsedErrors - list of previously parsed errors
* @param validationError - single validation error object
* @param parent - parent error
*/
exports.parseValidationError = (parsedErrors, validationError, parent) => {
if (validationError && validationError.constraints) {
const constraintsKeys = Object.keys(validationError.constraints);
for (let k = 0; k < constraintsKeys.length; k += 1) {
if (parsedErrors.indexOf(validationError.constraints[constraintsKeys[k]]) === -1) {
parsedErrors.push((parent || '') + validationError.constraints[constraintsKeys[k]]);
}
}
}
if (validationError.children && validationError.children.length) {
const childrenKeys = Object.keys(validationError.children);
for (let k = 0; k < childrenKeys.length; k += 1) {
exports.parseValidationError(parsedErrors,
// @ts-ignore
validationError.children[childrenKeys[k]], `${parent || ''}${validationError.property} -> `);
}
}
};
/**
* Validation of original method arguments
*
* @param argumentsList - list of method arguments
* @param validationDto - DTO used to validate arguments
* @param methodGroup - CRUD Request method type
* @param customDtoClass - Custom DTO used to validate arguments
*/
exports.validateMethodArguments = async (argumentsList, validationDto, methodGroup, customDtoClass) => {
const validationErrors = [];
const keys = Object.keys(argumentsList);
/* eslint-disable no-await-in-loop */
for (let i = 0; i < keys.length; i += 1) {
if (Object.prototype.hasOwnProperty.call(argumentsList[i], 'parsed') &&
Object.prototype.hasOwnProperty.call(argumentsList[i], 'options')) {
await exports.validateCrudRequest(argumentsList[i], validationDto, validationErrors, methodGroup);
}
if (Object.prototype.hasOwnProperty.call(argumentsList[i], 'req') && argumentsList[i].req) {
await exports.validateCrudRequest(argumentsList[i].req, validationDto, validationErrors, methodGroup);
}
if (Object.prototype.hasOwnProperty.call(argumentsList[i], 'dto') && argumentsList[i].dto) {
await exports.validateDto(argumentsList[i].dto, validationDto, validationErrors, methodGroup, customDtoClass);
}
}
/* eslint-enable no-await-in-loop */
if (validationErrors && validationErrors.length) {
const parsedErrors = [];
for (let i = 0; i < validationErrors.length; i += 1) {
exports.parseValidationError(parsedErrors, validationErrors[i]);
}
throw new NestJSMicroserviceValidationException(parsedErrors);
}
};
/**
* Micro-service data validation decorator
*
* @param validationDto - DTO used to validate arguments
* @constructor - constructor of micro-service validation decorator
*/
exports.MicroserviceValidation = (validationDto) => (target) => {
// TODO: does anyone have idea what type can be used for 'target' instead of 'any'?
const propertiesKeys = Object.getOwnPropertyNames(target.prototype);
for (let i = 0; i < propertiesKeys.length; i += 1) {
const descriptor = Object.getOwnPropertyDescriptor(target.prototype, propertiesKeys[i]);
if (descriptor && descriptor.value instanceof Function && descriptor.value !== target.prototype.constructor) {
const metadataFieldValues = exports.savePropertyMetadata(descriptor);
const originalMethod = descriptor.value;
descriptor.value = async function value(...args) {
const customDtoClass = Reflect.getMetadata(exports.RMQ_TRANSPORT_DTO_TYPE, target.prototype, propertiesKeys[i]);
const methodGroup = exports.detectMethodGroup(propertiesKeys[i]);
await exports.validateMethodArguments(args, validationDto, methodGroup, customDtoClass);
return originalMethod.apply(this, args);
};
exports.assignOldMetadataToNewDescriptor(descriptor, metadataFieldValues);
Object.defineProperty(target.prototype, propertiesKeys[i], descriptor);
}
}
};
var constants_1 = require("./common/constants");
Object.defineProperty(exports, "CrudRequestTypes", { enumerable: true, get: function () { return constants_1.CrudRequestTypes; } });
__exportStar(require("./decorators/microservice-validation.decorator"), exports);
__exportStar(require("./decorators/rmq-transport-dto-type.decorator"), exports);
__exportStar(require("./decorators/related-dto.decorator"), exports);
var validation_errors_1 = require("./validation/validation-errors");
Object.defineProperty(exports, "NestJSMicroserviceValidationException", { enumerable: true, get: function () { return validation_errors_1.NestJSMicroserviceValidationException; } });
var create_dto_instance_1 = require("./validation/validate-dto/create-dto-instance");
Object.defineProperty(exports, "createDtoInstance", { enumerable: true, get: function () { return create_dto_instance_1.createDtoInstance; } });
//# sourceMappingURL=index.js.map
{
"name": "nestjs-crud-microservice-validation",
"version": "1.0.6",
"version": "1.1.0",
"description": "Decorator used to validate @NestJSX/CRUD request params, and data that is sending between micro-services using validation DTO or preset custom DTOs",

@@ -13,2 +13,13 @@ "main": "lib/index.js",

"devDependencies": {
"@types/jest": "^26.0.20",
"@typescript-eslint/eslint-plugin": "3.9.1",
"@typescript-eslint/parser": "3.9.1",
"eslint": "7.7.0",
"eslint-config-airbnb-base": "^14.2.0",
"eslint-config-prettier": "^6.10.0",
"eslint-plugin-import": "^2.20.1",
"eslint-plugin-prettier": "^3.1.4",
"jest": "^26.6.3",
"prettier": "^1.19.1",
"ts-jest": "^26.4.4",
"typescript": "^4.0.5"

@@ -19,3 +30,15 @@ },

"reflect-metadata": "0.1.13"
},
"jest": {
"moduleFileExtensions": [
"js",
"json",
"ts"
],
"rootDir": "src",
"testRegex": ".spec.ts$",
"transform": {
"^.+\\.(t|j)s$": "ts-jest"
}
}
}
## Summary
The following decorator is used to validate `@NestJSX/CRUD` request params, and data that is sending between micro-services using validation `DTO` or preset custom `DTO`s
The following library is used to validate `@NestJSX/CRUD` request params, and data that is sending between micro-services using validation `DTO` or preset custom `DTO`s
## Installation
`npm install nestjs-crud-microservice-validation`
## Usage
In order to use the decorator, you need to add the `@MicroserviceValidation(@validationDTO: Class)` class decorator to a controller.
The decorator takes as an argument validationDTO class, which determines all default validation rules for each request parameters, with list of method groups (create, read, update, delete) these rules are applied to. While describing validation rules you might want to set up nested validation. To do that you need to add a `@ValidateNested` decorator with context property of `ValidationOptions` set to a `DTO` class this property should be validated with, additionally you need to put a `@Type` decorator with `DTO` class as an argument, and `@IsDefined` decorator with collection on groups to determine method types where the property is required.
@ValidateNested({context: CompanyValidationDto})
@IsDefined({groups: [CrudRequestTypes.CREATE]})
@Type(() => CompanyValidationDto)
company: CompanyValidationDto
For each controller endpoint there is specific arguments pattern: `data: {req: CrudRequest, dto?: any}`, where `req` is Crud Request, and `dto` is custom `DTO` class which you need to use in order to perform a custom validation which is different then the `@MicroserviceValidation` decorator of controller has. But, for the custom dto validation work you also need to add a `@RmqTransportDtoType`, which takes a custom `DTO` class as an argument.
###Adding default validation to controller
In order to use the library, you need to add the `@MicroserviceValidation(@validationDTO: Class)` class decorator to a controller.
The decorator takes as an argument validationDTO class, which determines all default validation rules for each request parameters, with list of method groups (create, read, update, delete) these rules are applied to.
@MessagePattern(getPortalServiceRMQMessagePattern({ company: 'createOne' }))
createOne(@RmqTransportDtoType(CompanyValidationDto) data: {req: CrudRequest, dto: CompanyValidationDto})
####Request group methods list:
```text
- CrudRequestTypes.CREATE - createOne, createMany;
- CrudRequestTypes.UPDATE - updateOne, replaceOne;
- CrudRequestTypes.DELETE - deleteOne;
- CrudRequestTypes.READ - getOne, getMany
```
To apply validation rules to specific request type methods you should add this request type to validation decorator groups option array.
##### Important!
Avoid using circular relation (for ex. building has field company, and the company has many buildings), cause in that case nested validation will not work, as properties will lose context. If it is necessary then you should use two type of validation `DTO`: base and with relations (extending base `DTO`).
`@IsBoolean({groups: [CrudRequestTypes.UPDATE, CrudRequestTypes.READ})`
Base `DTO` example:
All other method names are considered as custom methods and require custom validation dto added.
export class BuildingValidationBaseDto {
@IsString({ groups: [CrudRequestTypes.CREATE, CrudRequestTypes.READ, CrudRequestTypes.UPDATE] })
@IsNotEmpty({ groups: [CrudRequestTypes.CREATE, CrudRequestTypes.READ, CrudRequestTypes.UPDATE] })
@IsOptional({ groups: [CrudRequestTypes.READ, CrudRequestTypes.UPDATE] })
name?: string;
@IsString({ groups: [CrudRequestTypes.CREATE, CrudRequestTypes.READ, CrudRequestTypes.UPDATE] })
@IsNotEmpty({ groups: [CrudRequestTypes.CREATE, CrudRequestTypes.READ, CrudRequestTypes.UPDATE] })
@IsOptional({ groups: [CrudRequestTypes.READ, CrudRequestTypes.UPDATE] })
address?: string;
}
####Decorator usage:
`DTO` with relations example:
```typescript
import { MicroserviceValidation, CrudRequestTypes } from 'nestjs-crud-microservice-validation';
import { MessagePattern } from '@nestjs/microservices';
import { CrudRequest } from '@nestjsx/crud';
import { IsString } from 'class-validator';
export class BuildingValidationWithRelationsDto extends BuildingValidationBaseDto {
@IsOptional({ groups: [CrudRequestTypes.READ] })
@ValidateNested({
context: CompanyValidationBaseDto,
groups: [CrudRequestTypes.CREATE, CrudRequestTypes.UPDATE, CrudRequestTypes.READ],
})
@IsDefined({ groups: [CrudRequestTypes.CREATE, CrudRequestTypes.UPDATE] })
@Type(() => CompanyValidationBaseDto)
company?: CompanyValidationBaseDto;
}
//Validation dto
class ExampleDtoClass {
//Validation is added for both create and update request types.
@IsString({groups: [CrudRequestTypes.CREATE, CrudRequestTypes.UPDATE]})
//Field is optional only for update request types.
@IsOptional({groups: [CrudRequestTypes.UPDATE]})
stringField?: string;
}
//Adding validation to controller methods using dto created above
@MicroserviceValidation(ExampleDtoClass)
class ExapmleController {
//Because method is default @nestjsx/crud method and no custom validation is added
//"req" and "dto" properties of data argument will be validated
//based on rules in DTO added to @MicroserviceValidation decorator
@MessagePattern({exampleMicroservice: {example: 'createOne'}})
createOne(data: {req: CrudRequest; dto: ExampleDtoClass}){
//Some creating logic
}
}
```
###Adding custom validation to method
In case if you want to add custom dto to specific method you have to add `@RmqTrasportDtoType` to method argument.
Decorator should get custom Dto class as an argument.
```typescript
import { MicroserviceValidation, RmqTransportDtoType } from 'nestjs-crud-microservice-validation';
@MicroserviceValidation(BaseDto)
class ExampleController {
@MessagePattern({exampleMicroservice: {example: 'createOne'}})
//Because custom validation is added
//"req" and "dto" properties of data argument will be validated
//based on rules in CustomDto. Validation rules from BaseDto are ignored.
createOne(@RmqTransportDtoType(CustomDto) data: { req: CrudRequest; dto: CustomDto }) {
//Some creating logic
}
}
```
####Important
- Custom dto class should contain `Dto` part in class name. `CustonDto`, `GetUserDto`, `CreateUserDto` ... etc.
- `@RmqTransportDtoType` decorator is required for all custom methods added to controller.
- In case of custom method validation, crud request method groups are not required.
###Nested validation
In case if you want to manage relation between your dto classes you should add `@RelatedDto` decorator to property with nested dto validation.
`@RelatedDto` decorator allows managing nested relations for property (it's added to) from current dto or parent dto in case if your dto is nested one.
To specify nested relations for property you should add nested property names as strings to `nestedRelations` options.
All nested levels have to be separated by dot notation.
```typescript
import { ValidateNested } from 'class-validator';
import { RelatedDto, CrudRequestTypes } from 'nestjs-crud-microservice-validation';
class SecondNestedDto {
@RelatedDto()
@ValidateNested({
context: ThirdNestedDto,
groups: [CrudRequestTypes.CREATE, CrudRequestTypes.UPDATE]
})
thirdNested: ThirdNestedDto;
}
class FirstNestedDto {
//Decorator should be added for each property you want to manage nested relation for
@RelatedDto()
@ValidateNested({
context: SecondNestedDto,
groups: [CrudRequestTypes.CREATE, CrudRequestTypes.UPDATE]
})
secondNested: SecondNestedDto;
}
class MainDto {
//Allows to also include "secondNested" property of FirstNestedDto
//and "thirdNested" property of SecondNestedDto to validation process
@RelatedDto({nestedRelations: ['secondNested', 'secondNested.thirdNested']})
@ValidateNested({
context: FirstNestedDto,
groups: [CrudRequestTypes.CREATE, CrudRequestTypes.UPDATE]
})
firstNested?: FirstNestedDto;
}
```
Validation process checks only `nestedRelations` option of main Dto and ignores values of `nestedRelations` option for all nested Dto.
```typescript
import { ValidateNested } from 'class-validator';
import { RelatedDto, CrudRequestTypes, MicroserviceValidation } from 'nestjs-crud-microservice-validation';
class SecondNestedDto {
@RelatedDto()
@ValidateNested({context: ThirdNestedDto})
thirdNested: ThirdNestedDto;
@RelatedDto()
@ValidateNested({context: ForthNestedDto})
forthNested: ForthNestedDto;
}
class FirstNestedDto {
@RelatedDto({nestedRelations: ['forthNested']})
@ValidateNested({context: SecondNestedDto})
secondNested: SecondNestedDto;
}
class MainDto {
@RelatedDto({nestedRelations: ['secondNested', 'secondNested.thirdNested']})
@ValidateNested({
context: FirstNestedDto,
groups: [CrudRequestTypes.CREATE, CrudRequestTypes.UPDATE]
})
firstNested?: FirstNestedDto;
}
//Validates thirdNested field of SecondDto
@MicroserviceValidation(MainDto)
class ExampleClass {
//Some methods
}
//Validates forthNested field of SecondDto
@MicroserviceValidation(FirstNestedDto)
class AnotherExampleClass {
//Some methods
}
```
In example above base validation for `ExampleClass` will ignore `{nestedRelations: ['forthNested']}` option added to
`secondNested` property of `FirstNestedDto` because in this case its applied for nested dto and main dto is `MainDo` class,
so validation will check only it's `nestedRelations` option of `@RelatedDto`.
Validation for `AnotherExampleClass` will validate `forthNested` property of `SecondNestedDto` because in this main dto is `FirstNestedDto`
and it's `nestedRelations` options of `@RelatedDto` are considered as main options for all nested relations.
### Request validation
Request validation works with all queries from `@NestJSX/CRUD` request params, but there are few cases you should know about.
####"isnull" and "notnull" operators
`isnull` and `notnull` operators might be applied only to fields that are allowed to have `null` value.
```typescript
import { IsString, IsOptional } from 'class-validator';
class RequestValidationDto {
//Field is not allowed for `isnull` and `notnull` request query operators and will throw validation error
@IsString({groups: [CrudRequestTypes.READ]})
requiredField: string;
//Field is allowed for `isnull` and `notnull` request query operators
@IsString({groups: [CrudRequestTypes.READ]})
@IsOptional({groups: [CrudRequestTypes.READ]})
optionalField?: string;
}
```
####"ValidateIf" decorator
Request validation validate each dto property separately, so it is not work correct for properties with `@ValidateIf`
decorator that refers to other dto properties in argument function.
```typescript
import { IsString, IsOptional } from 'class-validator';
class RequestValidationDto {
//This validation is not work correct for request queries validation
//because it refers to `secondField` dto property
@ValidateIf(o => o.secondField !== 'test', {groups: [CrudRequestTypes.READ]})
@IsString({groups: [CrudRequestTypes.READ]})
firstField?: string;
//This validations works correct for request queries validation
//because it doesnt reffers to other dto properties
@ValidateIf(o => o.secondField !== 'test', {groups: [CrudRequestTypes.READ]})
@IsString({groups: [CrudRequestTypes.READ]})
secondField?: string;
}
```
####Joins
Join query is not allowed for properties without`@validateNested` decorator.
####Working with nested fields
All nested levels have to be separated by dot notation. Example: `{fields: ['user', 'user.name']}`

@@ -1,699 +0,9 @@

import "reflect-metadata";
import {
getMetadataStorage,
MetadataStorage,
validate,
ValidationArguments,
ValidationError,
ValidationTypes,
ValidatorOptions,
} from 'class-validator';
import { ConstraintMetadata } from 'class-validator/types/metadata/ConstraintMetadata';
import { ValidationMetadata } from 'class-validator/types/metadata/ValidationMetadata';
import 'reflect-metadata';
export class NestJSMicroserviceValidationException extends Error {
constructor(public readonly messages: string[]) {
super(messages.join(', '));
}
}
export { CrudRequestTypes } from './common/constants';
export const RMQ_TRANSPORT_DTO_TYPE = 'RMQ_TRANSPORT_DTO_TYPE';
export const RmqTransportDtoType = (type: any): ParameterDecorator => (
target: any,
propertyKey: string | symbol,
) => {
Reflect.defineMetadata(RMQ_TRANSPORT_DTO_TYPE, type, target, propertyKey);
};
export enum CrudRequestTypes {
CREATE = 'create',
READ = 'read',
UPDATE = 'update',
DELETE = 'delete',
}
/**
* Save metadata for each property of Descriptor
*
* @param descriptor - descriptor of original method
*/
export const savePropertyMetadata = (descriptor: any): Map<string, any> => {
const metadataFieldValues = new Map();
const fields = Reflect.getMetadataKeys(descriptor.value) || [];
for (let i = 0; i < fields.length; i += 1) {
metadataFieldValues.set(fields[i], Reflect.getMetadata(fields[i], descriptor.value));
}
return metadataFieldValues;
};
/**
* Assign metadata of Original Method to new Descriptor
*
* @param descriptor - descriptor of original method
* @param metadataFieldValues - metadata fields list of original method
*/
export const assignOldMetadataToNewDescriptor = (descriptor: PropertyDescriptor | undefined, metadataFieldValues: Map<string, any>): void => {
if (descriptor) {
for (const [key, value] of metadataFieldValues) {
Reflect.defineMetadata(key, value, descriptor.value);
}
}
};
/**
* Detect RequestType of Original Method
*
* @param methodName - name of method route
*/
export const detectMethodGroup = (methodName: string): CrudRequestTypes | undefined => {
switch (methodName) {
case 'getMany':
case 'getOne':
return CrudRequestTypes.READ;
case 'createOne':
case 'createMany':
return CrudRequestTypes.CREATE;
case 'updateOne':
case 'replaceOne':
return CrudRequestTypes.UPDATE;
case 'deleteOne':
return CrudRequestTypes.DELETE;
default:
return undefined;
}
};
/**
* Parse each CRUD Request Filter param
*
* @param requestFilter - CRUD Request Filter params
*/
export const parseCrudRequestFilter = (requestFilter: any): any => {
const result = [];
const requestFilterKeys = Object.keys(requestFilter);
for (let i = 0; i < requestFilterKeys.length; i += 1) {
const item: any = requestFilter[requestFilterKeys[i]];
const itemKeys = Object.keys(item);
if (Array.isArray(item.value)) {
for (let k = 0; k < itemKeys.length; k += 1) {
result.push({ [item.field]: item[itemKeys[i]] });
}
} else {
result.push({ [item.field]: item.value });
}
}
return result;
};
/**
* Parse of Search Params Operators Recursively
*
* @param property - property with search operators and conditions
* @param parsedList - previously parsed list of params
* @param propertyName - name of property
*/
export const recursiveSearchPropertiesParse = (
property: any,
parsedList: any[],
propertyName: string,
): void => {
const propertyKeys = Object.keys(property);
for (let i = 0; i < propertyKeys.length; i += 1) {
const condition = property[propertyKeys[i]];
if (!Array.isArray(condition) && typeof condition === 'object' && condition !== null) {
recursiveSearchPropertiesParse(condition, parsedList, propertyName);
} else if (propertyKeys[i].includes('isnull') || propertyKeys[i].includes('notnull')) {
parsedList.push({ [propertyName]: null });
} else if (Array.isArray(condition) && condition !== null) {
parsedList.push(...condition.map(item => ({ [propertyName]: item })));
} else {
parsedList.push({ [propertyName]: condition });
}
}
};
/**
* Parse of Search Params Recursively
*
* @param properties - object containing search param properties
* @param propertyKeys - keys of properties object
* @param parsedList - previously parsed list of params
*/
export const recursiveSearchParse = (properties: any, propertyKeys: any, parsedList: any) => {
for (let i = 0; i < propertyKeys.length; i += 1) {
const property = properties[propertyKeys[i]];
const objectKeys = Object.keys(property);
for (let k = 0; k < objectKeys.length; k += 1) {
if (!objectKeys[k].startsWith('$')) {
if (typeof property === 'object' && property !== null) {
recursiveSearchPropertiesParse(
{ [objectKeys[k]]: property[objectKeys[k]] },
parsedList,
objectKeys[k],
);
}
} else {
recursiveSearchParse(
property[objectKeys[k]],
Object.keys(property[objectKeys[k]]),
parsedList,
);
}
}
}
};
/**
* Parse CRUD Request Search param
*
* @param search - CRUD Request Search param
*/
export const parseCrudRequestSearch = (search: any): any[] => {
const parsedList: any[] = [];
if (Object.prototype.hasOwnProperty.call(search, '$and')) {
const properties = search.$and.filter((item: any) => item && JSON.stringify(item) !== '{}');
const propertyKeys = Object.keys(properties);
recursiveSearchParse(properties, propertyKeys, parsedList);
}
return parsedList;
};
/**
* Generate whiteList ValidationError
*
* @param target - DTO used for validation
* @param propertyName - name of property
* @param value - value of property
*/
export const generateWhitelistError = (target: any, propertyName: string, value: any): ValidationError => {
const validationError: ValidationError = new ValidationError();
validationError.target = target;
validationError.value = value;
validationError.property = propertyName;
validationError.constraints = {
[ValidationTypes.WHITELIST]: `property ${propertyName} should not exist`,
};
validationError.children = [];
return validationError;
};
/**
*
* Get dto instance metadata
*
* @param dtoInstance - instance of dto class
* @param metadataStorage - validation metadata storage
*/
export const receiveTargetMetadata = (dtoInstance: any, metadataStorage: MetadataStorage): any => {
const targetMetadata = [];
let instancePrototype = Object.getPrototypeOf(dtoInstance).constructor;
while (
Object.prototype.hasOwnProperty.call(instancePrototype, 'name') &&
instancePrototype.name.length
) {
targetMetadata.push(...metadataStorage.getTargetValidationMetadatas(instancePrototype, ''));
if (
!Object.getPrototypeOf(instancePrototype).name ||
Object.getPrototypeOf(instancePrototype).name === 'Function' ||
Object.getPrototypeOf(instancePrototype).name === instancePrototype.name
) {
break;
}
instancePrototype = Object.getPrototypeOf(instancePrototype);
}
return targetMetadata;
};
/**
* Receive properties keys of DTO
*
* @param dtoInstance - instance of DTO used to validate arguments
* @param group? - CRUD Request method type
*/
export const getDtoPropertiesFromInstance = <T>(dtoInstance: T, group?: null | CrudRequestTypes | undefined): any => {
const metadataStorage = getMetadataStorage();
const targetMetadata = receiveTargetMetadata(dtoInstance, metadataStorage);
const groupedMetadata = metadataStorage.groupByPropertyName(targetMetadata);
const properties = Object.getOwnPropertyNames(groupedMetadata);
const metadataKeys = {};
if (group) {
properties.forEach(key => {
if (
groupedMetadata[key].some(
item => item.always || !item.groups?.length || item.groups.indexOf(group) !== -1,
)
) {
// @ts-ignore
metadataKeys[key] = groupedMetadata[key];
}
});
}
return group ? metadataKeys : groupedMetadata;
};
/**
* Create DTO instance and patch all parameters values
*
* @param Dto - DTO used to validate arguments
* @param value - DTO object properties values without validation metadata
* @param validationErrors - list of previously detected errors
* @param isCustomDto - check if custom dto is passed
* @param group - CRUD Request method type
*/
export const createDtoInstance = (
Dto: any,
value: any,
validationErrors: ValidationError[],
isCustomDto?: boolean,
group?: CrudRequestTypes,
): any => {
const instance = new Dto();
const metaKeys = getDtoPropertiesFromInstance(instance, isCustomDto ? null : group);
const keys = Object.getOwnPropertyNames(metaKeys);
const valueKeys = Object.keys(value);
for (let i = 0; i < valueKeys.length; i += 1) {
if (
Object.prototype.hasOwnProperty.call(value, valueKeys[i]) &&
keys.indexOf(valueKeys[i]) !== -1
) {
const nestedValidationParam = metaKeys[valueKeys[i]].find(
(item: { type: string; }) => item.type === ValidationTypes.NESTED_VALIDATION,
);
if (nestedValidationParam && nestedValidationParam.context) {
if (nestedValidationParam.each && value[valueKeys[i]].length) {
const properties = value[valueKeys[i]];
const propertiesKeys = Object.keys(properties);
const propertyInstances = [];
for (let p = 0; p < propertiesKeys.length; p += 1) {
propertyInstances.push(
createDtoInstance(
nestedValidationParam.context,
properties[p],
validationErrors,
true,
),
);
}
instance[valueKeys[i]] = propertyInstances;
} else {
instance[valueKeys[i]] = createDtoInstance(
nestedValidationParam.context,
value[valueKeys[i]],
validationErrors,
true,
);
}
} else {
instance[valueKeys[i]] = value[valueKeys[i]];
}
} else {
validationErrors.push(generateWhitelistError(instance, valueKeys[i], value[valueKeys[i]]));
}
}
return instance;
};
/**
* Validate DTO using Validation DTO or Custom DTO
*
* @param dto - DTO object properties values without validation metadata
* @param validationDto - DTO used to validate arguments
* @param validationErrors - list of previously detected errors
* @param group - CRUD Request method type
* @param customDtoClass - Custom DTO used to validate arguments
*/
export const validateDto = async (
dto: any,
validationDto: any,
validationErrors: ValidationError[],
group?: CrudRequestTypes,
customDtoClass?: any,
): Promise<void> => {
const isCustomDto: boolean =
customDtoClass && customDtoClass.name && customDtoClass.name.includes('Dto');
const dtoObject = createDtoInstance(
isCustomDto ? customDtoClass : validationDto,
dto,
validationErrors,
isCustomDto,
group,
);
validationErrors.push(
...(await validate(dtoObject, {
groups: group && !isCustomDto ? [group] : [],
} as ValidatorOptions)),
);
};
/**
* Receives constraint type by metadata info
*
* @param metadata - validation metadata of property
* @param customValidatorMetadata - constraint metadata
*/
export const getConstraintType = (
metadata: ValidationMetadata,
customValidatorMetadata?: ConstraintMetadata,
): string => {
return customValidatorMetadata && customValidatorMetadata.name
? customValidatorMetadata.name
: metadata.type;
};
/**
* Replaces special tokens in default error message with real values
*
* @param message - default text error message with special tokens
* @param validationArguments - arguments used for validation
*/
export const replaceMessageSpecialTokens = (
message: string | ((args: ValidationArguments) => string),
validationArguments: ValidationArguments,
): string => {
let messageString: string = '';
if (message instanceof Function) {
messageString = (message as (args: ValidationArguments) => string)(validationArguments);
} else if (typeof message === 'string') {
messageString = message;
}
if (messageString) {
validationArguments.constraints.forEach((constraint, index) => {
messageString = messageString.replace(
new RegExp(`\\$constraint${index + 1}`, 'g'),
constraint,
);
});
}
if (
messageString &&
validationArguments.value !== undefined &&
validationArguments.value !== null &&
typeof validationArguments.value === 'string'
)
messageString = messageString.replace(/\$value/g, validationArguments.value);
if (messageString)
messageString = messageString.replace(/\$property/g, validationArguments.property);
if (messageString)
messageString = messageString.replace(/\$target/g, validationArguments.targetName);
return messageString;
};
/**
* Manually create validation error
*
* @param object - target object
* @param value - value of property
* @param propertyName - name of property
* @param type - constraint type
* @param message - text message of error
*/
export const generateConstraintError = (
object: any,
value: any,
propertyName: string,
type: string,
message: string,
): ValidationError => {
const validationError = new ValidationError();
validationError.property = propertyName;
validationError.children = [];
validationError.constraints = {};
validationError.target = object;
validationError.value = value;
validationError.constraints[type] = message;
return validationError;
};
/**
* Parse string dot notation to object
*
* @param target - results object
* @param property - string dot notation of object
*/
export const parseDotNotationToObject = (target: any, property: string) : any => {
if (property.indexOf('.') !== -1) {
const hierarchy: string[] = property.split('.');
const field: string = hierarchy[0];
const least: string = hierarchy.slice(1).join('.');
const res = { [field]: least };
res[field] = parseDotNotationToObject(res[field], least);
return res;
}
return property;
};
/**
* Validate single CRUD request parameter
*
* @param Dto - DTO used to validate arguments
* @param value - object that represents current parameter with value
* @param validationErrors - list of previously detected errors
* @param isCustomDto - check if custom dto is passed
* @param group - CRUD Request method type
*/
export const validateRequestParam = async (
Dto: any,
value: any,
validationErrors: ValidationError[],
isCustomDto?: boolean,
group?: CrudRequestTypes,
) => {
const instance = new Dto();
const metadataStorage = getMetadataStorage();
const metaKeys = getDtoPropertiesFromInstance(instance, isCustomDto ? null : group);
const valueKeys = Object.keys(value);
for (let i = 0, key: string | undefined = valueKeys[i]; i < valueKeys.length; i += 1, key = valueKeys[i]) {
if (key !== undefined && key !== null) {
if (key.indexOf('.') !== -1) {
const hierarchy = key.split('.');
if (hierarchy?.length > 1) {
const resultObject = parseDotNotationToObject({}, `${key}.${value[key]}`);
delete value[key];
const shiftResult = hierarchy.shift();
if (shiftResult !== undefined) {
key = shiftResult;
value[key] = resultObject[key];
}
}
}
if (
Object.prototype.hasOwnProperty.call(metaKeys, key) &&
metaKeys[key] &&
metaKeys[key].length
) {
const filteredMetakeys = metaKeys[key].filter(
(item: { constraintCls: any; groups: (CrudRequestTypes | undefined)[]; type: string; }) =>
(!!item.constraintCls && item.groups?.indexOf(group) !== -1) ||
item.type === ValidationTypes.NESTED_VALIDATION,
);
const metaObjectKeys = Object.keys(filteredMetakeys);
/* eslint-disable no-await-in-loop */
for (
let t = 0, metakey = metaObjectKeys[t], metadata = filteredMetakeys[metakey];
t < metaObjectKeys.length;
t += 1, metakey = metaObjectKeys[t], metadata = filteredMetakeys[metakey]
) {
const constraint = metadataStorage.getTargetValidatorConstraints(metadata.constraintCls);
if (
metadata.type === ValidationTypes.NESTED_VALIDATION &&
!!metadata.context &&
typeof value[key] === 'object' &&
value[key] !== null &&
!Array.isArray(value[key])
) {
await validateRequestParam(metadata.context, value[key], validationErrors, true);
}
for (let i = 0; i < constraint.length; i+=1) {
const custom: ConstraintMetadata = constraint[i];
const validationArguments: ValidationArguments = {
targetName: instance.constructor ? (instance.constructor as any).name : undefined,
property: metadata.propertyName,
object: instance,
value,
constraints: metadata.constraints,
};
const result = custom.instance.validate(value[key], validationArguments);
if (!result && custom && custom.instance && custom.instance.defaultMessage) {
let message: string | undefined = custom.instance.defaultMessage(validationArguments);
message = replaceMessageSpecialTokens(message, validationArguments);
const type = getConstraintType(metadata, custom);
validationErrors.push(generateConstraintError(value, value[key], key, type, message));
}
}
}
/* eslint-enable no-await-in-loop */
} else {
validationErrors.push(generateWhitelistError(value, key, value[key]));
}
}
}
};
/**
* Validate each CRUD Request param
*
* @param params - CRUD Request params
* @param validationDto - DTO used to validate arguments
* @param validationErrors - list of previously detected errors
* @param group - CRUD Request method type
*/
export const validateEachRequestParam = async (
params: any,
validationDto: any,
validationErrors: ValidationError[],
group?: CrudRequestTypes,
) => {
/* eslint-disable no-await-in-loop */
const keys = Object.keys(params);
for (let i = 0; i < keys.length; i += 1) {
await validateRequestParam(validationDto, params[keys[i]], validationErrors, false, group);
}
/* eslint-enable no-await-in-loop */
};
/**
* Validate CRUD Request params
*
* @param request - CRUD Request params
* @param validationDto - DTO used to validate arguments
* @param validationErrors - list of previously detected errors
* @param group - CRUD Request method type
*/
export const validateCrudRequest = async (
request: any,
validationDto: any,
validationErrors: ValidationError[],
group?: CrudRequestTypes,
): Promise<void> => {
const crudRequestFilters = parseCrudRequestFilter(request.parsed.filter);
await validateEachRequestParam(crudRequestFilters, validationDto, validationErrors, group);
const crudRequestParamsFilters = parseCrudRequestFilter(request.parsed.paramsFilter);
await validateEachRequestParam(crudRequestParamsFilters, validationDto, validationErrors, group);
const crudRequestParamsOrFilter = parseCrudRequestFilter(request.parsed.or);
await validateEachRequestParam(crudRequestParamsOrFilter, validationDto, validationErrors, group);
const crudRequestSearch = parseCrudRequestSearch(request.parsed.search);
await validateEachRequestParam(crudRequestSearch, validationDto, validationErrors, group);
};
/**
* Parse single ValidationError constraints to string
*
* @param parsedErrors - list of previously parsed errors
* @param validationError - single validation error object
* @param parent - parent error
*/
export const parseValidationError = (
parsedErrors: string[],
validationError: ValidationError,
parent?: string,
): void => {
if (validationError && validationError.constraints) {
const constraintsKeys = Object.keys(validationError.constraints);
for (let k = 0; k < constraintsKeys.length; k += 1) {
if (parsedErrors.indexOf(validationError.constraints[constraintsKeys[k]]) === -1) {
parsedErrors.push((parent || '') + validationError.constraints[constraintsKeys[k]]);
}
}
}
if (validationError.children && validationError.children.length) {
const childrenKeys: string[] = Object.keys(validationError.children);
for (let k = 0; k < childrenKeys.length; k += 1) {
parseValidationError(
parsedErrors,
// @ts-ignore
validationError.children[childrenKeys[k]],
`${parent || ''}${validationError.property} -> `,
);
}
}
};
/**
* Validation of original method arguments
*
* @param argumentsList - list of method arguments
* @param validationDto - DTO used to validate arguments
* @param methodGroup - CRUD Request method type
* @param customDtoClass - Custom DTO used to validate arguments
*/
export const validateMethodArguments = async (
argumentsList: any[],
validationDto: any,
methodGroup?: CrudRequestTypes,
customDtoClass?: any,
): Promise<any> => {
const validationErrors: ValidationError[] = [];
const keys = Object.keys(argumentsList);
/* eslint-disable no-await-in-loop */
for (let i = 0; i < keys.length; i += 1) {
if (
Object.prototype.hasOwnProperty.call(argumentsList[i], 'parsed') &&
Object.prototype.hasOwnProperty.call(argumentsList[i], 'options')
) {
await validateCrudRequest(argumentsList[i], validationDto, validationErrors, methodGroup);
}
if (Object.prototype.hasOwnProperty.call(argumentsList[i], 'req') && argumentsList[i].req) {
await validateCrudRequest(argumentsList[i].req, validationDto, validationErrors, methodGroup);
}
if (Object.prototype.hasOwnProperty.call(argumentsList[i], 'dto') && argumentsList[i].dto) {
await validateDto(
argumentsList[i].dto,
validationDto,
validationErrors,
methodGroup,
customDtoClass,
);
}
}
/* eslint-enable no-await-in-loop */
if (validationErrors && validationErrors.length) {
const parsedErrors: string[] = [];
for (let i = 0; i < validationErrors.length; i += 1) {
parseValidationError(parsedErrors, validationErrors[i]);
}
throw new NestJSMicroserviceValidationException(parsedErrors);
}
};
/**
* Micro-service data validation decorator
*
* @param validationDto - DTO used to validate arguments
* @constructor - constructor of micro-service validation decorator
*/
export const MicroserviceValidation = (validationDto: any) => (target: any) => {
// TODO: does anyone have idea what type can be used for 'target' instead of 'any'?
const propertiesKeys = Object.getOwnPropertyNames(target.prototype);
for (let i = 0; i < propertiesKeys.length; i += 1) {
const descriptor: PropertyDescriptor | undefined = Object.getOwnPropertyDescriptor(target.prototype, propertiesKeys[i]);
if (descriptor && descriptor.value instanceof Function && descriptor.value !== target.prototype.constructor) {
const metadataFieldValues = savePropertyMetadata(descriptor);
const originalMethod = descriptor.value;
descriptor.value = async function value(...args: any[]) {
const customDtoClass = Reflect.getMetadata(
RMQ_TRANSPORT_DTO_TYPE,
target.prototype,
propertiesKeys[i],
);
const methodGroup: CrudRequestTypes | undefined = detectMethodGroup(propertiesKeys[i]);
await validateMethodArguments(args, validationDto, methodGroup, customDtoClass);
return originalMethod.apply(this, args);
};
assignOldMetadataToNewDescriptor(descriptor, metadataFieldValues);
Object.defineProperty(target.prototype, propertiesKeys[i], descriptor);
}
}
};
export * from './decorators/microservice-validation.decorator';
export * from './decorators/rmq-transport-dto-type.decorator';
export * from './decorators/related-dto.decorator';
export { NestJSMicroserviceValidationException } from './validation/validation-errors';
export { createDtoInstance } from './validation/validate-dto/create-dto-instance';

@@ -62,4 +62,4 @@ {

/* Experimental Options */
// "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */
// "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */
"experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */
"emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */

@@ -66,0 +66,0 @@ /* Advanced Options */

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet