UNPKG

@canard/schema-form

Version:

React-based component library that renders forms based on JSON Schema with plugin system support for validators and UI components

277 lines (276 loc) 8.42 kB
import type { Fn } from '../@aileron/declare'; import type { JsonSchema } from './jsonSchema'; export declare enum ShowError { /** Always show error */ Always = 1, /** Never show error */ Never = 2, /** Show error when the input's value is updated */ Dirty = 4, /** Show error when the input is touched */ Touched = 8, /** Show error when the input's value is updated and touched */ DirtyTouched = 16 } /** * Factory function that creates validators for JSON Schema validation. * * Takes a JSON Schema and returns a validation function configured for that schema. * This abstraction allows different validation libraries (AJV, Joi, Yup, etc.) to be * integrated with schema-form by implementing this interface. * * @param schema - JSON Schema to compile into a validator * @returns A validation function for the given schema * * @example * Using AJV8 validator factory: * ```typescript * import Ajv from 'ajv'; * * const ajv = new Ajv({ allErrors: true }); * * const validatorFactory: ValidatorFactory = (jsonSchema) => { * const validate = ajv.compile(jsonSchema); * * return (value) => { * validate(value); * return validate.errors?.map(err => ({ * dataPath: err.instancePath, * keyword: err.keyword, * message: err.message, * details: err.params, * source: err, * })) || null; * }; * }; * ``` * * @example * Using async validation with AJV8 (from schema-form-ajv8-plugin): * ```typescript * import Ajv from 'ajv'; * * const ajv = new Ajv({ allErrors: true }); * * const validatorFactory: ValidatorFactory = (jsonSchema) => { * const validate = ajv.compile({ * ...jsonSchema, * $async: true, * }); * * return async (data) => { * try { * await validate(data); * return null; * } catch (thrown) { * if (Array.isArray(thrown?.errors)) { * return thrown.errors.map(err => ({ * dataPath: err.keyword === 'required' * ? err.instancePath + '/' + err.params.missingProperty * : err.instancePath, * keyword: err.keyword, * message: err.message, * details: err.params, * source: err, * })); * } * throw thrown; * } * }; * }; * ``` * * @example * Custom async validator with external API: * ```typescript * const asyncValidatorFactory: ValidatorFactory = (jsonSchema) => { * return async (value) => { * const errors: JsonSchemaError[] = []; * * // Custom validation logic * if (jsonSchema.type === 'string' && jsonSchema.format === 'email') { * const isValid = await checkEmailExists(value); * if (!isValid) { * errors.push({ * dataPath: '', * keyword: 'format', * message: 'Email does not exist', * details: { format: 'email', actual: value }, * }); * } * } * * return errors.length > 0 ? errors : null; * }; * }; * ``` */ export interface ValidatorFactory { (schema: JsonSchema): ValidateFunction<any>; } /** * Validation function that checks data against a pre-compiled JSON Schema. * * Created by a ValidatorFactory, this function performs the actual validation * of data values. It can be synchronous or asynchronous, returning either * an array of errors or null if validation passes. * * @typeParam Value - The expected type of data to validate * @param data - The data to validate * @returns Array of validation errors, or null if validation passes * * @example * Basic synchronous validation: * ```typescript * const validateString: ValidateFunction<string> = (data) => { * if (typeof data !== 'string') { * return [{ * dataPath: '', * keyword: 'type', * message: 'must be string', * details: { type: 'string', actual: typeof data }, * }]; * } * return null; * }; * * const errors = validateString(123); * // errors = [{ dataPath: '', keyword: 'type', ... }] * ``` * * @example * Async validation with external API: * ```typescript * const validateUsername: ValidateFunction<string> = async (username) => { * // Check format first * if (!/^[a-zA-Z0-9_]+$/.test(username)) { * return [{ * dataPath: '', * keyword: 'pattern', * message: 'Username can only contain letters, numbers, and underscores', * details: { pattern: '^[a-zA-Z0-9_]+$' }, * }]; * } * * // Check availability * const isAvailable = await checkUsernameAvailability(username); * if (!isAvailable) { * return [{ * dataPath: '', * keyword: 'uniqueUsername', * message: 'Username is already taken', * details: { value: username }, * }]; * } * * return null; * }; * ``` * * @example * Nested object validation: * ```typescript * const validateUser: ValidateFunction = (data) => { * const errors: JsonSchemaError[] = []; * * if (!data.email) { * errors.push({ * dataPath: '/email', * keyword: 'required', * message: 'Email is required', * }); * } * * if (data.age && data.age < 18) { * errors.push({ * dataPath: '/age', * keyword: 'minimum', * message: 'Must be at least 18 years old', * details: { minimum: 18, actual: data.age }, * }); * } * * return errors.length > 0 ? errors : null; * }; * ``` */ export type ValidateFunction<Value = unknown> = Fn<[ data: Value ], Promise<JsonSchemaError[] | null> | JsonSchemaError[] | null>; /** * Standardized JSON Schema validation error interface. * * This interface serves as a unified format for validation errors from different validators * (e.g., AJV, Joi, Yup, Zod). It normalizes error structures while preserving the original * validator-specific error data for debugging and advanced use cases. * * @template SourceError - Type of the source error from the specific validator * * @example * ```typescript * // AJV error transformation * const ajvError: JsonSchemaError<AjvErrorObject> = { * dataPath: '/user/email', * keyword: 'format', * message: 'Invalid email format', * source: originalAjvError * }; * * // Joi error transformation * const joiError: JsonSchemaError<ValidationError> = { * dataPath: '/user/age', * keyword: 'minimum', * message: 'Age must be at least 18', * source: originalJoiError * }; * ``` */ export interface PublicJsonSchemaError<SourceError = unknown> { /** * JSON Path to the data property that failed validation. * @note Use JSON Pointer notation for nested objects and arrays * @see https://datatracker.ietf.org/doc/html/rfc6901 * @example '/user/profile/email' or '/items/0/name' */ dataPath: string; /** * Validation rule/keyword that failed. * @note Common keywords: 'required', 'type', 'format', 'minimum', 'maximum', 'pattern' * @example 'required' | 'email' | 'minLength' */ keyword?: string; /** * Human-readable error message describing the validation failure. * @note Should be suitable for displaying to end users. * @example 'Email is required' | 'Must be a valid email address' */ message?: string; /** * Additional context and parameters related to the validation failure. * @note Content varies by validation keyword (e.g., limits, patterns, allowed values). * @example { minimum: 18, actual: 16 } | { pattern: '^[a-zA-Z]+$' } */ details?: Record<string, any>; /** * Source error object from the specific validator. * @note This preserves all validator-specific information, enabling: * - Advanced debugging and error analysis * - Access to validator-specific properties and methods * - Custom error handling based on the source validator * @example AjvErrorObject | ValidationError | ZodError */ source?: SourceError; } /** * JsonSchemaError extends PublicJsonSchemaError and adds a key property. * @internal */ export interface JsonSchemaError extends PublicJsonSchemaError { /** * Internal management property for array item errors. * @note This value is automatically managed and overwritten by the system. * @warning Users should not set or rely on this property directly. * @internal */ key?: number; }