UNPKG

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
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; }