perfect-validator
Version:
A TypeScript-based validation library that supports both static and dynamic validation with serializable models.
233 lines (210 loc) • 6.99 kB
text/typescript
import { PerfectValidator } from '../types';
import { ValidationTypeParams } from '../types/typeParams';
import { validateDataModel } from '../validators';
export const deserializeFunction = (
fnStr: string | { code: string; original?: string }
): Function => {
try {
const functionString =
typeof fnStr === 'object' ? fnStr.original || fnStr.code : fnStr;
// Normalize whitespace and remove 'function' keyword if present
let normalizedStr = functionString
.trim()
.replace(/^function\s*[a-zA-Z0-9_]*\s*/, '');
normalizedStr = normalizedStr
.replace(/function anonymous/, '')
.replace(/\s+/g, ' ')
.trim();
if (normalizedStr.includes('=>')) {
// Multi-line arrow with block
const blockArrowMatch = normalizedStr.match(
/^\s*(?:\((.*?)\)|([^=>\s]+))\s*=>\s*\{([\s\S]*)\}\s*$/
);
if (blockArrowMatch) {
const [, paramsWithParens, singleParam, body] = blockArrowMatch;
const params = paramsWithParens || singleParam;
return new Function(
...params.split(',').map(p => p.trim()),
body.trim()
);
}
// Single-line arrow with expression
const simpleArrowMatch = normalizedStr.match(
/^\s*(?:\((.*?)\)|([^=>\s]+))\s*=>\s*(.+?)\s*$/
);
if (simpleArrowMatch) {
const [, paramsWithParens, singleParam, expression] = simpleArrowMatch;
const params = paramsWithParens || singleParam;
// Handle potential multi-line expressions without braces
const cleanExpression = expression.includes('\n')
? expression
.split('\n')
.map(line => line.trim())
.join(' ')
: expression.trim();
return new Function(
...params.split(',').map(p => p.trim()),
`return ${cleanExpression};`
);
}
}
// Regular function (now without 'function' keyword)
const regularFnMatch = normalizedStr.match(
/^\s*\((.*?)\)\s*\{([\s\S]*)\}\s*$/
);
if (regularFnMatch) {
const [, params, body] = regularFnMatch;
return new Function(...params.split(',').map(p => p.trim()), body.trim());
}
throw new Error('Invalid function format');
} catch (error) {
if (error instanceof Error) {
throw new Error(`Failed to deserialize function: ${error.message}`);
}
throw new Error('Failed to deserialize function: Unknown error');
}
};
// Serialize validation model to string
export const serializeValidationModel = (
model: PerfectValidator.ValidationModel
): string => {
function serializeObject(obj: any): any {
// Handle functions
if (typeof obj === 'function') {
return serializeFunction(obj);
}
// Handle arrays
if (Array.isArray(obj)) {
return obj.map(item => serializeObject(item));
}
// Handle objects
if (obj && typeof obj === 'object') {
const result: Record<string, any> = {};
for (const [key, value] of Object.entries(obj)) {
result[key] = serializeObject(value);
}
return result;
}
// Handle primitive values
return obj;
}
try {
return JSON.stringify(serializeObject(model));
} catch (error) {
if (error instanceof Error) {
throw new Error(`Failed to serialize model: ${error.message}`);
}
throw new Error('Failed to serialize model: Unknown error');
}
};
function serializeFunction(fn: Function): any {
return {
__type: 'function',
code: fn
.toString()
.replace(/\n/g, ' ')
.trim(), // Remove newlines
original: fn
.toString()
.replace(/\n/g, ' ')
.trim(), // Remove newlines
};
}
export const deserializeValidationModel = (
serializedModel: string
): PerfectValidator.ValidationModel => {
function deserializeObject(obj: any): any {
if (obj && obj.__type === 'function' && obj.code) {
try {
const fn: Function = deserializeFunction(obj.original || obj.code);
return fn;
} catch (error) {
throw error;
}
}
// Handle arrays
if (Array.isArray(obj)) {
return obj.map(item => deserializeObject(item));
}
// Handle objects with dependsOn
if (obj && typeof obj === 'object') {
const result: Record<string, any> = {};
for (const [key, value] of Object.entries(obj)) {
if (key === 'dependsOn') {
// Type guard for dependency object
const isDependencyObject = (
v: any
): v is {
field: string;
message: string;
condition?: { __type: 'function'; code: string; original?: string };
validate?: { __type: 'function'; code: string; original?: string };
} => {
return typeof v === 'object' && v !== null && 'field' in v;
};
// Handle both single dependency and array of dependencies
if (Array.isArray(value)) {
result[key] = value.map(dep => {
if (!isDependencyObject(dep)) {
throw new Error('Invalid dependency object structure');
}
return {
field: dep.field,
message: dep.message,
condition: dep.condition
? deserializeObject(dep.condition)
: undefined,
validate: dep.validate
? deserializeObject(dep.validate)
: undefined,
};
});
} else if (isDependencyObject(value)) {
result[key] = {
field: value.field,
message: value.message,
condition: value.condition
? deserializeObject(value.condition)
: undefined,
validate: value.validate
? deserializeObject(value.validate)
: undefined,
};
} else {
throw new Error('Invalid dependency structure');
}
} else {
result[key] = deserializeObject(value);
}
}
return result;
}
return obj;
}
try {
const parsed = JSON.parse(serializedModel);
const deserialized = deserializeObject(parsed);
// Validate the deserialized model
const validation: PerfectValidator.ModelValidationResponse = validateDataModel(
deserialized
);
if (!validation.isValid) {
throw new Error(
`Invalid model after deserialization: ${validation.errors?.join(', ')}`
);
}
return deserialized;
} catch (error) {
throw error;
}
};
export function getValidationTypeParams(
type: PerfectValidator.ValidationType
): PerfectValidator.IValidationTypeParams {
const params: PerfectValidator.IValidationTypeParams =
ValidationTypeParams[type];
if (!params || params === undefined || params === null) {
throw new Error(`Invalid validation type: ${type}`);
}
return params;
}