UNPKG

nox-validation

Version:

validate dynamic schema

1,202 lines (1,098 loc) 35.6 kB
const constants = require("./constant"); const getAllFields = (obj, parentPath = "") => { return Object.keys(obj).flatMap((key) => { const path = parentPath ? `${parentPath}.${key}` : key; if (Array.isArray(obj[key])) { return obj[key].flatMap((item, index) => typeof item === "object" && item !== null ? getAllFields(item, `${path}[${index}]`) : `${path}[${index}]` ); } return typeof obj[key] === "object" && obj[key] !== null && !Array.isArray(obj[key]) ? getAllFields(obj[key], path) : path; }); }; const getValue = (obj, key, separator = ".") => { if (!key) return undefined; const fieldPathParts = key.replace(/\[(\d+)\]/g, ".$1").split(separator); return fieldPathParts.reduce((acc, part) => acc && acc[part], obj); }; const setValue = (obj, key, value, separator = ".") => { if (!key) return; const keys = key.replace(/\[(\d+)\]/g, ".$1").split(separator); const lastKey = keys.pop(); let deepObj = obj; for (const part of keys) { if (typeof deepObj[part] !== "object" || deepObj[part] === null) { deepObj[part] = {}; } deepObj = deepObj[part]; } if (typeof deepObj[lastKey] === "object" && deepObj[lastKey] !== null) { Object.assign(deepObj[lastKey], value); } else { deepObj[lastKey] = value; } }; const keyExists = (obj, key, separator = ".") => { if (!key) return false; const keys = key.replace(/\[(\d+)\]/g, ".$1").split(separator); return keys.every((part) => { if (obj && Object.prototype.hasOwnProperty.call(obj, part)) { obj = obj[part]; return true; } return false; }); }; const formatLabel = (str) => { return str ?.replace(/_/g, " ") ?.toLowerCase() ?.replace(/^./, (char) => char?.toUpperCase()); }; const getLastChildKey = (key) => { if (!key) return ""; return key .replace(/\[\d+\]/g, "") .split(".") .pop(); }; const getParentKey = (key) => { if (!key) return ""; const cleanedKey = key.replace(/\[\d+\]/g, "").split("."); cleanedKey.pop(); return cleanedKey.join("."); }; const generateDynamicKeys = (data, keys, parentKey = "") => { const addKey = (key) => { if (key) { keys.push(key); } }; if (Array.isArray(data)) { addKey(parentKey); data.forEach((item, index) => { const key = `${parentKey}[${index}]`; generateDynamicKeys(item, keys, key); }); } else if (typeof data === "object" && data !== null) { addKey(parentKey); for (const key in data) { if (Object.prototype.hasOwnProperty.call(data, key)) { const newKey = parentKey ? `${parentKey}.${key}` : key; generateDynamicKeys(data[key], keys, newKey); } } } else { addKey(parentKey); } }; const checkIsArrayKey = (key) => { const match = key.match(/\[\d+\]/g); const cleanedKey = key .replace(/\[\d+\]/g, "") .split(".") .join("."); return match ? { status: true, fieldPath: cleanedKey, formPath: key, parentKey: getParentKey(key), } : { status: false, fieldPath: key, formPath: key, parentKey: key }; }; const getFormPath = (inputFields, fieldPath) => { if (!fieldPath) return ""; const pathParts = fieldPath.split("."); let formPath = ""; for (let i = 0; i < pathParts.length; i++) { const currentPath = pathParts.slice(0, i + 1).join("."); const field = inputFields.find((f) => f.path === currentPath); if (field) { formPath += field.type === constants.types.ARRAY ? `${field.field}[0]` : field.field; } else { formPath += `.${pathParts[i]}`; } if (i < pathParts.length - 1) formPath += "."; } const cleanedFormPath = formPath.replace(/\.\[/g, "["); return cleanedFormPath.endsWith("[0]") ? cleanedFormPath.split("[0]")[0] : cleanedFormPath; }; const isEmpty = (val) => { if (val === undefined) return true; if (val == null) return true; if (typeof val === "boolean") return false; if (typeof val === "number") return false; if (typeof val === "string") return val.length === 0; if (Array.isArray(val)) return val.length === 0; if (typeof val === "object") return Object.keys(val).length === 0; return false; }; // field?.field_type (Possible Values => Single, Object, Array) // 1. Single => Root Field Then Its Single // 2. Object => Nested Field Like Inside Array Or Object // 3. Array => Any Kind Of Array Array Of String, Object etc // field?.type (Possible Values) // 1. String // 2. Number // 3. Date // 4. Buffer // 5. Boolean // 6. Mixed // 7. ObjectId // 8. Object // 9. Array // 10. Alias // field?.schema_definition?.type // it is used for specially when field?.type is Array, but some times both are Array Then We Have to check // field?.meta?.interface const generateType = (field, api) => { let { type, schema_definition, meta } = field; let interfaceType = meta?.interface; let array_type = schema_definition?.type; let fieldType = type; let find_relations = false; // When type and Array Type Are Same if (type === schema_definition?.type) { // Type And Array Type Both is Array if (type === constants.types.ARRAY) { array_type = constants.types.OBJECT; } // Todo: when both is Alias type === constants.types.ALIAS } switch (api) { case "v1": // is Relational Field if (interfaceType && interfaceType !== "none") { // We Need to find Relation if ( [constants.interfaces.MANY_TO_ANY, constants.interfaces.TRANSLATIONS].includes( interfaceType ) ) { find_relations = true; // update type and array type accordingly interface if ([constants.interfaces.MANY_TO_ANY].includes(interfaceType)) { fieldType = constants.types.ARRAY; array_type = constants.types.OBJECT; } else { fieldType = constants.types.OBJECT; } } else { // It is Relational Field, so we have to update type and array type accordingly interface if ([constants.interfaces.MANY_TO_ONE].includes(interfaceType)) { fieldType = constants.types.OBJECT_ID; } if ( [ constants.interfaces.ONE_TO_MANY, constants.interfaces.MANY_TO_MANY, constants.interfaces.FILES, ].includes(interfaceType) ) { fieldType = constants.types.ARRAY; array_type = constants.types.OBJECT_ID; } } } return { type: fieldType, array_type, find_relations, }; default: // API V2 if ( [ constants.interfaces.ONE_TO_MANY, constants.interfaces.MANY_TO_MANY, constants.interfaces.MANY_TO_ANY, constants.interfaces.MANY_TO_ONE, constants.interfaces.TRANSLATIONS, ].includes(interfaceType) ) { fieldType = constants.types.OBJECT; array_type = null; find_relations = true; } if (interfaceType === constants.interfaces.FILES) { fieldType = constants.types.ARRAY; array_type = constants.types.OBJECT_ID; find_relations = false; } return { type: fieldType, array_type, find_relations, }; } }; const convertTypes = (field) => { let { type, schema_definition, meta } = field; let array_type = schema_definition?.type ?? null; let interfaceType = meta?.interface; let find_relations = false; if ( [ constants.interfaces.ONE_TO_MANY, constants.interfaces.MANY_TO_MANY, constants.interfaces.MANY_TO_ANY, constants.interfaces.MANY_TO_ONE, constants.interfaces.TRANSLATIONS, ].includes(interfaceType) ) { return { type: constants.types.OBJECT, array_type: null, find_relations: true, }; } if (interfaceType === constants.interfaces.FILES) { return { type: constants.types.ARRAY, array_type: constants.types.OBJECT_ID, find_relations: false, }; } if (type !== schema_definition?.type && schema_definition?.type !== constants.types.ALIAS) { array_type = schema_definition.type; } return { type, array_type, find_relations }; }; const convertTypesV1 = (field) => { let { type, schema_definition, meta } = field; let array_type = schema_definition?.type ?? null; let interfaceType = meta?.interface; let find_relations = false; if ([constants.interfaces.TRANSLATIONS].includes(interfaceType)) { return { type: constants.types.OBJECT, array_type: null, find_relations: true, }; } if ([constants.interfaces.MANY_TO_ANY].includes(interfaceType)) { return { type: constants.types.ARRAY, array_type: constants.types.OBJECT, find_relations: true, }; } if ([constants.interfaces.MANY_TO_ONE].includes(interfaceType)) { return { type: constants.types.OBJECT_ID, array_type: null, find_relations: false, }; } if ( [ constants.interfaces.ONE_TO_MANY, constants.interfaces.MANY_TO_MANY, constants.interfaces.FILES, ].includes(interfaceType) ) { return { type: constants.types.ARRAY, array_type: constants.types.OBJECT_ID, find_relations: false, }; } if (type !== schema_definition?.type && schema_definition?.type !== constants.types.ALIAS) { array_type = schema_definition.type; find_relations = false; } return { type, array_type, find_relations }; }; const generateField = ( name, path, schema_definition_type, type, childrenFields = [], relationType = "none" ) => { childrenFields = childrenFields?.map((child) => { return { ...child, value: `${path}.${child.value}`, key: `${path}.${child.key}`, }; }); return { display_label: name, // field: name, key: path, value: path, children: childrenFields, type: type, meta: { required: false, nullable: false, hidden: false, interface: relationType, }, validations: [], schema_definition: { type: schema_definition_type, }, }; }; const createChildrenFieldsFiles = (key) => { const existingField = generateField( "existing", `${key}.existing`, constants.types.OBJECT_ID, constants.types.ARRAY ); const deleteField = generateField( "delete", `${key}.delete`, constants.types.OBJECT_ID, constants.types.ARRAY ); return [existingField, deleteField]; }; const generateRelationalFieldV1 = (key = "", collectionFields = [], relationType) => { if (relationType === constants.interfaces.MANY_TO_ANY) { return [ generateField( "collection", `${key}.collection`, constants.types.STRING, constants.types.STRING ), generateField("sort", `${key}.sort`, constants.types.NUMBER, constants.types.NUMBER), generateField("item", `${key}.item`, constants.types.OBJECT_ID, constants.types.OBJECT_ID), ]; } else { return null; } }; const generateRelationalField = (key = "", collectionFields = [], relationType) => { if (relationType === constants.interfaces.MANY_TO_ANY) { const existingField = generateField( "existing", `${key}.existing`, constants.types.OBJECT, constants.types.ARRAY ); existingField.children = [ generateField( "collection", `${key}.existing.collection`, constants.types.STRING, constants.types.STRING ), generateField("sort", `${key}.existing.sort`, constants.types.NUMBER, constants.types.NUMBER), generateField( "item", `${key}.existing.item`, constants.types.OBJECT_ID, constants.types.OBJECT_ID ), ]; const deleteField = generateField( "delete", `${key}.delete`, constants.types.OBJECT, constants.types.ARRAY ); deleteField.children = [ generateField( "collection", `${key}.delete.collection`, constants.types.STRING, constants.types.STRING ), generateField("sort", `${key}.delete.sort`, constants.types.NUMBER, constants.types.NUMBER), generateField( "item", `${key}.delete.item`, constants.types.OBJECT_ID, constants.types.OBJECT_ID ), ]; const createField = generateField( "create", `${key}.create`, constants.types.OBJECT, constants.types.ARRAY, collectionFields ); createField.children = [ generateField( "collection", `${key}.create.collection`, constants.types.STRING, constants.types.STRING ), generateField("sort", `${key}.create.sort`, constants.types.NUMBER, constants.types.NUMBER), generateField( "item", `${key}.create.item`, constants.types.OBJECT, constants.types.OBJECT, collectionFields, relationType ), ]; const updateField = generateField( "update", `${key}.update`, constants.types.OBJECT, constants.types.ARRAY, collectionFields ); updateField.children = [ generateField( "collection", `${key}.update.collection`, constants.types.STRING, constants.types.STRING ), generateField("sort", `${key}.update.sort`, constants.types.NUMBER, constants.types.NUMBER), generateField( "item", `${key}.update.item`, constants.types.OBJECT, constants.types.OBJECT, collectionFields, relationType ), ]; return [existingField, deleteField, createField, updateField]; } else { return [ generateField( "existing", `${key}.existing`, constants.types.OBJECT_ID, constants.types.ARRAY ), generateField("delete", `${key}.delete`, constants.types.OBJECT_ID, constants.types.ARRAY), generateField( "create", `${key}.create`, constants.types.OBJECT, constants.types.ARRAY, collectionFields ), generateField( "update", `${key}.update`, constants.types.OBJECT, constants.types.ARRAY, collectionFields ), ]; } }; const getForeignCollectionDetails = ({ relations, collection, field, iFace, findJunction = true, getRelationshipDetails = true, }) => { if (!relations.length > 0) return {}; const isListInterface = [ constants.interfaces.ONE_TO_MANY, constants.interfaces.MANY_TO_MANY, constants.interfaces.TRANSLATIONS, constants.interfaces.FILES, constants.interfaces.MANY_TO_ANY, ].includes(iFace); const isSingleRelation = [ constants.interfaces.MANY_TO_ONE, constants.interfaces.FILE, constants.interfaces.FILE_IMAGE, ].includes(iFace); if (isListInterface) { const mainTable = relations.find( (d) => d.one_collection_id === collection && d.one_field_id === field ); if (!mainTable) return {}; const isJunction = mainTable.junction_field && findJunction; const relational = isJunction ? relations.find( (d) => d.many_collection === mainTable.many_collection && d.junction_field === mainTable.many_field ) : []; if (getRelationshipDetails) { return { this_collection: mainTable.one_collection_id, this_field: "_id", foreign_collection: iFace === constants.interfaces.MANY_TO_MANY ? relational?.one_collection_id : iFace === constants.interfaces.MANY_TO_ANY ? relational?.one_allowed_collections_id : mainTable.many_collection_id, foreign_field: iFace === constants.interfaces.MANY_TO_MANY || iFace === constants.interfaces.TRANSLATIONS ? "_id" : iFace === constants.interfaces.MANY_TO_ANY ? "Primary Key" : mainTable.many_field_id, ...(isJunction && { junction_collection: relational?.many_collection_id, junction_field_this: relational?.junction_field, junction_field_foreign: iFace === constants.interfaces.MANY_TO_ANY ? "item" : relational?.many_field, }), ...(iFace === constants.interfaces.MANY_TO_ANY && { junction_field_ref: "collection", foreign_collection_ref: relational?.one_allowed_collections?.join(", "), }), }; } return { foreign_collection_id: isJunction ? iFace === constants.interfaces.MANY_TO_ANY ? relational?.one_allowed_collections_id : relational?.one_collection_id : mainTable.many_collection_id, ...(isJunction && { junction_collection_id: relational?.many_collection_id, junction_field: mainTable.junction_field, junction_field_local: mainTable.many_field, }), }; } if (isSingleRelation) { const mainTable = relations.find( (d) => d.many_collection_id === collection && d.many_field_id === field ); if (!mainTable) return {}; return getRelationshipDetails ? { this_collection: mainTable.many_collection_id, this_field: mainTable.many_field, foreign_collection: mainTable.one_collection_id, foreign_field: "_id", } : { foreign_collection_id: mainTable.one_collection_id, }; } return {}; }; const getCachedFields = (relationDetail, relational_fields) => { const isMultiple = Array.isArray(relationDetail.foreign_collection); const getField = (schemaId) => { if (relational_fields[schemaId]) { return relational_fields[schemaId]; // Return cached fields if available } else { return []; } }; if (!isMultiple) { return getField(relationDetail.foreign_collection); } else { return relationDetail.foreign_collection.flatMap((schemaId) => getField(schemaId)); } }; const getCachedOrFetchFields = (schemaId, allFields, relational_fields) => { if (relational_fields[schemaId]) { return relational_fields[schemaId]; // Return cached fields if available } const fields = allFields?.filter((field) => field.schema_id === schemaId) || []; relational_fields[schemaId] = fields; // Cache the fields return fields; }; const getChildFields = (relationDetail, allFields, relational_fields, isTranslation, name) => { let key = isTranslation ? [ ...(Array.isArray(relationDetail.junction_collection) ? relationDetail.junction_collection : [relationDetail.junction_collection]), ...(Array.isArray(relationDetail.foreign_collection) ? relationDetail.foreign_collection : [relationDetail.foreign_collection]), ] : relationDetail.foreign_collection; const isMultiple = Array.isArray(key); if (!isMultiple) { return getCachedOrFetchFields(key, allFields, relational_fields); } else { return key.flatMap((schemaId) => getCachedOrFetchFields(schemaId, allFields, relational_fields) ); } }; const default_fields = [ "updated_by", "created_by", "created_at", "updated_at", "nox_created_at", "nox_updated_at", "nox_created_by", "nox_updated_by", "_id", ]; const buildNestedStructure = ({ schemaFields, allFields, relations, relational_fields, isSeparatedFields, apiVersion, maxLevel, currentDepthMap, rootPath, isRoot, }) => { const root = {}; const nodeMap = new Map(); schemaFields.sort((a, b) => a.path.split(".").length - b.path.split(".").length); schemaFields.forEach((item) => { const pathParts = item.path.split("."); const key = pathParts.join("."); const isV2File = apiVersion === constants.API_VERSION.V2 && [ constants.interfaces.FILES, constants.interfaces.FILE, constants.interfaces.FILE_IMAGE, ].includes(item?.meta?.interface); const currentDepth = currentDepthMap.get(isRoot ? item.path : rootPath) || 0; let childFields; let definedType = generateType(item, apiVersion); if (definedType.find_relations && currentDepth <= maxLevel) { const relationDetail = getForeignCollectionDetails({ relations: relations, collection: item?.schema_id, field: item._id, iFace: item?.meta?.interface, findJunction: true, getRelationshipDetails: true, }); if (!isSeparatedFields) { childFields = getChildFields( relationDetail, allFields, relational_fields, item?.meta?.interface === constants.interfaces.TRANSLATIONS, key ); } else { childFields = getCachedFields(relationDetail, relational_fields); } if (childFields) { if (!isRoot) currentDepthMap.set(rootPath, currentDepth + 1); childFields = buildNestedStructure({ schemaFields: childFields, allFields: allFields, relations: relations, relational_fields: relational_fields, isSeparatedFields, apiVersion, maxLevel, currentDepthMap, rootPath: isRoot ? item.path : rootPath, isRoot: false, }); } } if (isV2File) { definedType.array_type = constants.types.OBJECT; definedType.type = constants.types.OBJECT; childFields = createChildrenFieldsFiles(key); } const node = { field_id: item?._id, schema_id: item?.schema_id, display_label: pathParts[pathParts.length - 1], key, value: key, meta: { interface: item.meta?.interface || "none", required: item.meta?.required || false, nullable: item.meta?.nullable || false, hidden: item.meta?.hidden || false, options: item.meta?.options || {}, }, validations: generateModifiedRules(item?.meta, item?.meta?.validations?.validation_msg), custom_error_message: item?.meta?.validations?.validation_msg, children: childFields?.length > 0 ? isV2File ? childFields : apiVersion === constants.API_VERSION.V1 && item.meta?.interface !== constants.interfaces.TRANSLATIONS ? generateRelationalFieldV1(key, childFields, item.meta?.interface) : generateRelationalField(key, childFields, item.meta?.interface) : [], type: definedType.type, array_type: definedType.array_type, default_value: item?.schema_definition?.default, }; nodeMap.set(key, node); if (pathParts.length === 1) { root[key] = node; } else { const parentPath = pathParts.slice(0, -1).join("."); if (nodeMap.has(parentPath)) { nodeMap.get(parentPath).children.push(node); } } }); const removeEmptyChildren = (nodes) => { return nodes.map(({ children, ...node }) => children && children?.length ? { ...node, children: removeEmptyChildren(children) } : node ); }; return removeEmptyChildren(Object.values(root)); }; const getAllKeys = (structure) => { const keys = new Set(); const traverse = (nodes) => { nodes.forEach((node) => { keys.add(node.key); if (node.children && node.children.length) { traverse(node.children); } }); }; traverse(structure); return keys; }; const normalizeKey = (key) => key.replace(/\[\d+\]/g, ""); const findDisallowedKeys = (formData, structure, maxLevel) => { const formKeys = []; generateDynamicKeys(formData, formKeys); const validKeys = getAllKeys(structure); return formKeys.filter((key) => { const keyParts = normalizeKey(key).split("."); const keyLevel = keyParts.length; const levelParent = keyParts.slice(0, maxLevel - 1).join("."); return !validKeys.has(normalizeKey(keyLevel > maxLevel ? levelParent : key)); }); }; const generateFieldCompareRules = (rule) => { const modifiedRule = { identifier: 1, case: constants.rulesTypes.FIELD_COMPARE, value: [], options: {}, }; switch (rule.type) { case "contains": modifiedRule.case = constants.rulesTypes.REGEX; modifiedRule.value = [rule[rule.type].value]; modifiedRule.options = { type: constants.regexTypes.CONTAINS, case_sensitive: !rule[rule.type].insensitive, multiline: false, global: false, }; break; case "doesNotContain": modifiedRule.case = constants.rulesTypes.REGEX; modifiedRule.value = [rule[rule.type].value]; modifiedRule.options = { type: constants.regexTypes.NOT_CONTAINS, case_sensitive: !rule[rule.type].insensitive, multiline: false, global: false, }; break; case "startsWith": modifiedRule.case = constants.rulesTypes.REGEX; modifiedRule.value = [rule[rule.type].value]; modifiedRule.options = { type: constants.regexTypes.START_WITH, case_sensitive: !rule[rule.type].insensitive, }; break; case "doesNotStartWith": modifiedRule.case = constants.rulesTypes.REGEX; modifiedRule.value = [rule[rule.type].value]; modifiedRule.options = { type: constants.regexTypes.NOT_START_WITH, case_sensitive: !rule[rule.type].insensitive, }; break; case "endsWith": modifiedRule.case = constants.rulesTypes.REGEX; modifiedRule.value = [rule[rule.type].value]; modifiedRule.options = { type: constants.regexTypes.ENDS_WITH, case_sensitive: !rule[rule.type].insensitive, }; break; case "doesNotEndWith": modifiedRule.case = constants.rulesTypes.REGEX; modifiedRule.value = [rule[rule.type].value]; modifiedRule.options = { type: constants.regexTypes.NOT_ENDS_WITH, case_sensitive: !rule[rule.type].insensitive, }; break; case "matchesRegExp": modifiedRule.case = constants.rulesTypes.REGEX; modifiedRule.value = [rule.matchesRegExp.value]; modifiedRule.options.type = constants.regexTypes.MATCH; break; case "equals": modifiedRule.case = constants.rulesTypes.OPERATOR; modifiedRule.options.operator = constants.operatorTypes.EQUAL; modifiedRule.value = [rule[rule.type].value]; break; case "doesNotEqual": modifiedRule.case = constants.rulesTypes.OPERATOR; modifiedRule.options.operator = constants.operatorTypes.NOT_EQUAL; modifiedRule.value = [rule[rule.type].value]; break; case "lessThan": modifiedRule.case = constants.rulesTypes.OPERATOR; modifiedRule.options.operator = constants.operatorTypes.LESS_THAN; modifiedRule.value = [rule[rule.type].value]; break; case "lessThanOrEqualTo": modifiedRule.case = constants.rulesTypes.OPERATOR; modifiedRule.options.operator = constants.operatorTypes.LESS_THAN_EQUAL; modifiedRule.value = [rule[rule.type].value]; break; case "greaterThan": modifiedRule.case = constants.rulesTypes.OPERATOR; modifiedRule.options.operator = constants.operatorTypes.GREATER_THAN; modifiedRule.value = [rule[rule.type].value]; break; case "greaterThanOrEqualTo": modifiedRule.case = constants.rulesTypes.OPERATOR; modifiedRule.options.operator = constants.operatorTypes.GREATER_THAN_EQUAL; modifiedRule.value = [rule[rule.type].value]; break; case "isEmpty": modifiedRule.case = constants.rulesTypes.EMPTY; modifiedRule.value = []; break; case "isNotEmpty": modifiedRule.case = constants.rulesTypes.NOT_EMPTY; modifiedRule.value = []; break; case "isOneOf": modifiedRule.case = constants.rulesTypes.ONE_OF; modifiedRule.value = rule[rule.type].value; break; case "isNotOneOf": modifiedRule.case = constants.rulesTypes.NOT_ONE_OF; modifiedRule.value = [rule[rule.type].value]; break; } modifiedRule.options.isFieldCompare = true; return modifiedRule; }; const generateModifiedRules = (meta, custom_message) => { let rules = []; if (meta?.validations?.rules?.length > 0) { rules = meta?.validations?.rules?.map((rule, index) => { let modifiedRule = { identifier: String(index), case: constants.rulesTypes.RULES_COMPARE, value: [], options: {}, custom_message, }; switch (rule.rule) { case "contains": modifiedRule.case = constants.rulesTypes.REGEX; modifiedRule.value = [rule[rule.rule].value]; modifiedRule.options = { type: constants.regexTypes.CONTAINS, case_sensitive: !rule[rule.rule].insensitive, multiline: false, global: false, }; break; case "doesNotContain": modifiedRule.case = constants.rulesTypes.REGEX; modifiedRule.value = [rule[rule.rule].value]; modifiedRule.options = { type: constants.regexTypes.NOT_CONTAINS, case_sensitive: !rule[rule.rule].insensitive, multiline: false, global: false, }; break; case "startsWith": modifiedRule.case = constants.rulesTypes.REGEX; modifiedRule.value = [rule[rule.rule].value]; modifiedRule.options = { type: constants.regexTypes.START_WITH, case_sensitive: !rule[rule.rule].insensitive, }; break; case "doesNotStartWith": modifiedRule.case = constants.rulesTypes.REGEX; modifiedRule.value = [rule[rule.rule].value]; modifiedRule.options = { type: constants.regexTypes.NOT_START_WITH, case_sensitive: !rule[rule.rule].insensitive, }; break; case "endsWith": modifiedRule.case = constants.rulesTypes.REGEX; modifiedRule.value = [rule[rule.rule].value]; modifiedRule.options = { type: constants.regexTypes.ENDS_WITH, case_sensitive: !rule[rule.rule].insensitive, }; break; case "doesNotEndWith": modifiedRule.case = constants.rulesTypes.REGEX; modifiedRule.value = [rule[rule.rule].value]; modifiedRule.options = { type: constants.regexTypes.NOT_ENDS_WITH, case_sensitive: !rule[rule.rule].insensitive, }; break; case "matchesRegExp": modifiedRule.case = constants.rulesTypes.REGEX; modifiedRule.value = [rule.matchesRegExp.value]; modifiedRule.options.type = constants.regexTypes.MATCH; break; case "equals": modifiedRule.case = constants.rulesTypes.OPERATOR; modifiedRule.options.operator = constants.operatorTypes.EQUAL; modifiedRule.value = [rule[rule.rule].value]; break; case "doesNotEqual": modifiedRule.case = constants.rulesTypes.OPERATOR; modifiedRule.options.operator = constants.operatorTypes.NOT_EQUAL; modifiedRule.value = [rule[rule.rule].value]; break; case "lessThan": modifiedRule.case = constants.rulesTypes.OPERATOR; modifiedRule.options.operator = constants.operatorTypes.LESS_THAN; modifiedRule.value = [rule[rule.rule].value]; break; case "lessThanOrEqualTo": modifiedRule.case = constants.rulesTypes.OPERATOR; modifiedRule.options.operator = constants.operatorTypes.LESS_THAN_EQUAL; modifiedRule.value = [rule[rule.rule].value]; break; case "greaterThan": modifiedRule.case = constants.rulesTypes.OPERATOR; modifiedRule.options.operator = constants.operatorTypes.GREATER_THAN; modifiedRule.value = [rule[rule.rule].value]; break; case "greaterThanOrEqualTo": modifiedRule.case = constants.rulesTypes.OPERATOR; modifiedRule.options.operator = constants.operatorTypes.GREATER_THAN_EQUAL; modifiedRule.value = [rule[rule.rule].value]; break; case "isEmpty": modifiedRule.case = constants.rulesTypes.EMPTY; modifiedRule.value = []; break; case "isNotEmpty": modifiedRule.case = constants.rulesTypes.NOT_EMPTY; modifiedRule.value = []; break; case "isOneOf": modifiedRule.case = constants.rulesTypes.ONE_OF; modifiedRule.value = rule[rule.rule].value; break; case "isNotOneOf": modifiedRule.case = constants.rulesTypes.NOT_ONE_OF; modifiedRule.value = [rule[rule.rule].value]; break; case constants.rulesTypes.FIELD_COMPARE: { const fieldRule = generateFieldCompareRules(rule); modifiedRule.case = fieldRule.case; modifiedRule.options = fieldRule.options; modifiedRule.value = fieldRule.value; break; } } return modifiedRule; }); } const { options } = meta; if (options?.choices?.length > 0) { const choices = options?.choices?.map((item) => item?.value); let modifiedRule = { identifier: String(rules?.length + 1), case: constants.rulesTypes.ONE_OF, value: choices, options: {}, custom_message, }; rules.push(modifiedRule); } return rules; }; const getFieldsGroupBySchemaId = (arr) => { return arr.reduce((acc, item) => { const key = item.schema_id; if (!acc[key]) { acc[key] = []; } acc[key].push(item); return acc; }, {}); }; const getDefaultValues = (tree) => { const defaultValues = {}; tree?.forEach((field) => { let { key, type, array_type, children, default_value } = field; let defaultValue = default_value !== undefined ? default_value : null; // Extract last part of the key (remove parent references) const keyParts = key.split("."); const cleanKey = keyParts[keyParts.length - 1]; if (type === "Object") { setValue(defaultValues, cleanKey, children?.length ? getDefaultValues(children) : {}); } else if (type === "Array") { if (array_type === "String" || array_type === "Number") { setValue(defaultValues, cleanKey, []); } else { // Prevent extra nesting by ensuring the array contains objects, not arrays setValue(defaultValues, cleanKey, children?.length ? [getDefaultValues(children)] : []); } } else { setValue(defaultValues, cleanKey, defaultValue); } }); return defaultValues; }; const extractRelationalParents=(path)=> { const match = path?.match(/^([^.\[\]]+)\.(create|update|delete|existing)\[(\d+)\](?:\.(.*))?/); if (match) { const secondParent = match[1]; const firstParent = match[0].split('.')[0] + '.' + match[2] + '[' + path.match(/\[(\d+)\]/)[1] + ']'; // const afterKey = match[3] || ''; return { firstParent, secondParent, afterKey }; } return null; } module.exports = { generateModifiedRules, getFieldsGroupBySchemaId, buildNestedStructure, getValue, setValue, keyExists, formatLabel, generateDynamicKeys, getLastChildKey, checkIsArrayKey, isEmpty, getParentKey, getFormPath, findDisallowedKeys, getAllFields, getForeignCollectionDetails, getDefaultValues, extractRelationalParents };