quival 0.2.8 → 0.3.0

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/src/Validator.js CHANGED
@@ -239,77 +239,83 @@ export default class Validator {
239
239
  this.#checkers.clearCaches();
240
240
  this.#errors = new ErrorBag();
241
241
 
242
+ const tasks = [];
243
+
242
244
  for (const [attribute, rules] of Object.entries(this.#rules)) {
243
245
  let value = this.getValue(attribute);
244
246
 
245
- if (rules.hasOwnProperty('sometimes') && typeof value === 'undefined') {
247
+ if (Object.hasOwn(rules, 'sometimes') && typeof value === 'undefined') {
246
248
  continue;
247
249
  }
248
250
 
249
- const doBail = this.#alwaysBail || rules.hasOwnProperty('bail');
250
- const isNullable = rules.hasOwnProperty('nullable');
251
- let hasError = false;
251
+ tasks.push(async () => {
252
+ const doBail = this.#alwaysBail || Object.hasOwn(rules, 'bail');
253
+ const isNullable = Object.hasOwn(rules, 'nullable');
254
+ let noError = true;
252
255
 
253
- for (const [rule, parameters] of Object.entries(rules)) {
254
- if (rule === '') {
255
- continue;
256
- }
256
+ for (const [rule, parameters] of Object.entries(rules)) {
257
+ if (rule === '') {
258
+ continue;
259
+ }
257
260
 
258
- if (
259
- !Validator.#implicitRules.includes(rule) &&
260
- (typeof value === 'undefined' || (typeof value === 'string' && value.trim() === '') || (isNullable && value === null))
261
- ) {
262
- continue;
263
- }
261
+ if (
262
+ !Validator.#implicitRules.includes(rule) &&
263
+ (typeof value === 'undefined' || (typeof value === 'string' && value.trim() === '') || (isNullable && value === null))
264
+ ) {
265
+ continue;
266
+ }
264
267
 
265
- let result, status, message;
266
- const camelRule = toCamelCase('check_' + rule);
268
+ let result, success, message;
269
+ const camelRule = toCamelCase('check_' + rule);
267
270
 
268
- if (typeof this.#checkers[camelRule] === 'function') {
269
- result = await this.#checkers[camelRule](attribute, value, parameters);
270
- } else if (Validator.#dummyRules.includes(rule)) {
271
- result = true;
272
- } else {
273
- throw new Error(`Invalid validation rule: ${rule}`);
274
- }
271
+ if (typeof this.#checkers[camelRule] === 'function') {
272
+ result = await this.#checkers[camelRule](attribute, value, parameters);
273
+ } else if (Validator.#dummyRules.includes(rule)) {
274
+ result = true;
275
+ } else {
276
+ throw new Error(`Invalid validation rule: ${rule}`);
277
+ }
275
278
 
276
- if (typeof result === 'boolean') {
277
- status = result;
278
- } else {
279
- ({ status, message } = result);
280
- }
279
+ if (typeof result === 'boolean') {
280
+ success = result;
281
+ } else {
282
+ ({ success, message } = result);
283
+ }
281
284
 
282
- if (!status) {
283
- hasError = true;
284
- message = isEmpty(message) ? this.getMessage(attribute, rule) : message;
285
- message = this.makeReplacements(message, attribute, rule, parameters);
285
+ if (!success) {
286
+ noError = false;
287
+ message = isEmpty(message) ? this.getMessage(attribute, rule) : message;
288
+ message = this.makeReplacements(message, attribute, rule, parameters);
286
289
 
287
- this.#errors.add(attribute, message);
290
+ this.#errors.add(attribute, message);
288
291
 
289
- if (doBail || Validator.#implicitRules.includes(rule)) {
290
- break;
292
+ if (doBail || Validator.#implicitRules.includes(rule)) {
293
+ break;
294
+ }
291
295
  }
292
296
  }
293
- }
294
297
 
295
- if (this.#stopOnFirstFailure && hasError) {
296
- break;
297
- }
298
+ return noError;
299
+ });
298
300
  }
299
301
 
300
- if (this.#errors.isNotEmpty()) {
301
- throw this.#errors;
302
+ if (this.#stopOnFirstFailure) {
303
+ for (const task of tasks) {
304
+ if (!(await task())) break;
305
+ }
306
+ } else {
307
+ await Promise.allSettled(tasks.map((task) => task()));
308
+
309
+ this.#errors.sortByKeys(Object.keys(this.#rules));
302
310
  }
311
+
312
+ return this.#errors;
303
313
  }
304
314
 
305
315
  async passes() {
306
- try {
307
- await this.validate();
308
- } catch (error) {
309
- if (error instanceof Error) {
310
- throw error;
311
- }
316
+ await this.validate();
312
317
 
318
+ if (this.#errors.isNotEmpty()) {
313
319
  return false;
314
320
  }
315
321
 
@@ -327,7 +333,7 @@ export default class Validator {
327
333
  let message;
328
334
 
329
335
  for (const key of [`${attribute}.${rule}`, rule]) {
330
- if (this.#customMessages.hasOwnProperty(key)) {
336
+ if (Object.hasOwn(this.#customMessages, key)) {
331
337
  message = this.#customMessages[key];
332
338
 
333
339
  break;
@@ -342,7 +348,7 @@ export default class Validator {
342
348
  key += '.array';
343
349
  } else if (value instanceof File || this.hasRule(attribute, this.fileRules)) {
344
350
  key += '.file';
345
- } else if (typeof value === 'number' || this.hasRule(attribute, this.numericRules)) {
351
+ } else if (isNumeric(value) || this.hasRule(attribute, this.numericRules)) {
346
352
  key += '.numeric';
347
353
  } else {
348
354
  key += '.string';
@@ -390,14 +396,14 @@ export default class Validator {
390
396
  const unparsed = this.getPrimaryAttribute(attribute);
391
397
 
392
398
  for (const name of [attribute, unparsed]) {
393
- if (this.#customAttributes.hasOwnProperty(name)) {
399
+ if (Object.hasOwn(this.#customAttributes, name)) {
394
400
  return this.#customAttributes[name];
395
401
  } else if (Lang.has(`attributes.${name}`)) {
396
402
  return Lang.get(`attributes.${name}`);
397
403
  }
398
404
  }
399
405
 
400
- if (this.#implicitAttributes.hasOwnProperty(attribute)) {
406
+ if (Object.hasOwn(this.#implicitAttributes, attribute)) {
401
407
  return attribute;
402
408
  }
403
409
 
@@ -413,7 +419,7 @@ export default class Validator {
413
419
  return 'empty';
414
420
  } else if (typeof value === 'boolean' || this.hasRule(attribute, 'boolean')) {
415
421
  return Number(value) ? 'true' : 'false';
416
- } else if (this.#customValues.hasOwnProperty(path)) {
422
+ } else if (Object.hasOwn(this.#customValues, path)) {
417
423
  return this.#customValues[path];
418
424
  } else if (Lang.has(`values.${path}`)) {
419
425
  return Lang.get(`values.${path}`);
@@ -431,7 +437,7 @@ export default class Validator {
431
437
  return value.size / 1024;
432
438
  } else if (isPlainObject(value)) {
433
439
  return Object.keys(value).length;
434
- } else if (value.hasOwnProperty('length')) {
440
+ } else if (Object.hasOwn(value, 'length')) {
435
441
  return value.length;
436
442
  }
437
443
 
@@ -448,7 +454,7 @@ export default class Validator {
448
454
  attribute = this.getPrimaryAttribute(attribute);
449
455
  rules = typeof rules === 'string' ? [rules] : rules;
450
456
 
451
- if (!this.#rules.hasOwnProperty(attribute)) {
457
+ if (!Object.hasOwn(this.#rules, attribute)) {
452
458
  return false;
453
459
  }
454
460
 
@@ -462,7 +468,7 @@ export default class Validator {
462
468
  }
463
469
 
464
470
  getPrimaryAttribute(attribute) {
465
- return this.#implicitAttributes.hasOwnProperty(attribute) ? this.#implicitAttributes[attribute] : attribute;
471
+ return Object.hasOwn(this.#implicitAttributes, attribute) ? this.#implicitAttributes[attribute] : attribute;
466
472
  }
467
473
 
468
474
  hasAttribute(attribute) {
package/src/helpers.js CHANGED
@@ -19,7 +19,7 @@ export function getByPath(obj, path, defaultValue) {
19
19
  let current = obj;
20
20
 
21
21
  for (const key of keys) {
22
- if (!current.hasOwnProperty(key)) {
22
+ if (!Object.hasOwn(current, key)) {
23
23
  return defaultValue;
24
24
  }
25
25
 
@@ -34,7 +34,7 @@ export function setByPath(obj, path, value) {
34
34
  let current = obj;
35
35
 
36
36
  for (const key of keys.slice(0, -1)) {
37
- if (!current.hasOwnProperty(key)) {
37
+ if (!Object.hasOwn(current, key)) {
38
38
  current[key] = {};
39
39
  }
40
40
 
@@ -173,7 +173,7 @@ export function parseDateByFormat(value, format) {
173
173
  let index = 1;
174
174
 
175
175
  for (const char of format) {
176
- if (formats.hasOwnProperty(char)) {
176
+ if (Object.hasOwn(formats, char)) {
177
177
  pattern += formats[char];
178
178
 
179
179
  if (['Y', 'y'].indexOf(char) !== -1) {
@@ -0,0 +1,85 @@
1
+ import { strict as assert } from 'assert';
2
+ import Validator from '../src/Validator.js';
3
+
4
+ describe('Functionalities', () => {
5
+ it('Create a new instance', () => {
6
+ const validator = new Validator({ field: 'abc' }, { field: 'string' });
7
+ assert(validator instanceof Validator);
8
+ });
9
+
10
+ it('Validate data', async () => {
11
+ const validator = new Validator(
12
+ {
13
+ val1: '@bc',
14
+ val2: 123,
15
+ },
16
+ {
17
+ val1: 'string|alpha|min:5',
18
+ val2: 'string|alpha',
19
+ },
20
+ );
21
+
22
+ assert.deepEqual((await validator.validate()).messages(), {
23
+ val1: ['validation.alpha', 'validation.min'],
24
+ val2: ['validation.string', 'validation.alpha'],
25
+ });
26
+ });
27
+
28
+ it('Stop on first failure', async () => {
29
+ const validator = new Validator(
30
+ {
31
+ val1: '@bc',
32
+ val2: 123,
33
+ },
34
+ {
35
+ val1: 'string|alpha|min:5',
36
+ val2: 'string|alpha',
37
+ },
38
+ );
39
+
40
+ validator.stopOnFirstFailure();
41
+
42
+ assert.deepEqual((await validator.validate()).messages(), {
43
+ val1: ['validation.alpha', 'validation.min'],
44
+ });
45
+ });
46
+
47
+ it('Always bail', async () => {
48
+ const validator = new Validator(
49
+ {
50
+ val1: '@bc',
51
+ val2: 123,
52
+ },
53
+ {
54
+ val1: 'string|alpha|min:5',
55
+ val2: 'string|alpha',
56
+ },
57
+ );
58
+
59
+ validator.alwaysBail();
60
+
61
+ assert.deepEqual((await validator.validate()).messages(), {
62
+ val1: ['validation.alpha'],
63
+ val2: ['validation.string'],
64
+ });
65
+ });
66
+
67
+ it('Stop on first failure and always bail', async () => {
68
+ const validator = new Validator(
69
+ {
70
+ val1: '@bc',
71
+ val2: 123,
72
+ },
73
+ {
74
+ val1: 'string|alpha|min:5',
75
+ val2: 'string|alpha',
76
+ },
77
+ );
78
+
79
+ validator.stopOnFirstFailure().alwaysBail();
80
+
81
+ assert.deepEqual((await validator.validate()).messages(), {
82
+ val1: ['validation.alpha'],
83
+ });
84
+ });
85
+ });
@@ -0,0 +1,174 @@
1
+ import { strict as assert } from 'assert';
2
+ import {
3
+ escapeRegExp,
4
+ flattenObject,
5
+ getByPath,
6
+ isDigits,
7
+ isEmpty,
8
+ isNumeric,
9
+ isPlainObject,
10
+ isValidDate,
11
+ parseCsvString,
12
+ parseDate,
13
+ parseDateByFormat,
14
+ setByPath,
15
+ toCamelCase,
16
+ toSnakeCase,
17
+ } from '../src/helpers.js';
18
+
19
+ describe('Helpers', () => {
20
+ it('toCamelCase', () => {
21
+ assert.equal(toCamelCase('a_test_name'), 'aTestName');
22
+ assert.equal(toCamelCase('_test_name'), 'testName');
23
+ assert.equal(toCamelCase('a-test-name'), 'aTestName');
24
+ assert.equal(toCamelCase('-test-name'), 'testName');
25
+ assert.equal(toCamelCase('a test name'), 'aTestName');
26
+ assert.equal(toCamelCase(' test name'), 'testName');
27
+ });
28
+
29
+ it('toSnakeCase', () => {
30
+ assert.equal(toSnakeCase('aTestName'), 'a_test_name');
31
+ assert.equal(toSnakeCase('a_testName'), 'a_test_name');
32
+ assert.equal(toSnakeCase('ATESTNAME'), 'a_t_e_s_t_n_a_m_e');
33
+ });
34
+
35
+ it('escapeRegExp', () => {
36
+ assert.equal(escapeRegExp('.*+?^${}()|[]\\'), '\\.\\*\\+\\?\\^\\$\\{\\}\\(\\)\\|\\[\\]\\\\');
37
+ });
38
+
39
+ it('getByPath', () => {
40
+ assert.equal(getByPath({ a: { b: 1 } }, 'a.b'), 1);
41
+ assert.equal(getByPath({ a: [1, { c: 2 }] }, 'a.1.c'), 2);
42
+ });
43
+
44
+ it('setByPath', () => {
45
+ let obj = { a: { b: 1 } };
46
+ setByPath(obj, 'a.b', 3);
47
+
48
+ assert.deepEqual(obj, { a: { b: 3 } });
49
+
50
+ obj = { a: [1, { c: 2 }] };
51
+ setByPath(obj, 'a.1.c', 5);
52
+
53
+ assert.deepEqual(obj, { a: [1, { c: 5 }] });
54
+ });
55
+
56
+ it('flattenObject', () => {
57
+ assert.deepEqual(flattenObject({ a: 1, b: { x: 41, y: 42 } }), { a: 1, 'b.x': 41, 'b.y': 42 });
58
+ assert.deepEqual(flattenObject({ a: 1, b: [71, 73, 75] }), { a: 1, 'b.0': 71, 'b.1': 73, 'b.2': 75 });
59
+ });
60
+
61
+ it('parseCsvString', () => {
62
+ assert.deepEqual(parseCsvString('a,b,c'), ['a', 'b', 'c']);
63
+ assert.deepEqual(parseCsvString('a, b, c'), ['a', ' b', ' c']);
64
+ assert.deepEqual(parseCsvString('"a","b","c"'), ['a', 'b', 'c']);
65
+ assert.deepEqual(parseCsvString('"a", "b", "c"'), ['a', 'b', 'c']);
66
+ assert.deepEqual(parseCsvString('"a", "b", ""'), ['a', 'b', '']);
67
+
68
+ assert.deepEqual(parseCsvString('"r2""c1","r2c2","r2c3"'), ['r2"c1', 'r2c2', 'r2c3']);
69
+ assert.deepEqual(parseCsvString('"r2""c1",r2c2,r2c3'), ['r2"c1', 'r2c2', 'r2c3']);
70
+ assert.deepEqual(parseCsvString('r1c1,"r1,c2",r1c3'), ['r1c1', 'r1,c2', 'r1c3']);
71
+ assert.deepEqual(parseCsvString('"r2""c1",r2c2,"r2""""c3"'), ['r2"c1', 'r2c2', 'r2""c3']);
72
+ assert.deepEqual(parseCsvString('r1c1,"r1,c2","r1"",""c3"'), ['r1c1', 'r1,c2', 'r1","c3']);
73
+ });
74
+
75
+ it('parseDate', () => {
76
+ const date1 = new Date('2023/08/11 00:00:00');
77
+ const date2 = new Date('2023/08/11 08:40:50 am');
78
+ const date3 = new Date();
79
+ date3.setHours(8);
80
+ date3.setMinutes(40);
81
+ date3.setSeconds(50);
82
+ date3.setMilliseconds(0);
83
+
84
+ assert.deepEqual(parseDate('11-08-2023'), date1);
85
+ assert.deepEqual(parseDate('11-08-2023 08:40:50'), date2);
86
+ assert.deepEqual(parseDate('11-08-2023 08:40:50 am'), date2);
87
+ assert.deepEqual(parseDate('11/08/2023'), date1);
88
+ assert.deepEqual(parseDate('11/08/2023 08:40:50'), date2);
89
+ assert.deepEqual(parseDate('11/08/2023 08:40:50 am'), date2);
90
+ assert.deepEqual(parseDate('11.08.2023'), date1);
91
+ assert.deepEqual(parseDate('11.08.2023 08:40:50'), date2);
92
+ assert.deepEqual(parseDate('11.08.2023 08:40:50 am'), date2);
93
+
94
+ assert.deepEqual(parseDate('2023-08-11'), date1);
95
+ assert.deepEqual(parseDate('2023-08-11 08:40:50'), date2);
96
+ assert.deepEqual(parseDate('2023-08-11 08:40:50 am'), date2);
97
+ assert.deepEqual(parseDate('2023/08/11'), date1);
98
+ assert.deepEqual(parseDate('2023/08/11 08:40:50'), date2);
99
+ assert.deepEqual(parseDate('2023/08/11 08:40:50 am'), date2);
100
+ assert.deepEqual(parseDate('2023.08.11'), date1);
101
+ assert.deepEqual(parseDate('2023.08.11 08:40:50'), date2);
102
+ assert.deepEqual(parseDate('2023.08.11 08:40:50 am'), date2);
103
+
104
+ assert.deepEqual(parseDate('08:40:50 2023-08-11'), date2);
105
+ assert.deepEqual(parseDate('08:40:50 am 2023-08-11'), date2);
106
+ assert.deepEqual(parseDate('08:40:50 2023/08/11'), date2);
107
+ assert.deepEqual(parseDate('08:40:50 am 2023/08/11'), date2);
108
+ assert.deepEqual(parseDate('08:40:50 2023.08.11'), date2);
109
+ assert.deepEqual(parseDate('08:40:50 am 2023.08.11'), date2);
110
+
111
+ assert.deepEqual(parseDate('08:40:50'), date3);
112
+ assert.deepEqual(parseDate('08:40:50 am'), date3);
113
+ assert.deepEqual(parseDate('08:40:50'), date3);
114
+ assert.deepEqual(parseDate('08:40:50 am'), date3);
115
+ assert.deepEqual(parseDate('08:40:50'), date3);
116
+ assert.deepEqual(parseDate('08:40:50 am'), date3);
117
+ });
118
+
119
+ it('parseDateByFormat', () => {
120
+ const date1 = new Date('2023/08/11 00:00:00');
121
+ const date2 = new Date('2023/08/11 08:40:50 am');
122
+ const date3 = new Date();
123
+ date3.setHours(8);
124
+ date3.setMinutes(40);
125
+ date3.setSeconds(50);
126
+ date3.setMilliseconds(0);
127
+
128
+ assert.deepEqual(parseDateByFormat('08-11-2023', 'm-d-Y'), date1);
129
+ assert.deepEqual(parseDateByFormat('08-11-23', 'm-d-y'), date1);
130
+ assert.deepEqual(parseDateByFormat('08-11-2023 08:40:50', 'm-d-Y H:i:s'), date2);
131
+ assert.deepEqual(parseDateByFormat('08-11-23 08:40:50', 'm-d-y H:i:s'), date2);
132
+ assert.deepEqual(parseDateByFormat('08-11-2023 08:40:50 am', 'm-d-Y H:i:s a'), date2);
133
+ assert.deepEqual(parseDateByFormat('08-11-23 08:40:50 am', 'm-d-y H:i:s a'), date2);
134
+ });
135
+
136
+ it('isDigits', () => {
137
+ assert(isDigits(123));
138
+ assert(isDigits('123'));
139
+ assert(!isDigits('abc'));
140
+ assert(!isDigits('abc123'));
141
+ });
142
+
143
+ it('isEmpty', () => {
144
+ assert(isEmpty(''));
145
+ assert(isEmpty(null));
146
+ assert(isEmpty(undefined));
147
+ assert(!isEmpty(123));
148
+ assert(!isEmpty('abc'));
149
+ assert(!isEmpty({}));
150
+ assert(!isEmpty([]));
151
+ });
152
+
153
+ it('isNumeric', () => {
154
+ assert(isNumeric(123));
155
+ assert(isNumeric('123'));
156
+ assert(!isNumeric('abc'));
157
+ assert(!isNumeric(true));
158
+ });
159
+
160
+ it('isPlainObject', () => {
161
+ assert(isPlainObject({}));
162
+ assert(!isPlainObject([]));
163
+ assert(!isPlainObject(new Date()));
164
+ });
165
+
166
+ it('isValidDate', () => {
167
+ assert(isValidDate(new Date()));
168
+ assert(isValidDate(new Date('2023/08/11 00:00:00')));
169
+ assert(isValidDate(new Date('2023/08/11 08:40:50 am')));
170
+ assert(isValidDate(new Date('08:40:50 am 2023/08/11')));
171
+ assert(!isValidDate(new Date('')));
172
+ assert(!isValidDate(new Date('08:40:50 am')));
173
+ });
174
+ });