@kenniy/godeye-data-contracts
Version:
Enterprise-grade base repository architecture for GOD-EYE microservices with zero overhead and maximum code reuse
384 lines (383 loc) • 14.1 kB
JavaScript
;
/**
* Unified Validation System
* Standardized validation decorators and pipes across all services
*/
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __metadata = (this && this.__metadata) || function (k, v) {
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.ValidationUtils = exports.QueryDto = exports.SearchDto = exports.PaginationDto = exports.ValidationPipe = void 0;
exports.IsValidId = IsValidId;
exports.IsRequiredEmail = IsRequiredEmail;
exports.IsOptionalEmail = IsOptionalEmail;
exports.IsPhoneNumber = IsPhoneNumber;
exports.ToLowerCase = ToLowerCase;
exports.Trim = Trim;
exports.TransformDate = TransformDate;
exports.TransformArray = TransformArray;
exports.IsValidPagination = IsValidPagination;
exports.IsValidSearch = IsValidSearch;
exports.IsValidEntity = IsValidEntity;
const common_1 = require("@nestjs/common");
const class_validator_1 = require("class-validator");
const class_transformer_1 = require("class-transformer");
const validation_constants_1 = require("../constants/validation.constants");
const pagination_constants_1 = require("../constants/pagination.constants");
/**
* Unified Validation Pipe
* Replaces separate validation pipes with single, configurable pipe
*/
let ValidationPipe = class ValidationPipe {
constructor(options = {}) {
this.options = {
whitelist: true,
forbidNonWhitelisted: true,
transform: true,
...options
};
}
async transform(value, { metatype }) {
if (!metatype || !this.toValidate(metatype)) {
return value;
}
const object = (0, class_transformer_1.plainToClass)(metatype, value);
const errors = await (0, class_validator_1.validate)(object);
if (errors.length > 0) {
const error_messages = this.buildErrorMessage(errors);
throw new common_1.BadRequestException({
message: validation_constants_1.VALIDATION_MESSAGES.VALIDATION_FAILED,
errors: error_messages,
});
}
return object;
}
toValidate(metatype) {
const types = [String, Boolean, Number, Array, Object];
return !types.includes(metatype);
}
buildErrorMessage(errors) {
const messages = [];
errors.forEach(error => {
if (error.constraints) {
messages.push(...Object.values(error.constraints));
}
if (error.children && error.children.length > 0) {
messages.push(...this.buildErrorMessage(error.children));
}
});
return messages;
}
};
exports.ValidationPipe = ValidationPipe;
exports.ValidationPipe = ValidationPipe = __decorate([
(0, common_1.Injectable)(),
__metadata("design:paramtypes", [Object])
], ValidationPipe);
/**
* ID Validation Decorator
* Replaces @IsUUID() for TypeORM and @IsValidObjectId() for Mongoose
*/
function IsValidId(options) {
return function (object, propertyName) {
(0, class_validator_1.registerDecorator)({
name: 'isValidId',
target: object.constructor,
propertyName: propertyName,
options: {
message: options?.message || validation_constants_1.VALIDATION_MESSAGES.INVALID_ID,
...options,
},
validator: {
validate(value) {
if (!value)
return false;
// Check UUID format
if (validation_constants_1.VALIDATION_PATTERNS.UUID.test(value))
return true;
// Check MongoDB ObjectId format
if (validation_constants_1.VALIDATION_PATTERNS.OBJECT_ID.test(value))
return true;
// Check numeric format
if (validation_constants_1.VALIDATION_PATTERNS.NUMERIC.test(value))
return true;
return false;
},
defaultMessage: () => validation_constants_1.VALIDATION_MESSAGES.INVALID_ID,
},
});
};
}
/**
* Required Email Validation Decorator
* Works with both simple email validation and complex business rules
*/
function IsRequiredEmail(options) {
return function (object, propertyName) {
(0, class_validator_1.registerDecorator)({
name: 'isRequiredEmail',
target: object.constructor,
propertyName: propertyName,
options: {
message: options?.message || validation_constants_1.VALIDATION_MESSAGES.INVALID_EMAIL,
...options,
},
validator: {
validate(value) {
if (!value)
return false;
return validation_constants_1.VALIDATION_PATTERNS.EMAIL.test(value);
},
defaultMessage: () => validation_constants_1.VALIDATION_MESSAGES.INVALID_EMAIL,
},
});
};
}
/**
* Optional Email Validation Decorator
*/
function IsOptionalEmail(options) {
return function (object, propertyName) {
(0, class_validator_1.registerDecorator)({
name: 'isOptionalEmail',
target: object.constructor,
propertyName: propertyName,
options: {
message: options?.message || validation_constants_1.VALIDATION_MESSAGES.INVALID_EMAIL,
...options,
},
validator: {
validate(value) {
if (!value || value === '')
return true; // Allow empty
return validation_constants_1.VALIDATION_PATTERNS.EMAIL.test(value);
},
defaultMessage: () => validation_constants_1.VALIDATION_MESSAGES.INVALID_EMAIL,
},
});
};
}
/**
* Phone Number Validation Decorator
* Supports multiple country formats
*/
function IsPhoneNumber(countries, options) {
return function (object, propertyName) {
(0, class_validator_1.registerDecorator)({
name: 'isPhoneNumber',
target: object.constructor,
propertyName: propertyName,
constraints: [countries],
options: {
message: options?.message || validation_constants_1.VALIDATION_MESSAGES.INVALID_PHONE,
...options,
},
validator: {
validate(value, args) {
if (!value)
return false;
// Basic phone validation - can be enhanced for specific countries
const phonePattern = /^\+?[\d\s\-\(\)]+$/;
if (!phonePattern.test(value))
return false;
// Remove all non-digit characters for length check
const digitsOnly = value.replace(/\D/g, '');
return digitsOnly.length >= 10 && digitsOnly.length <= 15;
},
defaultMessage: () => validation_constants_1.VALIDATION_MESSAGES.INVALID_PHONE,
},
});
};
}
/**
* Transform to Lowercase Decorator
*/
function ToLowerCase() {
return (0, class_transformer_1.Transform)(({ value }) => {
if (typeof value === 'string') {
return value.toLowerCase();
}
return value;
});
}
/**
* Trim Whitespace Decorator
*/
function Trim() {
return (0, class_transformer_1.Transform)(({ value }) => {
if (typeof value === 'string') {
return value.trim();
}
return value;
});
}
/**
* Transform Date Decorator
*/
function TransformDate() {
return (0, class_transformer_1.Transform)(({ value }) => {
if (!value)
return value;
if (value instanceof Date)
return value;
if (typeof value === 'string' || typeof value === 'number') {
const date = new Date(value);
return isNaN(date.getTime()) ? value : date;
}
return value;
});
}
/**
* Transform Array Decorator
*/
function TransformArray(options) {
const separator = options?.separator || ',';
return (0, class_transformer_1.Transform)(({ value }) => {
if (Array.isArray(value))
return value;
if (typeof value === 'string') {
return value.split(separator).map(item => item.trim()).filter(item => item);
}
return value;
});
}
/**
* Pagination Validation Decorator
*/
function IsValidPagination(options) {
return function (object, propertyName) {
(0, class_validator_1.registerDecorator)({
name: 'isValidPagination',
target: object.constructor,
propertyName: propertyName,
options: {
message: options?.message || validation_constants_1.VALIDATION_MESSAGES.INVALID_PAGINATION,
...options,
},
validator: {
validate(value) {
if (value === undefined || value === null)
return true;
const numValue = Number(value);
if (isNaN(numValue))
return false;
// Check if it's a page number
if (propertyName === 'page') {
return numValue >= pagination_constants_1.PAGINATION_DEFAULTS.MIN_PAGE;
}
// Check if it's a limit
if (propertyName === 'limit') {
return numValue >= pagination_constants_1.PAGINATION_DEFAULTS.MIN_LIMIT &&
numValue <= pagination_constants_1.PAGINATION_DEFAULTS.MAX_LIMIT;
}
return true;
},
defaultMessage: () => validation_constants_1.VALIDATION_MESSAGES.INVALID_PAGINATION,
},
});
};
}
/**
* Search Validation Decorator
*/
function IsValidSearch(options) {
return function (object, propertyName) {
const validateSearch = (value) => {
if (!value)
return true; // Allow empty values by default
if (typeof value !== 'string')
return false;
const length = value.length;
return length >= validation_constants_1.VALIDATION_DEFAULTS.MIN_SEARCH_LENGTH &&
length <= validation_constants_1.VALIDATION_DEFAULTS.MAX_SEARCH_LENGTH;
};
// Implementation would register with class-validator
};
}
/**
* Entity Validation Decorator
*/
function IsValidEntity(entity_type, options) {
return function (object, propertyName) {
const validateEntity = (value) => {
// Entity-specific validation logic based on entity_type
if (!value)
return false;
// Basic validation - can be extended per entity type
return typeof value === 'object' && value.id;
};
// Implementation would register with class-validator
};
}
/**
* Standard DTOs with validation
*/
class PaginationDto {
constructor() {
this.page = pagination_constants_1.PAGINATION_DEFAULTS.DEFAULT_PAGE;
this.limit = pagination_constants_1.PAGINATION_DEFAULTS.DEFAULT_LIMIT;
}
}
exports.PaginationDto = PaginationDto;
__decorate([
(0, class_transformer_1.Transform)(({ value }) => parseInt(value)),
IsValidPagination(),
__metadata("design:type", Number)
], PaginationDto.prototype, "page", void 0);
__decorate([
(0, class_transformer_1.Transform)(({ value }) => parseInt(value)),
IsValidPagination(),
__metadata("design:type", Number)
], PaginationDto.prototype, "limit", void 0);
class SearchDto {
}
exports.SearchDto = SearchDto;
__decorate([
IsValidSearch(),
__metadata("design:type", String)
], SearchDto.prototype, "search", void 0);
__decorate([
IsValidSearch(),
__metadata("design:type", Array)
], SearchDto.prototype, "search_fields", void 0);
class QueryDto extends PaginationDto {
}
exports.QueryDto = QueryDto;
__decorate([
IsValidSearch(),
__metadata("design:type", String)
], QueryDto.prototype, "search", void 0);
/**
* Validation utilities
*/
class ValidationUtils {
/**
* Validate ID format
*/
static isValidId(id) {
return validation_constants_1.VALIDATION_PATTERNS.UUID.test(id) ||
validation_constants_1.VALIDATION_PATTERNS.OBJECT_ID.test(id) ||
validation_constants_1.VALIDATION_PATTERNS.NUMERIC.test(id);
}
/**
* Validate email format
*/
static isValidEmail(email) {
return validation_constants_1.VALIDATION_PATTERNS.EMAIL.test(email);
}
/**
* Validate pagination parameters
*/
static validatePagination(page, limit) {
const validPage = Math.max(page || pagination_constants_1.PAGINATION_DEFAULTS.DEFAULT_PAGE, pagination_constants_1.PAGINATION_DEFAULTS.MIN_PAGE);
const validLimit = Math.min(Math.max(limit || pagination_constants_1.PAGINATION_DEFAULTS.DEFAULT_LIMIT, pagination_constants_1.PAGINATION_DEFAULTS.MIN_LIMIT), pagination_constants_1.PAGINATION_DEFAULTS.MAX_LIMIT);
return { page: validPage, limit: validLimit };
}
}
exports.ValidationUtils = ValidationUtils;