nox-validation 1.0.1 → 1.0.2

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/README.md CHANGED
@@ -11,12 +11,15 @@ npm install nox-validation
11
11
  ## Library Exports
12
12
 
13
13
  ### **Validation Functions:**
14
+
14
15
  - `validate` - The core function for validating data.
15
16
 
16
17
  ### **Constants:**
18
+
17
19
  - `CONSTANTS` - A collection of predefined constants (likely including data types, error messages, validation rules, etc.).
18
20
 
19
21
  ### **Helper Functions (`HELPERS`):**
22
+
20
23
  - `validateSingleField` - Validates a single field against defined rules.
21
24
  - `validateType` - Performs type checks for data validation.
22
25
  - `fieldsMapping` - Groups fields by `schemaId` for structured access.
@@ -26,7 +29,6 @@ npm install nox-validation
26
29
 
27
30
  ## Usage
28
31
 
29
-
30
32
  ```javascript
31
33
  const { validate, helpers } = require("nox-validation");
32
34
 
@@ -91,10 +93,10 @@ const schema = [
91
93
  },
92
94
  },
93
95
  ];
94
-
95
96
  ```
96
97
 
97
98
  #### When `isSeparatedFields` is `true`
99
+
98
100
  ```javascript
99
101
  const result = validate({
100
102
  formData: {
@@ -109,10 +111,12 @@ const result = validate({
109
111
  abortEarly: false,
110
112
  byPassKeys: [],
111
113
  apiVersion: "v1",
114
+ language: "nl",
112
115
  });
113
116
  ```
114
117
 
115
118
  #### When `isSeparatedFields` is `false`
119
+
116
120
  ```javascript
117
121
  const result = validate({
118
122
  formData: {
@@ -127,10 +131,12 @@ const result = validate({
127
131
  abortEarly: false,
128
132
  byPassKeys: [],
129
133
  apiVersion: "v1",
134
+ language: "nl",
130
135
  });
131
136
  ```
132
137
 
133
138
  #### Output Example:
139
+
134
140
  ```javascript
135
141
  {
136
142
  status: true, // Validation passed
@@ -190,6 +196,11 @@ const result = validate({
190
196
  - Specifies the API version (`"v1"` or `"v2"`).
191
197
  - Helps in maintaining compatibility and version control.
192
198
 
199
+ #### `language` (String)
200
+
201
+ - Specifies the locale language (`"nl"` or `"en"`).
202
+ - Determines the locale for validation messages.
203
+
193
204
  ## Result
194
205
 
195
206
  The `validate` function returns an object with two keys:
package/lib/constant.js CHANGED
@@ -151,6 +151,104 @@ const API_VERSION = Object.freeze({
151
151
 
152
152
  const API_VERSIONS = Object.freeze(Object.values(API_VERSION));
153
153
 
154
+ const LANGUAGES = Object.freeze({
155
+ en: "en",
156
+ nl: "nl",
157
+ });
158
+
159
+ const LOCALE_NL_MESSAGES = Object.freeze({
160
+ INVALID_VALUE: `{field} bevat een ongeldige waarde.`,
161
+ EMPTY: `{field} moet leeg zijn.`,
162
+ NOT_EMPTY: `{field} mag niet leeg zijn.`,
163
+ ONE_OF: `{field} moet een van de volgende waarden zijn: {value}.`,
164
+ NOT_ONE_OF: `{field} mag niet een van de volgende waarden zijn: {value}.`,
165
+ REQUIRED: `{field} is verplicht.`,
166
+ OPTIONAL: `{field} is optioneel.`,
167
+ NOT_NULLABLE: `{field} mag niet null zijn.`,
168
+ INVALID_TYPE: "{field} bevat een ongeldig invoertype. Alleen {type} is toegestaan.",
169
+ NOT_ALLOWED_FIELD: `{field} is niet toegestaan.`,
170
+ COMPARE: `{field} komt niet overeen met de verwachte waarde.`,
171
+ RULES_COMPARE: `{field} voldoet niet aan de vergelijkingsregels.`,
172
+ ALLOW: `{field} bevat alleen toegestane tekens.`,
173
+ NOT_ALLOWED: `{field} bevat niet-toegestane tekens.`,
174
+ REGEX_MATCH: `{field} heeft een ongeldig formaat.`,
175
+ REGEX_START_WITH: `{field} moet beginnen met {value}.`,
176
+ REGEX_ENDS_WITH: `{field} moet eindigen op {value}.`,
177
+ REGEX_CONTAINS: `{field} moet {value} bevatten.`,
178
+ REGEX_EXACT: `{field} moet exact {value} zijn.`,
179
+ REGEX_NOT_START_WITH: `{field} mag niet beginnen met {value}.`,
180
+ REGEX_NOT_ENDS_WITH: `{field} mag niet eindigen op {value}.`,
181
+ REGEX_NOT_CONTAINS: `{field} mag {value} niet bevatten.`,
182
+ MIN_STRING: `{field} moet minstens {min} tekens lang zijn.`,
183
+ MAX_STRING: `{field} mag maximaal {max} tekens lang zijn.`,
184
+ MIN_NUMBER: `{field} moet minstens {min} zijn.`,
185
+ MAX_NUMBER: `{field} mag maximaal {max} zijn.`,
186
+ AND: "{field} moet een van {value} zijn.",
187
+ OR: "{field} moet een van {value} zijn.",
188
+ EQUAL: "{field} moet gelijk zijn aan {value}.",
189
+ NOT_EQUAL: "{field} mag niet gelijk zijn aan {value}.",
190
+ LESS_THAN: "{field} moet kleiner zijn dan {value}.",
191
+ LESS_THAN_EQUAL: "{field} moet kleiner dan of gelijk zijn aan {value}.",
192
+ GREATER_THAN: "{field} moet groter zijn dan {value}.",
193
+ GREATER_THAN_EQUAL: "{field} moet groter dan of gelijk zijn aan {value}.",
194
+ IN: "{field} moet een van de volgende waarden zijn: {value}.",
195
+ NOT_IN: "{field} mag geen van de volgende waarden bevatten: {value}.",
196
+ EXISTS: "{field} moet bestaan.",
197
+ TYPE: "{field} moet van type {value} zijn.",
198
+ MOD: "{field} moet een restwaarde van {value[1]} hebben wanneer gedeeld door {value[0]}.",
199
+ ALL: "{field} moet alle van de volgende waarden bevatten: {value}.",
200
+ SIZE: "{field} moet een grootte van {value} hebben.",
201
+ });
202
+
203
+ const LOCALE_EN_MESSAGES = Object.freeze({
204
+ INVALID_VALUE: `{field} contains invalid value.`,
205
+ EMPTY: `{field} should be empty.`,
206
+ NOT_EMPTY: `{field} should not be empty.`,
207
+ ONE_OF: `{field} should be one of the following: {value}.`,
208
+ NOT_ONE_OF: `{field} should not be one of the following: {value}.`,
209
+ REQUIRED: `{field} is required.`,
210
+ OPTIONAL: `{field} is optional.`,
211
+ NOT_NULLABLE: `{field} can not be null.`,
212
+ INVALID_TYPE: "{field} contains invalid input type. Only {type} is allowed.",
213
+ NOT_ALLOWED_FIELD: `{field} is not allowed.`,
214
+ COMPARE: `{field} does not match the expected value.`,
215
+ RULES_COMPARE: `{field} does not meet the comparison rules.`,
216
+ ALLOW: `{field} contains allowed characters only.`,
217
+ NOT_ALLOWED: `{field} contains disallowed characters.`,
218
+ REGEX_MATCH: `{field} format is invalid.`,
219
+ REGEX_START_WITH: `{field} should start with {value}.`,
220
+ REGEX_ENDS_WITH: `{field} should end with {value}.`,
221
+ REGEX_CONTAINS: `{field} should contain {value}.`,
222
+ REGEX_EXACT: `{field} should be exactly {value}.`,
223
+ REGEX_NOT_START_WITH: `{field} should not be start with {value}.`,
224
+ REGEX_NOT_ENDS_WITH: `{field} should not be ends with {value}.`,
225
+ REGEX_NOT_CONTAINS: `{field} should not contains {value}.`,
226
+ MIN_STRING: `{field} must be at least {min} characters long.`,
227
+ MAX_STRING: `{field} must be at most {max} characters long.`,
228
+ MIN_NUMBER: `{field} must be at least {min}.`,
229
+ MAX_NUMBER: `{field} must be at most {max}.`,
230
+ AND: "{field} must be any of {value}",
231
+ OR: "{field} must be any of {value}",
232
+ EQUAL: "{field} must be equal to {value}",
233
+ NOT_EQUAL: "{field} must not be equal to {value}",
234
+ LESS_THAN: "{field} must be less than {value}",
235
+ LESS_THAN_EQUAL: "{field} must be less than or equal to {value}",
236
+ GREATER_THAN: "{field} must be greater than {value}",
237
+ GREATER_THAN_EQUAL: "{field} must be greater than or equal to {value}",
238
+ IN: "{field} must be one of the following: {value}",
239
+ NOT_IN: "{field} must not contain any of these values: {value}",
240
+ EXISTS: "{field} must exist",
241
+ TYPE: "{field} must be of type {value}",
242
+ MOD: "{field} must have a remainder of {value[1]} when divided by {value[0]}",
243
+ ALL: "{field} must contain all of the following values: {value}",
244
+ SIZE: "{field} must have a size of {value}",
245
+ });
246
+
247
+ const LOCALE_MESSAGES = Object.freeze({
248
+ [LANGUAGES.en]:LOCALE_EN_MESSAGES,
249
+ [LANGUAGES.nl]:LOCALE_NL_MESSAGES
250
+ })
251
+
154
252
  module.exports = {
155
253
  dataTypes,
156
254
  operatorTypes,
@@ -162,4 +260,6 @@ module.exports = {
162
260
  interfaces,
163
261
  API_VERSIONS,
164
262
  API_VERSION,
263
+ LANGUAGES,
264
+ LOCALE_MESSAGES
165
265
  };
package/lib/helpers.js CHANGED
@@ -293,12 +293,7 @@ const generateRelationalFieldV1 = (key = "", collectionFields = [], relationType
293
293
  return [
294
294
  generateField("collection", "collection", constants.types.STRING, constants.types.STRING),
295
295
  generateField("sort", "sort", constants.types.NUMBER, constants.types.NUMBER),
296
- generateField(
297
- "item",
298
- "item",
299
- constants.types.OBJECT_ID,
300
- constants.types.OBJECT_ID,
301
- ),
296
+ generateField("item", "item", constants.types.OBJECT_ID, constants.types.OBJECT_ID),
302
297
  ];
303
298
  } else {
304
299
  return [];
@@ -556,16 +551,15 @@ const getCachedOrFetchFields = (schemaId, allFields, relational_fields) => {
556
551
 
557
552
  const getChildFields = (relationDetail, allFields, relational_fields, isTranslation) => {
558
553
  let key = isTranslation
559
- ? [
560
- ...(Array.isArray(relationDetail.junction_collection)
561
- ? relationDetail.junction_collection
562
- : [relationDetail.junction_collection]),
563
- ...(Array.isArray(relationDetail.foreign_collection)
564
- ? relationDetail.foreign_collection
565
- : [relationDetail.foreign_collection])
566
- ]
567
- : relationDetail.foreign_collection;
568
-
554
+ ? [
555
+ ...(Array.isArray(relationDetail.junction_collection)
556
+ ? relationDetail.junction_collection
557
+ : [relationDetail.junction_collection]),
558
+ ...(Array.isArray(relationDetail.foreign_collection)
559
+ ? relationDetail.foreign_collection
560
+ : [relationDetail.foreign_collection]),
561
+ ]
562
+ : relationDetail.foreign_collection;
569
563
 
570
564
  const isMultiple = Array.isArray(key);
571
565
 
@@ -647,8 +641,9 @@ const buildNestedStructure = (
647
641
  },
648
642
  validations:
649
643
  item?.meta?.validations?.rules?.length > 0
650
- ? generateModifiedRules(item?.meta, formData)
644
+ ? generateModifiedRules(item?.meta, item?.meta?.validations?.validation_msg)
651
645
  : [],
646
+ custom_error_message: item?.meta?.validations?.validation_msg,
652
647
  children:
653
648
  childFields?.length > 0
654
649
  ? apiVersion === constants.API_VERSION.V1 &&
@@ -826,13 +821,14 @@ const generateFieldCompareRules = (rule) => {
826
821
  return modifiedRule;
827
822
  };
828
823
 
829
- const generateModifiedRules = (meta) => {
824
+ const generateModifiedRules = (meta,custom_message) => {
830
825
  return meta?.validations?.rules?.map((rule, index) => {
831
826
  let modifiedRule = {
832
827
  identifier: String(index),
833
828
  case: constants.rulesTypes.RULES_COMPARE,
834
829
  value: [],
835
830
  options: {},
831
+ custom_message
836
832
  };
837
833
  switch (rule.rule) {
838
834
  case "contains":
package/lib/validate.js CHANGED
@@ -65,14 +65,11 @@ const typeChecks = {
65
65
  },
66
66
 
67
67
  [constants.types.TIME]: (val) =>
68
- typeof val === "string" &&
69
- /^([01]\d|2[0-3]):([0-5]\d):([0-5]\d)$/.test(val),
68
+ typeof val === "string" && /^([01]\d|2[0-3]):([0-5]\d):([0-5]\d)$/.test(val),
70
69
  [constants.types.STRING]: (val) => typeof val === "string",
71
- [constants.types.OBJECT]: (val) =>
72
- typeof val === "object" && val !== null && !Array.isArray(val),
70
+ [constants.types.OBJECT]: (val) => typeof val === "object" && val !== null && !Array.isArray(val),
73
71
  [constants.types.ARRAY]: (val) => Array.isArray(val),
74
- [constants.types.OBJECT_ID]: (val) =>
75
- typeof val === "string" && /^[0-9a-fA-F]{24}$/.test(val),
72
+ [constants.types.OBJECT_ID]: (val) => typeof val === "string" && /^[0-9a-fA-F]{24}$/.test(val),
76
73
  [constants.types.MIXED]: (val) => (val ? true : false),
77
74
  [constants.types.BUFFER]: (val) => val instanceof Buffer,
78
75
  [constants.types.ALIAS]: (val) => (val ? true : false),
@@ -85,6 +82,8 @@ const handleMinMaxValidation = (
85
82
  field,
86
83
  addError,
87
84
  currentPath,
85
+ error_messages,
86
+ custom_message
88
87
  ) => {
89
88
  let message = "";
90
89
  const fieldLabel = formatLabel(field.display_label);
@@ -95,19 +94,15 @@ const handleMinMaxValidation = (
95
94
  typeChecks[constants.types.STRING](fieldValue) &&
96
95
  fieldValue?.length < ruleValue[0]
97
96
  ) {
98
- message = constants.rulesMessages.MIN_STRING.replace(
99
- `{field}`,
100
- fieldLabel,
101
- ).replace(`{min}`, ruleValue[0]);
97
+ message = custom_message ?? error_messages.MIN_STRING;
98
+ message = message?.replace(`{field}`, fieldLabel).replace(`{min}`, ruleValue[0]);
102
99
  } else if (
103
100
  field.type === constants.types.NUMBER &&
104
101
  typeChecks[constants.types.NUMBER](fieldValue) &&
105
102
  fieldValue < ruleValue[0]
106
103
  ) {
107
- message = constants.rulesMessages.MIN_NUMBER.replace(
108
- `{field}`,
109
- fieldLabel,
110
- ).replace(`{min}`, ruleValue[0]);
104
+ message = custom_message ?? error_messages.MIN_NUMBER;
105
+ message = message?.replace(`{field}`, fieldLabel).replace(`{min}`, ruleValue[0]);
111
106
  }
112
107
  }
113
108
 
@@ -117,19 +112,15 @@ const handleMinMaxValidation = (
117
112
  typeChecks[constants.types.STRING](fieldValue) &&
118
113
  fieldValue?.length > ruleValue[0]
119
114
  ) {
120
- message = constants.rulesMessages.MAX_STRING.replace(
121
- `{field}`,
122
- fieldLabel,
123
- ).replace(`{max}`, ruleValue[0]);
115
+ message = custom_message ?? error_messages.MAX_STRING;
116
+ message = message?.replace(`{field}`, fieldLabel).replace(`{max}`, ruleValue[0]);
124
117
  } else if (
125
118
  field.type === constants.types.NUMBER &&
126
119
  typeChecks[constants.types.NUMBER](fieldValue) &&
127
120
  fieldValue > ruleValue[0]
128
121
  ) {
129
- message = constants.rulesMessages.MAX_NUMBER.replace(
130
- `{field}`,
131
- fieldLabel,
132
- ).replace(`{max}`, ruleValue[0]);
122
+ message = custom_message ?? error_messages.MAX_NUMBER;
123
+ message = message?.replace(`{field}`, fieldLabel).replace(`{max}`, ruleValue[0]);
133
124
  }
134
125
  }
135
126
 
@@ -137,7 +128,7 @@ const handleMinMaxValidation = (
137
128
  addError(
138
129
  currentPath,
139
130
  { label: fieldLabel, fieldPath: currentPath, description: "", message },
140
- field,
131
+ field
141
132
  );
142
133
  }
143
134
  };
@@ -148,15 +139,13 @@ const validateMetaRules = (
148
139
  providedValue,
149
140
  currentPath,
150
141
  updateValue,
142
+ error_messages
151
143
  ) => {
152
144
  const fieldValue = providedValue;
153
145
  const { required = false, nullable = false } = field?.meta ?? {};
154
146
 
155
147
  if (required && isEmpty(fieldValue) && fieldValue !== null) {
156
- const message = constants.rulesMessages.REQUIRED.replace(
157
- `{field}`,
158
- formatLabel(field.display_label),
159
- );
148
+ const message = error_messages.REQUIRED.replace(`{field}`, formatLabel(field.display_label));
160
149
  addError(
161
150
  currentPath,
162
151
  {
@@ -165,14 +154,14 @@ const validateMetaRules = (
165
154
  description: "",
166
155
  message,
167
156
  },
168
- field,
157
+ field
169
158
  );
170
159
  }
171
160
 
172
161
  if (!nullable && fieldValue === null) {
173
- const message = constants.rulesMessages.NOT_NULLABLE.replace(
162
+ const message = error_messages.NOT_NULLABLE.replace(
174
163
  `{field}`,
175
- formatLabel(field.display_label),
164
+ formatLabel(field.display_label)
176
165
  );
177
166
  addError(
178
167
  currentPath,
@@ -182,7 +171,7 @@ const validateMetaRules = (
182
171
  description: "",
183
172
  message,
184
173
  },
185
- field,
174
+ field
186
175
  );
187
176
  }
188
177
 
@@ -191,9 +180,9 @@ const validateMetaRules = (
191
180
  typeChecks[field.type] &&
192
181
  !typeChecks[field.type](fieldValue, { key: currentPath, updateValue })
193
182
  ) {
194
- const message = constants.rulesMessages.INVALID_TYPE.replace(
183
+ const message = error_messages.INVALID_TYPE.replace(
195
184
  `{field}`,
196
- formatLabel(field.display_label),
185
+ formatLabel(field.display_label)
197
186
  ).replace(`{type}`, field?.type);
198
187
  addError(
199
188
  currentPath,
@@ -203,7 +192,7 @@ const validateMetaRules = (
203
192
  description: "",
204
193
  message,
205
194
  },
206
- field,
195
+ field
207
196
  );
208
197
  }
209
198
  };
@@ -214,6 +203,8 @@ const handleRegexValidation = (
214
203
  field,
215
204
  addError,
216
205
  currentPath,
206
+ error_messages,
207
+ custom_message
217
208
  ) => {
218
209
  let message = "";
219
210
  const fieldLabel = formatLabel(field.display_label);
@@ -225,38 +216,28 @@ const handleRegexValidation = (
225
216
  const isValid = (() => {
226
217
  switch (rule.options.type) {
227
218
  case constants.regexTypes.MATCH:
228
- message = constants.rulesMessages.REGEX_MATCH;
219
+ message = custom_message ?? error_messages.REGEX_MATCH;
229
220
  return regex.test(fieldValue);
230
221
  case constants.regexTypes.START_WITH:
231
- message = constants.rulesMessages.REGEX_START_WITH;
232
- return fieldValue?.startsWith(
233
- rule.value[0].replace(/^\//, "").replace(/\/$/, ""),
234
- );
222
+ message = custom_message ?? error_messages.REGEX_START_WITH;
223
+ return fieldValue?.startsWith(rule.value[0].replace(/^\//, "").replace(/\/$/, ""));
235
224
  case constants.regexTypes.ENDS_WITH:
236
- message = constants.rulesMessages.REGEX_ENDS_WITH;
237
- return fieldValue?.endsWith(
238
- rule.value[0].replace(/^\//, "").replace(/\/$/, ""),
239
- );
225
+ message = custom_message ?? error_messages.REGEX_ENDS_WITH;
226
+ return fieldValue?.endsWith(rule.value[0].replace(/^\//, "").replace(/\/$/, ""));
240
227
  case constants.regexTypes.CONTAINS:
241
- message = constants.rulesMessages.REGEX_CONTAINS;
228
+ message = custom_message ?? error_messages.REGEX_CONTAINS;
242
229
  return regex.test(fieldValue);
243
230
  case constants.regexTypes.EXACT:
244
- message = constants.rulesMessages.REGEX_EXACT;
245
- return (
246
- fieldValue === rule.value[0].replace(/^\//, "").replace(/\/$/, "")
247
- );
231
+ message = custom_message ?? error_messages.REGEX_EXACT;
232
+ return fieldValue === rule.value[0].replace(/^\//, "").replace(/\/$/, "");
248
233
  case constants.regexTypes.NOT_START_WITH:
249
- message = constants.rulesMessages.REGEX_NOT_START_WITH;
250
- return !fieldValue?.startsWith(
251
- rule.value[0].replace(/^\//, "").replace(/\/$/, ""),
252
- );
234
+ message = custom_message ?? error_messages.REGEX_NOT_START_WITH;
235
+ return !fieldValue?.startsWith(rule.value[0].replace(/^\//, "").replace(/\/$/, ""));
253
236
  case constants.regexTypes.NOT_ENDS_WITH:
254
- message = constants.rulesMessages.REGEX_NOT_ENDS_WITH;
255
- return !fieldValue?.endsWith(
256
- rule.value[0].replace(/^\//, "").replace(/\/$/, ""),
257
- );
237
+ message = custom_message ?? error_messages.REGEX_NOT_ENDS_WITH;
238
+ return !fieldValue?.endsWith(rule.value[0].replace(/^\//, "").replace(/\/$/, ""));
258
239
  case constants.regexTypes.NOT_CONTAINS:
259
- message = constants.rulesMessages.REGEX_NOT_CONTAINS;
240
+ message = custom_message ?? error_messages.REGEX_NOT_CONTAINS;
260
241
  return !regex.test(fieldValue);
261
242
  default:
262
243
  return false;
@@ -264,13 +245,11 @@ const handleRegexValidation = (
264
245
  })();
265
246
 
266
247
  if (!isValid) {
267
- message = message
268
- ?.replace(`{field}`, fieldLabel)
269
- ?.replace(`{value}`, rule.value[0]);
248
+ message = message?.replace(`{field}`, fieldLabel)?.replace(`{value}`, rule.value[0]);
270
249
  addError(
271
250
  currentPath,
272
251
  { label: fieldLabel, fieldPath: currentPath, description: "", message },
273
- field,
252
+ field
274
253
  );
275
254
  }
276
255
  };
@@ -282,6 +261,8 @@ const validateOperatorRule = (
282
261
  addError,
283
262
  currentPath,
284
263
  formData,
264
+ error_messages,
265
+ custom_message
285
266
  ) => {
286
267
  const { isFieldCompare = false } = rule?.options ?? {};
287
268
  let valid = false;
@@ -304,7 +285,10 @@ const validateOperatorRule = (
304
285
  const date = new Date(value);
305
286
  date.setHours(0, 0, 0, 0);
306
287
  if (forMessage) {
307
- return `${date.getUTCFullYear()}-${String(date.getUTCMonth() + 1).padStart(2, "0")}-${String(date.getUTCDate()).padStart(2, "0")}`;
288
+ return `${date.getUTCFullYear()}-${String(date.getUTCMonth() + 1).padStart(
289
+ 2,
290
+ "0"
291
+ )}-${String(date.getUTCDate()).padStart(2, "0")}`;
308
292
  }
309
293
  return Math.floor(date.getTime() / 1000);
310
294
  }
@@ -313,8 +297,13 @@ const validateOperatorRule = (
313
297
 
314
298
  if (forMessage) {
315
299
  return (
316
- `${date.getUTCFullYear()}-${String(date.getUTCMonth() + 1).padStart(2, "0")}-${String(date.getUTCDate()).padStart(2, "0")} ` +
317
- `${String(date.getUTCHours()).padStart(2, "0")}:${String(date.getUTCMinutes()).padStart(2, "0")}:${String(date.getUTCSeconds()).padStart(2, "0")}`
300
+ `${date.getUTCFullYear()}-${String(date.getUTCMonth() + 1).padStart(2, "0")}-${String(
301
+ date.getUTCDate()
302
+ ).padStart(2, "0")} ` +
303
+ `${String(date.getUTCHours()).padStart(2, "0")}:${String(date.getUTCMinutes()).padStart(
304
+ 2,
305
+ "0"
306
+ )}:${String(date.getUTCSeconds()).padStart(2, "0")}`
318
307
  );
319
308
  }
320
309
 
@@ -338,123 +327,116 @@ const validateOperatorRule = (
338
327
 
339
328
  const fieldValueParsed = getComparableValue(fieldValue);
340
329
 
341
- const messageValue = Array.isArray(rule.value)
342
- ? rule.value.join(", ")
343
- : String(rule.value);
330
+ const messageValue = Array.isArray(rule.value) ? rule.value.join(", ") : String(rule.value);
344
331
  let message = "";
345
332
 
346
333
  switch (rule.options.operator) {
347
334
  case constants.operatorTypes.AND:
348
- message = constants.rulesMessages.AND?.replace(
349
- `{field}`,
350
- formatLabel(field.display_label),
351
- )?.replace("{value}", messageValue);
335
+ message = custom_message ?? error_messages.AND;
336
+ message = message
337
+ ?.replace(`{field}`, formatLabel(field.display_label))
338
+ ?.replace("{value}", messageValue);
352
339
  valid = isArray
353
340
  ? rule.value.every((val) => fieldValue.includes(val))
354
341
  : rule.value.every((val) => val === fieldValueParsed);
355
342
  break;
356
343
  case constants.operatorTypes.OR:
357
- message = constants.rulesMessages.OR?.replace(
358
- `{field}`,
359
- formatLabel(field.display_label),
360
- )?.replace("{value}", messageValue);
344
+ message = custom_message ?? error_messages.OR;
345
+ message = message
346
+ ?.replace(`{field}`, formatLabel(field.display_label))
347
+ ?.replace("{value}", messageValue);
361
348
  valid = isArray
362
349
  ? rule.value.some((val) => fieldValue.includes(val))
363
350
  : rule.value.some((val) => val === fieldValueParsed);
364
351
  break;
365
352
  case constants.operatorTypes.EQUAL:
366
- message = constants.rulesMessages.EQUAL?.replace(
367
- `{field}`,
368
- formatLabel(field.display_label),
369
- )?.replace("{value}", rule.value[0]);
353
+ message = custom_message ?? error_messages.EQUAL;
354
+ message = message
355
+ ?.replace(`{field}`, formatLabel(field.display_label))
356
+ ?.replace("{value}", rule.value[0]);
370
357
  valid = fieldValueParsed === getComparableValue(rule.value[0]);
371
358
  break;
372
359
  case constants.operatorTypes.NOT_EQUAL:
373
- message = constants.rulesMessages.NOT_EQUAL?.replace(
374
- `{field}`,
375
- formatLabel(field.display_label),
376
- )?.replace("{value}", rule.value[0]);
360
+ message = custom_message ?? error_messages.NOT_EQUAL;
361
+ message = message
362
+ ?.replace(`{field}`, formatLabel(field.display_label))
363
+ ?.replace("{value}", rule.value[0]);
377
364
  valid = fieldValueParsed !== getComparableValue(rule.value[0]);
378
365
  break;
379
366
  case constants.operatorTypes.LESS_THAN:
380
- message = constants.rulesMessages.LESS_THAN?.replace(
381
- `{field}`,
382
- formatLabel(field.display_label),
383
- )?.replace("{value}", rule.value[0]);
367
+ message = custom_message ?? error_messages.LESS_THAN;
368
+ message = message
369
+ ?.replace(`{field}`, formatLabel(field.display_label))
370
+ ?.replace("{value}", rule.value[0]);
384
371
  valid = fieldValueParsed < getComparableValue(rule.value[0]);
385
372
  break;
386
373
  case constants.operatorTypes.LESS_THAN_EQUAL:
387
- message = constants.rulesMessages.LESS_THAN_EQUAL?.replace(
388
- `{field}`,
389
- formatLabel(field.display_label),
390
- )?.replace("{value}", rule.value[0]);
374
+ message = custom_message ?? error_messages.LESS_THAN_EQUAL;
375
+ message = message
376
+ ?.replace(`{field}`, formatLabel(field.display_label))
377
+ ?.replace("{value}", rule.value[0]);
391
378
  valid = fieldValueParsed <= getComparableValue(rule.value[0]);
392
379
  break;
393
380
  case constants.operatorTypes.GREATER_THAN:
394
- message = constants.rulesMessages.GREATER_THAN?.replace(
395
- `{field}`,
396
- formatLabel(field.display_label),
397
- )?.replace("{value}", rule.value[0]);
381
+ message = custom_message ?? error_messages.GREATER_THAN;
382
+ message = message
383
+ ?.replace(`{field}`, formatLabel(field.display_label))
384
+ ?.replace("{value}", rule.value[0]);
398
385
  valid = fieldValueParsed > getComparableValue(rule.value[0]);
399
386
  break;
400
387
  case constants.operatorTypes.GREATER_THAN_EQUAL:
401
- message = constants.rulesMessages.GREATER_THAN_EQUAL?.replace(
402
- `{field}`,
403
- formatLabel(field.display_label),
404
- )?.replace("{value}", rule.value[0]);
388
+ message = custom_message ?? error_messages.GREATER_THAN_EQUAL;
389
+ message = message
390
+ ?.replace(`{field}`, formatLabel(field.display_label))
391
+ ?.replace("{value}", rule.value[0]);
405
392
  valid = fieldValueParsed >= getComparableValue(rule.value[0]);
406
393
  break;
407
394
  case constants.operatorTypes.IN:
408
- message = constants.rulesMessages.IN?.replace(
409
- `{field}`,
410
- formatLabel(field.display_label),
411
- )?.replace("{value}", messageValue);
395
+ message = custom_message ?? error_messages.IN;
396
+ message = message
397
+ ?.replace(`{field}`, formatLabel(field.display_label))
398
+ ?.replace("{value}", messageValue);
412
399
  valid = rule.value.includes(fieldValue);
413
400
  break;
414
401
  case constants.operatorTypes.NOT_IN:
415
- message = constants.rulesMessages.NOT_IN?.replace(
416
- `{field}`,
417
- formatLabel(field.display_label),
418
- )?.replace("{value}", messageValue);
402
+ message = custom_message ?? error_messages.NOT_IN;
403
+ message = message
404
+ ?.replace(`{field}`, formatLabel(field.display_label))
405
+ ?.replace("{value}", messageValue);
419
406
  valid = !rule.value.includes(fieldValue);
420
407
  break;
421
408
  case constants.operatorTypes.EXISTS:
422
- message = constants.rulesMessages.EXISTS?.replace(
423
- `{field}`,
424
- formatLabel(field.display_label),
425
- );
426
- valid = rule.value[0]
427
- ? fieldValue !== undefined
428
- : fieldValue === undefined;
409
+ message = custom_message ?? error_messages.EXISTS;
410
+ message = message?.replace(`{field}`, formatLabel(field.display_label));
411
+ valid = rule.value[0] ? fieldValue !== undefined : fieldValue === undefined;
429
412
  break;
430
413
  case constants.operatorTypes.TYPE:
431
- message = constants.rulesMessages.TYPE?.replace(
432
- `{field}`,
433
- formatLabel(field.display_label),
434
- )?.replace("{value}", rule.value[0]);
414
+ message = custom_message ?? error_messages.TYPE;
415
+ message = message
416
+ ?.replace(`{field}`, formatLabel(field.display_label))
417
+ ?.replace("{value}", rule.value[0]);
435
418
  valid = typeChecks[rule.value[0]](fieldValue);
436
419
  break;
437
420
  case constants.operatorTypes.MOD:
438
- message = constants.rulesMessages.MOD?.replace(
439
- `{field}`,
440
- formatLabel(field.display_label),
441
- )
421
+ message = custom_message ?? error_messages.MOD;
422
+ message = message
423
+ ?.replace(`{field}`, formatLabel(field.display_label))
442
424
  ?.replace("{value[1]}", rule.value[1])
443
425
  ?.replace("{value[0]}", rule.value[0]);
444
426
  valid = isNumber && fieldValue % rule.value[0] === rule.value[1];
445
427
  break;
446
428
  case constants.operatorTypes.ALL:
447
- message = constants.rulesMessages.ALL?.replace(
448
- `{field}`,
449
- formatLabel(field.display_label),
450
- )?.replace("{value}", messageValue);
429
+ message = custom_message ?? error_messages.ALL;
430
+ message = message
431
+ ?.replace(`{field}`, formatLabel(field.display_label))
432
+ ?.replace("{value}", messageValue);
451
433
  valid = isArray && rule.value.every((val) => fieldValue.includes(val));
452
434
  break;
453
435
  case constants.operatorTypes.SIZE:
454
- message = constants.rulesMessages.SIZE?.replace(
455
- `{field}`,
456
- formatLabel(field.display_label),
457
- )?.replace("{value}", rule.value[0]);
436
+ message = custom_message ?? error_messages.SIZE;
437
+ message = message
438
+ ?.replace(`{field}`, formatLabel(field.display_label))
439
+ ?.replace("{value}", rule.value[0]);
458
440
  valid = isArray && fieldValue.length === rule.value[0];
459
441
  break;
460
442
  default:
@@ -470,58 +452,67 @@ const validateOperatorRule = (
470
452
  description: "",
471
453
  message,
472
454
  },
473
- field,
455
+ field
474
456
  );
475
457
  }
476
458
  };
477
459
 
478
- const generateErrorMessage = (ruleKey, fieldLabel, additionalValues = {}) => {
479
- let message = constants.rulesMessages[ruleKey]?.replace(
480
- `{field}`,
481
- fieldLabel,
482
- );
460
+ const generateErrorMessage = (
461
+ ruleKey,
462
+ fieldLabel,
463
+ additionalValues = {},
464
+ error_messages,
465
+ custom_message
466
+ ) => {
467
+ let message = custom_message ?? error_messages[ruleKey];
468
+ message = message?.replace(`{field}`, fieldLabel);
483
469
  Object.entries(additionalValues).forEach(([key, value]) => {
484
470
  message = message.replace(`{${key}}`, value);
485
471
  });
486
472
  return message;
487
473
  };
488
474
 
489
- const handleRule = (rule, field, value, addError, currentPath, formData) => {
475
+ const handleRule = (rule, field, value, addError, currentPath, formData, error_messages) => {
490
476
  const ruleValue = rule?.value;
491
- const messageValue = Array.isArray(ruleValue)
492
- ? ruleValue.join(", ")
493
- : String(ruleValue);
477
+ const messageValue = Array.isArray(ruleValue) ? ruleValue.join(", ") : String(ruleValue);
494
478
  const fieldLabel = formatLabel(field.display_label);
479
+ const custom_message = rule?.custom_message;
495
480
 
496
- const addValidationError = (ruleKey, extraValues = {}) =>
481
+ const addValidationError = (ruleKey, extraValues = {}, custom_message) =>
497
482
  addError(
498
483
  currentPath,
499
484
  {
500
485
  label: fieldLabel,
501
486
  fieldPath: currentPath,
502
487
  description: "",
503
- message: generateErrorMessage(ruleKey, fieldLabel, extraValues),
488
+ message: generateErrorMessage(
489
+ ruleKey,
490
+ fieldLabel,
491
+ extraValues,
492
+ error_messages,
493
+ custom_message
494
+ ),
504
495
  },
505
- field,
496
+ field
506
497
  );
507
498
 
508
499
  switch (rule.case) {
509
500
  case constants.rulesTypes.EMPTY:
510
- if (value) addValidationError("EMPTY");
501
+ if (value) addValidationError("EMPTY", {}, custom_message);
511
502
  break;
512
503
  case constants.rulesTypes.NOT_EMPTY:
513
- if (!value || value.length === 0) addValidationError("NOT_EMPTY");
504
+ if (!value || value.length === 0) addValidationError("NOT_EMPTY", {}, custom_message);
514
505
  break;
515
506
  case constants.rulesTypes.ONE_OF:
516
507
  if (!ruleValue?.includes(value))
517
- addValidationError("ONE_OF", { value: messageValue });
508
+ addValidationError("ONE_OF", { value: messageValue }, custom_message);
518
509
  break;
519
510
  case constants.rulesTypes.NOT_ONE_OF:
520
511
  if (ruleValue?.includes(value))
521
- addValidationError("NOT_ONE_OF", { value: messageValue });
512
+ addValidationError("NOT_ONE_OF", { value: messageValue }, custom_message);
522
513
  break;
523
514
  case constants.rulesTypes.NOT_ALLOWED:
524
- if (ruleValue?.includes(value)) addValidationError("NOT_ALLOWED");
515
+ if (ruleValue?.includes(value)) addValidationError("NOT_ALLOWED", {}, custom_message);
525
516
  break;
526
517
  case constants.rulesTypes.MIN:
527
518
  case constants.rulesTypes.MAX:
@@ -532,13 +523,32 @@ const handleRule = (rule, field, value, addError, currentPath, formData) => {
532
523
  field,
533
524
  addError,
534
525
  currentPath,
526
+ error_messages,
527
+ custom_message
535
528
  );
536
529
  break;
537
530
  case constants.rulesTypes.REGEX:
538
- handleRegexValidation(value, rule, field, addError, currentPath);
531
+ handleRegexValidation(
532
+ value,
533
+ rule,
534
+ field,
535
+ addError,
536
+ currentPath,
537
+ error_messages,
538
+ custom_message
539
+ );
539
540
  break;
540
541
  case constants.rulesTypes.OPERATOR:
541
- validateOperatorRule(value, rule, field, addError, currentPath, formData);
542
+ validateOperatorRule(
543
+ value,
544
+ rule,
545
+ field,
546
+ addError,
547
+ currentPath,
548
+ formData,
549
+ error_messages,
550
+ custom_message
551
+ );
542
552
  break;
543
553
  default:
544
554
  console.warn(`Unknown Rule: ${rule.case}`, fieldLabel);
@@ -552,13 +562,12 @@ const validateField = (
552
562
  addError,
553
563
  formData,
554
564
  updateValue,
565
+ error_messages
555
566
  ) => {
556
- const currentPath = fieldPath
557
- ? `${fieldPath}.${field.key.split(".").pop()}`
558
- : field.key;
567
+ const currentPath = fieldPath ? `${fieldPath}.${field.key.split(".").pop()}` : field.key;
559
568
  const fieldLabel = formatLabel(field.display_label);
560
569
 
561
- validateMetaRules(field, addError, value, currentPath, updateValue);
570
+ validateMetaRules(field, addError, value, currentPath, updateValue, error_messages);
562
571
 
563
572
  if (
564
573
  field.type === constants.types.OBJECT &&
@@ -573,7 +582,8 @@ const validateField = (
573
582
  currentPath,
574
583
  addError,
575
584
  updateValue,
576
- ),
585
+ error_messages
586
+ )
577
587
  );
578
588
  } else if (field.type === constants.types.ARRAY && Array.isArray(value)) {
579
589
  const itemType = field?.array_type || field?.schema_definition?.type;
@@ -584,9 +594,14 @@ const validateField = (
584
594
  label: fieldLabel,
585
595
  fieldPath: `${currentPath}[${index}]`,
586
596
  description: "",
587
- message: generateErrorMessage("INVALID_TYPE", fieldLabel, {
588
- type: itemType,
589
- }),
597
+ message: generateErrorMessage(
598
+ "INVALID_TYPE",
599
+ fieldLabel,
600
+ {
601
+ type: itemType,
602
+ },
603
+ error_messages
604
+ ),
590
605
  });
591
606
  }
592
607
  });
@@ -600,31 +615,32 @@ const validateField = (
600
615
  `${currentPath}[${index}]`,
601
616
  addError,
602
617
  updateValue,
603
- ),
618
+ error_messages
619
+ )
604
620
  );
605
621
  });
606
622
  }
607
623
  } else {
608
- if (!applyValidations(field, value, addError, currentPath, formData)) {
624
+ if (!applyValidations(field, value, addError, currentPath, formData, error_messages)) {
609
625
  addError(
610
626
  currentPath,
611
627
  {
612
628
  label: fieldLabel,
613
629
  fieldPath: currentPath,
614
630
  description: "",
615
- message: `Invalid value for ${currentPath}`,
631
+ message: error_messages.INVALID_VALUE?.replace(`{field}`, fieldLabel),
616
632
  },
617
- field,
633
+ field
618
634
  );
619
635
  }
620
636
  }
621
637
  };
622
638
 
623
- const applyValidations = (field, value, addError, currentPath, formData) => {
639
+ const applyValidations = (field, value, addError, currentPath, formData, error_messages) => {
624
640
  if (!field.validations || value === null || value === undefined) return true;
625
641
  return !field.validations.some(
626
642
  (rule) =>
627
- handleRule(rule, field, value, addError, currentPath, formData) === false,
643
+ handleRule(rule, field, value, addError, currentPath, formData, error_messages) === false
628
644
  );
629
645
  };
630
646
 
@@ -644,6 +660,7 @@ const schema = {
644
660
  array_type: constants.types.STRING,
645
661
  },
646
662
  apiVersion: { type: constants.types.STRING, array_type: null },
663
+ language: { type: constants.types.STRING, array_type: null },
647
664
  };
648
665
 
649
666
  const validate = (data) => {
@@ -657,7 +674,12 @@ const validate = (data) => {
657
674
  abortEarly,
658
675
  byPassKeys,
659
676
  apiVersion,
677
+ language,
660
678
  } = data;
679
+ const defaultLanguage = constants.LANGUAGES.length > 0 ? constants.LANGUAGES[0] : "en"; // Replace "en" with your actual default
680
+ const error_messages =
681
+ constants.LOCALE_MESSAGES[language] ?? constants.LOCALE_MESSAGES[defaultLanguage];
682
+
661
683
  let result = { status: true, errors: {}, data: formData };
662
684
 
663
685
  const updateValue = (key, value) => {
@@ -678,10 +700,7 @@ const validate = (data) => {
678
700
  // Validate Data
679
701
  const defaultField = { meta: { hidden: false } };
680
702
  if (!data) {
681
- const message = constants.rulesMessages.REQUIRED.replace(
682
- `{field}`,
683
- formatLabel("data"),
684
- );
703
+ const message = error_messages.REQUIRED.replace(`{field}`, formatLabel("data"));
685
704
  addError(
686
705
  "data",
687
706
  {
@@ -690,17 +709,17 @@ const validate = (data) => {
690
709
  description: "",
691
710
  message,
692
711
  },
693
- defaultField,
712
+ defaultField
694
713
  );
695
714
  return result;
696
715
  }
697
716
 
698
717
  // validate data type
699
718
  if (!typeChecks[constants.types.OBJECT](data)) {
700
- const message = constants.rulesMessages.INVALID_TYPE.replace(
701
- `{field}`,
702
- formatLabel("data"),
703
- ).replace(`{type}`, constants.types.OBJECT);
719
+ const message = error_messages.INVALID_TYPE.replace(`{field}`, formatLabel("data")).replace(
720
+ `{type}`,
721
+ constants.types.OBJECT
722
+ );
704
723
  addError(
705
724
  "data",
706
725
  {
@@ -709,7 +728,7 @@ const validate = (data) => {
709
728
  description: "",
710
729
  message,
711
730
  },
712
- defaultField,
731
+ defaultField
713
732
  );
714
733
  return result;
715
734
  }
@@ -720,10 +739,7 @@ const validate = (data) => {
720
739
  const fieldValue = data[key];
721
740
  // Skip empty values
722
741
  if (fieldValue == null || fieldValue == undefined) {
723
- const message = constants.rulesMessages.REQUIRED.replace(
724
- `{field}`,
725
- formatLabel(key),
726
- );
742
+ const message = error_messages.REQUIRED.replace(`{field}`, formatLabel(key));
727
743
  addError(
728
744
  key,
729
745
  {
@@ -732,17 +748,17 @@ const validate = (data) => {
732
748
  description: "",
733
749
  message,
734
750
  },
735
- defaultField,
751
+ defaultField
736
752
  );
737
753
  return;
738
754
  }
739
755
 
740
756
  // Validate field type
741
757
  if (!typeChecks[expectedType] || !typeChecks[expectedType](fieldValue)) {
742
- const message = constants.rulesMessages.INVALID_TYPE.replace(
743
- `{field}`,
744
- key,
745
- ).replace(`{type}`, expectedType);
758
+ const message = error_messages.INVALID_TYPE.replace(`{field}`, key).replace(
759
+ `{type}`,
760
+ expectedType
761
+ );
746
762
  addError(
747
763
  key,
748
764
  {
@@ -751,7 +767,7 @@ const validate = (data) => {
751
767
  description: "",
752
768
  message,
753
769
  },
754
- defaultField,
770
+ defaultField
755
771
  );
756
772
  return;
757
773
  }
@@ -762,14 +778,10 @@ const validate = (data) => {
762
778
  // Determine the expected type of array items
763
779
  const arrayItemType = schema[key].array_type; // Define item types like "relations[]": "object"
764
780
 
765
- if (
766
- arrayItemType &&
767
- typeChecks[arrayItemType] &&
768
- !typeChecks[arrayItemType](item)
769
- ) {
770
- const message = constants.rulesMessages.INVALID_TYPE.replace(
781
+ if (arrayItemType && typeChecks[arrayItemType] && !typeChecks[arrayItemType](item)) {
782
+ const message = error_messages.INVALID_TYPE.replace(
771
783
  `{field}`,
772
- `${key}[${index}]`,
784
+ `${key}[${index}]`
773
785
  ).replace(`{type}`, arrayItemType);
774
786
  addError(
775
787
  `${key}[${index}]`,
@@ -779,7 +791,7 @@ const validate = (data) => {
779
791
  description: "",
780
792
  message,
781
793
  },
782
- defaultField,
794
+ defaultField
783
795
  );
784
796
  }
785
797
  });
@@ -788,10 +800,10 @@ const validate = (data) => {
788
800
 
789
801
  // Validate API Version
790
802
  if (!constants.API_VERSIONS.includes(apiVersion)) {
791
- const message = constants.rulesMessages.IN.replace(
792
- `{field}`,
793
- formatLabel("apiVersion"),
794
- ).replace(`{value}`, constants.API_VERSIONS.join(", "));
803
+ const message = error_messages.IN.replace(`{field}`, formatLabel("apiVersion")).replace(
804
+ `{value}`,
805
+ constants.API_VERSIONS.join(", ")
806
+ );
795
807
  addError(
796
808
  "apiVersion",
797
809
  {
@@ -800,7 +812,7 @@ const validate = (data) => {
800
812
  description: "",
801
813
  message,
802
814
  },
803
- defaultField,
815
+ defaultField
804
816
  );
805
817
  }
806
818
 
@@ -815,9 +827,7 @@ const validate = (data) => {
815
827
  let allFields = isSeparatedFields ? [] : fields;
816
828
 
817
829
  if (!isSeparatedFields) {
818
- schemaFields = fields.filter(
819
- (field) => field?.schema_id?.toString() === formId?.toString(),
820
- );
830
+ schemaFields = fields.filter((field) => field?.schema_id?.toString() === formId?.toString());
821
831
  }
822
832
 
823
833
  const fieldOptions =
@@ -828,7 +838,7 @@ const validate = (data) => {
828
838
  formData,
829
839
  relationalFields,
830
840
  isSeparatedFields,
831
- apiVersion,
841
+ apiVersion
832
842
  ) || [];
833
843
 
834
844
  findDisallowedKeys(formData, fieldOptions).forEach((fieldPath) => {
@@ -845,6 +855,7 @@ const validate = (data) => {
845
855
  {
846
856
  field: formatLabel(fieldKey),
847
857
  },
858
+ error_messages
848
859
  ),
849
860
  });
850
861
  }
@@ -859,9 +870,10 @@ const validate = (data) => {
859
870
  addError,
860
871
  formData,
861
872
  updateValue,
873
+ error_messages
862
874
  );
863
875
  });
864
876
  return result;
865
877
  };
866
878
 
867
- module.exports = { validate, validateField ,typeChecks};
879
+ module.exports = { validate, validateField, typeChecks };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nox-validation",
3
- "version": "1.0.1",
3
+ "version": "1.0.2",
4
4
  "description": "validate dynamic schema",
5
5
  "main": "index.js",
6
6
  "scripts": {