nox-validation
Version:
validate dynamic schema
1,202 lines (1,098 loc) • 35.6 kB
JavaScript
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
};