fortify-schema
Version:
A modern TypeScript validation library designed around familiar interface syntax and powerful conditional validation. Experience schema validation that feels natural to TypeScript developers while unlocking advanced runtime validation capabilities.
1,439 lines (1,227 loc) • 37.8 kB
text/typescript
/**
* Constraint Parser Module - Enhanced Robust Version
*
* Optimized parsing of constraint syntax like "string(3,20)", "number(0,100)?", etc.
* Uses caching, pre-compiled patterns, and robust regex validation for maximum reliability.
*/
import {
ParsedConstraints,
ConstraintType,
} from "../../../../types/parser.type";
import {
SUPPORTED_VALIDATOR_TYPES,
VALID_CONDITIONNAL_TYPES,
} from "../../../../types/ValidatorTypes";
/**
* Enhanced constraint parser with robust regex patterns and bulletproof validation
*/
export class ConstraintParser {
// Private static properties for better encapsulation
private static readonly _parseCache = new Map<string, ParsedConstraints>();
private static readonly _maxCacheSize = 1000;
private static _cacheHits = 0;
private static _cacheMisses = 0;
// pre-compiled regex patterns with stronger validation
private static readonly _patterns = {
// Robust constraint pattern - handles complex nested patterns
constraint: /^([a-zA-Z_][a-zA-Z0-9_]*(?:\[\])*)\((.+)\)$/,
// Enhanced regex pattern detection with proper escaping
regex: /^\/(.+?)\/([gimsuvy]*)$/,
// Precise comma splitting that handles whitespace
commaSplit: /\s*,\s*/,
// Robust numeric validation including scientific notation and edge cases
numeric: /^[+-]?(?:\d+\.?\d*|\.\d+)(?:[eE][+-]?\d+)?$/,
// Strict base type validation
baseType: /^[a-zA-Z_][a-zA-Z0-9_]*(?:\[\])*$/,
// Enhanced complex constraint detection
complexConstraint: /^[a-zA-Z_][a-zA-Z0-9_]*(?:\[\])*\([^)]*\)$/,
// Union type detection with balanced parentheses
unionType: /^\([^()]*(?:\|[^()]*)+\)$/,
// Record type detection (both cases)
recordTypeLower: /^record<[^<>]+>$/i,
recordTypeUpper: /^Record<[^<>]+>$/,
// Optional marker validation
optionalMarker: /\?$/,
// Required marker validation
requiredMarker: /!$/,
// Whitespace validation
whitespaceOnly: /^\s*$/,
// Constraint key validation
constraintKey: /^[a-zA-Z][a-zA-Z0-9_]*$/,
// Balanced parentheses validation
balancedParens: /^[^()]*(?:\([^()]*\)[^()]*)*$/,
// Array type detection
arrayType: /^[a-zA-Z_][a-zA-Z0-9_]*\[\]$/,
// Equal sign prefix for literal values
literalValue: /^=/,
// Boolean value validation
booleanValue: /^(?:true|false)$/i,
// Enhanced semicolon split for complex constraints
semicolonSplit: /\s*;\s*/,
// Colon split for key-value pairs
colonSplit: /\s*:\s*/,
} as const;
// Character codes for optimized checks
private static readonly _charCodes = {
questionMark: 63, // '?'
exclamation: 33, // '!'
openParen: 40, // '('
closeParen: 41, // ')'
comma: 44, // ','
semicolon: 59, // ';'
colon: 58, // ':'
pipe: 124, // '|'
openBracket: 91, // '['
closeBracket: 93, // ']'
slash: 47, // '/'
backslash: 92, // '\'
equal: 61, // '='
} as const;
/**
* Parse constraint syntax with aggressive caching and robust validation
*/
static parseConstraints(fieldType: string): ParsedConstraints {
if (!this._isValidInput(fieldType)) {
throw new Error("Invalid field type: must be a non-empty string");
}
// Check cache first
const cached = this._parseCache.get(fieldType);
if (cached) {
this._cacheHits++;
return this._deepClone(cached);
}
this._cacheMisses++;
const result = this._parseConstraintsInternal(fieldType);
// Cache the result with size limit
this._addToCache(fieldType, result);
return result;
}
/**
* Enhanced input validation
*/
private static _isValidInput(input: unknown): input is string {
return (
typeof input === "string" &&
input.length > 0 &&
!this._patterns.whitespaceOnly.test(input)
);
}
/**
* Deep clone utility for cache results
*/
private static _deepClone(obj: ParsedConstraints): ParsedConstraints {
const cloned: ParsedConstraints = {
type: obj.type,
constraints: {},
optional: obj.optional,
required: obj.required,
};
// Deep clone constraints object
if (obj.constraints && typeof obj.constraints === "object") {
for (const [key, value] of Object.entries(obj.constraints)) {
if (value instanceof RegExp) {
cloned.constraints[key] = new RegExp(value.source, value.flags);
} else if (typeof value === "object" && value !== null) {
cloned.constraints[key] = { ...value };
} else {
cloned.constraints[key] = value;
}
}
}
return cloned;
}
/**
* Enhanced internal parsing logic with robust error handling
*/
private static _parseConstraintsInternal(
fieldType: string
): ParsedConstraints {
let optional = false;
let required = false;
let type = this._sanitizeInput(fieldType);
let constraints: any = {};
if (!type) {
throw new Error("Field type cannot be empty after sanitization");
}
// Enhanced optional marker detection
if (this._hasOptionalMarker(type)) {
optional = true;
type = this._removeOptionalMarker(type);
if (!type) {
throw new Error('Invalid field type: cannot be only "?"');
}
}
// Enhanced required marker detection
if (this._hasRequiredMarker(type)) {
required = true;
type = this._removeRequiredMarker(type);
if (!type) {
throw new Error('Invalid field type: cannot be only "!"');
}
}
// Enhanced union type detection
if (this._isUnionTypeInParentheses(type)) {
return { type, constraints: {}, optional, required };
}
// Enhanced constraint parsing with robust validation
const constraintMatch = this._parseBalancedConstraints(type);
if (constraintMatch) {
const { baseType, constraintStr } = constraintMatch;
type = baseType;
if (constraintStr) {
try {
constraints = this._parseConstraintString(constraintStr, baseType);
} catch (error) {
throw new Error(
`Invalid constraint syntax in "${fieldType}": ${this._getErrorMessage(error)}`
);
}
}
}
// Check for conditional expressions (when ... *? ... : ...)
if (this._isConditionalExpression(type)) {
return { type, constraints: {}, optional, required };
}
// Enhanced base type validation
if (!this._isValidBaseType(type)) {
throw new Error(`Invalid base type: "${type}"`);
}
return { type, constraints, optional, required };
}
/**
* Check if type is a conditional expression using secure regex pattern
*/
private static _isConditionalExpression(type: string): boolean {
// Secure regex pattern to match: when <condition> *? <thenValue> [: <elseValue>]
const conditionalPattern = /^\s*when\s+.+?\s*\*\?\s*.+/;
return conditionalPattern.test(type);
}
/**
* Enhanced input sanitization
*/
private static _sanitizeInput(input: string): string {
if (typeof input !== "string") {
throw new Error("Input must be a string");
}
// Trim whitespace but preserve internal structure
return input.trim().replace(/\s+/g, " ");
}
/**
* Robust optional marker detection
*/
private static _hasOptionalMarker(type: string): boolean {
return (
type.length > 0 &&
type.charCodeAt(type.length - 1) === this._charCodes.questionMark &&
this._patterns.optionalMarker.test(type)
);
}
/**
* Safe optional marker removal
*/
private static _removeOptionalMarker(type: string): string {
if (!this._hasOptionalMarker(type)) {
return type;
}
return type.slice(0, -1).trim();
}
/**
* Robust required marker detection
*/
private static _hasRequiredMarker(type: string): boolean {
return (
type.length > 0 &&
type.charCodeAt(type.length - 1) === this._charCodes.exclamation &&
this._patterns.requiredMarker.test(type)
);
}
/**
* Safe required marker removal
*/
private static _removeRequiredMarker(type: string): string {
if (!this._hasRequiredMarker(type)) {
return type;
}
return type.slice(0, -1).trim();
}
/**
* Enhanced union type detection with regex validation
*/
private static _isUnionTypeInParentheses(type: string): boolean {
return (
this._patterns.unionType.test(type) &&
this._containsPipeOperator(type) &&
this._hasBalancedParentheses(type)
);
}
/**
* Check for pipe operator in union types
*/
private static _containsPipeOperator(type: string): boolean {
for (let i = 0; i < type.length; i++) {
if (type.charCodeAt(i) === this._charCodes.pipe) {
return true;
}
}
return false;
}
/**
* Validate balanced parentheses
*/
private static _hasBalancedParentheses(input: string): boolean {
let depth = 0;
for (let i = 0; i < input.length; i++) {
const charCode = input.charCodeAt(i);
if (charCode === this._charCodes.openParen) {
depth++;
} else if (charCode === this._charCodes.closeParen) {
depth--;
if (depth < 0) {
return false;
}
}
}
return depth === 0;
}
/**
* Enhanced constraint parsing with robust parentheses handling
*/
private static _parseBalancedConstraints(
type: string
): { baseType: string; constraintStr: string } | null {
const match = type.match(this._patterns.constraint);
if (!match) {
return null;
}
const [, baseType, constraintStr] = match;
// Enhanced base type validation
if (!this._patterns.baseType.test(baseType)) {
throw new Error(`Invalid base type format: "${baseType}"`);
}
// Validate constraint string is not empty
if (!constraintStr || this._patterns.whitespaceOnly.test(constraintStr)) {
throw new Error(`Empty constraint string in: "${type}"`);
}
// Validate balanced parentheses in constraint string
if (!this._validateConstraintParentheses(constraintStr)) {
throw new Error(`Unbalanced parentheses in constraint: "${type}"`);
}
return { baseType, constraintStr };
}
/**
* Validate parentheses balance in constraint strings
*/
private static _validateConstraintParentheses(
constraintStr: string
): boolean {
let depth = 0;
let inRegex = false;
let escaped = false;
for (let i = 0; i < constraintStr.length; i++) {
const charCode = constraintStr.charCodeAt(i);
const char = constraintStr[i];
// Handle regex pattern detection
if (charCode === this._charCodes.slash && !escaped) {
inRegex = !inRegex;
escaped = false;
continue;
}
// Handle escaping
if (charCode === this._charCodes.backslash && !escaped) {
escaped = true;
continue;
}
// Skip parentheses inside regex patterns
if (!inRegex) {
if (charCode === this._charCodes.openParen && !escaped) {
depth++;
} else if (charCode === this._charCodes.closeParen && !escaped) {
depth--;
if (depth < 0) {
return false;
}
}
}
escaped = false;
}
return depth === 0;
}
/**
* Enhanced constraint string parsing with robust error handling
*/
private static _parseConstraintString(
constraintStr: string,
baseType: string
): any {
const trimmed = constraintStr.trim();
if (!trimmed) {
return {};
}
const constraints: any = {};
// Enhanced regex pattern detection
const regexMatch = this._parseRegexPattern(trimmed);
if (regexMatch) {
constraints.pattern = regexMatch;
return constraints;
}
// Enhanced semicolon-separated complex constraints
if (this._containsCharCode(trimmed, this._charCodes.semicolon)) {
return this._parseComplexConstraints(trimmed);
}
// Enhanced min,max constraints
if (this._containsCharCode(trimmed, this._charCodes.comma)) {
return this._parseMinMaxConstraints(trimmed, baseType);
}
// Enhanced single value constraint
if (this._isValidNumericValue(trimmed)) {
const val = this._parseNumericValue(trimmed);
this._setConstraintValue(constraints, baseType, ConstraintType.Max, val);
return constraints;
}
throw new Error(`Unrecognized constraint format: "${trimmed}"`);
}
/**
* Enhanced regex pattern parsing
*/
private static _parseRegexPattern(input: string): RegExp | null {
const match = input.match(this._patterns.regex);
if (!match) {
return null;
}
try {
const [, pattern, flags] = match;
// Validate flags
if (flags && !this._isValidRegexFlags(flags)) {
throw new Error(`Invalid regex flags: "${flags}"`);
}
return new RegExp(pattern, flags || "");
} catch (error) {
throw new Error(`Invalid regex pattern: ${this._getErrorMessage(error)}`);
}
}
/**
* Validate regex flags
*/
private static _isValidRegexFlags(flags: string): boolean {
const validFlags = new Set(["g", "i", "m", "s", "u", "v", "y"]);
const flagSet = new Set();
for (const flag of flags) {
if (!validFlags.has(flag)) {
return false;
}
if (flagSet.has(flag)) {
return false; // Duplicate flag
}
flagSet.add(flag);
}
return true;
}
/**
* Check if string contains specific character code
*/
private static _containsCharCode(str: string, charCode: number): boolean {
for (let i = 0; i < str.length; i++) {
if (str.charCodeAt(i) === charCode) {
return true;
}
}
return false;
}
/**
* Enhanced numeric value validation
*/
private static _isValidNumericValue(value: string): boolean {
if (!value || typeof value !== "string") {
return false;
}
return this._patterns.numeric.test(value.trim());
}
/**
* Safe numeric value parsing
*/
private static _parseNumericValue(value: string): number {
const trimmed = value.trim();
if (!this._isValidNumericValue(trimmed)) {
throw new Error(`Invalid numeric value: "${value}"`);
}
const parsed = Number(trimmed);
if (!Number.isFinite(parsed)) {
throw new Error(`Non-finite numeric value: "${value}"`);
}
return parsed;
}
/**
* Enhanced min,max constraint parsing
*/
private static _parseMinMaxConstraints(
constraintStr: string,
baseType: string
): any {
const constraints: any = {};
const parts = constraintStr.split(this._patterns.commaSplit);
if (parts.length > 2) {
throw new Error(
`Too many constraint values. Expected format: "min,max" but got: "${constraintStr}"`
);
}
const [minStr, maxStr] = parts;
// Enhanced minimum value parsing
if (minStr && !this._patterns.whitespaceOnly.test(minStr)) {
const minVal = this._parseNumericValue(minStr);
this._setConstraintValue(
constraints,
baseType,
ConstraintType.Min,
minVal
);
}
// Enhanced maximum value parsing
if (maxStr && !this._patterns.whitespaceOnly.test(maxStr)) {
const maxVal = this._parseNumericValue(maxStr);
this._setConstraintValue(
constraints,
baseType,
ConstraintType.Max,
maxVal
);
}
// Enhanced min/max relationship validation
this._validateMinMaxRelationship(constraints);
return constraints;
}
/**
* Validate min/max constraint relationships
*/
private static _validateMinMaxRelationship(constraints: any): void {
const pairs = [
["min", "max"],
["minLength", "maxLength"],
["minItems", "maxItems"],
];
for (const [minKey, maxKey] of pairs) {
if (
constraints[minKey] !== undefined &&
constraints[maxKey] !== undefined
) {
if (constraints[minKey] > constraints[maxKey]) {
throw new Error(
`${minKey} value (${constraints[minKey]}) cannot be greater than ${maxKey} value (${constraints[maxKey]})`
);
}
}
}
}
/**
* Enhanced constraint value setting with robust validation
*/
private static _setConstraintValue(
constraints: any,
baseType: string,
constraintType: ConstraintType.Min | ConstraintType.Max,
value: number
): void {
// Enhanced negative value validation
if (!Number.isFinite(value)) {
throw new Error(`Constraint value must be finite: ${value}`);
}
// Type-specific negative value validation
if (this._isStringType(baseType) && value < 0) {
throw new Error("String length constraints cannot be negative");
}
if (this._isArrayType(baseType) && value < 0) {
throw new Error("Array length constraints cannot be negative");
}
// Set appropriate constraint based on type
if (this._isArrayType(baseType)) {
constraints[
constraintType === ConstraintType.Min
? ConstraintType.MinItems
: ConstraintType.MaxItems
] = value;
} else if (this._isStringType(baseType)) {
constraints[
constraintType === ConstraintType.Min
? ConstraintType.MinLength
: ConstraintType.MaxLength
] = value;
} else {
constraints[constraintType] = value;
}
}
/**
* Enhanced string type detection
*/
private static _isStringType(type: string): boolean {
return (
type === "string" ||
(this._patterns.baseType.test(type) && type.includes("string"))
);
}
/**
* Enhanced array type detection
*/
private static _isArrayType(type: string): boolean {
return this._patterns.arrayType.test(type) || type.endsWith("[]");
}
/**
* Enhanced base type validation with comprehensive checks
*/
private static _isValidBaseType(type: string): boolean {
if (!type || typeof type !== "string") {
return false;
}
// Enhanced whitespace validation
if (this._patterns.whitespaceOnly.test(type)) {
return false;
}
// Check valid conditional types
if (VALID_CONDITIONNAL_TYPES.includes(type)) {
return true;
}
// Enhanced union type validation
if (this._containsPipeOperator(type)) {
return this._validateUnionType(type);
}
// Enhanced literal value validation
if (this._patterns.literalValue.test(type)) {
return true;
}
// Enhanced Record type validation
if (this._isRecordType(type)) {
return true;
}
// Enhanced supported validator types check
if (SUPPORTED_VALIDATOR_TYPES.includes(type as any)) {
return true;
}
// Enhanced base type pattern validation
return this._patterns.baseType.test(type);
}
/**
* Validate union types
*/
private static _validateUnionType(type: string): boolean {
const parts = type.split("|");
if (parts.length < 2) {
return false;
}
return parts.every((part) => {
const trimmed = part.trim();
return trimmed.length > 0 && !this._patterns.whitespaceOnly.test(trimmed);
});
}
/**
* Enhanced Record type detection
*/
private static _isRecordType(type: string): boolean {
return (
this._patterns.recordTypeLower.test(type) ||
this._patterns.recordTypeUpper.test(type)
);
}
/**
* Enhanced cache management with deep cloning
*/
private static _addToCache(key: string, value: ParsedConstraints): void {
if (this._parseCache.size >= this._maxCacheSize) {
const firstKey = this._parseCache.keys().next().value;
if (firstKey) {
this._parseCache.delete(firstKey);
}
}
this._parseCache.set(key, this._deepClone(value));
}
/**
* Enhanced complex constraint parsing
*/
private static _parseComplexConstraints(constraintStr: string): any {
const constraints: any = {};
const parts = constraintStr
.split(this._patterns.semicolonSplit)
.map((part) => part.trim())
.filter((part) => !this._patterns.whitespaceOnly.test(part));
if (parts.length === 0) {
throw new Error("Empty complex constraint string");
}
for (const part of parts) {
try {
this._parseComplexConstraintPart(constraints, part);
} catch (error) {
throw new Error(
`Error parsing constraint "${part}": ${this._getErrorMessage(error)}`
);
}
}
return constraints;
}
/**
* Parse individual complex constraint part
*/
private static _parseComplexConstraintPart(
constraints: any,
part: string
): void {
const colonParts = part.split(this._patterns.colonSplit);
if (colonParts.length !== 2) {
throw new Error(
`Invalid constraint format. Expected "key:value" but got: "${part}"`
);
}
const [key, value] = colonParts;
if (
this._patterns.whitespaceOnly.test(key) ||
this._patterns.whitespaceOnly.test(value)
) {
throw new Error(
`Invalid constraint format. Both key and value must be non-empty: "${part}"`
);
}
if (!this._patterns.constraintKey.test(key)) {
throw new Error(`Invalid constraint key format: "${key}"`);
}
this._parseComplexConstraintPair(constraints, key, value);
}
/**
* Enhanced individual constraint key-value pair parsing
*/
private static _parseComplexConstraintPair(
constraints: any,
key: string,
value: string
): void {
switch (key) {
case ConstraintType.Min:
case ConstraintType.Max:
case ConstraintType.MinLength:
case ConstraintType.MaxLength:
case ConstraintType.MinItems:
case ConstraintType.MaxItems:
const numValue = this._parseNumericValue(value);
if (numValue < 0 && (key.includes("Length") || key.includes("Items"))) {
throw new Error(`"${key}" cannot be negative`);
}
constraints[key] = numValue;
break;
case "pattern":
const regexPattern = this._parseRegexPattern(value);
if (!regexPattern) {
throw new Error(
`Pattern must be in regex format (/pattern/flags): "${value}"`
);
}
constraints.pattern = regexPattern;
break;
case "unique":
if (!this._patterns.booleanValue.test(value)) {
throw new Error(
`"unique" must be "true" or "false", got: "${value}"`
);
}
constraints.unique = value.toLowerCase() === "true";
break;
default:
throw new Error(`Unknown constraint key: "${key}"`);
}
}
/**
* Enhanced error message extraction
*/
private static _getErrorMessage(error: unknown): string {
if (error instanceof Error) {
return error.message;
}
if (typeof error === "string") {
return error;
}
return "Unknown error";
}
// Enhanced public utility methods with stronger validation
/**
* Check if a field type has constraints - enhanced validation
*/
static hasConstraints(fieldType: string): boolean {
if (!this._isValidInput(fieldType)) {
return false;
}
const sanitized = this._sanitizeInput(fieldType);
const withoutModifiers = sanitized
.replace(this._patterns.optionalMarker, "")
.replace(this._patterns.requiredMarker, "");
return this._patterns.complexConstraint.test(withoutModifiers);
}
/**
* Extract base type without constraints - enhanced
*/
static getBaseType(fieldType: string): string {
if (!this._isValidInput(fieldType)) {
return "";
}
try {
const parsed = this.parseConstraints(fieldType);
return parsed.type;
} catch {
// Enhanced fallback with regex
const sanitized = this._sanitizeInput(fieldType);
return sanitized
.replace(/\([^)]*\)/, "")
.replace(this._patterns.optionalMarker, "")
.replace(this._patterns.requiredMarker, "")
.trim();
}
}
/**
* Check if field type is optional - enhanced
*/
static isOptional(fieldType: string): boolean {
return this._isValidInput(fieldType) && this._hasOptionalMarker(fieldType);
}
/**
* Check if field type is required - enhanced
*/
static isRequired(fieldType: string): boolean {
return this._isValidInput(fieldType) && this._hasRequiredMarker(fieldType);
}
/**
* Check if field type is an array - enhanced
*/
static isArrayType(fieldType: string): boolean {
if (!this._isValidInput(fieldType)) {
return false;
}
try {
const parsed = this.parseConstraints(fieldType);
return this._isArrayType(parsed.type);
} catch {
return false;
}
}
/**
* Get element type for array types - enhanced
*/
static getElementType(fieldType: string): string {
if (!this._isValidInput(fieldType)) {
return fieldType;
}
try {
const parsed = this.parseConstraints(fieldType);
if (this._isArrayType(parsed.type)) {
return parsed.type.replace(/\[\]$/, "");
}
return parsed.type;
} catch {
return fieldType;
}
}
/**
* Enhanced constraint validation
*/
static validateConstraints(constraints: any, type: string): string[] {
if (!constraints || typeof constraints !== "object") {
return [];
}
const errors: string[] = [];
try {
// Enhanced min/max validation
this._validateMinMaxRelationship(constraints);
// Enhanced type-specific validation
this._validateTypeSpecificConstraints(constraints, type, errors);
// Enhanced regex pattern validation
if (constraints.pattern && !(constraints.pattern instanceof RegExp)) {
errors.push("Pattern constraint must be a RegExp object");
}
// Enhanced negative value validation
this._validateNegativeConstraints(constraints, errors);
} catch (error) {
errors.push(
`Constraint validation error: ${this._getErrorMessage(error)}`
);
}
return errors;
}
/**
* Validate type-specific constraints
*/
private static _validateTypeSpecificConstraints(
constraints: any,
type: string,
errors: string[]
): void {
if (
type === "positive" &&
constraints.min !== undefined &&
constraints.min < 0
) {
errors.push("Positive type cannot have negative minimum value");
}
if (
type === "negative" &&
constraints.max !== undefined &&
constraints.max > 0
) {
errors.push("Negative type cannot have positive maximum value");
}
}
/**
* Validate negative constraints
*/
private static _validateNegativeConstraints(
constraints: any,
errors: string[]
): void {
const lengthKeys = [
ConstraintType.MinLength,
ConstraintType.MaxLength,
ConstraintType.MinItems,
ConstraintType.MaxItems,
];
lengthKeys.forEach((key) => {
if (constraints[key] !== undefined && constraints[key] < 0) {
errors.push(`${key} cannot be negative`);
}
});
}
/**
* Enhanced constraint merging
*/
static mergeConstraints(baseOptions: any, constraints: any): any {
if (!baseOptions && !constraints) {
return {};
}
if (!baseOptions) {
return this._deepCloneObject(constraints);
}
if (!constraints) {
return this._deepCloneObject(baseOptions);
}
const merged = { ...baseOptions };
// Enhanced merging with conflict resolution
for (const [key, value] of Object.entries(constraints)) {
if (value instanceof RegExp) {
merged[key] = new RegExp(value.source, value.flags);
} else if (typeof value === "object" && value !== null) {
merged[key] = { ...value };
} else {
merged[key] = value;
}
}
return merged;
}
/**
* Deep clone utility for objects
*/
private static _deepCloneObject(obj: any): any {
if (!obj || typeof obj !== "object") {
return obj;
}
const cloned: any = {};
for (const [key, value] of Object.entries(obj)) {
if (value instanceof RegExp) {
cloned[key] = new RegExp(value.source, value.flags);
} else if (typeof value === "object" && value !== null) {
cloned[key] = { ...value };
} else {
cloned[key] = value;
}
}
return cloned;
}
/**
* Enhanced cache clearing with statistics reset
*/
static clearCache(): void {
this._parseCache.clear();
this._cacheHits = 0;
this._cacheMisses = 0;
}
/**
* Enhanced cache statistics with additional metrics
*/
static getCacheStats(): {
size: number;
hitRate: number;
hits: number;
misses: number;
maxSize: number;
efficiency: number;
} {
const total = this._cacheHits + this._cacheMisses;
const hitRate = total > 0 ? this._cacheHits / total : 0;
const efficiency = this._parseCache.size > 0 ? hitRate : 0;
return {
size: this._parseCache.size,
hitRate: Number(hitRate.toFixed(4)),
hits: this._cacheHits,
misses: this._cacheMisses,
maxSize: this._maxCacheSize,
efficiency: Number(efficiency.toFixed(4)),
};
}
/**
* Enhanced constraint description with better formatting
*/
static getConstraintDescription(constraints: any, _type?: string): string {
if (!constraints || typeof constraints !== "object") {
return "";
}
const descriptions: string[] = [];
try {
const constraintKeys = [
ConstraintType.Min,
ConstraintType.Max,
ConstraintType.MinLength,
ConstraintType.MaxLength,
ConstraintType.MinItems,
ConstraintType.MaxItems,
] as const;
constraintKeys.forEach((key) => {
if (
constraints[key] !== undefined &&
Number.isFinite(constraints[key])
) {
descriptions.push(`${key}: ${constraints[key]}`);
}
});
if (constraints.pattern instanceof RegExp) {
const flags = constraints.pattern.flags
? `/${constraints.pattern.flags}`
: "";
descriptions.push(`pattern: /${constraints.pattern.source}${flags}`);
}
if (constraints.unique === true) {
descriptions.push("unique: true");
}
// Enhanced description formatting
return descriptions.length > 0 ? `(${descriptions.join(", ")})` : "";
} catch (error) {
return `(invalid constraints: ${this._getErrorMessage(error)})`;
}
}
/**
* Enhanced complex constraint parsing (public interface)
*/
static parseComplexConstraints(constraintStr: string): any {
if (!this._isValidInput(constraintStr)) {
throw new Error("Invalid constraint string: must be a non-empty string");
}
try {
return this._parseComplexConstraints(constraintStr);
} catch (error) {
throw new Error(
`Failed to parse complex constraints "${constraintStr}": ${this._getErrorMessage(error)}`
);
}
}
// Additional enhanced utility methods
/**
* Validate field type format comprehensively
*/
static isValidFieldType(fieldType: string): boolean {
if (!this._isValidInput(fieldType)) {
return false;
}
try {
this.parseConstraints(fieldType);
return true;
} catch {
return false;
}
}
/**
* Extract all modifiers from field type
*/
static getFieldTypeModifiers(fieldType: string): {
optional: boolean;
required: boolean;
hasConstraints: boolean;
} {
if (!this._isValidInput(fieldType)) {
return { optional: false, required: false, hasConstraints: false };
}
try {
const parsed = this.parseConstraints(fieldType);
return {
optional: parsed.optional,
required: parsed.required,
hasConstraints: Object.keys(parsed.constraints).length > 0,
};
} catch {
return {
optional: this.isOptional(fieldType),
required: this.isRequired(fieldType),
hasConstraints: this.hasConstraints(fieldType),
};
}
}
/**
* Normalize field type string
*/
static normalizeFieldType(fieldType: string): string {
if (!this._isValidInput(fieldType)) {
return "";
}
try {
const parsed = this.parseConstraints(fieldType);
let normalized = parsed.type;
// Add constraints back if they exist
if (Object.keys(parsed.constraints).length > 0) {
const constraintDesc = this._formatConstraintsForType(
parsed.constraints
);
if (constraintDesc) {
normalized += `(${constraintDesc})`;
}
}
// Add modifiers back
if (parsed.required) {
normalized += "!";
}
if (parsed.optional) {
normalized += "?";
}
return normalized;
} catch {
return this._sanitizeInput(fieldType);
}
}
/**
* Format constraints for type reconstruction
*/
private static _formatConstraintsForType(constraints: any): string {
if (!constraints || typeof constraints !== "object") {
return "";
}
const parts: string[] = [];
// Handle regex pattern
if (constraints.pattern instanceof RegExp) {
const flags = constraints.pattern.flags || "";
parts.push(`/${constraints.pattern.source}/${flags}`);
return parts.join(";");
}
// Handle min/max pairs
if (constraints.min !== undefined && constraints.max !== undefined) {
parts.push(`${constraints.min},${constraints.max}`);
} else if (constraints.min !== undefined) {
parts.push(`${constraints.min},`);
} else if (constraints.max !== undefined) {
parts.push(`${constraints.max}`);
}
// Handle length constraints
if (
constraints.minLength !== undefined &&
constraints.maxLength !== undefined
) {
if (parts.length === 0) {
parts.push(`${constraints.minLength},${constraints.maxLength}`);
} else {
parts.push(
`minLength:${constraints.minLength};maxLength:${constraints.maxLength}`
);
}
}
// Handle items constraints
if (
constraints.minItems !== undefined &&
constraints.maxItems !== undefined
) {
if (parts.length === 0) {
parts.push(`${constraints.minItems},${constraints.maxItems}`);
} else {
parts.push(
`minItems:${constraints.minItems};maxItems:${constraints.maxItems}`
);
}
}
// Handle unique
if (constraints.unique === true) {
parts.push("unique:true");
}
return parts.join(";");
}
/**
* Performance monitoring utilities
*/
static getParsingPerformance(): {
totalParses: number;
cacheHitRatio: number;
averageParseTime: number;
memoryUsage: number;
} {
const total = this._cacheHits + this._cacheMisses;
const hitRatio = total > 0 ? this._cacheHits / total : 0;
return {
totalParses: total,
cacheHitRatio: Number(hitRatio.toFixed(4)),
averageParseTime: 0, // Would need instrumentation
memoryUsage: this._parseCache.size * 100, // Rough estimate
};
}
/**
* Optimize cache by removing least recently used entries
*/
static optimizeCache(): void {
if (this._parseCache.size <= this._maxCacheSize * 0.8) {
return; // No optimization needed
}
const entries = Array.from(this._parseCache.entries());
const keepSize = Math.floor(this._maxCacheSize * 0.6);
// Keep most recent entries (simple LRU approximation)
this._parseCache.clear();
entries.slice(-keepSize).forEach(([key, value]) => {
this._parseCache.set(key, value);
});
}
/**
* Validate constraint compatibility with field type
*/
static validateConstraintCompatibility(
fieldType: string,
constraints: any
): { valid: boolean; errors: string[] } {
const errors: string[] = [];
if (!this._isValidInput(fieldType)) {
errors.push("Invalid field type");
return { valid: false, errors };
}
if (!constraints || typeof constraints !== "object") {
return { valid: true, errors: [] };
}
try {
const parsed = this.parseConstraints(fieldType);
const baseType = parsed.type;
// Check string-specific constraints
if (!this._isStringType(baseType)) {
if (
constraints.minLength !== undefined ||
constraints.maxLength !== undefined
) {
errors.push("Length constraints are only valid for string types");
}
if (constraints.pattern !== undefined) {
errors.push("Pattern constraints are only valid for string types");
}
}
// Check array-specific constraints
if (!this._isArrayType(baseType)) {
if (
constraints.minItems !== undefined ||
constraints.maxItems !== undefined
) {
errors.push("Item constraints are only valid for array types");
}
if (constraints.unique !== undefined) {
errors.push("Unique constraints are only valid for array types");
}
}
// Check numeric-specific constraints
const numericTypes = ["number", "integer", "positive", "negative"];
if (!numericTypes.some((t) => baseType.includes(t))) {
if (constraints.min !== undefined || constraints.max !== undefined) {
errors.push("Min/Max constraints are only valid for numeric types");
}
}
} catch (error) {
errors.push(`Type parsing error: ${this._getErrorMessage(error)}`);
}
return { valid: errors.length === 0, errors };
}
}