import { type OutputUnit, Validator } from "@cfworker/json-schema"; import type { CustomValidator, ErrorSchema, ErrorTransformer, FormContextType, RJSFSchema, RJSFValidationError, StrictRJSFSchema, UiSchema, ValidationData, ValidatorType } from "@rjsf/utils"; import { toErrorSchema } from "@rjsf/utils"; import { get } from "lodash-es"; function removeUndefinedKeys(obj: any): any { if (!obj) return obj; if (typeof obj === "object") { Object.keys(obj).forEach((key) => { if (obj[key] === undefined) { delete obj[key]; } else if (typeof obj[key] === "object") { removeUndefinedKeys(obj[key]); } }); } if (Array.isArray(obj)) { return obj.filter((item) => item !== undefined); } return obj; } function onlyKeepMostSpecific(errors: OutputUnit[]) { const mostSpecific = errors.filter((error) => { return !errors.some((other) => { return error !== other && other.instanceLocation.startsWith(error.instanceLocation); }); }); return mostSpecific; } const debug = true; const validate = true; export class JsonSchemaValidator< T = any, S extends StrictRJSFSchema = RJSFSchema, F extends FormContextType = any > implements ValidatorType { // @ts-ignore rawValidation(schema: S, formData?: T) { if (!validate) return { errors: [], validationError: null }; debug && console.log("JsonSchemaValidator.rawValidation", schema, formData); const validator = new Validator(schema as any); const validation = validator.validate(removeUndefinedKeys(formData)); const specificErrors = onlyKeepMostSpecific(validation.errors); return { errors: specificErrors, validationError: null as any }; } validateFormData( formData: T | undefined, schema: S, customValidate?: CustomValidator, transformErrors?: ErrorTransformer, uiSchema?: UiSchema ): ValidationData { if (!validate) return { errors: [], errorSchema: {} as any }; debug && console.log( "JsonSchemaValidator.validateFormData", formData, schema, customValidate, transformErrors, uiSchema ); const { errors } = this.rawValidation(schema, formData); debug && console.log("errors", { errors }); const transformedErrors = errors //.filter((error) => error.keyword !== "properties") .map((error) => { const schemaLocation = error.keywordLocation.replace(/^#\/?/, "").split("/").join("."); const propertyError = get(schema, schemaLocation); const errorText = `${error.error.replace(/\.$/, "")}${propertyError ? ` "${propertyError}"` : ""}`; //console.log(error, schemaLocation, get(schema, schemaLocation)); return { name: error.keyword, message: errorText, property: "." + error.instanceLocation.replace(/^#\/?/, "").split("/").join("."), schemaPath: error.keywordLocation, stack: error.error }; }); debug && console.log("transformed", transformedErrors); return { errors: transformedErrors, errorSchema: toErrorSchema(transformedErrors) } as any; } toErrorList(errorSchema?: ErrorSchema, fieldPath?: string[]): RJSFValidationError[] { debug && console.log("JsonSchemaValidator.toErrorList", errorSchema, fieldPath); return []; } isValid(schema: S, formData: T | undefined, rootSchema: S): boolean { if (!validate) return true; debug && console.log("JsonSchemaValidator.isValid", schema, formData, rootSchema); return this.rawValidation(schema, formData).errors.length === 0; } }