import { ErrorObject } from 'ajv'; import get from 'lodash/get'; import isEqual from 'lodash/isEqual'; import { CustomValidator, ErrorSchema, ErrorTransformer, FormContextType, hashForSchema, ID_KEY, JUNK_OPTION_ID, RJSFSchema, StrictRJSFSchema, toErrorList, UiSchema, ValidationData, ValidatorType, retrieveSchema, } from '@rjsf/utils'; import { CompiledValidateFunction, Localizer, ValidatorFunctions } from './types'; import processRawValidationErrors, { RawValidationErrorsType } from './processRawValidationErrors'; /** `ValidatorType` implementation that uses an AJV 8 precompiled validator as created by the * `compileSchemaValidators()` function provided by the `@rjsf/validator-ajv8` library. */ export default class AJV8PrecompiledValidator< T = any, S extends StrictRJSFSchema = RJSFSchema, F extends FormContextType = any > implements ValidatorType { /** The root schema object used to construct this validator * * @private */ readonly rootSchema: S; /** The `ValidatorFunctions` map used to construct this validator * * @private */ readonly validateFns: ValidatorFunctions; /** The main validator function associated with the base schema in the `precompiledValidator` * * @private */ readonly mainValidator: CompiledValidateFunction; /** The Localizer function to use for localizing Ajv errors * * @private */ readonly localizer?: Localizer; /** Constructs an `AJV8PrecompiledValidator` instance using the `validateFns` and `rootSchema` * * @param validateFns - The map of the validation functions that are generated by the `schemaCompile()` function * @param rootSchema - The root schema that was used with the `compileSchema()` function * @param [localizer] - If provided, is used to localize a list of Ajv `ErrorObject`s * @throws - Error when the base schema of the precompiled validator does not have a matching validator function */ constructor(validateFns: ValidatorFunctions, rootSchema: S, localizer?: Localizer) { this.rootSchema = rootSchema; this.validateFns = validateFns; this.localizer = localizer; this.mainValidator = this.getValidator(rootSchema); } /** Returns the precompiled validator associated with the given `schema` from the map of precompiled validator * functions. * * @param schema - The schema for which a precompiled validator function is desired * @returns - The precompiled validator function associated with this schema */ getValidator(schema: S) { const key = get(schema, ID_KEY) || hashForSchema(schema); const validator = this.validateFns[key]; if (!validator) { throw new Error(`No precompiled validator function was found for the given schema for "${key}"`); } return validator; } /** Ensures that the validator is using the same schema as the root schema used to construct the precompiled * validator. It first compares the given `schema` against the root schema and if they aren't the same, then it * checks against the resolved root schema, on the chance that a resolved version of the root schema was passed in * instead of the raw root schema. * * @param schema - The schema against which to validate the form data * @param [formData] - The form data to validate if any */ ensureSameRootSchema(schema: S, formData?: T) { if (!isEqual(schema, this.rootSchema)) { // Resolve the root schema with the passed in form data since that may affect the resolution const resolvedRootSchema = retrieveSchema(this, this.rootSchema, this.rootSchema, formData); if (!isEqual(schema, resolvedRootSchema)) { throw new Error( 'The schema associated with the precompiled validator differs from the rootSchema provided for validation' ); } } return true; } /** Converts an `errorSchema` into a list of `RJSFValidationErrors` * * @param errorSchema - The `ErrorSchema` instance to convert * @param [fieldPath=[]] - The current field path, defaults to [] if not specified * @deprecated - Use the `toErrorList()` function provided by `@rjsf/utils` instead. This function will be removed in * the next major release. */ toErrorList(errorSchema?: ErrorSchema, fieldPath: string[] = []) { return toErrorList(errorSchema, fieldPath); } /** Runs the pure validation of the `schema` and `formData` without any of the RJSF functionality. Provided for use * by the playground. Returns the `errors` from the validation * * @param schema - The schema against which to validate the form data * @param [formData] - The form data to validate, if any * @throws - Error when the schema provided does not match the base schema of the precompiled validator */ rawValidation(schema: S, formData?: T): RawValidationErrorsType { this.ensureSameRootSchema(schema, formData); this.mainValidator(formData); if (typeof this.localizer === 'function') { this.localizer(this.mainValidator.errors); } const errors = this.mainValidator.errors || undefined; // Clear errors to prevent persistent errors, see #1104 this.mainValidator.errors = null; return { errors: errors as unknown as Result[] }; } /** This function processes the `formData` with an optional user contributed `customValidate` function, which receives * the form data and a `errorHandler` function that will be used to add custom validation errors for each field. Also * supports a `transformErrors` function that will take the raw AJV validation errors, prior to custom validation and * transform them in what ever way it chooses. * * @param formData - The form data to validate * @param schema - The schema against which to validate the form data * @param [customValidate] - An optional function that is used to perform custom validation * @param [transformErrors] - An optional function that is used to transform errors after AJV validation * @param [uiSchema] - An optional uiSchema that is passed to `transformErrors` and `customValidate` */ validateFormData( formData: T | undefined, schema: S, customValidate?: CustomValidator, transformErrors?: ErrorTransformer, uiSchema?: UiSchema ): ValidationData { const rawErrors = this.rawValidation(schema, formData); return processRawValidationErrors(this, rawErrors, formData, schema, customValidate, transformErrors, uiSchema); } /** Validates data against a schema, returning true if the data is valid, or false otherwise. If the schema is * invalid, then this function will return false. * * @param schema - The schema against which to validate the form data * @param formData - The form data to validate * @param rootSchema - The root schema used to provide $ref resolutions * @returns - true if the formData validates against the schema, false otherwise * @throws - Error when the schema provided does not match the base schema of the precompiled validator OR if there * isn't a precompiled validator function associated with the schema */ isValid(schema: S, formData: T | undefined, rootSchema: S) { this.ensureSameRootSchema(rootSchema, formData); if (get(schema, ID_KEY) === JUNK_OPTION_ID) { return false; } const validator = this.getValidator(schema); return validator(formData); } }