Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 | 14x 14x 30x 30x 21x 21x 21x 21x 51x 51x 21x 62x 62x 62x 62x 62x 83x 83x 83x 22x 62x 83x 83x 83x 271x 21x 21x 21x 21x 21x 21x 82x 61x 61x 51x 21x 30x 18x 16x 16x 30x 14x 14x 14x 30x 2x 2x 30x 16x 14x 14x 30x 6x 6x 6x 4x 4x 2x 30x 2x 2x 30x 16x 14x 14x 30x 6x 6x 6x 30x 2x 2x 2x 2x 2x 14x | import "reflect-metadata";
import type { ValidationError, ValidationResult, ValidationRule, ValidatorFunction } from "./types";
/**
* ValidationEngine - Executes validation rules stored by decorators
*
* This engine reads validation metadata stored by decorators like @Min, @Max, @Email
* and executes them against entity instances to produce validation results.
*/
export class ValidationEngine {
private validators: Map<string, ValidatorFunction> = new Map();
constructor() {
this.registerBuiltinValidators();
}
/**
* Validates an entity instance against all its validation rules
*/
async validate(entity: any): Promise<ValidationResult> {
const errors: ValidationError[] = [];
const target = entity.constructor.name;
// Get all properties that have validation rules
const properties = this.getPropertiesWithValidation(entity);
for (const propertyKey of properties) {
const propertyResult = await this.validateProperty(entity, propertyKey, target);
errors.push(...propertyResult.errors);
}
return {
isValid: errors.length === 0,
errors,
};
}
/**
* Validates a specific property of an entity
*/
async validateProperty(entity: any, propertyKey: string, target?: string): Promise<ValidationResult> {
const errors: ValidationError[] = [];
const entityTarget = target || entity.constructor.name;
const value = entity[propertyKey];
// Get validation rules for this property
const rules: ValidationRule[] = Reflect.getMetadata("validation:rules", entity, propertyKey) || [];
for (const rule of rules) {
try {
const isValid = await this.executeRule(value, rule);
if (!isValid) {
errors.push({
property: propertyKey,
value,
message: rule.message || `Validation failed for ${propertyKey}`,
rule: rule.type,
target: entityTarget,
});
}
} catch (error) {
errors.push({
property: propertyKey,
value,
message: `Validation error: ${error instanceof Error ? error.message : 'Unknown error'}`,
rule: rule.type,
target: entityTarget,
});
}
}
return {
isValid: errors.length === 0,
errors,
};
}
/**
* Executes a single validation rule
*/
private async executeRule(value: any, rule: ValidationRule): Promise<boolean> {
const validator = this.validators.get(rule.type);
Iif (!validator) {
throw new Error(`Unknown validation rule: ${rule.type}`);
}
return await validator(value, rule.options);
}
/**
* Registers a custom validator function
*/
registerValidator(type: string, validator: ValidatorFunction): void {
this.validators.set(type, validator);
}
/**
* Gets all properties that have validation metadata
*/
private getPropertiesWithValidation(entity: any): string[] {
const properties: string[] = [];
const prototype = Object.getPrototypeOf(entity);
// Get all own property names
const ownProps = Object.getOwnPropertyNames(entity);
const prototypeProps = Object.getOwnPropertyNames(prototype);
const allProps = [...new Set([...ownProps, ...prototypeProps])];
for (const prop of allProps) {
if (prop === 'constructor') continue;
const hasValidationRules = Reflect.hasMetadata("validation:rules", entity, prop) ||
Reflect.hasMetadata("validation:rules", prototype, prop);
if (hasValidationRules) {
properties.push(prop);
}
}
return properties;
}
/**
* Registers all built-in validators
*/
private registerBuiltinValidators(): void {
// Min validator
this.registerValidator("min", (value: any, options: any) => {
if (value == null) return true; // null/undefined values are handled by required validation
const num = Number(value);
return !isNaN(num) && num >= options.value;
});
// Max validator
this.registerValidator("max", (value: any, options: any) => {
Iif (value == null) return true;
const num = Number(value);
return !isNaN(num) && num <= options.value;
});
// Pattern validator
this.registerValidator("pattern", (value: any, options: any) => {
Iif (value == null) return true;
return options.pattern.test(String(value));
});
// Email validator
this.registerValidator("email", (value: any, options: any) => {
if (value == null) return true;
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
return emailRegex.test(String(value));
});
// URL validator
this.registerValidator("url", (value: any, options: any) => {
Iif (value == null) return true;
try {
const url = new URL(String(value));
const protocols = options.protocols || ['http:', 'https:'];
return protocols.includes(url.protocol);
} catch {
return false;
}
});
// Custom validator
this.registerValidator("custom", async (value: any, options: any) => {
Iif (value == null) return true;
return await options.validator(value);
});
// MinLength validator
this.registerValidator("minLength", (value: any, options: any) => {
if (value == null) return true;
const length = Array.isArray(value) ? value.length : String(value).length;
return length >= options.value;
});
// MaxLength validator
this.registerValidator("maxLength", (value: any, options: any) => {
Iif (value == null) return true;
const length = Array.isArray(value) ? value.length : String(value).length;
return length <= options.value;
});
// Length validator (range)
this.registerValidator("length", (value: any, options: any) => {
Iif (value == null) return true;
const length = Array.isArray(value) ? value.length : String(value).length;
const min = options.min || 0;
const max = options.max || Infinity;
return length >= min && length <= max;
});
}
}
// Export a default instance for convenience
export const defaultValidationEngine = new ValidationEngine(); |