nox-validation 1.4.1 → 1.4.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/constant.js +5 -0
- package/lib/helpers.js +83 -5
- package/lib/validate.js +166 -63
- package/package.json +1 -1
package/lib/constant.js
CHANGED
|
@@ -143,6 +143,11 @@ const interfaces = Object.freeze({
|
|
|
143
143
|
FILES: "files",
|
|
144
144
|
TRANSLATIONS: "translations",
|
|
145
145
|
RELATIONAL: "relational",
|
|
146
|
+
CHECKBOX: "checkboxes",
|
|
147
|
+
ITEMS: "items",
|
|
148
|
+
MULTIPLE_DROPDOWN: "dropdown_multiple",
|
|
149
|
+
TAGS: "tags",
|
|
150
|
+
ARRAY_OF_VALUES: "array_of_values",
|
|
146
151
|
});
|
|
147
152
|
|
|
148
153
|
const API_VERSION = Object.freeze({
|
package/lib/helpers.js
CHANGED
|
@@ -59,6 +59,52 @@ const keyExists = (obj, key, separator = ".") => {
|
|
|
59
59
|
});
|
|
60
60
|
};
|
|
61
61
|
|
|
62
|
+
const remainingItems = ({ all = [], obj = {}, isM2A = false }) => {
|
|
63
|
+
const resultSet = new Set(all);
|
|
64
|
+
|
|
65
|
+
if (Array.isArray(obj.existing)) {
|
|
66
|
+
obj.existing.forEach((id) => {
|
|
67
|
+
if (isM2A && id && typeof id === "object" && "item" in id) {
|
|
68
|
+
resultSet.add(id.item);
|
|
69
|
+
} else {
|
|
70
|
+
resultSet.add(id);
|
|
71
|
+
}
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if (Array.isArray(obj.create)) {
|
|
76
|
+
obj.create.forEach((item, idx) => {
|
|
77
|
+
if (item && item._id) {
|
|
78
|
+
resultSet.add(item._id);
|
|
79
|
+
} else if (item) {
|
|
80
|
+
resultSet.add(`create_${idx}`);
|
|
81
|
+
}
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
if (Array.isArray(obj.update)) {
|
|
86
|
+
obj.update.forEach((item) => {
|
|
87
|
+
if (isM2A && item && item.item) {
|
|
88
|
+
resultSet.add(item.item);
|
|
89
|
+
} else if (item && item._id) {
|
|
90
|
+
resultSet.add(item._id);
|
|
91
|
+
}
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
if (Array.isArray(obj.delete)) {
|
|
96
|
+
obj.delete.forEach((id) => {
|
|
97
|
+
if (isM2A && id && typeof id === "object" && "item" in id) {
|
|
98
|
+
resultSet.delete(id.item);
|
|
99
|
+
} else {
|
|
100
|
+
resultSet.delete(id);
|
|
101
|
+
}
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
return resultSet.size > 0;
|
|
106
|
+
};
|
|
107
|
+
|
|
62
108
|
const formatLabel = (str) => {
|
|
63
109
|
return str
|
|
64
110
|
?.replace(/_/g, " ")
|
|
@@ -275,6 +321,7 @@ const generateField = (
|
|
|
275
321
|
...child,
|
|
276
322
|
value: childKey,
|
|
277
323
|
key: childValue,
|
|
324
|
+
isRelationalUpdate: path.includes("update"),
|
|
278
325
|
};
|
|
279
326
|
});
|
|
280
327
|
|
|
@@ -780,7 +827,7 @@ const buildNestedStructure = ({
|
|
|
780
827
|
childFields = getCachedFields(relationDetail, relational_fields);
|
|
781
828
|
}
|
|
782
829
|
if (item.meta.interface === constants.interfaces.TRANSLATIONS) {
|
|
783
|
-
childFields = childFields.map(f => {
|
|
830
|
+
childFields = childFields.map((f) => {
|
|
784
831
|
if (
|
|
785
832
|
f.field === relationDetail.foreign_field ||
|
|
786
833
|
f.field === relationDetail.junction_field_this
|
|
@@ -789,8 +836,8 @@ const buildNestedStructure = ({
|
|
|
789
836
|
...f,
|
|
790
837
|
meta: {
|
|
791
838
|
...f.meta,
|
|
792
|
-
interface: "none"
|
|
793
|
-
}
|
|
839
|
+
interface: "none",
|
|
840
|
+
},
|
|
794
841
|
};
|
|
795
842
|
}
|
|
796
843
|
return f;
|
|
@@ -926,7 +973,7 @@ const buildNestedStructure = ({
|
|
|
926
973
|
|
|
927
974
|
if (parentNode) {
|
|
928
975
|
parentNode.children.push(node);
|
|
929
|
-
}
|
|
976
|
+
}
|
|
930
977
|
// else {
|
|
931
978
|
// console.warn("[WARN] Parent node not found for:", node);
|
|
932
979
|
// }
|
|
@@ -1325,7 +1372,37 @@ const getSelectedNodes = ({
|
|
|
1325
1372
|
|
|
1326
1373
|
if (Array.isArray(currentNode.children)) {
|
|
1327
1374
|
currentNode.children.forEach((child) => {
|
|
1328
|
-
|
|
1375
|
+
let childNode = {
|
|
1376
|
+
...child,
|
|
1377
|
+
key:
|
|
1378
|
+
currentNode.type === constants.types.ARRAY
|
|
1379
|
+
? child.key.replace(currentNode.key, `${currentNode.key}[0]`)
|
|
1380
|
+
: child.key,
|
|
1381
|
+
};
|
|
1382
|
+
|
|
1383
|
+
if (Array.isArray(child.children) && child.children.length > 0) {
|
|
1384
|
+
const updateChildKeys = (node, parentKey, newParentKey) => {
|
|
1385
|
+
let updatedNode = {
|
|
1386
|
+
...node,
|
|
1387
|
+
key: node.key.replace(parentKey, newParentKey),
|
|
1388
|
+
};
|
|
1389
|
+
if (Array.isArray(node.children) && node.children.length > 0) {
|
|
1390
|
+
updatedNode.children = node.children.map((c) =>
|
|
1391
|
+
updateChildKeys(c, parentKey, newParentKey)
|
|
1392
|
+
);
|
|
1393
|
+
}
|
|
1394
|
+
return updatedNode;
|
|
1395
|
+
};
|
|
1396
|
+
if (currentNode.type === constants.types.ARRAY) {
|
|
1397
|
+
childNode = updateChildKeys(
|
|
1398
|
+
child,
|
|
1399
|
+
currentNode.key,
|
|
1400
|
+
`${currentNode.key}[0]`
|
|
1401
|
+
);
|
|
1402
|
+
}
|
|
1403
|
+
}
|
|
1404
|
+
|
|
1405
|
+
if (!skipFn(currentNode)) traverse(childNode);
|
|
1329
1406
|
});
|
|
1330
1407
|
}
|
|
1331
1408
|
}
|
|
@@ -1355,4 +1432,5 @@ module.exports = {
|
|
|
1355
1432
|
extractRelationalParents,
|
|
1356
1433
|
getM2AItemParentPath,
|
|
1357
1434
|
getSelectedNodes,
|
|
1435
|
+
remainingItems,
|
|
1358
1436
|
};
|
package/lib/validate.js
CHANGED
|
@@ -11,6 +11,7 @@ const {
|
|
|
11
11
|
extractRelationalParents,
|
|
12
12
|
getM2AItemParentPath,
|
|
13
13
|
getSelectedNodes,
|
|
14
|
+
remainingItems,
|
|
14
15
|
} = require("./helpers");
|
|
15
16
|
|
|
16
17
|
const choices = ["radio", "checkboxes", "dropdown_multiple", "dropdown"];
|
|
@@ -60,9 +61,16 @@ const typeChecks = {
|
|
|
60
61
|
[constants.types.TIME]: (val) =>
|
|
61
62
|
typeof val === "string" &&
|
|
62
63
|
/^([01]\d|2[0-3]):([0-5]\d):([0-5]\d)$/.test(val),
|
|
63
|
-
[constants.types.TIMESTAMP]: (val) =>
|
|
64
|
-
|
|
65
|
-
|
|
64
|
+
[constants.types.TIMESTAMP]: (val, data) => {
|
|
65
|
+
if (val instanceof Date && !isNaN(val)) return true;
|
|
66
|
+
if (typeof val === "string" && !isNaN(Date.parse(val))) {
|
|
67
|
+
if (data && data?.key && data?.updateValue) {
|
|
68
|
+
data.updateValue(data.key, new Date(val));
|
|
69
|
+
}
|
|
70
|
+
return true;
|
|
71
|
+
}
|
|
72
|
+
return false;
|
|
73
|
+
},
|
|
66
74
|
|
|
67
75
|
[constants.types.BOOLEAN]: (val, data) => {
|
|
68
76
|
if (typeof val === "boolean") return true;
|
|
@@ -164,36 +172,70 @@ const handleMinMaxValidation = (
|
|
|
164
172
|
}
|
|
165
173
|
};
|
|
166
174
|
|
|
167
|
-
const isEmptyRelational = ({
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
175
|
+
const isEmptyRelational = ({
|
|
176
|
+
api_version,
|
|
177
|
+
value,
|
|
178
|
+
interface,
|
|
179
|
+
onlyFormFields,
|
|
180
|
+
existingValue,
|
|
181
|
+
}) => {
|
|
182
|
+
if (!onlyFormFields) {
|
|
183
|
+
if (
|
|
184
|
+
api_version === constants.API_VERSION.V1 &&
|
|
185
|
+
interface !== constants.interfaces.TRANSLATIONS
|
|
186
|
+
) {
|
|
187
|
+
if (interface === constants.interfaces.MANY_TO_ANY) {
|
|
188
|
+
return (
|
|
189
|
+
value &&
|
|
190
|
+
typeChecks[constants.types.OBJECT](value) &&
|
|
191
|
+
value.hasOwnProperty("collection") &&
|
|
192
|
+
value.hasOwnProperty("sort") &&
|
|
193
|
+
value.hasOwnProperty("item") &&
|
|
194
|
+
value.collection !== null &&
|
|
195
|
+
value.collection !== "" &&
|
|
196
|
+
value.sort !== null &&
|
|
197
|
+
value.sort !== "" &&
|
|
198
|
+
value.item !== null &&
|
|
199
|
+
value.item !== ""
|
|
200
|
+
);
|
|
201
|
+
} else {
|
|
202
|
+
return value?.length > 0;
|
|
203
|
+
}
|
|
204
|
+
} else {
|
|
173
205
|
return (
|
|
174
|
-
value
|
|
175
|
-
|
|
176
|
-
value
|
|
177
|
-
value.hasOwnProperty("sort") &&
|
|
178
|
-
value.hasOwnProperty("item") &&
|
|
179
|
-
value.collection !== null &&
|
|
180
|
-
value.collection !== "" &&
|
|
181
|
-
value.sort !== null &&
|
|
182
|
-
value.sort !== "" &&
|
|
183
|
-
value.item !== null &&
|
|
184
|
-
value.item !== ""
|
|
206
|
+
value?.create?.length > 0 ||
|
|
207
|
+
value?.existing?.length > 0 ||
|
|
208
|
+
value?.update?.length > 0
|
|
185
209
|
);
|
|
186
|
-
}
|
|
187
|
-
|
|
210
|
+
}
|
|
211
|
+
} else if (api_version === constants.API_VERSION.V2) {
|
|
212
|
+
if (isEmpty(existingValue)) return true;
|
|
213
|
+
|
|
214
|
+
switch (interface) {
|
|
215
|
+
case constants.interfaces.FILE:
|
|
216
|
+
case constants.interfaces.FILE_IMAGE:
|
|
217
|
+
case constants.interfaces.FILES:
|
|
218
|
+
case constants.interfaces.MANY_TO_MANY:
|
|
219
|
+
case constants.interfaces.ONE_TO_MANY:
|
|
220
|
+
case constants.interfaces.MANY_TO_ONE:
|
|
221
|
+
return remainingItems({
|
|
222
|
+
all: existingValue.map((item) => item._id),
|
|
223
|
+
obj: value,
|
|
224
|
+
});
|
|
225
|
+
case constants.interfaces.MANY_TO_ANY:
|
|
226
|
+
return remainingItems({
|
|
227
|
+
all: existingValue.map((item) => item.item),
|
|
228
|
+
obj: value,
|
|
229
|
+
isM2A: true,
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
// constants.interfaces.TRANSLATIONS,
|
|
233
|
+
default:
|
|
234
|
+
return true;
|
|
188
235
|
}
|
|
189
236
|
} else {
|
|
190
|
-
return
|
|
191
|
-
value?.create?.length > 0 ||
|
|
192
|
-
value?.existing?.length > 0 ||
|
|
193
|
-
value?.update?.length > 0
|
|
194
|
-
);
|
|
237
|
+
return true;
|
|
195
238
|
}
|
|
196
|
-
return false;
|
|
197
239
|
};
|
|
198
240
|
|
|
199
241
|
const validateMetaRules = (
|
|
@@ -207,12 +249,16 @@ const validateMetaRules = (
|
|
|
207
249
|
apiVersion,
|
|
208
250
|
fieldOptions,
|
|
209
251
|
formData,
|
|
210
|
-
language_codes = []
|
|
252
|
+
language_codes = [],
|
|
253
|
+
existingForm = {}
|
|
211
254
|
) => {
|
|
212
255
|
const fieldValue = providedValue;
|
|
213
256
|
|
|
214
257
|
const { required = false, nullable = false, options } = field?.meta ?? {};
|
|
215
258
|
|
|
259
|
+
if (field.isRelationalUpdate) {
|
|
260
|
+
onlyFormFields = true;
|
|
261
|
+
}
|
|
216
262
|
const isRelational = relational_interfaces.includes(field?.meta?.interface);
|
|
217
263
|
|
|
218
264
|
if (
|
|
@@ -235,16 +281,53 @@ const validateMetaRules = (
|
|
|
235
281
|
);
|
|
236
282
|
}
|
|
237
283
|
|
|
284
|
+
const arrayInterfaces = [
|
|
285
|
+
constants.interfaces.CHECKBOX,
|
|
286
|
+
constants.interfaces.ITEMS,
|
|
287
|
+
constants.interfaces.MULTIPLE_DROPDOWN,
|
|
288
|
+
constants.interfaces.TAGS,
|
|
289
|
+
constants.interfaces.ARRAY_OF_VALUES,
|
|
290
|
+
];
|
|
291
|
+
|
|
292
|
+
if (
|
|
293
|
+
required &&
|
|
294
|
+
arrayInterfaces.includes(field?.meta?.interface) &&
|
|
295
|
+
onlyFormFields &&
|
|
296
|
+
fieldValue &&
|
|
297
|
+
Array.isArray(fieldValue) &&
|
|
298
|
+
fieldValue?.length === 0
|
|
299
|
+
) {
|
|
300
|
+
const message = error_messages.NOT_EMPTY.replace(
|
|
301
|
+
`{field}`,
|
|
302
|
+
formatLabel(field.display_label)
|
|
303
|
+
);
|
|
304
|
+
addError(
|
|
305
|
+
currentPath,
|
|
306
|
+
{
|
|
307
|
+
label: formatLabel(field.display_label),
|
|
308
|
+
fieldPath: currentPath,
|
|
309
|
+
description: "",
|
|
310
|
+
message,
|
|
311
|
+
},
|
|
312
|
+
field
|
|
313
|
+
);
|
|
314
|
+
}
|
|
315
|
+
|
|
238
316
|
const isValidRelational =
|
|
239
|
-
required && isRelational
|
|
317
|
+
required && isRelational
|
|
240
318
|
? isEmptyRelational({
|
|
241
319
|
api_version: apiVersion,
|
|
242
320
|
value: fieldValue,
|
|
243
321
|
interface: field?.meta?.interface,
|
|
322
|
+
onlyFormFields,
|
|
323
|
+
existingValue: getValue(existingForm, currentPath),
|
|
244
324
|
})
|
|
245
325
|
: true;
|
|
246
326
|
|
|
247
|
-
if (
|
|
327
|
+
if (
|
|
328
|
+
(required && !onlyFormFields && isEmpty(fieldValue)) ||
|
|
329
|
+
!isValidRelational
|
|
330
|
+
) {
|
|
248
331
|
const message = error_messages.REQUIRED.replace(
|
|
249
332
|
`{field}`,
|
|
250
333
|
formatLabel(field.display_label)
|
|
@@ -279,18 +362,21 @@ const validateMetaRules = (
|
|
|
279
362
|
}
|
|
280
363
|
|
|
281
364
|
if (
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
365
|
+
(isEmpty(fieldValue) &&
|
|
366
|
+
!field?.meta?.hidden &&
|
|
367
|
+
![
|
|
368
|
+
constants.interfaces.FILES,
|
|
369
|
+
constants.interfaces.FILE,
|
|
370
|
+
constants.interfaces.FILE_IMAGE,
|
|
371
|
+
constants.interfaces.MANY_TO_MANY,
|
|
372
|
+
constants.interfaces.ONE_TO_MANY,
|
|
373
|
+
constants.interfaces.MANY_TO_ONE,
|
|
374
|
+
constants.interfaces.MANY_TO_ANY,
|
|
375
|
+
"none",
|
|
376
|
+
].includes(field?.meta?.interface)) ||
|
|
377
|
+
(field?.meta?.interface === constants.interfaces.TRANSLATIONS &&
|
|
378
|
+
language_codes?.length &&
|
|
379
|
+
!onlyFormFields)
|
|
294
380
|
) {
|
|
295
381
|
const isTranslationChild =
|
|
296
382
|
field?.meta?.interface === constants.interfaces.TRANSLATIONS;
|
|
@@ -299,11 +385,14 @@ const validateMetaRules = (
|
|
|
299
385
|
node: field,
|
|
300
386
|
skipFn: (node) =>
|
|
301
387
|
node.meta?.interface === constants.interfaces.MANY_TO_ANY,
|
|
302
|
-
conditionFn: (node) =>
|
|
303
|
-
|
|
388
|
+
conditionFn: (node) =>
|
|
389
|
+
node.meta?.required === true && !node.isRelationalUpdate,
|
|
390
|
+
mapFn: (node) => ({
|
|
391
|
+
key: node.key,
|
|
392
|
+
label: node.display_label,
|
|
393
|
+
}),
|
|
304
394
|
actionFn: (node) => {
|
|
305
395
|
let fPath = node.key;
|
|
306
|
-
|
|
307
396
|
if (fPath.includes("update.")) return;
|
|
308
397
|
if (fPath.includes("create.") && !isTranslationChild) return;
|
|
309
398
|
if (fPath.includes("delete.") && !isTranslationChild) return;
|
|
@@ -332,16 +421,31 @@ const validateMetaRules = (
|
|
|
332
421
|
|
|
333
422
|
if (isMultiLang) {
|
|
334
423
|
language_codes.forEach((lang, index) => {
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
424
|
+
let langPath,
|
|
425
|
+
transformedPath,
|
|
426
|
+
isAdded = false;
|
|
427
|
+
|
|
428
|
+
if (fPath.includes("create[0].")) {
|
|
429
|
+
const exist = fieldValue?.create?.find(
|
|
430
|
+
(item) => item.languages_code === lang
|
|
431
|
+
);
|
|
432
|
+
if (exist) isAdded = true;
|
|
433
|
+
|
|
434
|
+
langPath = fPath.replace("create[0].", `create[${index}].`);
|
|
435
|
+
transformedPath = fPath.replace("create[0].", `create[${lang}].`);
|
|
436
|
+
} else {
|
|
437
|
+
langPath = fPath.replace("create.", `create[${index}].`);
|
|
438
|
+
transformedPath = fPath.replace("create.", `create[${lang}].`);
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
if (!isAdded) buildError(langPath, transformedPath);
|
|
338
442
|
});
|
|
339
443
|
} else {
|
|
340
444
|
const singlePath = fPath.replace(
|
|
341
445
|
"create.",
|
|
342
446
|
isTranslationChild ? "create[lang_code]." : "create[0]."
|
|
343
447
|
);
|
|
344
|
-
buildError(singlePath);
|
|
448
|
+
if (isEmpty(fieldValue)) buildError(singlePath);
|
|
345
449
|
}
|
|
346
450
|
},
|
|
347
451
|
});
|
|
@@ -789,16 +893,10 @@ const validateField = (
|
|
|
789
893
|
onlyFormFields,
|
|
790
894
|
apiVersion,
|
|
791
895
|
fieldOptions,
|
|
792
|
-
language_codes
|
|
896
|
+
language_codes,
|
|
897
|
+
existingForm
|
|
793
898
|
) => {
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
if (
|
|
797
|
-
onlyFormFields == true &&
|
|
798
|
-
isRelational &&
|
|
799
|
-
(value === undefined || value === null)
|
|
800
|
-
)
|
|
801
|
-
return;
|
|
899
|
+
if (onlyFormFields == true && (value === undefined || value === null)) return;
|
|
802
900
|
|
|
803
901
|
const { is_m2a_item } = field?.meta;
|
|
804
902
|
const currentPath = fieldPath
|
|
@@ -818,7 +916,8 @@ const validateField = (
|
|
|
818
916
|
apiVersion,
|
|
819
917
|
fieldOptions,
|
|
820
918
|
formData,
|
|
821
|
-
language_codes
|
|
919
|
+
language_codes,
|
|
920
|
+
existingForm
|
|
822
921
|
);
|
|
823
922
|
|
|
824
923
|
if (
|
|
@@ -854,7 +953,8 @@ const validateField = (
|
|
|
854
953
|
onlyFormFields,
|
|
855
954
|
apiVersion,
|
|
856
955
|
fieldOptions,
|
|
857
|
-
language_codes
|
|
956
|
+
language_codes,
|
|
957
|
+
existingForm
|
|
858
958
|
)
|
|
859
959
|
);
|
|
860
960
|
} else if (field.type === constants.types.ARRAY && Array.isArray(value)) {
|
|
@@ -905,7 +1005,8 @@ const validateField = (
|
|
|
905
1005
|
onlyFormFields,
|
|
906
1006
|
apiVersion,
|
|
907
1007
|
fieldOptions,
|
|
908
|
-
language_codes
|
|
1008
|
+
language_codes,
|
|
1009
|
+
existingForm
|
|
909
1010
|
)
|
|
910
1011
|
);
|
|
911
1012
|
});
|
|
@@ -1012,6 +1113,7 @@ const validate = (data) => {
|
|
|
1012
1113
|
language,
|
|
1013
1114
|
maxLevel,
|
|
1014
1115
|
onlyFormFields,
|
|
1116
|
+
existingForm = {},
|
|
1015
1117
|
} = data;
|
|
1016
1118
|
|
|
1017
1119
|
const error_messages =
|
|
@@ -1262,7 +1364,8 @@ const validate = (data) => {
|
|
|
1262
1364
|
onlyFormFields,
|
|
1263
1365
|
apiVersion,
|
|
1264
1366
|
fieldOptions,
|
|
1265
|
-
data.language_codes
|
|
1367
|
+
data.language_codes,
|
|
1368
|
+
existingForm
|
|
1266
1369
|
);
|
|
1267
1370
|
});
|
|
1268
1371
|
return result;
|