UNPKG

@snups/rjsf-validator-ajv8

Version:
413 lines (403 loc) 19 kB
"use strict"; var __create = Object.create; var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __getProtoOf = Object.getPrototypeOf; var __hasOwnProp = Object.prototype.hasOwnProperty; var __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: true }); }; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps( // If the importer is in node compatibility mode or this is not an ESM // file that has been converted to a CommonJS file using a Babel- // compatible transform (i.e. "__esModule" has not been set), then set // "default" to the CommonJS "module.exports" for node compatibility. isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target, mod )); var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); // src/index.ts var index_exports = {}; __export(index_exports, { createPrecompiledValidator: () => createPrecompiledValidator, customizeValidator: () => customizeValidator, default: () => index_default }); module.exports = __toCommonJS(index_exports); // src/validator.ts var import_rjsf_utils3 = require("@snups/rjsf-utils"); // src/createAjvInstance.ts var import_ajv = __toESM(require("ajv")); var import_ajv_errors = __toESM(require("ajv-errors")); var import_ajv_formats = __toESM(require("ajv-formats")); var import_isObject = __toESM(require("lodash/isObject")); var import_rjsf_utils = require("@snups/rjsf-utils"); var AJV_CONFIG = { allErrors: true, multipleOfPrecision: 8, strict: false, verbose: true, discriminator: false // TODO enable this in V6 }; var COLOR_FORMAT_REGEX = /^(#?([0-9A-Fa-f]{3}){1,2}\b|aqua|black|blue|fuchsia|gray|green|lime|maroon|navy|olive|orange|purple|red|silver|teal|white|yellow|(rgb\(\s*\b([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])\b\s*,\s*\b([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])\b\s*,\s*\b([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])\b\s*\))|(rgb\(\s*(\d?\d%|100%)+\s*,\s*(\d?\d%|100%)+\s*,\s*(\d?\d%|100%)+\s*\)))$/; var DATA_URL_FORMAT_REGEX = /^data:([a-z]+\/[a-z0-9-+.]+)?;(?:name=(.*);)?base64,(.*)$/; function createAjvInstance(additionalMetaSchemas, customFormats, ajvOptionsOverrides = {}, ajvFormatOptions, AjvClass = import_ajv.default, useAjvErrors) { const ajv = new AjvClass({ ...AJV_CONFIG, ...ajvOptionsOverrides }); if (ajvFormatOptions) { (0, import_ajv_formats.default)(ajv, ajvFormatOptions); } else if (ajvFormatOptions !== false) { (0, import_ajv_formats.default)(ajv); } ajv.addFormat("data-url", DATA_URL_FORMAT_REGEX); ajv.addFormat("color", COLOR_FORMAT_REGEX); ajv.addKeyword(import_rjsf_utils.ADDITIONAL_PROPERTY_FLAG); ajv.addKeyword(import_rjsf_utils.RJSF_ADDITIONAL_PROPERTIES_FLAG); if (Array.isArray(additionalMetaSchemas)) { ajv.addMetaSchema(additionalMetaSchemas); } if ((0, import_isObject.default)(customFormats)) { Object.keys(customFormats).forEach((formatName) => { ajv.addFormat(formatName, customFormats[formatName]); }); } if (useAjvErrors) { (0, import_ajv_errors.default)(ajv); } return ajv; } // src/processRawValidationErrors.ts var import_get = __toESM(require("lodash/get")); var import_rjsf_utils2 = require("@snups/rjsf-utils"); function transformRJSFValidationErrors(errors = [], uiSchema) { return errors.map((e) => { const { instancePath, keyword, params, schemaPath, parentSchema, ...rest } = e; let { message = "" } = rest; let property = instancePath.replace(/\//g, "."); let stack = `${property} ${message}`.trim(); let uiTitle = ""; const rawPropertyNames = [ ...params.deps?.split(", ") || [], params.missingProperty, params.property ].filter((item) => item); if (rawPropertyNames.length > 0) { rawPropertyNames.forEach((currentProperty) => { const path = property ? `${property}.${currentProperty}` : currentProperty; let uiSchemaTitle = (0, import_rjsf_utils2.getUiOptions)((0, import_get.default)(uiSchema, `${path.replace(/^\./, "")}`)).title; if (uiSchemaTitle === void 0) { const uiSchemaPath = schemaPath.replace(/\/properties\//g, "/").split("/").slice(1, -1).concat([currentProperty]); uiSchemaTitle = (0, import_rjsf_utils2.getUiOptions)((0, import_get.default)(uiSchema, uiSchemaPath)).title; } if (uiSchemaTitle) { message = message.replace(`'${currentProperty}'`, `'${uiSchemaTitle}'`); uiTitle = uiSchemaTitle; } else { const parentSchemaTitle = (0, import_get.default)(parentSchema, [import_rjsf_utils2.PROPERTIES_KEY, currentProperty, "title"]); if (parentSchemaTitle) { message = message.replace(`'${currentProperty}'`, `'${parentSchemaTitle}'`); uiTitle = parentSchemaTitle; } } }); stack = message; } else { const uiSchemaTitle = (0, import_rjsf_utils2.getUiOptions)((0, import_get.default)(uiSchema, `${property.replace(/^\./, "")}`)).title; if (uiSchemaTitle) { stack = `'${uiSchemaTitle}' ${message}`.trim(); uiTitle = uiSchemaTitle; } else { const parentSchemaTitle = parentSchema?.title; if (parentSchemaTitle) { stack = `'${parentSchemaTitle}' ${message}`.trim(); uiTitle = parentSchemaTitle; } } } if ("missingProperty" in params) { property = property ? `${property}.${params.missingProperty}` : params.missingProperty; } return { name: keyword, property, message, params, // specific to ajv stack, schemaPath, title: uiTitle }; }); } function processRawValidationErrors(validator, rawErrors, formData, schema, customValidate, transformErrors, uiSchema) { const { validationError: invalidSchemaError } = rawErrors; let errors = transformRJSFValidationErrors(rawErrors.errors, uiSchema); if (invalidSchemaError) { errors = [...errors, { stack: invalidSchemaError.message }]; } if (typeof transformErrors === "function") { errors = transformErrors(errors, uiSchema); } let errorSchema = (0, import_rjsf_utils2.toErrorSchema)(errors); if (invalidSchemaError) { errorSchema = { ...errorSchema, $schema: { __errors: [invalidSchemaError.message] } }; } if (typeof customValidate !== "function") { return { errors, errorSchema }; } const newFormData = (0, import_rjsf_utils2.getDefaultFormState)(validator, schema, formData, schema, true); const errorHandler = customValidate(newFormData, (0, import_rjsf_utils2.createErrorHandler)(newFormData), uiSchema); const userErrorSchema = (0, import_rjsf_utils2.unwrapErrorHandler)(errorHandler); return (0, import_rjsf_utils2.validationDataMerge)({ errors, errorSchema }, userErrorSchema); } // src/validator.ts var AJV8Validator = class { /** Constructs an `AJV8Validator` instance using the `options` * * @param options - The `CustomValidatorOptionsType` options that are used to create the AJV instance * @param [localizer] - If provided, is used to localize a list of Ajv `ErrorObject`s */ constructor(options, localizer) { const { additionalMetaSchemas, customFormats, ajvOptionsOverrides, ajvFormatOptions, AjvClass } = options; this.ajv = createAjvInstance(additionalMetaSchemas, customFormats, ajvOptionsOverrides, ajvFormatOptions, AjvClass); this.localizer = localizer; } /** Resets the internal AJV validator to clear schemas from it. Can be helpful for resetting the validator for tests. */ reset() { this.ajv.removeSchema(); } /** 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 schema * @param formData - The form data to validate */ rawValidation(schema, formData) { let compilationError = void 0; let compiledValidator; try { if (schema[import_rjsf_utils3.ID_KEY]) { compiledValidator = this.ajv.getSchema(schema[import_rjsf_utils3.ID_KEY]); } if (compiledValidator === void 0) { compiledValidator = this.ajv.compile(schema); } compiledValidator(formData); } catch (err) { compilationError = err; } let errors; if (compiledValidator) { if (typeof this.localizer === "function") { (compiledValidator.errors ?? []).forEach((error) => { ["missingProperty", "property"].forEach((key) => { if (error.params?.[key]) { error.params[key] = `'${error.params[key]}'`; } }); if (error.params?.deps) { error.params.deps = error.params.deps.split(", ").map((v) => `'${v}'`).join(", "); } }); this.localizer(compiledValidator.errors); (compiledValidator.errors ?? []).forEach((error) => { ["missingProperty", "property"].forEach((key) => { if (error.params?.[key]) { error.params[key] = error.params[key].slice(1, -1); } }); if (error.params?.deps) { error.params.deps = error.params.deps.split(", ").map((v) => v.slice(1, -1)).join(", "); } }); } errors = compiledValidator.errors || void 0; compiledValidator.errors = null; } return { errors, validationError: compilationError }; } /** 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, schema, customValidate, transformErrors, uiSchema) { const rawErrors = this.rawValidation(schema, formData); return processRawValidationErrors(this, rawErrors, formData, schema, customValidate, transformErrors, uiSchema); } /** * This function checks if a schema needs to be added and if the root schemas don't match it removes the old root schema from the ajv instance and adds the new one. * @param rootSchema - The root schema used to provide $ref resolutions */ handleSchemaUpdate(rootSchema) { const rootSchemaId = rootSchema[import_rjsf_utils3.ID_KEY] ?? import_rjsf_utils3.ROOT_SCHEMA_PREFIX; if (this.ajv.getSchema(rootSchemaId) === void 0) { this.ajv.addSchema(rootSchema, rootSchemaId); } else if (!(0, import_rjsf_utils3.deepEquals)(rootSchema, this.ajv.getSchema(rootSchemaId)?.schema)) { this.ajv.removeSchema(rootSchemaId); this.ajv.addSchema(rootSchema, rootSchemaId); } } /** 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 */ isValid(schema, formData, rootSchema) { try { this.handleSchemaUpdate(rootSchema); const schemaWithIdRefPrefix = (0, import_rjsf_utils3.withIdRefPrefix)(schema); const schemaId = schemaWithIdRefPrefix[import_rjsf_utils3.ID_KEY] ?? (0, import_rjsf_utils3.hashForSchema)(schemaWithIdRefPrefix); let compiledValidator; compiledValidator = this.ajv.getSchema(schemaId); if (compiledValidator === void 0) { compiledValidator = this.ajv.addSchema(schemaWithIdRefPrefix, schemaId).getSchema(schemaId) || this.ajv.compile(schemaWithIdRefPrefix); } const result = compiledValidator(formData); return result; } catch (e) { console.warn("Error encountered compiling schema:", e); return false; } } }; // src/customizeValidator.ts function customizeValidator(options = {}, localizer) { return new AJV8Validator(options, localizer); } // src/precompiledValidator.ts var import_get2 = __toESM(require("lodash/get")); var import_rjsf_utils4 = require("@snups/rjsf-utils"); var AJV8PrecompiledValidator = class { /** 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, rootSchema, 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) { const key = (0, import_get2.default)(schema, import_rjsf_utils4.ID_KEY) || (0, import_rjsf_utils4.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, formData) { if (!(0, import_rjsf_utils4.deepEquals)(schema, this.rootSchema)) { const resolvedRootSchema = (0, import_rjsf_utils4.retrieveSchema)(this, this.rootSchema, this.rootSchema, formData); if (!(0, import_rjsf_utils4.deepEquals)(schema, resolvedRootSchema)) { throw new Error( "The schema associated with the precompiled validator differs from the rootSchema provided for validation" ); } } return true; } /** 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, formData) { this.ensureSameRootSchema(schema, formData); this.mainValidator(formData); if (typeof this.localizer === "function") { this.localizer(this.mainValidator.errors); } const errors = this.mainValidator.errors || void 0; this.mainValidator.errors = null; return { errors }; } /** 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, schema, customValidate, transformErrors, uiSchema) { 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, formData, rootSchema) { this.ensureSameRootSchema(rootSchema, formData); if ((0, import_get2.default)(schema, import_rjsf_utils4.ID_KEY) === import_rjsf_utils4.JUNK_OPTION_ID) { return false; } const validator = this.getValidator(schema); return validator(formData); } }; // src/createPrecompiledValidator.ts function createPrecompiledValidator(validateFns, rootSchema, localizer) { return new AJV8PrecompiledValidator(validateFns, rootSchema, localizer); } // src/index.ts var index_default = customizeValidator(); //# sourceMappingURL=index.js.map