UNPKG

nox-validation

Version:

validate dynamic schema

1,053 lines (985 loc) 31.6 kB
const constants = require("./constant"); const { findDisallowedKeys, formatLabel, getLastChildKey, buildNestedStructure, getValue, setValue, isEmpty, getParentKey, extractRelationalParents, } = require("./helpers"); const choices = ["radio", "checkboxes", "dropdown_multiple", "dropdown"]; const typeChecks = { date: (val, data) => { if (val instanceof Date && !isNaN(val)) return true; if (typeof val === "string" && !isNaN(Date.parse(val))) { if (data && data?.key && data?.updateValue) { data.updateValue(data.key, new Date(val)); } return true; } return false; }, [constants.types.DATE]: (val, data) => { if (val instanceof Date && !isNaN(val)) return true; if (typeof val === "string" && !isNaN(Date.parse(val))) { if (data && data?.key && data?.updateValue) { data.updateValue(data.key, new Date(val)); } return true; } return false; }, [constants.types.BOOLEAN]: (val, data) => { if (typeof val === "boolean") return true; if (typeof val === "string") { const lowerVal = val.toLowerCase(); if (lowerVal === "true" || lowerVal === "false") { if (data && data?.key && data?.updateValue) { data.updateValue(data.key, lowerVal === "true"); } return true; } } return false; }, [constants.types.DATE_TIME]: (val, data) => { if (val instanceof Date && !isNaN(val)) return true; if (typeof val === "string" && !isNaN(Date.parse(val))) { if (data && data?.key && data?.updateValue) { data.updateValue(data.key, new Date(val)); } return true; } return false; }, [constants.types.NUMBER]: (val, data) => { if (typeof val === "number" && !isNaN(val)) return true; if (typeof val === "string" && !isNaN(parseFloat(val))) { if (data && data?.key && data?.updateValue) { data.updateValue(data.key, parseFloat(val)); } return true; } return false; }, [constants.types.TIME]: (val) => typeof val === "string" && /^([01]\d|2[0-3]):([0-5]\d):([0-5]\d)$/.test(val), [constants.types.STRING]: (val) => typeof val === "string", [constants.types.OBJECT]: (val) => typeof val === "object" && val !== null && !Array.isArray(val), [constants.types.ARRAY]: (val) => Array.isArray(val), [constants.types.OBJECT_ID]: (val) => typeof val === "string" && /^[0-9a-fA-F]{24}$/.test(val), [constants.types.MIXED]: (val) => (val ? true : false), [constants.types.BUFFER]: (val) => val instanceof Buffer, [constants.types.ALIAS]: (val) => (val ? true : false), }; const handleMinMaxValidation = ( fieldValue, ruleValue, ruleType, field, addError, currentPath, error_messages, custom_message ) => { let message = ""; const fieldLabel = formatLabel(field.display_label); if (ruleType === constants.rulesTypes.MIN) { if ( field.type === constants.types.STRING && typeChecks[constants.types.STRING](fieldValue) && fieldValue?.length < ruleValue[0] ) { message = custom_message ?? error_messages.MIN_STRING; message = message?.replace(`{field}`, fieldLabel).replace(`{min}`, ruleValue[0]); } else if ( field.type === constants.types.NUMBER && typeChecks[constants.types.NUMBER](fieldValue) && fieldValue < ruleValue[0] ) { message = custom_message ?? error_messages.MIN_NUMBER; message = message?.replace(`{field}`, fieldLabel).replace(`{min}`, ruleValue[0]); } } if (ruleType === constants.rulesTypes.MAX) { if ( field.type === constants.types.STRING && typeChecks[constants.types.STRING](fieldValue) && fieldValue?.length > ruleValue[0] ) { message = custom_message ?? error_messages.MAX_STRING; message = message?.replace(`{field}`, fieldLabel).replace(`{max}`, ruleValue[0]); } else if ( field.type === constants.types.NUMBER && typeChecks[constants.types.NUMBER](fieldValue) && fieldValue > ruleValue[0] ) { message = custom_message ?? error_messages.MAX_NUMBER; message = message?.replace(`{field}`, fieldLabel).replace(`{max}`, ruleValue[0]); } } if (message) { addError( currentPath, { label: fieldLabel, fieldPath: currentPath, description: "", message }, field ); } }; const isEmptyRelational = ({ api_version, value, interface }) => { if (api_version === constants.API_VERSION.V1 && interface !== constants.interfaces.TRANSLATIONS) { if (interface === constants.interfaces.MANY_TO_ANY) { return ( value && typeChecks[constants.types.OBJECT](value) && value.hasOwnProperty("collection") && value.hasOwnProperty("sort") && value.hasOwnProperty("item") && value.collection !== null && value.collection !== "" && value.sort !== null && value.sort !== "" && value.item !== null && value.item !== "" ); } else { return value?.length > 0; } } else { return value?.create?.length > 0 || value?.existing?.length > 0; } return false; }; const validateMetaRules = ( field, addError, providedValue, currentPath, updateValue, error_messages, onlyFormFields, apiVersion, fieldOptions, formData ) => { const fieldValue = providedValue; const { required = false, nullable = false, options } = field?.meta ?? {}; const isRelational = [ constants.interfaces.FILES, constants.interfaces.FILE, constants.interfaces.FILE_IMAGE, constants.interfaces.MANY_TO_MANY, constants.interfaces.ONE_TO_MANY, constants.interfaces.MANY_TO_ONE, constants.interfaces.MANY_TO_ANY, constants.interfaces.TRANSLATIONS, ].includes(field?.meta?.interface); if ( choices.includes(field?.meta?.interface) && (!options?.choices || !options?.choices?.length > 0) ) { const message = error_messages.ADD_CHOICE.replace(`{field}`, formatLabel(field.display_label)); addError( currentPath, { label: formatLabel(field.display_label), fieldPath: currentPath, description: "", message, }, field ); } const isValidRelational = required && isRelational && !onlyFormFields ? isEmptyRelational({ api_version: apiVersion, value: fieldValue, interface: field?.meta?.interface, }) : true; if ((required && isEmpty(fieldValue)) || !isValidRelational) { const message = error_messages.REQUIRED.replace(`{field}`, formatLabel(field.display_label)); addError( currentPath, { label: formatLabel(field.display_label), fieldPath: currentPath, description: "", message, }, field ); } if (!nullable && fieldValue === null) { const message = error_messages.NOT_NULLABLE.replace( `{field}`, formatLabel(field.display_label) ); addError( currentPath, { label: formatLabel(field.display_label), fieldPath: currentPath, description: "", message, }, field ); } if ( !isEmpty(fieldValue) && typeChecks[field.type] && !typeChecks[field.type](fieldValue, { key: currentPath, updateValue }) ) { const message = error_messages.INVALID_TYPE.replace( `{field}`, formatLabel(field.display_label) ).replace(`{type}`, field?.type); addError( currentPath, { label: formatLabel(field.display_label), fieldPath: currentPath, description: "", message, }, field ); } }; const handleRegexValidation = ( fieldValue, rule, field, addError, currentPath, error_messages, custom_message ) => { let message = ""; const fieldLabel = formatLabel(field.display_label); const flags = `${rule.options.case_sensitive ? "" : "i"}${rule.options.multiline ? "m" : ""}${ rule.options.global ? "g" : "" }`; const regex = new RegExp(rule.value[0], flags); const isValid = (() => { switch (rule.options.type) { case constants.regexTypes.MATCH: message = custom_message ?? error_messages.REGEX_MATCH; return regex.test(fieldValue); case constants.regexTypes.START_WITH: message = custom_message ?? error_messages.REGEX_START_WITH; return fieldValue?.startsWith(rule.value[0].replace(/^\//, "").replace(/\/$/, "")); case constants.regexTypes.ENDS_WITH: message = custom_message ?? error_messages.REGEX_ENDS_WITH; return fieldValue?.endsWith(rule.value[0].replace(/^\//, "").replace(/\/$/, "")); case constants.regexTypes.CONTAINS: message = custom_message ?? error_messages.REGEX_CONTAINS; return regex.test(fieldValue); case constants.regexTypes.EXACT: message = custom_message ?? error_messages.REGEX_EXACT; return fieldValue === rule.value[0].replace(/^\//, "").replace(/\/$/, ""); case constants.regexTypes.NOT_START_WITH: message = custom_message ?? error_messages.REGEX_NOT_START_WITH; return !fieldValue?.startsWith(rule.value[0].replace(/^\//, "").replace(/\/$/, "")); case constants.regexTypes.NOT_ENDS_WITH: message = custom_message ?? error_messages.REGEX_NOT_ENDS_WITH; return !fieldValue?.endsWith(rule.value[0].replace(/^\//, "").replace(/\/$/, "")); case constants.regexTypes.NOT_CONTAINS: message = custom_message ?? error_messages.REGEX_NOT_CONTAINS; return !regex.test(fieldValue); default: return false; } })(); if (!isValid) { message = message?.replace(`{field}`, fieldLabel)?.replace(`{value}`, rule.value[0]); addError( currentPath, { label: fieldLabel, fieldPath: currentPath, description: "", message }, field ); } }; const validateOperatorRule = ( fieldValue, rule, field, addError, currentPath, formData, error_messages, custom_message ) => { const { isFieldCompare = false } = rule?.options ?? {}; let valid = false; const isNumber = field.type === constants.types.NUMBER; const isDate = field.type === constants.types.DATE || field.type === "date"; const isDateTime = field.type === constants.types.DATE_TIME; const isTime = field.type === constants.types.TIME; const isArray = field.type === constants.types.ARRAY; const parseTime = (timeStr) => { if (!timeStr || typeof timeStr !== "string") return null; const [hh, mm, ss] = timeStr.split(":").map(Number); return hh * 3600 + mm * 60 + ss; }; const getComparableValue = (value, forMessage = false) => { if (isNumber) return Number(value); if (isDate) { const date = new Date(value); date.setHours(0, 0, 0, 0); if (forMessage) { return `${date.getUTCFullYear()}-${String(date.getUTCMonth() + 1).padStart( 2, "0" )}-${String(date.getUTCDate()).padStart(2, "0")}`; } return Math.floor(date.getTime() / 1000); } if (isDateTime) { const date = new Date(value); if (forMessage) { return ( `${date.getUTCFullYear()}-${String(date.getUTCMonth() + 1).padStart(2, "0")}-${String( date.getUTCDate() ).padStart(2, "0")} ` + `${String(date.getUTCHours()).padStart(2, "0")}:${String(date.getUTCMinutes()).padStart( 2, "0" )}:${String(date.getUTCSeconds()).padStart(2, "0")}` ); } return Math.floor(date.getTime() / 1000); } if (isTime) { if (forMessage) { return value; } return parseTime(value); } return value; }; rule.value = rule.value?.map((key) => { const value = isFieldCompare ? getComparableValue(getValue(formData, key), true) : getComparableValue(key, true); return value ? value : key; }); const fieldValueParsed = getComparableValue(fieldValue); const messageValue = Array.isArray(rule.value) ? rule.value.join(", ") : String(rule.value); let message = ""; switch (rule.options.operator) { case constants.operatorTypes.AND: message = custom_message ?? error_messages.AND; message = message ?.replace(`{field}`, formatLabel(field.display_label)) ?.replace("{value}", messageValue); valid = isArray ? rule.value.every((val) => fieldValue.includes(val)) : rule.value.every((val) => val === fieldValueParsed); break; case constants.operatorTypes.OR: message = custom_message ?? error_messages.OR; message = message ?.replace(`{field}`, formatLabel(field.display_label)) ?.replace("{value}", messageValue); valid = isArray ? rule.value.some((val) => fieldValue.includes(val)) : rule.value.some((val) => val === fieldValueParsed); break; case constants.operatorTypes.EQUAL: message = custom_message ?? error_messages.EQUAL; message = message ?.replace(`{field}`, formatLabel(field.display_label)) ?.replace("{value}", rule.value[0]); valid = fieldValueParsed === getComparableValue(rule.value[0]); break; case constants.operatorTypes.NOT_EQUAL: message = custom_message ?? error_messages.NOT_EQUAL; message = message ?.replace(`{field}`, formatLabel(field.display_label)) ?.replace("{value}", rule.value[0]); valid = fieldValueParsed !== getComparableValue(rule.value[0]); break; case constants.operatorTypes.LESS_THAN: message = custom_message ?? error_messages.LESS_THAN; message = message ?.replace(`{field}`, formatLabel(field.display_label)) ?.replace("{value}", rule.value[0]); valid = fieldValueParsed < getComparableValue(rule.value[0]); break; case constants.operatorTypes.LESS_THAN_EQUAL: message = custom_message ?? error_messages.LESS_THAN_EQUAL; message = message ?.replace(`{field}`, formatLabel(field.display_label)) ?.replace("{value}", rule.value[0]); valid = fieldValueParsed <= getComparableValue(rule.value[0]); break; case constants.operatorTypes.GREATER_THAN: message = custom_message ?? error_messages.GREATER_THAN; message = message ?.replace(`{field}`, formatLabel(field.display_label)) ?.replace("{value}", rule.value[0]); valid = fieldValueParsed > getComparableValue(rule.value[0]); break; case constants.operatorTypes.GREATER_THAN_EQUAL: message = custom_message ?? error_messages.GREATER_THAN_EQUAL; message = message ?.replace(`{field}`, formatLabel(field.display_label)) ?.replace("{value}", rule.value[0]); valid = fieldValueParsed >= getComparableValue(rule.value[0]); break; case constants.operatorTypes.IN: message = custom_message ?? error_messages.IN; message = message ?.replace(`{field}`, formatLabel(field.display_label)) ?.replace("{value}", messageValue); valid = rule.value.includes(fieldValue); break; case constants.operatorTypes.NOT_IN: message = custom_message ?? error_messages.NOT_IN; message = message ?.replace(`{field}`, formatLabel(field.display_label)) ?.replace("{value}", messageValue); valid = !rule.value.includes(fieldValue); break; case constants.operatorTypes.EXISTS: message = custom_message ?? error_messages.EXISTS; message = message?.replace(`{field}`, formatLabel(field.display_label)); valid = rule.value[0] ? fieldValue !== undefined : fieldValue === undefined; break; case constants.operatorTypes.TYPE: message = custom_message ?? error_messages.TYPE; message = message ?.replace(`{field}`, formatLabel(field.display_label)) ?.replace("{value}", rule.value[0]); valid = typeChecks[rule.value[0]](fieldValue); break; case constants.operatorTypes.MOD: message = custom_message ?? error_messages.MOD; message = message ?.replace(`{field}`, formatLabel(field.display_label)) ?.replace("{value[1]}", rule.value[1]) ?.replace("{value[0]}", rule.value[0]); valid = isNumber && fieldValue % rule.value[0] === rule.value[1]; break; case constants.operatorTypes.ALL: message = custom_message ?? error_messages.ALL; message = message ?.replace(`{field}`, formatLabel(field.display_label)) ?.replace("{value}", messageValue); valid = isArray && rule.value.every((val) => fieldValue.includes(val)); break; case constants.operatorTypes.SIZE: message = custom_message ?? error_messages.SIZE; message = message ?.replace(`{field}`, formatLabel(field.display_label)) ?.replace("{value}", rule.value[0]); valid = isArray && fieldValue.length === rule.value[0]; break; default: console.log(`Unknown Operator : ${rule.options.operator}`); break; } if (!valid) { addError( currentPath, { label: formatLabel(field.display_label), fieldPath: currentPath, description: "", message, }, field ); } }; const generateErrorMessage = ( ruleKey, fieldLabel, additionalValues = {}, error_messages, custom_message ) => { let message = custom_message ?? error_messages[ruleKey]; message = message?.replace(`{field}`, fieldLabel); Object.entries(additionalValues).forEach(([key, value]) => { message = message.replace(`{${key}}`, value); }); return message; }; const handleRule = (rule, field, value, addError, currentPath, formData, error_messages) => { const ruleValue = rule?.value; const messageValue = Array.isArray(ruleValue) ? ruleValue.join(", ") : String(ruleValue); const fieldLabel = formatLabel(field.display_label); const custom_message = rule?.custom_message; const addValidationError = (ruleKey, extraValues = {}, custom_message) => addError( currentPath, { label: fieldLabel, fieldPath: currentPath, description: "", message: generateErrorMessage( ruleKey, fieldLabel, extraValues, error_messages, custom_message ), }, field ); switch (rule.case) { case constants.rulesTypes.EMPTY: if (value) addValidationError("EMPTY", {}, custom_message); break; case constants.rulesTypes.NOT_EMPTY: if (!value || value.length === 0) addValidationError("NOT_EMPTY", {}, custom_message); break; case constants.rulesTypes.ONE_OF: if (!ruleValue?.includes(value)) addValidationError("ONE_OF", { value: messageValue }, custom_message); break; case constants.rulesTypes.NOT_ONE_OF: if (ruleValue?.includes(value)) addValidationError("NOT_ONE_OF", { value: messageValue }, custom_message); break; case constants.rulesTypes.NOT_ALLOWED: if (ruleValue?.includes(value)) addValidationError("NOT_ALLOWED", {}, custom_message); break; case constants.rulesTypes.MIN: case constants.rulesTypes.MAX: handleMinMaxValidation( value, ruleValue, rule.case, field, addError, currentPath, error_messages, custom_message ); break; case constants.rulesTypes.REGEX: handleRegexValidation( value, rule, field, addError, currentPath, error_messages, custom_message ); break; case constants.rulesTypes.OPERATOR: validateOperatorRule( value, rule, field, addError, currentPath, formData, error_messages, custom_message ); break; default: console.warn(`Unknown Rule: ${rule.case}`, fieldLabel); } }; const validateField = ( field, value, fieldPath = "", addError, formData, updateValue, error_messages, onlyFormFields, apiVersion, fieldOptions ) => { if (onlyFormFields == true && (value === undefined || value === null)) return; const currentPath = fieldPath ? `${fieldPath}.${field.key.split(".").pop()}` : field.key; const fieldLabel = formatLabel(field.display_label); validateMetaRules( field, addError, value, currentPath, updateValue, error_messages, onlyFormFields, apiVersion, fieldOptions, formData ); if ( field.type === constants.types.OBJECT && field.children && value && typeChecks[constants.types.OBJECT](value) ) { const isManyToAnyItem = field.display_label === "item" && field?.meta?.interface === constants.interfaces.MANY_TO_ANY; const defaultKeys = new Set([ "_id", "created_at", "updated_at", "__v", "created_by", "updated_by", "nox_created_by", "nox_updated_by", "nox_created_at", "nox_updated_at", ]); let itemSchemaId = null; if (isManyToAnyItem) { const itemKey = Object.keys(value).find((key) => !defaultKeys.has(key)); if (itemKey) { const childField = field.children.find((child) => child.display_label === itemKey); itemSchemaId = childField?.schema_id ?? null; } } const childrenToValidate = itemSchemaId ? field.children.filter((child) => child.schema_id === itemSchemaId) : field.children; childrenToValidate.forEach((child) => validateField( child, value[child.key.split(".").pop()], currentPath, addError, formData, updateValue, error_messages, onlyFormFields, apiVersion, fieldOptions ) ); } else if (field.type === constants.types.ARRAY && Array.isArray(value)) { const itemType = field?.array_type || field?.schema_definition?.type; if (itemType) { value.forEach((item, index) => { const itemPath = `${currentPath}[${index}]`; if (choices.includes(field?.meta?.interface) && !isEmpty(item)) { applyValidations(field, item, addError, itemPath, formData, error_messages); } if (!typeChecks[itemType](item)) { addError(itemPath, { label: fieldLabel, fieldPath: `${currentPath}[${index}]`, description: "", message: generateErrorMessage( "INVALID_TYPE", fieldLabel, { type: itemType, }, error_messages ), }); } }); } if (field.children?.length > 0) { value.forEach((item, index) => { field.children.forEach((child) => validateField( child, item[child.key.split(".").pop()], `${currentPath}[${index}]`, addError, formData, updateValue, error_messages, onlyFormFields, apiVersion, fieldOptions ) ); }); } } else { if (!applyValidations(field, value, addError, currentPath, formData, error_messages)) { addError( currentPath, { label: fieldLabel, fieldPath: currentPath, description: "", message: error_messages.INVALID_VALUE?.replace(`{field}`, fieldLabel), }, field ); } } }; const applyValidations = (field, value, addError, currentPath, formData, error_messages) => { if (!field.validations || value === null || value === undefined || value === "") return true; return !field.validations.some( (rule) => handleRule(rule, field, value, addError, currentPath, formData, error_messages) === false ); }; const schema = { formData: { type: constants.types.OBJECT, array_type: null }, formId: { type: constants.types.OBJECT_ID, array_type: null }, isSeparatedFields: { type: constants.types.BOOLEAN, array_type: null }, relations: { type: constants.types.ARRAY, array_type: constants.types.OBJECT, }, fields: { type: constants.types.ARRAY, array_type: constants.types.OBJECT }, relationalFields: { type: constants.types.OBJECT, array_type: null }, abortEarly: { type: constants.types.BOOLEAN, array_type: null }, byPassKeys: { type: constants.types.ARRAY, array_type: constants.types.STRING, }, apiVersion: { type: constants.types.STRING, array_type: null }, language: { type: constants.types.STRING, array_type: null }, maxLevel: { type: constants.types.NUMBER, array_type: null }, onlyFormFields: { type: constants.types.BOOLEAN, array_type: null }, }; const validate = (data) => { if (!data?.language) { data.language = constants.LANGUAGES.en; } if (data.onlyFormFields === undefined) { data.onlyFormFields = false; } let { formData, isSeparatedFields, fields, relationalFields, relations, formId, abortEarly, byPassKeys, apiVersion, language, maxLevel, onlyFormFields, } = data; const error_messages = constants.LOCALE_MESSAGES[language] ?? constants.LOCALE_MESSAGES[constants.LANGUAGES.en]; let result = { status: true, errors: {}, data: structuredClone(formData) }; const updateValue = (key, value) => { setValue(result.data, key, value); }; const addError = (fieldPath, obj, field) => { if (byPassKeys.some((key) => fieldPath.startsWith(key))) return; if (!result.errors[fieldPath] && !field?.meta?.hidden) { const pathResult = extractRelationalParents(fieldPath); if (pathResult) { const { firstParent, secondParent } = pathResult; const secondParentField = fields.find((f) => f.path === secondParent); if ( secondParentField && secondParentField?.meta?.interface === constants.interfaces.TRANSLATIONS ) { const languageKey = secondParentField?.meta?.options?.language_field; const firstParentValue = getValue(formData, firstParent); if (firstParentValue && typeChecks[constants.types.OBJECT](firstParentValue)) { const codeKey = Object.keys(firstParentValue).find((key) => key.includes(languageKey)); const codeValue = codeKey ? firstParentValue[codeKey] : null; if (codeValue) { const translation_key = fieldPath.replace( firstParent, `${secondParent}.${codeValue}` ); if (translation_key) { obj.translation_path = translation_key; } } } } } result.errors[fieldPath] = obj; result.status = false; } if (abortEarly && !result.status) return result; }; // Validate Parameters Starts // Validate Data const defaultField = { meta: { hidden: false } }; if (!data) { const message = error_messages.REQUIRED.replace(`{field}`, formatLabel("data")); addError( "data", { label: formatLabel("data"), fieldPath: "data", description: "", message, }, defaultField ); return result; } // validate data type if (!typeChecks[constants.types.OBJECT](data)) { const message = error_messages.INVALID_TYPE.replace(`{field}`, formatLabel("data")).replace( `{type}`, constants.types.OBJECT ); addError( "data", { label: formatLabel("data"), fieldPath: "data", description: "", message, }, defaultField ); return result; } // Validate Parameters Object.keys(schema).forEach((key) => { const expectedType = schema[key].type; const fieldValue = data[key]; // Skip empty values if (fieldValue == null || fieldValue == undefined) { const message = error_messages.REQUIRED.replace(`{field}`, formatLabel(key)); addError( key, { label: formatLabel(key), fieldPath: key, description: "", message, }, defaultField ); return; } // Validate field type if (!typeChecks[expectedType] || !typeChecks[expectedType](fieldValue)) { const message = error_messages.INVALID_TYPE.replace(`{field}`, key).replace( `{type}`, expectedType ); addError( key, { label: formatLabel(key), fieldPath: key, description: "", message, }, defaultField ); return; } // Check array items if the field is an array if (expectedType === constants.types.ARRAY && typeChecks[expectedType]) { fieldValue.forEach((item, index) => { // Determine the expected type of array items const arrayItemType = schema[key].array_type; // Define item types like "relations[]": "object" if (arrayItemType && typeChecks[arrayItemType] && !typeChecks[arrayItemType](item)) { const message = error_messages.INVALID_TYPE.replace( `{field}`, `${key}[${index}]` ).replace(`{type}`, arrayItemType); addError( `${key}[${index}]`, { label: formatLabel(`${key}[${index}]`), fieldPath: `${key}[${index}]`, description: "", message, }, defaultField ); } }); } }); // Validate API Version if (!constants.API_VERSIONS.includes(apiVersion)) { const message = error_messages.IN.replace(`{field}`, formatLabel("apiVersion")).replace( `{value}`, constants.API_VERSIONS.join(", ") ); addError( "apiVersion", { label: formatLabel("apiVersion"), fieldPath: "apiVersion", description: "", message, }, defaultField ); } if (!result.status) { return result; } // Validate Parameters END // Get Relational Field let schemaFields = fields; let allFields = isSeparatedFields ? [] : fields; if (!isSeparatedFields) { schemaFields = fields.filter((field) => field?.schema_id?.toString() === formId?.toString()); } let currentDepthMap = new Map(); const fieldOptions = buildNestedStructure({ schemaFields: schemaFields || [], allFields: allFields, relations: relations, relational_fields: relationalFields, isSeparatedFields, apiVersion, maxLevel, currentDepthMap, rootPath: "", isRoot: true, }) || []; findDisallowedKeys(formData, fieldOptions, maxLevel).forEach((fieldPath) => { if (abortEarly && !result.status) return result; const fieldKey = getLastChildKey(fieldPath); if (fieldKey && !result.errors[fieldPath]) { addError(fieldPath, { label: formatLabel(fieldKey), fieldPath, description: "", message: generateErrorMessage( "NOT_ALLOWED_FIELD", formatLabel(fieldKey), { field: formatLabel(fieldKey), }, error_messages ), }); } }); fieldOptions.forEach((field) => { if (abortEarly && !result.status) return result; validateField( field, formData[field.value], "", addError, formData, updateValue, error_messages, onlyFormFields, apiVersion, fieldOptions ); }); return result; }; module.exports = { validate, validateField, typeChecks };