UNPKG

@nori-zk/proof-conversion

Version:

Verifying zkVM proofs inside o1js circuits, to generate Mina compatible proof

224 lines 8.64 kB
// ============================================================================ // CORE TYPES & REGISTRY // ============================================================================ /** * Global registry mapping validator functions to their metadata. * Used by diagnostic and display functions to provide detailed error messages. */ const GUARD_REGISTRY = new Map(); /** * Retrieves metadata for a registered validator function. * * @template T - The validator's return type * @template O - The constraint type (defaults to never) * @param fn - The validator function to look up * @returns The validator's metadata, or undefined if not registered */ export const getMeta = (fn) => GUARD_REGISTRY.get(fn); // Implementation export function guard(fn, meta) { if (!fn.name || fn.name === 'anonymous') { throw new TypeError(`Named function required: ${String(fn).slice(0, 50)}`); } const entry = { name: (meta?.name || fn.name).replace(/^is/, ''), }; if (meta?.inner) { entry.inner = meta.inner; } if (meta?.constraint !== undefined) { entry.constraint = meta.constraint; entry.printConstraint = meta.printConstraint; entry.printDiagnosis = meta.printDiagnosis; } GUARD_REGISTRY.set(fn, entry); return fn; } // ============================================================================ // TYPE DISPLAY // ============================================================================ /** * Generates a human-readable type name for a validator function. * Includes constraints and nested types in the output. * * @template T - The validator's return type * @param fn - The validator function * @returns A formatted string representing the full type (e.g., "Array<String>", "Number(0..255)") * * @example * getFullTypeName(isString) // "String" * getFullTypeName(isUint8) // "Number(0..255)" * getFullTypeName(isStringArray) // "Array<String>" */ export const getFullTypeName = (fn) => { const meta = getMeta(fn); if (!meta) return 'unknown'; let display = meta.name; if (meta.printConstraint && meta.constraint !== undefined) { display += meta.printConstraint(meta.constraint); } if (meta.inner) { display += `<${getFullTypeName(meta.inner)}>`; } return display; }; // ============================================================================ // IDENTIFICATION - Scans registry to identify value type // ============================================================================ /** * Identifies the runtime type of a value using registered validators. * Returns a human-readable string describing the value's type structure. * * For primitives, tries to match against registered validators. * For arrays and objects, recursively identifies nested structure. * * @param val - The value to identify * @returns A string describing the value's type (e.g., "string", "[number, string]", "{x: string, y: number}") * * @example * identify("hello") // "string" * identify([1, 2, 3]) // "[number, number, number]" * identify({ x: "a", y: 5 }) // "{x: string, y: number}" */ export const identify = (val) => { // Try to identify using registered validators (primitives only, no constraints) for (const [validator, meta] of GUARD_REGISTRY) { if (!meta.inner && !meta.constraint && validator(val)) { return meta.name; } } // Recurse into arrays to show element types if (Array.isArray(val)) { const elementTypes = val.map((elem) => identify(elem)); return `[${elementTypes.join(', ')}]`; } // Recurse into objects to show property types if (val && typeof val === 'object') { const obj = val; const constructorName = obj.constructor?.name; if (constructorName && constructorName !== 'Object') { return constructorName; } const propTypes = Object.entries(obj) .map(([key, value]) => `${key}: ${identify(value)}`) .join(', '); return `{${propTypes}}`; } // Special cases last if (val === null) return 'null'; if (val === undefined) return 'undefined'; // Final fallback for primitives return typeof val; }; /** * Recursively converts a schema definition into a human-readable description. * Replaces validator functions with their type names while preserving structure. * * @param schema - The schema definition to describe * @returns A human-readable description of the schema structure * * @example * describeSchema({ x: isString, y: isNumber }) * // Returns: { x: "String", y: "Number" } * * describeSchema({ items: isStringArray }) * // Returns: { items: "Array<String>" } */ export const describeSchema = (schema) => { // Literal primitives if (typeof schema === 'string') return schema; if (typeof schema === 'number') return schema; if (typeof schema === 'boolean') return schema; if (schema === null) return null; // Validator function if (typeof schema === 'function') { return getFullTypeName(schema); } // Nested object schema if (typeof schema === 'object') { const result = {}; for (const [key, value] of Object.entries(schema)) { result[key] = describeSchema(value); } return result; } return 'unknown'; }; // ============================================================================ // DIAGNOSE - Recursive with bespoke diagnostics and preserved path // ============================================================================ /** * Recursively validates a value and collects detailed error messages. * Provides path-aware diagnostics showing exactly where and why validation failed. * * The function: * 1. Validates the value using the provided validator * 2. If validation fails, adds a detailed error message with path * 3. If validation passes but the type has inner validators (array/object elements), * recursively validates nested values * 4. Returns all collected errors with proper indentation and context * * @template T - The expected type * @param fn - The validator function to use * @param val - The value to validate * @param path - The current path for error reporting (default: "value") * @param errors - Accumulated error messages (internal use) * @returns Array of error messages (empty if validation succeeds) * * @example * const validator = isArrayOfLength(isUint8, 3); * const errors = diagnose(validator, [1, 2, 300]); * // Returns: [ * // "value should have type Array[3]<Number(0..255)>", * // " value[2]: expected Number(0..255), got 300 which exceeds maximum 255" * // ] */ export const diagnose = (fn, val, path = 'value', errors = []) => { const meta = getMeta(fn); // Check constraint (guard function checks type + bounds, not inner elements) if (!fn(val)) { // Constraint failed - add error with path information if (meta?.printDiagnosis && meta.constraint !== undefined) { errors.push(`${path}: expected ${getFullTypeName(fn)}, ${meta.printDiagnosis(meta.constraint, val)}`); } else { errors.push(`${path}: expected ${getFullTypeName(fn) || 'valid value'}, got ${identify(val)}`); } // Don't recurse if constraint failed return errors; } // Constraint passed - recurse into inner elements and collect all errors if (meta?.inner) { const elementErrorsBefore = errors.length; if (Array.isArray(val)) { // Recurse into array elements for (let i = 0; i < val.length; i++) { diagnose(meta.inner, val[i], `${path}[${i}]`, errors); } } else if (val && typeof val === 'object') { // Recurse into object properties (Object.entries works for both arrays and objects) for (const [key, value] of Object.entries(val)) { diagnose(meta.inner, value, `${path}.${key}`, errors); } } // If elements had errors, prepend parent type context and indent child errors if (errors.length > elementErrorsBefore) { // Indent all element errors for (let i = elementErrorsBefore; i < errors.length; i++) { errors[i] = ` ${errors[i]}`; } // Prepend parent type context errors.splice(elementErrorsBefore, 0, `${path} should have type ${getFullTypeName(fn)}`); } } return errors; }; //# sourceMappingURL=core.js.map