/* eslint-disable @typescript-eslint/no-explicit-any */ import type * as joi from "joi"; import type * as yup from "yup"; import type * as zod from "zod"; import { FieldValidation, ValidateResult, ValidationRule, ValidationValue, ValidationValueMessage, ValidatorType, } from "./form-adapter.types"; import { FormAdapter } from "./form-adapter"; // Note: checks for schemas in such way that bundle size is not affected export const isJoiSchema = (value: any): value is joi.Schema => { if (typeof value !== "object") return false; const joiBase = value.$; if (!joiBase) return false; // TODO: getPrototypeOf doesn't work after build time, since it has different name after that, // find out new way to check whether it's Joi Schema const prototypeName = Object.getPrototypeOf(joiBase)?.constructor?.name; if (!prototypeName) return false; return prototypeName === "Base"; }; export const isYupSchema = (value: any): value is yup.Schema => { if (typeof value !== "object") return false; return !!value.__isYupSchema__; }; export const isZodSchema = (value: any): value is zod.ZodSchema => { if (typeof value !== "object") return false; const typeName = value?._def?.typeName; if (typeof typeName !== "string") return false; return typeName.startsWith("Zod"); }; export const fieldValidatorParser = (valueToParse: any, fieldValue: any): ValidateResult => { // Zod output parse if (isZodSchema(valueToParse)) { const res = valueToParse.safeParse(fieldValue); if (res.success === true) return false; return res.error.issues[0].message || true; } // Yup output parse if (isYupSchema(valueToParse)) { try { valueToParse.validateSync(fieldValue); return false; } catch (error: any) { return error?.message || true; } } // Joi output parse if (isJoiSchema(valueToParse)) { const res = valueToParse.validate(fieldValue); if (res.error) return res.error.message || true; return false; } // if (isJsonschema) { // ...code // } // if (isSuperStruct) { // ...code // } // Default output parse // NOTE: if "void" is returned -> then the field is "valid", hence there is no error -> false return valueToParse ?? false; }; // Run schema based validation export const runFieldValidator = async ( form: FormAdapter, value: any, validator: ValidatorType, any>, ): Promise => { const isCallback = typeof validator === "function"; const valueToCheck = isCallback ? await validator({ value, values: form.getValues(), form }) : validator; return fieldValidatorParser(valueToCheck, value); }; export const getNativeValidation = ( rule?: ValidationRule | ValidationValueMessage, ): T | undefined => { if (typeof rule === undefined || rule === null) { return undefined; } if (rule instanceof RegExp) { return rule.source as T | undefined; } if (typeof rule === "object") { if (rule.value instanceof RegExp) { return rule.value.source as T | undefined; } return rule.value; } return rule; }; const getValidationMessage = (value: ValidationRule, defaultMessage = ""): ValidateResult => { if (typeof value === "string") return value; if (typeof value === "object") return value.message; return defaultMessage; }; export const runNativeValidation = ( value: any, validations: FieldValidation, any> | undefined, ) => { const errors: Partial> = {}; const { required, min, max, minLength, maxLength, pattern } = validations || {}; // Required validation if (required) { const requiredValue = typeof required === "string" ? true : getNativeValidation(required); const message = typeof required === "string" ? required : getValidationMessage(required, "This field is required"); if (!!requiredValue && (value === undefined || value === null || value === "")) { errors.required = message; } } // Min validation if (min) { const minValue = getNativeValidation(min); if (typeof minValue === "number" && typeof value === "number" && value < minValue) { errors.min = getValidationMessage(min, `Minimum value is ${minValue}`); } } // Max validation if (max) { const maxValue = getNativeValidation(max); if (typeof maxValue === "number" && typeof value === "number" && value > maxValue) { errors.max = getValidationMessage(max, `Maximum value is ${maxValue}`); } } // Min length validation if (minLength) { const minLengthValue = getNativeValidation(minLength); if ( typeof minLengthValue === "number" && (typeof value === "string" || Array.isArray(value)) && value.length < minLengthValue ) { errors.minLength = getValidationMessage(minLength, `Minimum length is ${minLengthValue}`); } } // Max length validation if (maxLength) { const maxLengthValue = getNativeValidation(maxLength); if ( typeof maxLengthValue === "number" && (typeof value === "string" || Array.isArray(value)) && value.length > maxLengthValue ) { errors.maxLength = getValidationMessage(maxLength, `Maximum length is ${maxLengthValue}`); } } // Pattern validation if (pattern) { const patternValue = getNativeValidation(pattern); if (patternValue instanceof RegExp && typeof value === "string" && !patternValue.test(value)) { errors.pattern = getValidationMessage(pattern, "Invalid pattern"); } } return errors; };