kanun 0.1.7 → 1.0.1
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 +15 -0
- package/dist/index.cjs +1492 -1227
- package/dist/index.d.ts +194 -22
- package/dist/index.js +1482 -1227
- package/package.json +13 -1
package/dist/index.cjs
CHANGED
|
@@ -26,11 +26,50 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
26
26
|
}) : target, mod));
|
|
27
27
|
|
|
28
28
|
//#endregion
|
|
29
|
+
let node_async_hooks = require("node:async_hooks");
|
|
29
30
|
let dayjs_plugin_customParseFormat_js = require("dayjs/plugin/customParseFormat.js");
|
|
30
31
|
dayjs_plugin_customParseFormat_js = __toESM(dayjs_plugin_customParseFormat_js);
|
|
31
32
|
let dayjs = require("dayjs");
|
|
32
33
|
dayjs = __toESM(dayjs);
|
|
33
34
|
|
|
35
|
+
//#region src/Context.ts
|
|
36
|
+
const validatorContextStorage = new node_async_hooks.AsyncLocalStorage();
|
|
37
|
+
/**
|
|
38
|
+
* Get the current validator context.
|
|
39
|
+
*
|
|
40
|
+
* @returns
|
|
41
|
+
*/
|
|
42
|
+
function getValidatorContext() {
|
|
43
|
+
return validatorContextStorage.getStore() ?? {};
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Use a context for the current validator instance and all of its children.
|
|
47
|
+
*
|
|
48
|
+
* @param context
|
|
49
|
+
* @returns
|
|
50
|
+
*/
|
|
51
|
+
function useValidatorContext(context = {}) {
|
|
52
|
+
const nextContext = {
|
|
53
|
+
...getValidatorContext(),
|
|
54
|
+
...context
|
|
55
|
+
};
|
|
56
|
+
validatorContextStorage.enterWith(nextContext);
|
|
57
|
+
return nextContext;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Run a function with a given validator context.
|
|
61
|
+
*
|
|
62
|
+
* @param context The context to use during the execution of the callback
|
|
63
|
+
* @param callback The function to execute with the given context
|
|
64
|
+
*/
|
|
65
|
+
function runWithValidatorContext(context, callback) {
|
|
66
|
+
return validatorContextStorage.run({
|
|
67
|
+
...getValidatorContext(),
|
|
68
|
+
...context
|
|
69
|
+
}, callback);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
//#endregion
|
|
34
73
|
//#region src/Rules/baseRule.ts
|
|
35
74
|
var BaseRule = class {};
|
|
36
75
|
|
|
@@ -266,6 +305,61 @@ var locales_default = {
|
|
|
266
305
|
ar: ar_default
|
|
267
306
|
};
|
|
268
307
|
|
|
308
|
+
//#endregion
|
|
309
|
+
//#region src/utilities/helpers.ts
|
|
310
|
+
/**
|
|
311
|
+
* Pluralizes a word based on the count.
|
|
312
|
+
*
|
|
313
|
+
* @param word
|
|
314
|
+
* @param count
|
|
315
|
+
* @returns
|
|
316
|
+
*/
|
|
317
|
+
const plural = (word, count) => {
|
|
318
|
+
return count === 1 ? word : `${word}s`;
|
|
319
|
+
};
|
|
320
|
+
/**
|
|
321
|
+
* Determine if a value is an object. Arrays and null are not considered objects.
|
|
322
|
+
*
|
|
323
|
+
* @param value
|
|
324
|
+
* @returns
|
|
325
|
+
*/
|
|
326
|
+
function isObject(value) {
|
|
327
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
328
|
+
}
|
|
329
|
+
/**
|
|
330
|
+
* Deeply merge two objects.
|
|
331
|
+
* The source object will overwrite the target object when there is a conflict.
|
|
332
|
+
* Arrays and non-object values will be overwritten, not merged.
|
|
333
|
+
*
|
|
334
|
+
* @param target
|
|
335
|
+
* @param source
|
|
336
|
+
* @returns
|
|
337
|
+
*/
|
|
338
|
+
function mergeDeep(target, source) {
|
|
339
|
+
const output = Object.assign({}, target);
|
|
340
|
+
if (!isObject(target) || !isObject(source)) return output;
|
|
341
|
+
for (const key in source) if (isObject(source[key])) if (!target[key]) Object.assign(output, { [key]: source[key] });
|
|
342
|
+
else output[key] = mergeDeep(target[key], source[key]);
|
|
343
|
+
else Object.assign(output, { [key]: source[key] });
|
|
344
|
+
return output;
|
|
345
|
+
}
|
|
346
|
+
/**
|
|
347
|
+
* Deeply find a value in an object using a dot-notated path.
|
|
348
|
+
* Returns undefined if the path does not exist.
|
|
349
|
+
*
|
|
350
|
+
* @param obj
|
|
351
|
+
* @param path
|
|
352
|
+
* @returns
|
|
353
|
+
*/
|
|
354
|
+
function deepFindMessage(obj, path) {
|
|
355
|
+
const paths = path.split(".");
|
|
356
|
+
for (const segment of paths) {
|
|
357
|
+
if (typeof obj[segment] === "undefined") return;
|
|
358
|
+
obj = obj[segment];
|
|
359
|
+
}
|
|
360
|
+
return obj;
|
|
361
|
+
}
|
|
362
|
+
|
|
269
363
|
//#endregion
|
|
270
364
|
//#region src/Lang.ts
|
|
271
365
|
var Lang = class {
|
|
@@ -286,6 +380,10 @@ var Lang = class {
|
|
|
286
380
|
*/
|
|
287
381
|
static translations = {};
|
|
288
382
|
/**
|
|
383
|
+
* Store translations contributed by plugins.
|
|
384
|
+
*/
|
|
385
|
+
static translationExtensions = {};
|
|
386
|
+
/**
|
|
289
387
|
* Stores the messages that are already loaded
|
|
290
388
|
*/
|
|
291
389
|
static messages = {};
|
|
@@ -316,6 +414,16 @@ var Lang = class {
|
|
|
316
414
|
static setTranslationObject(translations) {
|
|
317
415
|
this.translations = translations;
|
|
318
416
|
this.existingLangs = Array.from(new Set([...this.existingLangs, ...Object.keys(translations)]));
|
|
417
|
+
this.resetLoadedMessages();
|
|
418
|
+
this.setDefaultLang(this.defaultLang);
|
|
419
|
+
}
|
|
420
|
+
/**
|
|
421
|
+
* Merge additional translations into the global catalog.
|
|
422
|
+
*/
|
|
423
|
+
static extendTranslationObject(translations) {
|
|
424
|
+
this.translationExtensions = mergeDeep(this.translationExtensions, translations);
|
|
425
|
+
this.existingLangs = Array.from(new Set([...this.existingLangs, ...Object.keys(translations)]));
|
|
426
|
+
this.resetLoadedMessages();
|
|
319
427
|
this.setDefaultLang(this.defaultLang);
|
|
320
428
|
}
|
|
321
429
|
/**
|
|
@@ -336,6 +444,7 @@ var Lang = class {
|
|
|
336
444
|
this.fallbackLang = lang;
|
|
337
445
|
this.fallbackMessages = locales_default.en;
|
|
338
446
|
if (Object.prototype.hasOwnProperty.call(locales_default, lang)) this.fallbackMessages = mergeDeep(this.fallbackMessages, locales_default[lang]);
|
|
447
|
+
if (Object.prototype.hasOwnProperty.call(this.translationExtensions, lang)) this.fallbackMessages = mergeDeep(this.fallbackMessages, this.translationExtensions[lang]);
|
|
339
448
|
if (Object.prototype.hasOwnProperty.call(this.translations, lang)) this.fallbackMessages = mergeDeep(this.fallbackMessages, this.translations[lang]);
|
|
340
449
|
}
|
|
341
450
|
/**
|
|
@@ -356,8 +465,13 @@ var Lang = class {
|
|
|
356
465
|
if (this.messages[lang]) return;
|
|
357
466
|
if (Object.prototype.hasOwnProperty.call(locales_default, lang)) this.messages[lang] = mergeDeep(this.fallbackMessages, locales_default[lang]);
|
|
358
467
|
else this.messages[lang] = mergeDeep({}, this.fallbackMessages);
|
|
468
|
+
if (Object.prototype.hasOwnProperty.call(this.translationExtensions, lang)) this.messages[lang] = mergeDeep(this.messages[lang], this.translationExtensions[lang]);
|
|
359
469
|
if (Object.prototype.hasOwnProperty.call(this.translations, lang)) this.messages[lang] = mergeDeep(this.messages[lang], this.translations[lang]);
|
|
360
470
|
}
|
|
471
|
+
static resetLoadedMessages() {
|
|
472
|
+
this.messages = {};
|
|
473
|
+
this.fallbackMessages = locales_default.en;
|
|
474
|
+
}
|
|
361
475
|
};
|
|
362
476
|
|
|
363
477
|
//#endregion
|
|
@@ -405,7 +519,7 @@ var IRuleContract = class {
|
|
|
405
519
|
* Get the translated error message based on the specified path
|
|
406
520
|
*/
|
|
407
521
|
trans(path, params = {}) {
|
|
408
|
-
let message =
|
|
522
|
+
let message = deepFindMessage(Lang.get(this.lang), path) || "";
|
|
409
523
|
if (!message) return message;
|
|
410
524
|
for (const key in params) message = message.replace(`:${key}`, params[key]);
|
|
411
525
|
return message;
|
|
@@ -413,1473 +527,1506 @@ var IRuleContract = class {
|
|
|
413
527
|
};
|
|
414
528
|
|
|
415
529
|
//#endregion
|
|
416
|
-
//#region src/utilities/
|
|
417
|
-
const implicitRues = [
|
|
418
|
-
"accepted",
|
|
419
|
-
"accepted_if",
|
|
420
|
-
"declined",
|
|
421
|
-
"declined_if",
|
|
422
|
-
"filled",
|
|
423
|
-
"present",
|
|
424
|
-
"required",
|
|
425
|
-
"required_if",
|
|
426
|
-
"required_unless",
|
|
427
|
-
"required_with",
|
|
428
|
-
"required_with_all",
|
|
429
|
-
"required_without",
|
|
430
|
-
"required_without_all"
|
|
431
|
-
];
|
|
432
|
-
/**
|
|
433
|
-
* Get the size of a value based on its type
|
|
434
|
-
*/
|
|
435
|
-
function getSize(value, hasNumericRule = false) {
|
|
436
|
-
if (typeof value === "number" || isNaN(value) === false && hasNumericRule === true) return Number(value);
|
|
437
|
-
else if (typeof value === "string" || Array.isArray(value)) return value.length;
|
|
438
|
-
else if (typeof value === "object" && value !== null) return Object.keys(value).length;
|
|
439
|
-
return -1;
|
|
440
|
-
}
|
|
441
|
-
/**
|
|
442
|
-
* Check if two values are of the same type
|
|
443
|
-
*/
|
|
444
|
-
function sameType(value, otherValue) {
|
|
445
|
-
return (Array.isArray(value) ? "array" : value === null ? null : typeof value) === (Array.isArray(otherValue) ? "array" : otherValue === null ? null : typeof otherValue);
|
|
446
|
-
}
|
|
447
|
-
/**
|
|
448
|
-
* Check if Value is an Ineteger
|
|
449
|
-
*/
|
|
450
|
-
function isInteger(value) {
|
|
451
|
-
return value !== null && isNaN(value) === false && value % 1 === 0;
|
|
452
|
-
}
|
|
453
|
-
/**
|
|
454
|
-
* Check if the value can be considered as rule
|
|
455
|
-
*/
|
|
456
|
-
function isRule(value) {
|
|
457
|
-
return typeof value === "string" || typeof value === "function" || value instanceof IRuleContract || value instanceof BaseRule;
|
|
458
|
-
}
|
|
459
|
-
/**
|
|
460
|
-
* Check if the array contain any potentiel valid rule
|
|
461
|
-
*/
|
|
462
|
-
function isArrayOfRules(values) {
|
|
463
|
-
for (let i = 0; i < values.length; i++) if (isRule(values[i])) return true;
|
|
464
|
-
return false;
|
|
465
|
-
}
|
|
466
|
-
/**
|
|
467
|
-
* Check if the rule is related to size
|
|
468
|
-
*/
|
|
469
|
-
function isSizeRule(rule) {
|
|
470
|
-
return [
|
|
471
|
-
"size",
|
|
472
|
-
"between",
|
|
473
|
-
"min",
|
|
474
|
-
"max",
|
|
475
|
-
"gt",
|
|
476
|
-
"lt",
|
|
477
|
-
"gte",
|
|
478
|
-
"lte"
|
|
479
|
-
].indexOf(rule) !== -1;
|
|
480
|
-
}
|
|
481
|
-
/**
|
|
482
|
-
* Check if rule implies that the field is required
|
|
483
|
-
*/
|
|
484
|
-
function isImplicitRule(rule) {
|
|
485
|
-
if (rule instanceof IRuleContract && rule.__isImplicitRule === true) return true;
|
|
486
|
-
if (typeof rule === "string") return implicitRues.indexOf(rule) !== -1;
|
|
487
|
-
return false;
|
|
488
|
-
}
|
|
489
|
-
/**
|
|
490
|
-
* Add a new implicit rule
|
|
491
|
-
*/
|
|
492
|
-
function addImplicitRule(rule) {
|
|
493
|
-
implicitRues.push(rule);
|
|
494
|
-
}
|
|
495
|
-
/**
|
|
496
|
-
* Returns the numeric rules
|
|
497
|
-
*/
|
|
498
|
-
function getNumericRules() {
|
|
499
|
-
return ["numeric", "integer"];
|
|
500
|
-
}
|
|
501
|
-
/**
|
|
502
|
-
* Check if the rule is numeric
|
|
503
|
-
*/
|
|
504
|
-
function isNumericRule(rule) {
|
|
505
|
-
return getNumericRules().indexOf(rule) !== -1;
|
|
506
|
-
}
|
|
507
|
-
/**
|
|
508
|
-
* Determine if a comparison passes between the given values.
|
|
509
|
-
*/
|
|
510
|
-
function compare(first, second, operator, strict = false) {
|
|
511
|
-
switch (operator) {
|
|
512
|
-
case "<": return first < second;
|
|
513
|
-
case ">": return first > second;
|
|
514
|
-
case "<=": return first <= second;
|
|
515
|
-
case ">=": return first >= second;
|
|
516
|
-
case "=":
|
|
517
|
-
if (strict === true) return first === second;
|
|
518
|
-
return first == second;
|
|
519
|
-
default: throw "Invalid operator parameter";
|
|
520
|
-
}
|
|
521
|
-
}
|
|
522
|
-
/**
|
|
523
|
-
* Convert the given values to boolean if they are string "true" / "false".
|
|
524
|
-
*/
|
|
525
|
-
function convertValuesToBoolean(values) {
|
|
526
|
-
return values.map((value) => {
|
|
527
|
-
if (value === "true") return true;
|
|
528
|
-
else if (value === "false") return false;
|
|
529
|
-
return value;
|
|
530
|
-
});
|
|
531
|
-
}
|
|
532
|
-
/**
|
|
533
|
-
* Convert the given values to numbers if they are numbers in a string "1", "2"
|
|
534
|
-
*/
|
|
535
|
-
function convertValuesToNumber(values) {
|
|
536
|
-
return values.map((value) => {
|
|
537
|
-
if (!isNaN(Number(value))) return Number(value);
|
|
538
|
-
return value;
|
|
539
|
-
});
|
|
540
|
-
}
|
|
530
|
+
//#region src/utilities/date.ts
|
|
541
531
|
/**
|
|
542
|
-
* Convert
|
|
532
|
+
* Convert value to date instance
|
|
543
533
|
*/
|
|
544
|
-
function
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
return value;
|
|
548
|
-
});
|
|
534
|
+
function toDate(value) {
|
|
535
|
+
const date = Date.parse(value);
|
|
536
|
+
return !isNaN(date) ? new Date(date) : null;
|
|
549
537
|
}
|
|
550
538
|
|
|
551
539
|
//#endregion
|
|
552
|
-
//#region src/
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
}
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
}
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
}
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
}
|
|
589
|
-
}
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
function
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
}
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
}
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
540
|
+
//#region src/validators/replaceAttributes.ts
|
|
541
|
+
const replaceAttributes = {
|
|
542
|
+
replaceAcceptedIf: function({ data, message, parameters, getDisplayableAttribute }) {
|
|
543
|
+
const [other] = parameters;
|
|
544
|
+
const result = deepFind(data, other);
|
|
545
|
+
const values = {
|
|
546
|
+
":other": getDisplayableAttribute(other),
|
|
547
|
+
":value": result
|
|
548
|
+
};
|
|
549
|
+
return message.replace(/:other|:value/gi, (matched) => values[matched]);
|
|
550
|
+
},
|
|
551
|
+
replaceAfter: function(payload) {
|
|
552
|
+
return this.replaceBefore(payload);
|
|
553
|
+
},
|
|
554
|
+
replaceAfterOrEqual: function(payload) {
|
|
555
|
+
return this.replaceBefore(payload);
|
|
556
|
+
},
|
|
557
|
+
replaceBefore: function({ message, parameters, getDisplayableAttribute }) {
|
|
558
|
+
if (!toDate(parameters[0])) return message.replace(":date", getDisplayableAttribute(parameters[0]));
|
|
559
|
+
return message.replace(":date", parameters[0]);
|
|
560
|
+
},
|
|
561
|
+
replaceBeforeOrEqual: function(payload) {
|
|
562
|
+
return this.replaceBefore(payload);
|
|
563
|
+
},
|
|
564
|
+
replaceBetween: function({ message, parameters }) {
|
|
565
|
+
const values = {
|
|
566
|
+
":min": parameters[0],
|
|
567
|
+
":max": parameters[1]
|
|
568
|
+
};
|
|
569
|
+
return message.replace(/:min|:max/gi, (matched) => values[matched]);
|
|
570
|
+
},
|
|
571
|
+
replaceDateEquals: function(payload) {
|
|
572
|
+
return this.replaceBefore(payload);
|
|
573
|
+
},
|
|
574
|
+
replaceDatetime: function({ message, parameters }) {
|
|
575
|
+
return message.replace(":format", parameters[0]);
|
|
576
|
+
},
|
|
577
|
+
replaceDeclinedIf: function({ message, parameters, data, getDisplayableAttribute }) {
|
|
578
|
+
const [other] = parameters;
|
|
579
|
+
const result = deepFind(data, other);
|
|
580
|
+
const values = {
|
|
581
|
+
":other": getDisplayableAttribute(other),
|
|
582
|
+
":value": result
|
|
583
|
+
};
|
|
584
|
+
return message.replace(/:other|:value/gi, (matched) => values[matched]);
|
|
585
|
+
},
|
|
586
|
+
replaceDifferent: function(payload) {
|
|
587
|
+
return this.replaceSame(payload);
|
|
588
|
+
},
|
|
589
|
+
replaceDigits: function({ message, parameters }) {
|
|
590
|
+
return message.replace(":digits", parameters[0]);
|
|
591
|
+
},
|
|
592
|
+
replaceDigitsBetween: function(payload) {
|
|
593
|
+
return this.replaceBetween(payload);
|
|
594
|
+
},
|
|
595
|
+
replaceEndsWith: function({ message, parameters }) {
|
|
596
|
+
return message.replace(":values", parameters.join(", "));
|
|
597
|
+
},
|
|
598
|
+
replaceExists: function({ message, parameters, data }) {
|
|
599
|
+
return message.replace(":value", data[parameters[1]]);
|
|
600
|
+
},
|
|
601
|
+
replaceIn: function({ message, parameters }) {
|
|
602
|
+
return message.replace(":values", parameters.join(", "));
|
|
603
|
+
},
|
|
604
|
+
replaceIncludes: function({ message, parameters }) {
|
|
605
|
+
return message.replace(":values", parameters.join(", "));
|
|
606
|
+
},
|
|
607
|
+
replaceStartsWith: function({ message, parameters }) {
|
|
608
|
+
return message.replace(":values", parameters.join(", "));
|
|
609
|
+
},
|
|
610
|
+
replaceMin: function({ message, parameters }) {
|
|
611
|
+
return message.replace(":min", parameters[0]);
|
|
612
|
+
},
|
|
613
|
+
replaceMax: function({ message, parameters }) {
|
|
614
|
+
return message.replace(":max", parameters[0]);
|
|
615
|
+
},
|
|
616
|
+
replaceNotIncludes: function({ message, parameters }) {
|
|
617
|
+
return message.replace(":values", parameters.join(", "));
|
|
618
|
+
},
|
|
619
|
+
replaceRequiredWith: function({ message, parameters, getDisplayableAttribute }) {
|
|
620
|
+
return message.replace(":values", parameters.map((attribute) => getDisplayableAttribute(attribute)).join(", "));
|
|
621
|
+
},
|
|
622
|
+
replaceRequiredWithAll: function(payload) {
|
|
623
|
+
return this.replaceRequiredWith(payload);
|
|
624
|
+
},
|
|
625
|
+
replaceRequiredWithout: function(payload) {
|
|
626
|
+
return this.replaceRequiredWith(payload);
|
|
627
|
+
},
|
|
628
|
+
replaceRequiredWithoutAll: function(payload) {
|
|
629
|
+
return this.replaceRequiredWith(payload);
|
|
630
|
+
},
|
|
631
|
+
replaceGt: function({ message, parameters, data, hasNumericRule }) {
|
|
632
|
+
const [value] = parameters;
|
|
633
|
+
const result = deepFind(data, value);
|
|
634
|
+
if (typeof result === "undefined") return message.replace(":value", value);
|
|
635
|
+
return message.replace(":value", getSize(result, hasNumericRule).toString());
|
|
636
|
+
},
|
|
637
|
+
replaceLt: function(payload) {
|
|
638
|
+
return this.replaceGt(payload);
|
|
639
|
+
},
|
|
640
|
+
replaceGte: function(payload) {
|
|
641
|
+
return this.replaceGt(payload);
|
|
642
|
+
},
|
|
643
|
+
replaceLte: function(payload) {
|
|
644
|
+
return this.replaceGt(payload);
|
|
645
|
+
},
|
|
646
|
+
replaceRequiredIf: function({ message, parameters, data, getDisplayableAttribute }) {
|
|
647
|
+
const [other] = parameters;
|
|
648
|
+
const result = deepFind(data, other);
|
|
649
|
+
const values = {
|
|
650
|
+
":other": getDisplayableAttribute(other),
|
|
651
|
+
":value": result
|
|
652
|
+
};
|
|
653
|
+
return message.replace(/:other|:value/gi, (matched) => values[matched]);
|
|
654
|
+
},
|
|
655
|
+
replaceRequiredUnless: function({ message, parameters, getDisplayableAttribute }) {
|
|
656
|
+
const [other] = parameters;
|
|
657
|
+
const values = {
|
|
658
|
+
":other": getDisplayableAttribute(other),
|
|
659
|
+
":values": parameters.slice(1).join(", ")
|
|
660
|
+
};
|
|
661
|
+
return message.replace(/:other|:values/gi, (matched) => values[matched]);
|
|
662
|
+
},
|
|
663
|
+
replaceSame: function({ message, parameters, getDisplayableAttribute }) {
|
|
664
|
+
return message.replace(":other", getDisplayableAttribute(parameters[0]));
|
|
665
|
+
},
|
|
666
|
+
replaceSize: function({ message, parameters }) {
|
|
667
|
+
return message.replace(":size", parameters[0]);
|
|
668
|
+
},
|
|
669
|
+
replaceUnique: function({ message, parameters, data }) {
|
|
670
|
+
return message.replace(":value", data[parameters[1]]);
|
|
671
|
+
}
|
|
672
|
+
};
|
|
651
673
|
|
|
652
674
|
//#endregion
|
|
653
|
-
//#region src/
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
const [attribute, primaryAttribute] = attributes;
|
|
667
|
-
const translatedMessages = dotify(Lang.get(lang)["custom"] || {});
|
|
668
|
-
const keys = getKeyCombinations(`${attribute}.${rule}`);
|
|
669
|
-
let allKeys = keys;
|
|
670
|
-
if (primaryAttribute) {
|
|
671
|
-
allKeys = [];
|
|
672
|
-
const primaryAttributeKeys = getKeyCombinations(`${primaryAttribute}.${rule}`);
|
|
673
|
-
for (let i = 0; i < keys.length; i++) {
|
|
674
|
-
allKeys.push(keys[i]);
|
|
675
|
-
if (keys[i] !== primaryAttributeKeys[i]) allKeys.push(primaryAttributeKeys[i]);
|
|
676
|
-
}
|
|
675
|
+
//#region src/Rules/closureValidationRule.ts
|
|
676
|
+
var ClosureValidationRule = class extends IRuleContract {
|
|
677
|
+
/**
|
|
678
|
+
* The callback that validates the attribute
|
|
679
|
+
*/
|
|
680
|
+
callback;
|
|
681
|
+
/**
|
|
682
|
+
* Indicates if the validation callback failed.
|
|
683
|
+
*/
|
|
684
|
+
failed = false;
|
|
685
|
+
constructor(callback) {
|
|
686
|
+
super();
|
|
687
|
+
this.callback = callback;
|
|
677
688
|
}
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
689
|
+
/**
|
|
690
|
+
* Determine if the validation rule passes.
|
|
691
|
+
*/
|
|
692
|
+
passes(value, attribute) {
|
|
693
|
+
this.failed = false;
|
|
694
|
+
const result = this.callback(value, (message) => {
|
|
695
|
+
this.failed = true;
|
|
696
|
+
this.message = message;
|
|
697
|
+
}, attribute);
|
|
698
|
+
if (result instanceof Promise) return result.then(() => !this.failed);
|
|
699
|
+
return !this.failed;
|
|
682
700
|
}
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
701
|
+
};
|
|
702
|
+
|
|
703
|
+
//#endregion
|
|
704
|
+
//#region src/validators/validationData.ts
|
|
705
|
+
const validationData = {
|
|
706
|
+
initializeAndGatherData: function(attribute, masterData) {
|
|
707
|
+
const data = dotify(this.initializeAttributeOnData(attribute, masterData));
|
|
708
|
+
return {
|
|
709
|
+
...data,
|
|
710
|
+
...this.extractValuesFromWildCards(masterData, data, attribute)
|
|
711
|
+
};
|
|
712
|
+
},
|
|
713
|
+
initializeAttributeOnData: function(attribute, masterData) {
|
|
714
|
+
const explicitPath = this.getLeadingExplicitAttributePath(attribute);
|
|
715
|
+
const data = this.extractDataFromPath(explicitPath, JSON.parse(JSON.stringify(masterData)));
|
|
716
|
+
if (attribute.indexOf("*") === -1 || attribute.indexOf("*") === attribute.length - 1) return data;
|
|
717
|
+
deepSet(data, attribute, null);
|
|
718
|
+
return data;
|
|
719
|
+
},
|
|
720
|
+
extractValuesFromWildCards(masterData, data, attribute) {
|
|
721
|
+
const keys = [];
|
|
722
|
+
const pattern = new RegExp("^" + attribute.replace(/\*/g, "[^\\.]*"));
|
|
723
|
+
let result = null;
|
|
724
|
+
for (const key in data) {
|
|
725
|
+
result = key.match(pattern);
|
|
726
|
+
if (result) keys.push(result[0]);
|
|
727
|
+
}
|
|
728
|
+
data = {};
|
|
729
|
+
keys.forEach((key) => data[key] = deepFind(masterData, key));
|
|
730
|
+
return data;
|
|
731
|
+
},
|
|
732
|
+
getLeadingExplicitAttributePath: function(attribute) {
|
|
733
|
+
return attribute.split("*")[0].replace(/\.$/, "");
|
|
734
|
+
},
|
|
735
|
+
extractDataFromPath(path, masterData) {
|
|
736
|
+
const results = {};
|
|
737
|
+
const value = deepFind(masterData, path);
|
|
738
|
+
if (value !== void 0) deepSet(results, path, value);
|
|
739
|
+
return results;
|
|
690
740
|
}
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
function
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
741
|
+
};
|
|
742
|
+
|
|
743
|
+
//#endregion
|
|
744
|
+
//#region src/validators/validationRuleParser.ts
|
|
745
|
+
const validationRuleParser = {
|
|
746
|
+
explodeRules: function(rules, data = {}) {
|
|
747
|
+
const implicitAttributes = {};
|
|
748
|
+
for (const key in rules) if (key.indexOf("*") !== -1) {
|
|
749
|
+
rules = this.explodeWildCardRules(rules, key, data, implicitAttributes);
|
|
750
|
+
delete rules[key];
|
|
751
|
+
} else if (Object.prototype.hasOwnProperty.call(rules, key) && Array.isArray(rules)) rules[Number(key)] = this.explodeExplicitRules(rules[Number(key)]);
|
|
752
|
+
else if (Object.prototype.hasOwnProperty.call(rules, key) && !Array.isArray(rules)) rules[key] = this.explodeExplicitRules(rules[key]);
|
|
753
|
+
return {
|
|
754
|
+
rules,
|
|
755
|
+
implicitAttributes
|
|
756
|
+
};
|
|
757
|
+
},
|
|
758
|
+
explodeWildCardRules: function(results, attribute, masterData, implicitAttributes) {
|
|
759
|
+
const pattern = new RegExp("^" + attribute.replace(/\*/g, "[^.]*") + "$");
|
|
760
|
+
const data = validationData.initializeAndGatherData(attribute, masterData);
|
|
761
|
+
const rule = results[attribute];
|
|
762
|
+
for (const key in data) if (key.slice(0, attribute.length) === attribute || key.match(pattern) !== null) {
|
|
763
|
+
if (Array.isArray(implicitAttributes[attribute])) implicitAttributes[attribute].push(key);
|
|
764
|
+
else implicitAttributes[attribute] = [key];
|
|
765
|
+
results = this.mergeRulesForAttribute(results, key, rule);
|
|
766
|
+
}
|
|
767
|
+
return results;
|
|
768
|
+
},
|
|
769
|
+
mergeRulesForAttribute(results, attribute, rules) {
|
|
770
|
+
const merge = this.explodeRules([rules]).rules[0];
|
|
771
|
+
results[attribute] = [...results[attribute] ? this.explodeExplicitRules(results[attribute]) : [], ...merge];
|
|
772
|
+
return results;
|
|
773
|
+
},
|
|
774
|
+
explodeExplicitRules: function(rules) {
|
|
775
|
+
if (typeof rules === "string") return rules.split("|");
|
|
776
|
+
if (!Array.isArray(rules)) return [this.prepareRule(rules)];
|
|
777
|
+
return rules.map((rule) => this.prepareRule(rule));
|
|
778
|
+
},
|
|
779
|
+
prepareRule(rule) {
|
|
780
|
+
if (rule instanceof IRuleContract) return rule;
|
|
781
|
+
if (typeof rule === "function") return new ClosureValidationRule(rule);
|
|
782
|
+
return rule.toString();
|
|
783
|
+
},
|
|
784
|
+
parse(rule) {
|
|
785
|
+
if (rule instanceof IRuleContract) return [rule, []];
|
|
786
|
+
return this.parseStringRule(rule);
|
|
787
|
+
},
|
|
788
|
+
parseStringRule: function(rule) {
|
|
789
|
+
let parameters = [];
|
|
790
|
+
let parameter;
|
|
791
|
+
if (rule.indexOf(":") !== -1) {
|
|
792
|
+
[rule, parameter] = rule.split(/:(.+)/);
|
|
793
|
+
parameters = parameter.split(",");
|
|
794
|
+
}
|
|
795
|
+
return [rule, parameters];
|
|
796
|
+
},
|
|
797
|
+
getRule: function(attribute, searchRules, availableRules) {
|
|
798
|
+
if (!availableRules[attribute]) return [];
|
|
799
|
+
searchRules = Array.isArray(searchRules) ? searchRules : [searchRules];
|
|
800
|
+
for (let i = 0; i < availableRules[attribute].length; i++) {
|
|
801
|
+
const [rule, parameters] = this.parse(availableRules[attribute][i]);
|
|
802
|
+
if (searchRules.indexOf(rule) !== -1) return [rule, parameters];
|
|
803
|
+
}
|
|
804
|
+
return [];
|
|
805
|
+
},
|
|
806
|
+
hasRule: function(attribute, searchRules, availableRules) {
|
|
807
|
+
return this.getRule(attribute, searchRules, availableRules).length > 0;
|
|
725
808
|
}
|
|
726
|
-
|
|
727
|
-
}
|
|
728
|
-
/**
|
|
729
|
-
* The purpose of this method if to get the primary key associated with a path
|
|
730
|
-
* For example the primary key for path 'user.info.email' will be 'email'
|
|
731
|
-
*/
|
|
732
|
-
function getPrimaryKeyFromPath(path) {
|
|
733
|
-
const splittedPath = path.split(".");
|
|
734
|
-
if (splittedPath.length <= 1) return path;
|
|
735
|
-
const key = splittedPath.pop();
|
|
736
|
-
if (isNaN(parseInt(key)) === false) return getPrimaryKeyFromPath(splittedPath.join("."));
|
|
737
|
-
return key;
|
|
738
|
-
}
|
|
809
|
+
};
|
|
739
810
|
|
|
740
811
|
//#endregion
|
|
741
|
-
//#region src/validators/
|
|
742
|
-
var
|
|
812
|
+
//#region src/validators/validateAttributes.ts
|
|
813
|
+
var validateAttributes = class {
|
|
743
814
|
/**
|
|
744
|
-
*
|
|
815
|
+
* Stores the data object
|
|
745
816
|
*/
|
|
746
|
-
|
|
817
|
+
data;
|
|
747
818
|
/**
|
|
748
|
-
*
|
|
819
|
+
* Stores the rules object
|
|
749
820
|
*/
|
|
750
|
-
|
|
821
|
+
rules;
|
|
751
822
|
/**
|
|
752
|
-
* Stores
|
|
823
|
+
* Stores validator context that plugins can consume.
|
|
753
824
|
*/
|
|
754
|
-
|
|
825
|
+
context;
|
|
826
|
+
constructor(data = {}, rules = {}, context = {}) {
|
|
827
|
+
this.data = data;
|
|
828
|
+
this.rules = rules;
|
|
829
|
+
this.context = context;
|
|
830
|
+
}
|
|
755
831
|
/**
|
|
756
|
-
*
|
|
832
|
+
* Validate that an attribute was "accepted".
|
|
833
|
+
*
|
|
834
|
+
* This validation rule implies the attribute is "required".
|
|
757
835
|
*/
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
836
|
+
validateAccepted(value) {
|
|
837
|
+
return this.validateRequired(value) && [
|
|
838
|
+
"yes",
|
|
839
|
+
"on",
|
|
840
|
+
"1",
|
|
841
|
+
1,
|
|
842
|
+
true,
|
|
843
|
+
"true"
|
|
844
|
+
].indexOf(value) !== -1;
|
|
764
845
|
}
|
|
765
846
|
/**
|
|
766
|
-
*
|
|
847
|
+
* Validate that an attribute was "accepted" when another attribute has a given value.
|
|
767
848
|
*/
|
|
768
|
-
|
|
769
|
-
this.
|
|
770
|
-
|
|
849
|
+
validateAcceptedIf(value, parameters) {
|
|
850
|
+
this.requireParameterCount(2, parameters, "accepted_if");
|
|
851
|
+
const other = deepFind(this.data, parameters[0]);
|
|
852
|
+
if (!other) return true;
|
|
853
|
+
if (parameters.slice(1).indexOf(other) !== -1) return this.validateAccepted(value);
|
|
854
|
+
return true;
|
|
771
855
|
}
|
|
772
856
|
/**
|
|
773
|
-
*
|
|
857
|
+
* Validate the date is after a given date.
|
|
774
858
|
*/
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
this.messages[key].push(error.message);
|
|
779
|
-
} else {
|
|
780
|
-
this.errors[key] = [error];
|
|
781
|
-
this.messages[key] = [error.message];
|
|
782
|
-
}
|
|
783
|
-
this.firstMessage = this.firstMessage || error.message;
|
|
859
|
+
validateAfter(value, parameters) {
|
|
860
|
+
this.requireParameterCount(1, parameters, "after");
|
|
861
|
+
return this.compareDates(value, parameters[0], ">", "after");
|
|
784
862
|
}
|
|
785
863
|
/**
|
|
786
|
-
*
|
|
864
|
+
* Validate the date is after or equal a given date.
|
|
787
865
|
*/
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
return "";
|
|
866
|
+
validateAfterOrEqual(value, parameters) {
|
|
867
|
+
this.requireParameterCount(1, parameters, "after_or_equal");
|
|
868
|
+
return this.compareDates(value, parameters[0], ">=", "after_or_equal");
|
|
792
869
|
}
|
|
793
870
|
/**
|
|
794
|
-
*
|
|
871
|
+
* Validate that an attribute contains only alphabetic characters.
|
|
795
872
|
*/
|
|
796
|
-
|
|
797
|
-
return
|
|
873
|
+
validateAlpha(value) {
|
|
874
|
+
return typeof value === "string" && /^[a-zA-Z]+$/.test(value);
|
|
798
875
|
}
|
|
799
876
|
/**
|
|
800
|
-
*
|
|
877
|
+
* Validate that an attribute contains only alpha-numeric characters, dashes, and underscores.
|
|
801
878
|
*/
|
|
802
|
-
|
|
803
|
-
if (
|
|
804
|
-
|
|
805
|
-
return this.messages[key];
|
|
879
|
+
validateAlphaDash(value) {
|
|
880
|
+
if (typeof value != "string" && typeof value != "number") return false;
|
|
881
|
+
return /^(?=.*[a-zA-Z0-9])[a-zA-Z0-9-_]+$/.test(value.toString());
|
|
806
882
|
}
|
|
807
883
|
/**
|
|
808
|
-
*
|
|
884
|
+
* Validate that an attribute contains only alpha-numeric characters.
|
|
809
885
|
*/
|
|
810
|
-
|
|
811
|
-
|
|
886
|
+
validateAlphaNum(value) {
|
|
887
|
+
if (typeof value != "string" && typeof value != "number") return false;
|
|
888
|
+
return /^[a-zA-Z0-9]+$/.test(value.toString());
|
|
812
889
|
}
|
|
813
890
|
/**
|
|
814
|
-
*
|
|
891
|
+
* Validate that an attribute is an array
|
|
815
892
|
*/
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
893
|
+
validateArray(value) {
|
|
894
|
+
return Array.isArray(value);
|
|
895
|
+
}
|
|
896
|
+
/**
|
|
897
|
+
* Validate that an attribute is an array and that his values are unique
|
|
898
|
+
*/
|
|
899
|
+
validateArrayUnique(value) {
|
|
900
|
+
if (!Array.isArray(value)) return false;
|
|
901
|
+
return new Set(value).size === value.length;
|
|
820
902
|
}
|
|
821
903
|
/**
|
|
822
|
-
*
|
|
904
|
+
* Always returns true - this method will be used in conbination with other rules and will be used to stop validation of first failure
|
|
823
905
|
*/
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
this.errors = {};
|
|
827
|
-
this.messages = {};
|
|
828
|
-
this.firstMessage = "";
|
|
829
|
-
return this;
|
|
830
|
-
}
|
|
831
|
-
keys.forEach((key) => {
|
|
832
|
-
if (Object.prototype.hasOwnProperty.call(this.messages, key)) {
|
|
833
|
-
delete this.messages[key];
|
|
834
|
-
delete this.errors[key];
|
|
835
|
-
}
|
|
836
|
-
});
|
|
837
|
-
this.firstMessage = "";
|
|
838
|
-
if (this.keys().length > 0) this.firstMessage = this.messages[Object.keys(this.messages)[0]][0];
|
|
839
|
-
return this;
|
|
906
|
+
validateBail() {
|
|
907
|
+
return true;
|
|
840
908
|
}
|
|
841
909
|
/**
|
|
842
|
-
*
|
|
910
|
+
* Validate the date is before a given date.
|
|
843
911
|
*/
|
|
844
|
-
|
|
845
|
-
|
|
912
|
+
validateBefore(value, parameters) {
|
|
913
|
+
this.requireParameterCount(1, parameters, "before");
|
|
914
|
+
return this.compareDates(value, parameters[0], "<", "before");
|
|
846
915
|
}
|
|
847
|
-
};
|
|
848
|
-
|
|
849
|
-
//#endregion
|
|
850
|
-
//#region src/Rules/password.ts
|
|
851
|
-
var Password$1 = class Password$1 extends IRuleContract {
|
|
852
916
|
/**
|
|
853
|
-
*
|
|
854
|
-
*/
|
|
855
|
-
validator;
|
|
856
|
-
/**
|
|
857
|
-
* The minimum size of the password.
|
|
917
|
+
* Validate the date is before or equal a given date.
|
|
858
918
|
*/
|
|
859
|
-
|
|
919
|
+
validateBeforeOrEqual(value, parameters) {
|
|
920
|
+
this.requireParameterCount(1, parameters, "before_or_equal");
|
|
921
|
+
return this.compareDates(value, parameters[0], "<=", "before_or_equal");
|
|
922
|
+
}
|
|
860
923
|
/**
|
|
861
|
-
*
|
|
924
|
+
* Validate the size of an attribute is between a set of values
|
|
862
925
|
*/
|
|
863
|
-
|
|
926
|
+
validateBetween(value, parameters, attribute) {
|
|
927
|
+
if (typeof value !== "string" && typeof value !== "number" && typeof value !== "object") throw "Validation rule between requires the field under validation to be a number, string, array, or object.";
|
|
928
|
+
this.requireParameterCount(2, parameters, "between");
|
|
929
|
+
let [min, max] = parameters;
|
|
930
|
+
if (isNaN(min) || isNaN(max)) throw "Validation rule between requires both parameters to be numbers.";
|
|
931
|
+
min = Number(min);
|
|
932
|
+
max = Number(max);
|
|
933
|
+
if (min >= max) throw "Validation rule between requires that the first parameter be greater than the second one.";
|
|
934
|
+
const size = getSize(value, validationRuleParser.hasRule(attribute, getNumericRules(), this.rules));
|
|
935
|
+
return size >= min && size <= max;
|
|
936
|
+
}
|
|
864
937
|
/**
|
|
865
|
-
*
|
|
938
|
+
* Validate that an attribute is boolean
|
|
866
939
|
*/
|
|
867
|
-
|
|
940
|
+
validateBoolean(value, parameters, attribute) {
|
|
941
|
+
if (validationRuleParser.hasRule(attribute, "strict", this.rules)) return typeof value === "boolean";
|
|
942
|
+
return [
|
|
943
|
+
true,
|
|
944
|
+
false,
|
|
945
|
+
0,
|
|
946
|
+
1,
|
|
947
|
+
"0",
|
|
948
|
+
"1"
|
|
949
|
+
].indexOf(value) !== -1;
|
|
950
|
+
}
|
|
868
951
|
/**
|
|
869
|
-
*
|
|
952
|
+
* Validate that an attribute has matching confirmation.
|
|
870
953
|
*/
|
|
871
|
-
|
|
954
|
+
validateConfirmed(value, parameters, attribute) {
|
|
955
|
+
return this.validateSame(value, [`${attribute}_confirmation`]) || this.validateSame(value, [`${attribute}Confirmation`]);
|
|
956
|
+
}
|
|
872
957
|
/**
|
|
873
|
-
*
|
|
958
|
+
* Validate that an attribute is a valid date.
|
|
874
959
|
*/
|
|
875
|
-
|
|
960
|
+
validateDate(value) {
|
|
961
|
+
return toDate(value) ? true : false;
|
|
962
|
+
}
|
|
876
963
|
/**
|
|
877
|
-
*
|
|
964
|
+
* Validate that an attribute is equal to another date.
|
|
878
965
|
*/
|
|
879
|
-
|
|
966
|
+
validateDateEquals(value, paramters) {
|
|
967
|
+
this.requireParameterCount(1, paramters, "date_equals");
|
|
968
|
+
return this.compareDates(value, paramters[0], "=", "date_equals");
|
|
969
|
+
}
|
|
880
970
|
/**
|
|
881
|
-
*
|
|
971
|
+
* Validate that an attribute was "declined".
|
|
972
|
+
*
|
|
973
|
+
* This validation rule implies the attribute is "required".
|
|
882
974
|
*/
|
|
883
|
-
|
|
975
|
+
validateDeclined(value) {
|
|
976
|
+
return this.validateRequired(value) && [
|
|
977
|
+
"no",
|
|
978
|
+
"off",
|
|
979
|
+
"0",
|
|
980
|
+
0,
|
|
981
|
+
false,
|
|
982
|
+
"false"
|
|
983
|
+
].indexOf(value) !== -1;
|
|
984
|
+
}
|
|
884
985
|
/**
|
|
885
|
-
*
|
|
986
|
+
* Validate that an attribute was "declined" when another attribute has a given value.
|
|
886
987
|
*/
|
|
887
|
-
|
|
988
|
+
validateDeclinedIf(value, parameters) {
|
|
989
|
+
this.requireParameterCount(2, parameters, "declined_if");
|
|
990
|
+
const other = deepFind(this.data, parameters[0]);
|
|
991
|
+
if (!other) return true;
|
|
992
|
+
if (parameters.slice(1).indexOf(other) !== -1) return this.validateDeclined(value);
|
|
993
|
+
return true;
|
|
994
|
+
}
|
|
888
995
|
/**
|
|
889
|
-
*
|
|
996
|
+
* Validate that an attribute is different from another attribute.
|
|
890
997
|
*/
|
|
891
|
-
|
|
892
|
-
|
|
998
|
+
validateDifferent(value, parameters) {
|
|
999
|
+
this.requireParameterCount(1, parameters, "different");
|
|
1000
|
+
const other = deepFind(this.data, parameters[0]);
|
|
1001
|
+
if (!sameType(value, other)) return true;
|
|
1002
|
+
if (value !== null && typeof value === "object") return !deepEqual(value, other);
|
|
1003
|
+
return value !== other;
|
|
893
1004
|
}
|
|
894
1005
|
/**
|
|
895
|
-
*
|
|
1006
|
+
* Validate that an attribute has a given number of digits.
|
|
896
1007
|
*/
|
|
897
|
-
|
|
898
|
-
this.
|
|
899
|
-
|
|
1008
|
+
validateDigits(value, parameters) {
|
|
1009
|
+
this.requireParameterCount(1, parameters, "digits");
|
|
1010
|
+
if (isInteger(parameters[0]) === false) throw "Validation rule digits requires the parameter to be an integer.";
|
|
1011
|
+
if (parameters[0] <= 0) throw "Validation rule digits requires the parameter to be an integer greater than 0.";
|
|
1012
|
+
if (typeof value !== "string" && typeof value !== "number") return false;
|
|
1013
|
+
value = value.toString();
|
|
1014
|
+
return /^\d+$/.test(value) && value.length === parseInt(parameters[0]);
|
|
900
1015
|
}
|
|
901
1016
|
/**
|
|
902
|
-
*
|
|
1017
|
+
* Validate that an attribute is between a given number of digits.
|
|
903
1018
|
*/
|
|
904
|
-
|
|
905
|
-
this.
|
|
906
|
-
|
|
1019
|
+
validateDigitsBetween(value, parameters) {
|
|
1020
|
+
this.requireParameterCount(2, parameters, "digits_between");
|
|
1021
|
+
let [min, max] = parameters;
|
|
1022
|
+
if (isInteger(min) === false || isInteger(max) === false) throw "Validation rule digits_between requires both parameters to be integers.";
|
|
1023
|
+
min = parseInt(min);
|
|
1024
|
+
max = parseInt(max);
|
|
1025
|
+
if (min <= 0 || max <= 0) throw "Validation rule digits_between requires the parameters to be an integer greater than 0.";
|
|
1026
|
+
if (min >= max) throw "Validation rule digits_between requires the max param to be greater than the min param.";
|
|
1027
|
+
if (typeof value !== "string" && typeof value !== "number") return false;
|
|
1028
|
+
value = value.toString();
|
|
1029
|
+
const valueLength = value.length;
|
|
1030
|
+
return /^\d+$/.test(value) && valueLength >= min && valueLength <= max;
|
|
907
1031
|
}
|
|
908
1032
|
/**
|
|
909
|
-
*
|
|
1033
|
+
* Validate that an attribute is a valid email address.
|
|
910
1034
|
*/
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
1035
|
+
validateEmail(value) {
|
|
1036
|
+
if (typeof value !== "string") return false;
|
|
1037
|
+
/**
|
|
1038
|
+
* Max allowed length for a top-level-domain is 24 characters.
|
|
1039
|
+
* reference to list of top-level-domains: https://data.iana.org/TLD/tlds-alpha-by-domain.txt
|
|
1040
|
+
*/
|
|
1041
|
+
return value.toLowerCase().match(/^\w+([.-]?\w+)*@\w+([.-]?\w+)*(\.\w{2,24})+$/) !== null;
|
|
915
1042
|
}
|
|
916
1043
|
/**
|
|
917
|
-
*
|
|
1044
|
+
* Validate the attribute ends with a given substring.
|
|
918
1045
|
*/
|
|
919
|
-
|
|
920
|
-
this.
|
|
921
|
-
|
|
1046
|
+
validateEndsWith(value, parameters) {
|
|
1047
|
+
this.requireParameterCount(1, parameters, "ends_with");
|
|
1048
|
+
if (typeof value !== "string") throw "The field under validation must be a string";
|
|
1049
|
+
const valueLength = value.length;
|
|
1050
|
+
for (let i = 0; i < parameters.length; i++) if (typeof parameters[i] === "string" && value.indexOf(parameters[i], valueLength - parameters[i].length) !== -1) return true;
|
|
1051
|
+
return false;
|
|
922
1052
|
}
|
|
923
1053
|
/**
|
|
924
|
-
*
|
|
1054
|
+
* Validate that two attributes match.
|
|
925
1055
|
*/
|
|
926
|
-
|
|
927
|
-
this.
|
|
928
|
-
|
|
1056
|
+
validateSame(value, paramaters) {
|
|
1057
|
+
this.requireParameterCount(1, paramaters, "same");
|
|
1058
|
+
const other = deepFind(this.data, paramaters[0]);
|
|
1059
|
+
if (!sameType(value, other)) return false;
|
|
1060
|
+
if (value !== null && typeof value === "object") return deepEqual(value, other);
|
|
1061
|
+
return value === other;
|
|
929
1062
|
}
|
|
930
1063
|
/**
|
|
931
|
-
*
|
|
1064
|
+
* Validate the size of an attribute.
|
|
932
1065
|
*/
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
`min:${this.minLength}`,
|
|
937
|
-
...this.customRules
|
|
938
|
-
] }, this.validator.customMessages, this.validator.customAttributes).setLang(this.lang);
|
|
939
|
-
if (!validator.validate()) {
|
|
940
|
-
const errors = validator.errors().addErrorTypes().get(attribute);
|
|
941
|
-
for (const key in errors) this.validator.errors().add(attribute, errors[key]);
|
|
942
|
-
}
|
|
943
|
-
if (typeof value !== "string") value = "";
|
|
944
|
-
let pattern;
|
|
945
|
-
const formattedAttribute = this.validator.getDisplayableAttribute(attribute);
|
|
946
|
-
if (this.minLowerCase) {
|
|
947
|
-
pattern = this.minLowerCase === 1 ? /\p{Ll}/u : new RegExp(`(.*\\p{Ll}){${this.minLowerCase}}.*`, "u");
|
|
948
|
-
if (!value || pattern.test(value) === false) this.validator.errors().add(attribute, {
|
|
949
|
-
error_type: "min_lower_case",
|
|
950
|
-
message: this.trans(`password.${this.minLowerCase === 1 ? "lower_case" : "lower_cases"}`, {
|
|
951
|
-
attribute: formattedAttribute,
|
|
952
|
-
amount: this.minLowerCase
|
|
953
|
-
})
|
|
954
|
-
});
|
|
955
|
-
}
|
|
956
|
-
if (this.minUpperCase) {
|
|
957
|
-
pattern = this.minUpperCase === 1 ? /\p{Lu}/u : new RegExp(`(.*\\p{Lu}){${this.minUpperCase}}.*`, "u");
|
|
958
|
-
if (!value || pattern.test(value) === false) this.validator.errors().add(attribute, {
|
|
959
|
-
error_type: "min_upper_case",
|
|
960
|
-
message: this.trans(`password.${this.minUpperCase === 1 ? "upper_case" : "upper_cases"}`, {
|
|
961
|
-
attribute: formattedAttribute,
|
|
962
|
-
amount: this.minUpperCase
|
|
963
|
-
})
|
|
964
|
-
});
|
|
965
|
-
}
|
|
966
|
-
if (this.minLetters) {
|
|
967
|
-
pattern = this.minLetters === 1 ? /\p{L}/u : new RegExp(`(.*\\p{L}){${this.minLetters}}.*`, "u");
|
|
968
|
-
if (!value || pattern.test(value) === false) this.validator.errors().add(attribute, {
|
|
969
|
-
error_type: "min_letters",
|
|
970
|
-
message: this.trans(`password.${this.minLetters === 1 ? "letter" : "letters"}`, {
|
|
971
|
-
attribute: formattedAttribute,
|
|
972
|
-
amount: this.minLetters
|
|
973
|
-
})
|
|
974
|
-
});
|
|
975
|
-
}
|
|
976
|
-
if (this.minNumbers) {
|
|
977
|
-
pattern = this.minNumbers === 1 ? /\p{N}/u : new RegExp(`(.*\\p{N}){${this.minNumbers}}.*`, "u");
|
|
978
|
-
if (!value || pattern.test(value) === false) this.validator.errors().add(attribute, {
|
|
979
|
-
error_type: "min_numbers",
|
|
980
|
-
message: this.trans(`password.${this.minNumbers === 1 ? "number" : "numbers"}`, {
|
|
981
|
-
attribute: formattedAttribute,
|
|
982
|
-
amount: this.minNumbers
|
|
983
|
-
})
|
|
984
|
-
});
|
|
985
|
-
}
|
|
986
|
-
if (this.minSymbols) {
|
|
987
|
-
pattern = this.minSymbols === 1 ? /\p{Z}|\p{S}|\p{P}/u : new RegExp(`(.*(\\p{Z}|\\p{S}|\\p{P})){${this.minSymbols}}.*`, "u");
|
|
988
|
-
if (!value || pattern.test(value) === false) this.validator.errors().add(attribute, {
|
|
989
|
-
error_type: "min_symbols",
|
|
990
|
-
message: this.trans(`password.${this.minSymbols === 1 ? "symbol" : "symbols"}`, {
|
|
991
|
-
attribute: formattedAttribute,
|
|
992
|
-
amount: this.minSymbols
|
|
993
|
-
})
|
|
994
|
-
});
|
|
995
|
-
}
|
|
996
|
-
if (this.validator.errors().has(attribute)) return false;
|
|
997
|
-
return true;
|
|
1066
|
+
validateSize(value, parameters, attribute) {
|
|
1067
|
+
this.requireParameterCount(1, parameters, "size");
|
|
1068
|
+
return getSize(value, validationRuleParser.hasRule(attribute, getNumericRules(), this.rules)) === Number(parameters[0]);
|
|
998
1069
|
}
|
|
999
1070
|
/**
|
|
1000
|
-
*
|
|
1071
|
+
* Validate Optinial attributes. Always return true, just lets us put sometimes in rule.
|
|
1001
1072
|
*/
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
return this;
|
|
1073
|
+
validateSometimes() {
|
|
1074
|
+
return true;
|
|
1005
1075
|
}
|
|
1006
1076
|
/**
|
|
1007
|
-
*
|
|
1077
|
+
* Validate the attribute starts with a given substring.
|
|
1008
1078
|
*/
|
|
1009
|
-
|
|
1010
|
-
|
|
1079
|
+
validateStartsWith(value, parameters) {
|
|
1080
|
+
this.requireParameterCount(1, parameters, "starts_with");
|
|
1081
|
+
if (typeof value !== "string") throw "The field under validation must be a string";
|
|
1082
|
+
for (let i = 0; i < parameters.length; i++) if (typeof parameters[i] === "string" && value.substr(0, parameters[i].length) === parameters[i]) return true;
|
|
1083
|
+
return false;
|
|
1011
1084
|
}
|
|
1012
1085
|
/**
|
|
1013
|
-
*
|
|
1086
|
+
* Validate that a required attribute exists
|
|
1014
1087
|
*/
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
return
|
|
1088
|
+
validateRequired(value) {
|
|
1089
|
+
if (value === null || typeof value === "undefined") return false;
|
|
1090
|
+
else if (typeof value === "string" && value.trim() === "") return false;
|
|
1091
|
+
else if (Array.isArray(value) && value.length < 1) return false;
|
|
1092
|
+
else if (typeof value === "object" && Object.keys(value).length < 1) return false;
|
|
1093
|
+
return true;
|
|
1018
1094
|
}
|
|
1019
1095
|
/**
|
|
1020
|
-
*
|
|
1096
|
+
* Validate that an attribute exists when another atteribute has a given value
|
|
1021
1097
|
*/
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
this.defaultCallback = callback;
|
|
1098
|
+
validateRequiredIf(value, parameters) {
|
|
1099
|
+
this.requireParameterCount(2, parameters, "required_if");
|
|
1100
|
+
const other = deepFind(this.data, parameters[0]);
|
|
1101
|
+
if (typeof other === "undefined") return true;
|
|
1102
|
+
if (this.parseDependentRuleParameters(other, parameters).indexOf(other) !== -1) return this.validateRequired(value);
|
|
1103
|
+
return true;
|
|
1029
1104
|
}
|
|
1030
1105
|
/**
|
|
1031
|
-
*
|
|
1106
|
+
* Validate that an attribute exists when another attribute does not have a given value.
|
|
1032
1107
|
*/
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1108
|
+
validateRequiredUnless(value, parameters) {
|
|
1109
|
+
this.requireParameterCount(2, parameters, "required_unless");
|
|
1110
|
+
let other = deepFind(this.data, parameters[0]);
|
|
1111
|
+
other = typeof other === "undefined" ? null : other;
|
|
1112
|
+
if (this.parseDependentRuleParameters(other, parameters).indexOf(other) === -1) return this.validateRequired(value);
|
|
1113
|
+
return true;
|
|
1036
1114
|
}
|
|
1037
|
-
};
|
|
1038
|
-
|
|
1039
|
-
//#endregion
|
|
1040
|
-
//#region src/utilities/build.ts
|
|
1041
|
-
function buildValidationMethodName(rule) {
|
|
1042
|
-
if (!rule) return rule;
|
|
1043
|
-
return rule.split("_").map((rule) => `${rule[0].toUpperCase()}${rule.slice(1)}`).join("");
|
|
1044
|
-
}
|
|
1045
|
-
|
|
1046
|
-
//#endregion
|
|
1047
|
-
//#region src/payloads/replaceAttributePayload.ts
|
|
1048
|
-
var replaceAttributePayload = class {
|
|
1049
1115
|
/**
|
|
1050
|
-
*
|
|
1116
|
+
* Validate that an attribute exists when any other attribute exists.
|
|
1051
1117
|
*/
|
|
1052
|
-
|
|
1118
|
+
validateRequiredWith(value, parameters) {
|
|
1119
|
+
if (!this.allFailingRequired(parameters)) return this.validateRequired(value);
|
|
1120
|
+
return true;
|
|
1121
|
+
}
|
|
1053
1122
|
/**
|
|
1054
|
-
*
|
|
1123
|
+
* Validate that an attribute exists when all other attributes exist.
|
|
1055
1124
|
*/
|
|
1056
|
-
|
|
1125
|
+
validateRequiredWithAll(value, parameters) {
|
|
1126
|
+
if (!this.anyFailingRequired(parameters)) return this.validateRequired(value);
|
|
1127
|
+
return true;
|
|
1128
|
+
}
|
|
1057
1129
|
/**
|
|
1058
|
-
*
|
|
1130
|
+
* Validate that an attribute exists when another attribute does not.
|
|
1059
1131
|
*/
|
|
1060
|
-
parameters
|
|
1132
|
+
validateRequiredWithout(value, parameters) {
|
|
1133
|
+
if (this.anyFailingRequired(parameters)) return this.validateRequired(value);
|
|
1134
|
+
return true;
|
|
1135
|
+
}
|
|
1061
1136
|
/**
|
|
1062
|
-
*
|
|
1137
|
+
* Validate that an attribute exists when all other attributes do not.
|
|
1063
1138
|
*/
|
|
1064
|
-
|
|
1139
|
+
validateRequiredWithoutAll(value, parameters) {
|
|
1140
|
+
if (this.allFailingRequired(parameters)) return this.validateRequired(value);
|
|
1141
|
+
return true;
|
|
1142
|
+
}
|
|
1065
1143
|
/**
|
|
1066
|
-
*
|
|
1144
|
+
* Determine if any of the given attributes fail the required test.
|
|
1067
1145
|
*/
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
this.parameters = parameters;
|
|
1073
|
-
this.hasNumericRule = hasNumericRule;
|
|
1074
|
-
this.getDisplayableAttribute = getDisplayableAttribute;
|
|
1075
|
-
}
|
|
1076
|
-
};
|
|
1077
|
-
|
|
1078
|
-
//#endregion
|
|
1079
|
-
//#region src/utilities/date.ts
|
|
1080
|
-
/**
|
|
1081
|
-
* Convert value to date instance
|
|
1082
|
-
*/
|
|
1083
|
-
function toDate(value) {
|
|
1084
|
-
const date = Date.parse(value);
|
|
1085
|
-
return !isNaN(date) ? new Date(date) : null;
|
|
1086
|
-
}
|
|
1087
|
-
|
|
1088
|
-
//#endregion
|
|
1089
|
-
//#region src/validators/replaceAttributes.ts
|
|
1090
|
-
const replaceAttributes = {
|
|
1091
|
-
replaceAcceptedIf: function({ data, message, parameters, getDisplayableAttribute }) {
|
|
1092
|
-
const [other] = parameters;
|
|
1093
|
-
const result = deepFind(data, other);
|
|
1094
|
-
const values = {
|
|
1095
|
-
":other": getDisplayableAttribute(other),
|
|
1096
|
-
":value": result
|
|
1097
|
-
};
|
|
1098
|
-
return message.replace(/:other|:value/gi, (matched) => values[matched]);
|
|
1099
|
-
},
|
|
1100
|
-
replaceAfter: function(payload) {
|
|
1101
|
-
return this.replaceBefore(payload);
|
|
1102
|
-
},
|
|
1103
|
-
replaceAfterOrEqual: function(payload) {
|
|
1104
|
-
return this.replaceBefore(payload);
|
|
1105
|
-
},
|
|
1106
|
-
replaceBefore: function({ message, parameters, getDisplayableAttribute }) {
|
|
1107
|
-
if (!toDate(parameters[0])) return message.replace(":date", getDisplayableAttribute(parameters[0]));
|
|
1108
|
-
return message.replace(":date", parameters[0]);
|
|
1109
|
-
},
|
|
1110
|
-
replaceBeforeOrEqual: function(payload) {
|
|
1111
|
-
return this.replaceBefore(payload);
|
|
1112
|
-
},
|
|
1113
|
-
replaceBetween: function({ message, parameters }) {
|
|
1114
|
-
const values = {
|
|
1115
|
-
":min": parameters[0],
|
|
1116
|
-
":max": parameters[1]
|
|
1117
|
-
};
|
|
1118
|
-
return message.replace(/:min|:max/gi, (matched) => values[matched]);
|
|
1119
|
-
},
|
|
1120
|
-
replaceDateEquals: function(payload) {
|
|
1121
|
-
return this.replaceBefore(payload);
|
|
1122
|
-
},
|
|
1123
|
-
replaceDatetime: function({ message, parameters }) {
|
|
1124
|
-
return message.replace(":format", parameters[0]);
|
|
1125
|
-
},
|
|
1126
|
-
replaceDeclinedIf: function({ message, parameters, data, getDisplayableAttribute }) {
|
|
1127
|
-
const [other] = parameters;
|
|
1128
|
-
const result = deepFind(data, other);
|
|
1129
|
-
const values = {
|
|
1130
|
-
":other": getDisplayableAttribute(other),
|
|
1131
|
-
":value": result
|
|
1132
|
-
};
|
|
1133
|
-
return message.replace(/:other|:value/gi, (matched) => values[matched]);
|
|
1134
|
-
},
|
|
1135
|
-
replaceDifferent: function(payload) {
|
|
1136
|
-
return this.replaceSame(payload);
|
|
1137
|
-
},
|
|
1138
|
-
replaceDigits: function({ message, parameters }) {
|
|
1139
|
-
return message.replace(":digits", parameters[0]);
|
|
1140
|
-
},
|
|
1141
|
-
replaceDigitsBetween: function(payload) {
|
|
1142
|
-
return this.replaceBetween(payload);
|
|
1143
|
-
},
|
|
1144
|
-
replaceEndsWith: function({ message, parameters }) {
|
|
1145
|
-
return message.replace(":values", parameters.join(", "));
|
|
1146
|
-
},
|
|
1147
|
-
replaceExists: function({ message, parameters, data }) {
|
|
1148
|
-
return message.replace(":value", data[parameters[1]]);
|
|
1149
|
-
},
|
|
1150
|
-
replaceIn: function({ message, parameters }) {
|
|
1151
|
-
return message.replace(":values", parameters.join(", "));
|
|
1152
|
-
},
|
|
1153
|
-
replaceIncludes: function({ message, parameters }) {
|
|
1154
|
-
return message.replace(":values", parameters.join(", "));
|
|
1155
|
-
},
|
|
1156
|
-
replaceStartsWith: function({ message, parameters }) {
|
|
1157
|
-
return message.replace(":values", parameters.join(", "));
|
|
1158
|
-
},
|
|
1159
|
-
replaceMin: function({ message, parameters }) {
|
|
1160
|
-
return message.replace(":min", parameters[0]);
|
|
1161
|
-
},
|
|
1162
|
-
replaceMax: function({ message, parameters }) {
|
|
1163
|
-
return message.replace(":max", parameters[0]);
|
|
1164
|
-
},
|
|
1165
|
-
replaceNotIncludes: function({ message, parameters }) {
|
|
1166
|
-
return message.replace(":values", parameters.join(", "));
|
|
1167
|
-
},
|
|
1168
|
-
replaceRequiredWith: function({ message, parameters, getDisplayableAttribute }) {
|
|
1169
|
-
return message.replace(":values", parameters.map((attribute) => getDisplayableAttribute(attribute)).join(", "));
|
|
1170
|
-
},
|
|
1171
|
-
replaceRequiredWithAll: function(payload) {
|
|
1172
|
-
return this.replaceRequiredWith(payload);
|
|
1173
|
-
},
|
|
1174
|
-
replaceRequiredWithout: function(payload) {
|
|
1175
|
-
return this.replaceRequiredWith(payload);
|
|
1176
|
-
},
|
|
1177
|
-
replaceRequiredWithoutAll: function(payload) {
|
|
1178
|
-
return this.replaceRequiredWith(payload);
|
|
1179
|
-
},
|
|
1180
|
-
replaceGt: function({ message, parameters, data, hasNumericRule }) {
|
|
1181
|
-
const [value] = parameters;
|
|
1182
|
-
const result = deepFind(data, value);
|
|
1183
|
-
if (typeof result === "undefined") return message.replace(":value", value);
|
|
1184
|
-
return message.replace(":value", getSize(result, hasNumericRule).toString());
|
|
1185
|
-
},
|
|
1186
|
-
replaceLt: function(payload) {
|
|
1187
|
-
return this.replaceGt(payload);
|
|
1188
|
-
},
|
|
1189
|
-
replaceGte: function(payload) {
|
|
1190
|
-
return this.replaceGt(payload);
|
|
1191
|
-
},
|
|
1192
|
-
replaceLte: function(payload) {
|
|
1193
|
-
return this.replaceGt(payload);
|
|
1194
|
-
},
|
|
1195
|
-
replaceRequiredIf: function({ message, parameters, data, getDisplayableAttribute }) {
|
|
1196
|
-
const [other] = parameters;
|
|
1197
|
-
const result = deepFind(data, other);
|
|
1198
|
-
const values = {
|
|
1199
|
-
":other": getDisplayableAttribute(other),
|
|
1200
|
-
":value": result
|
|
1201
|
-
};
|
|
1202
|
-
return message.replace(/:other|:value/gi, (matched) => values[matched]);
|
|
1203
|
-
},
|
|
1204
|
-
replaceRequiredUnless: function({ message, parameters, getDisplayableAttribute }) {
|
|
1205
|
-
const [other] = parameters;
|
|
1206
|
-
const values = {
|
|
1207
|
-
":other": getDisplayableAttribute(other),
|
|
1208
|
-
":values": parameters.slice(1).join(", ")
|
|
1209
|
-
};
|
|
1210
|
-
return message.replace(/:other|:values/gi, (matched) => values[matched]);
|
|
1211
|
-
},
|
|
1212
|
-
replaceSame: function({ message, parameters, getDisplayableAttribute }) {
|
|
1213
|
-
return message.replace(":other", getDisplayableAttribute(parameters[0]));
|
|
1214
|
-
},
|
|
1215
|
-
replaceSize: function({ message, parameters }) {
|
|
1216
|
-
return message.replace(":size", parameters[0]);
|
|
1217
|
-
},
|
|
1218
|
-
replaceUnique: function({ message, parameters, data }) {
|
|
1219
|
-
return message.replace(":value", data[parameters[1]]);
|
|
1220
|
-
}
|
|
1221
|
-
};
|
|
1222
|
-
|
|
1223
|
-
//#endregion
|
|
1224
|
-
//#region src/Rules/closureValidationRule.ts
|
|
1225
|
-
var ClosureValidationRule = class extends IRuleContract {
|
|
1146
|
+
anyFailingRequired(attributes) {
|
|
1147
|
+
for (let i = 0; i < attributes.length; i++) if (!this.validateRequired(deepFind(this.data, attributes[i]))) return true;
|
|
1148
|
+
return false;
|
|
1149
|
+
}
|
|
1226
1150
|
/**
|
|
1227
|
-
*
|
|
1151
|
+
* Determine if all of the given attributes fail the required test.
|
|
1228
1152
|
*/
|
|
1229
|
-
|
|
1153
|
+
allFailingRequired(attributes) {
|
|
1154
|
+
for (let i = 0; i < attributes.length; i++) if (this.validateRequired(deepFind(this.data, attributes[i]))) return false;
|
|
1155
|
+
return true;
|
|
1156
|
+
}
|
|
1230
1157
|
/**
|
|
1231
|
-
*
|
|
1158
|
+
* Validate that an attribute is a string.
|
|
1232
1159
|
*/
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
super();
|
|
1236
|
-
this.callback = callback;
|
|
1160
|
+
validateString(value) {
|
|
1161
|
+
return typeof value === "string";
|
|
1237
1162
|
}
|
|
1238
1163
|
/**
|
|
1239
|
-
*
|
|
1164
|
+
* Validate the size of an attribute is less than a maximum value.
|
|
1240
1165
|
*/
|
|
1241
|
-
|
|
1242
|
-
this.
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
this.message = message;
|
|
1246
|
-
}, attribute);
|
|
1247
|
-
if (result instanceof Promise) return result.then(() => !this.failed);
|
|
1248
|
-
return !this.failed;
|
|
1166
|
+
validateMax(value, parameters, attribute) {
|
|
1167
|
+
this.requireParameterCount(1, parameters, "max");
|
|
1168
|
+
if (isNaN(parameters[0])) throw "Validation rule max requires parameter to be a number.";
|
|
1169
|
+
return getSize(value, validationRuleParser.hasRule(attribute, getNumericRules(), this.rules)) <= Number(parameters[0]);
|
|
1249
1170
|
}
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
return {
|
|
1258
|
-
...data,
|
|
1259
|
-
...this.extractValuesFromWildCards(masterData, data, attribute)
|
|
1260
|
-
};
|
|
1261
|
-
},
|
|
1262
|
-
initializeAttributeOnData: function(attribute, masterData) {
|
|
1263
|
-
const explicitPath = this.getLeadingExplicitAttributePath(attribute);
|
|
1264
|
-
const data = this.extractDataFromPath(explicitPath, JSON.parse(JSON.stringify(masterData)));
|
|
1265
|
-
if (attribute.indexOf("*") === -1 || attribute.indexOf("*") === attribute.length - 1) return data;
|
|
1266
|
-
deepSet(data, attribute, null);
|
|
1267
|
-
return data;
|
|
1268
|
-
},
|
|
1269
|
-
extractValuesFromWildCards(masterData, data, attribute) {
|
|
1270
|
-
const keys = [];
|
|
1271
|
-
const pattern = new RegExp("^" + attribute.replace(/\*/g, "[^\\.]*"));
|
|
1272
|
-
let result = null;
|
|
1273
|
-
for (const key in data) {
|
|
1274
|
-
result = key.match(pattern);
|
|
1275
|
-
if (result) keys.push(result[0]);
|
|
1276
|
-
}
|
|
1277
|
-
data = {};
|
|
1278
|
-
keys.forEach((key) => data[key] = deepFind(masterData, key));
|
|
1279
|
-
return data;
|
|
1280
|
-
},
|
|
1281
|
-
getLeadingExplicitAttributePath: function(attribute) {
|
|
1282
|
-
return attribute.split("*")[0].replace(/\.$/, "");
|
|
1283
|
-
},
|
|
1284
|
-
extractDataFromPath(path, masterData) {
|
|
1285
|
-
const results = {};
|
|
1286
|
-
const value = deepFind(masterData, path);
|
|
1287
|
-
if (value !== void 0) deepSet(results, path, value);
|
|
1288
|
-
return results;
|
|
1171
|
+
/**
|
|
1172
|
+
* Validate the size of an attribute is greater than a minimum value.
|
|
1173
|
+
*/
|
|
1174
|
+
validateMin(value, parameters, attribute) {
|
|
1175
|
+
this.requireParameterCount(1, parameters, "min");
|
|
1176
|
+
if (isNaN(parameters[0])) throw "Validation rule min requires parameter to be a number.";
|
|
1177
|
+
return getSize(value, validationRuleParser.hasRule(attribute, getNumericRules(), this.rules)) >= Number(parameters[0]);
|
|
1289
1178
|
}
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
const implicitAttributes = {};
|
|
1297
|
-
for (const key in rules) if (key.indexOf("*") !== -1) {
|
|
1298
|
-
rules = this.explodeWildCardRules(rules, key, data, implicitAttributes);
|
|
1299
|
-
delete rules[key];
|
|
1300
|
-
} else if (Object.prototype.hasOwnProperty.call(rules, key) && Array.isArray(rules)) rules[Number(key)] = this.explodeExplicitRules(rules[Number(key)]);
|
|
1301
|
-
else if (Object.prototype.hasOwnProperty.call(rules, key) && !Array.isArray(rules)) rules[key] = this.explodeExplicitRules(rules[key]);
|
|
1302
|
-
return {
|
|
1303
|
-
rules,
|
|
1304
|
-
implicitAttributes
|
|
1305
|
-
};
|
|
1306
|
-
},
|
|
1307
|
-
explodeWildCardRules: function(results, attribute, masterData, implicitAttributes) {
|
|
1308
|
-
const pattern = new RegExp("^" + attribute.replace(/\*/g, "[^.]*") + "$");
|
|
1309
|
-
const data = validationData.initializeAndGatherData(attribute, masterData);
|
|
1310
|
-
const rule = results[attribute];
|
|
1311
|
-
for (const key in data) if (key.slice(0, attribute.length) === attribute || key.match(pattern) !== null) {
|
|
1312
|
-
if (Array.isArray(implicitAttributes[attribute])) implicitAttributes[attribute].push(key);
|
|
1313
|
-
else implicitAttributes[attribute] = [key];
|
|
1314
|
-
results = this.mergeRulesForAttribute(results, key, rule);
|
|
1315
|
-
}
|
|
1316
|
-
return results;
|
|
1317
|
-
},
|
|
1318
|
-
mergeRulesForAttribute(results, attribute, rules) {
|
|
1319
|
-
const merge = this.explodeRules([rules]).rules[0];
|
|
1320
|
-
results[attribute] = [...results[attribute] ? this.explodeExplicitRules(results[attribute]) : [], ...merge];
|
|
1321
|
-
return results;
|
|
1322
|
-
},
|
|
1323
|
-
explodeExplicitRules: function(rules) {
|
|
1324
|
-
if (typeof rules === "string") return rules.split("|");
|
|
1325
|
-
if (!Array.isArray(rules)) return [this.prepareRule(rules)];
|
|
1326
|
-
return rules.map((rule) => this.prepareRule(rule));
|
|
1327
|
-
},
|
|
1328
|
-
prepareRule(rule) {
|
|
1329
|
-
if (rule instanceof IRuleContract) return rule;
|
|
1330
|
-
if (typeof rule === "function") return new ClosureValidationRule(rule);
|
|
1331
|
-
return rule.toString();
|
|
1332
|
-
},
|
|
1333
|
-
parse(rule) {
|
|
1334
|
-
if (rule instanceof IRuleContract) return [rule, []];
|
|
1335
|
-
return this.parseStringRule(rule);
|
|
1336
|
-
},
|
|
1337
|
-
parseStringRule: function(rule) {
|
|
1338
|
-
let parameters = [];
|
|
1339
|
-
let parameter;
|
|
1340
|
-
if (rule.indexOf(":") !== -1) {
|
|
1341
|
-
[rule, parameter] = rule.split(/:(.+)/);
|
|
1342
|
-
parameters = parameter.split(",");
|
|
1343
|
-
}
|
|
1344
|
-
return [rule, parameters];
|
|
1345
|
-
},
|
|
1346
|
-
getRule: function(attribute, searchRules, availableRules) {
|
|
1347
|
-
if (!availableRules[attribute]) return [];
|
|
1348
|
-
searchRules = Array.isArray(searchRules) ? searchRules : [searchRules];
|
|
1349
|
-
for (let i = 0; i < availableRules[attribute].length; i++) {
|
|
1350
|
-
const [rule, parameters] = this.parse(availableRules[attribute][i]);
|
|
1351
|
-
if (searchRules.indexOf(rule) !== -1) return [rule, parameters];
|
|
1352
|
-
}
|
|
1353
|
-
return [];
|
|
1354
|
-
},
|
|
1355
|
-
hasRule: function(attribute, searchRules, availableRules) {
|
|
1356
|
-
return this.getRule(attribute, searchRules, availableRules).length > 0;
|
|
1179
|
+
/**
|
|
1180
|
+
* Validate that an attribute is numeric.
|
|
1181
|
+
*/
|
|
1182
|
+
validateNumeric(value, parameters, attribute) {
|
|
1183
|
+
if (validationRuleParser.hasRule(attribute, "strict", this.rules) && typeof value !== "number") return false;
|
|
1184
|
+
return value !== null && isNaN(value) === false;
|
|
1357
1185
|
}
|
|
1358
|
-
};
|
|
1359
|
-
|
|
1360
|
-
//#endregion
|
|
1361
|
-
//#region src/validators/validateAttributes.ts
|
|
1362
|
-
var validateAttributes = class {
|
|
1363
1186
|
/**
|
|
1364
|
-
*
|
|
1187
|
+
* Validate that an attribute is an object
|
|
1365
1188
|
*/
|
|
1366
|
-
|
|
1189
|
+
validateObject(value) {
|
|
1190
|
+
return isObject(value);
|
|
1191
|
+
}
|
|
1367
1192
|
/**
|
|
1368
|
-
*
|
|
1193
|
+
* Validate that an attribute exists even if not filled.
|
|
1369
1194
|
*/
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
this.data = data;
|
|
1373
|
-
this.rules = rules;
|
|
1195
|
+
validatePresent(value, parameters, attribute) {
|
|
1196
|
+
return typeof deepFind(this.data, attribute) !== "undefined";
|
|
1374
1197
|
}
|
|
1375
1198
|
/**
|
|
1376
|
-
* Validate that an attribute
|
|
1377
|
-
*
|
|
1378
|
-
* This validation rule implies the attribute is "required".
|
|
1199
|
+
* Validate that an attribute is an integer.
|
|
1379
1200
|
*/
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
"on",
|
|
1384
|
-
"1",
|
|
1385
|
-
1,
|
|
1386
|
-
true,
|
|
1387
|
-
"true"
|
|
1388
|
-
].indexOf(value) !== -1;
|
|
1201
|
+
validateInteger(value, parameters, attribute) {
|
|
1202
|
+
if (validationRuleParser.hasRule(attribute, "strict", this.rules) && typeof value !== "number") return false;
|
|
1203
|
+
return isInteger(value);
|
|
1389
1204
|
}
|
|
1390
1205
|
/**
|
|
1391
|
-
* Validate that
|
|
1206
|
+
* Validate that the attribute is a valid JSON string
|
|
1392
1207
|
*/
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1208
|
+
validateJson(value) {
|
|
1209
|
+
if (!value || typeof value !== "string") return false;
|
|
1210
|
+
try {
|
|
1211
|
+
JSON.parse(value);
|
|
1212
|
+
} catch {
|
|
1213
|
+
return false;
|
|
1214
|
+
}
|
|
1398
1215
|
return true;
|
|
1399
1216
|
}
|
|
1400
1217
|
/**
|
|
1401
|
-
*
|
|
1218
|
+
* Validate that an attribute is greater than another attribute.
|
|
1402
1219
|
*/
|
|
1403
|
-
|
|
1404
|
-
this.requireParameterCount(1, parameters, "
|
|
1405
|
-
|
|
1220
|
+
validateGt(value, parameters, attribute) {
|
|
1221
|
+
this.requireParameterCount(1, parameters, "gt");
|
|
1222
|
+
if (typeof value !== "number" && typeof value !== "string" && typeof value !== "object") throw "The field under validation must be a number, string, array or object";
|
|
1223
|
+
const compartedToValue = deepFind(this.data, parameters[0]) || parameters[0];
|
|
1224
|
+
if (!Array.isArray(compartedToValue) && isNaN(compartedToValue) === false) return getSize(value, validationRuleParser.hasRule(attribute, getNumericRules(), this.rules)) > compartedToValue;
|
|
1225
|
+
if (sameType(value, compartedToValue) === false) throw "The fields under validation must be of the same type";
|
|
1226
|
+
return getSize(value) > getSize(compartedToValue);
|
|
1406
1227
|
}
|
|
1407
1228
|
/**
|
|
1408
|
-
* Validate
|
|
1229
|
+
* Validate that an attribute is greater than or equal another attribute.
|
|
1409
1230
|
*/
|
|
1410
|
-
|
|
1411
|
-
this.requireParameterCount(1, parameters, "
|
|
1412
|
-
|
|
1231
|
+
validateGte(value, parameters, attribute) {
|
|
1232
|
+
this.requireParameterCount(1, parameters, "gte");
|
|
1233
|
+
if (typeof value !== "number" && typeof value !== "string" && typeof value !== "object") throw "The field under validation must be a number, string, array or object";
|
|
1234
|
+
const compartedToValue = deepFind(this.data, parameters[0]) || parameters[0];
|
|
1235
|
+
if (!Array.isArray(compartedToValue) && isNaN(compartedToValue) === false) return getSize(value, validationRuleParser.hasRule(attribute, getNumericRules(), this.rules)) >= compartedToValue;
|
|
1236
|
+
if (sameType(value, compartedToValue) === false) throw "The fields under validation must be of the same type";
|
|
1237
|
+
return getSize(value) >= getSize(compartedToValue);
|
|
1413
1238
|
}
|
|
1414
1239
|
/**
|
|
1415
|
-
* Validate that an attribute
|
|
1240
|
+
* Validate that an attribute is less than another attribute.
|
|
1416
1241
|
*/
|
|
1417
|
-
|
|
1418
|
-
|
|
1242
|
+
validateLt(value, parameters, attribute) {
|
|
1243
|
+
this.requireParameterCount(1, parameters, "lt");
|
|
1244
|
+
if (typeof value !== "number" && typeof value !== "string" && typeof value !== "object") throw "The field under validation must be a number, string, array or object";
|
|
1245
|
+
const compartedToValue = deepFind(this.data, parameters[0]) || parameters[0];
|
|
1246
|
+
if (!Array.isArray(compartedToValue) && isNaN(compartedToValue) === false) return getSize(value, validationRuleParser.hasRule(attribute, getNumericRules(), this.rules)) < compartedToValue;
|
|
1247
|
+
if (sameType(value, compartedToValue) === false) throw "The fields under validation must be of the same type";
|
|
1248
|
+
return getSize(value) < getSize(compartedToValue);
|
|
1249
|
+
}
|
|
1250
|
+
/**
|
|
1251
|
+
* Validate that an attribute is less than or equal another attribute.
|
|
1252
|
+
*/
|
|
1253
|
+
validateLte(value, parameters, attribute) {
|
|
1254
|
+
this.requireParameterCount(1, parameters, "lte");
|
|
1255
|
+
if (typeof value !== "number" && typeof value !== "string" && typeof value !== "object") throw "The field under validation must be a number, string, array or object";
|
|
1256
|
+
const compartedToValue = deepFind(this.data, parameters[0]) || parameters[0];
|
|
1257
|
+
if (!Array.isArray(compartedToValue) && isNaN(compartedToValue) === false) return getSize(value, validationRuleParser.hasRule(attribute, getNumericRules(), this.rules)) <= compartedToValue;
|
|
1258
|
+
if (sameType(value, compartedToValue) === false) throw "The fields under validation must be of the same type";
|
|
1259
|
+
return getSize(value) <= getSize(compartedToValue);
|
|
1260
|
+
}
|
|
1261
|
+
/**
|
|
1262
|
+
* Validate an attribute is contained within a list of values.
|
|
1263
|
+
*/
|
|
1264
|
+
validateIn(value, parameters) {
|
|
1265
|
+
this.requireParameterCount(1, parameters, "in");
|
|
1266
|
+
if (Array.isArray(value)) {
|
|
1267
|
+
for (let index = 0; index < value.length; index++) if (typeof value[index] !== "number" && typeof value[index] !== "string") return false;
|
|
1268
|
+
return value.filter((element) => parameters.indexOf(element.toString()) === -1).length === 0;
|
|
1269
|
+
}
|
|
1270
|
+
if (typeof value !== "number" && typeof value !== "string") return false;
|
|
1271
|
+
return parameters.indexOf(value.toString()) !== -1;
|
|
1419
1272
|
}
|
|
1420
1273
|
/**
|
|
1421
|
-
*
|
|
1274
|
+
* "Indicate" validation should pass if value is null
|
|
1275
|
+
*
|
|
1276
|
+
* Always returns true, just lets us put "nullable" in rules.
|
|
1422
1277
|
*/
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
return /^(?=.*[a-zA-Z0-9])[a-zA-Z0-9-_]+$/.test(value.toString());
|
|
1278
|
+
validateNullable() {
|
|
1279
|
+
return true;
|
|
1426
1280
|
}
|
|
1427
1281
|
/**
|
|
1428
|
-
* Validate
|
|
1282
|
+
* Validate an attribute is not contained within a list of values.
|
|
1429
1283
|
*/
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1284
|
+
validateNotIn(value, parameters) {
|
|
1285
|
+
this.requireParameterCount(1, parameters, "not_in");
|
|
1286
|
+
const valuesToCheck = [];
|
|
1287
|
+
if (Array.isArray(value)) {
|
|
1288
|
+
for (let index = 0; index < value.length; index++) if (typeof value[index] === "number" || typeof value[index] === "string") valuesToCheck.push(value[index]);
|
|
1289
|
+
if (valuesToCheck.length === 0) return true;
|
|
1290
|
+
return valuesToCheck.filter((element) => parameters.indexOf(element.toString()) !== -1).length === 0;
|
|
1291
|
+
}
|
|
1292
|
+
if (typeof value !== "number" && typeof value !== "string") return true;
|
|
1293
|
+
return parameters.indexOf(value.toString()) === -1;
|
|
1433
1294
|
}
|
|
1434
1295
|
/**
|
|
1435
|
-
*
|
|
1296
|
+
* Always returns true - this method will be used in conbination with other rules
|
|
1436
1297
|
*/
|
|
1437
|
-
|
|
1438
|
-
return
|
|
1298
|
+
validateStrict() {
|
|
1299
|
+
return true;
|
|
1439
1300
|
}
|
|
1440
1301
|
/**
|
|
1441
|
-
* Validate that an attribute is
|
|
1302
|
+
* Validate that an attribute is a valid URL.
|
|
1442
1303
|
*/
|
|
1443
|
-
|
|
1444
|
-
if (
|
|
1445
|
-
return new
|
|
1304
|
+
validateUrl(value) {
|
|
1305
|
+
if (typeof value !== "string") return false;
|
|
1306
|
+
return new RegExp("^(https?:\\/\\/)?((([a-z\\d]([a-z\\d-]*[a-z\\d])*)\\.)+[a-z]{2,}|localhost|((\\d{1,3}\\.){3}\\d{1,3}))(\\:\\d+)?(\\/[-a-z\\d%_.~+]*)*(\\?[;&a-z\\d%_.~+=-]*)?(\\#[-a-z\\d_]*)?$", "i").test(value);
|
|
1446
1307
|
}
|
|
1447
1308
|
/**
|
|
1448
|
-
*
|
|
1309
|
+
* Determine if a comparison passes between the given values.
|
|
1449
1310
|
*/
|
|
1450
|
-
|
|
1451
|
-
|
|
1311
|
+
compareDates(value, parameter, operator, rule) {
|
|
1312
|
+
value = toDate(value);
|
|
1313
|
+
if (!value) throw `Validation rule ${rule} requires the field under valation to be a date.`;
|
|
1314
|
+
const compartedToValue = toDate(deepFind(this.data, parameter) || parameter);
|
|
1315
|
+
if (!compartedToValue) throw `Validation rule ${rule} requires the parameter to be a date.`;
|
|
1316
|
+
return compare(value.getTime(), compartedToValue.getTime(), operator);
|
|
1452
1317
|
}
|
|
1453
1318
|
/**
|
|
1454
|
-
*
|
|
1319
|
+
* Require a certain number of parameters to be present
|
|
1455
1320
|
*/
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
return this.compareDates(value, parameters[0], "<", "before");
|
|
1321
|
+
requireParameterCount(count, parameters, rule) {
|
|
1322
|
+
if (parameters.length < count) throw `Validation rule ${rule} requires at least ${count} parameters.`;
|
|
1459
1323
|
}
|
|
1460
1324
|
/**
|
|
1461
|
-
*
|
|
1325
|
+
* Prepare the values for validation
|
|
1462
1326
|
*/
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1327
|
+
parseDependentRuleParameters(other, parameters) {
|
|
1328
|
+
let values = parameters.slice(1);
|
|
1329
|
+
if (other === null) values = convertValuesToNull(values);
|
|
1330
|
+
if (typeof other === "number") values = convertValuesToNumber(values);
|
|
1331
|
+
if (typeof other === "boolean") values = convertValuesToBoolean(values);
|
|
1332
|
+
return values;
|
|
1466
1333
|
}
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1334
|
+
};
|
|
1335
|
+
|
|
1336
|
+
//#endregion
|
|
1337
|
+
//#region src/Rules/registerRule.ts
|
|
1338
|
+
function register(rule, validate, replaceMessage) {
|
|
1339
|
+
const method = buildValidationMethodName(rule);
|
|
1340
|
+
if (new validateAttributes()[`validate${method}`]) return false;
|
|
1341
|
+
validateAttributes.prototype[`validate${method}`] = validate;
|
|
1342
|
+
if (typeof replaceMessage === "function") replaceAttributes[`replace${method}`] = ({ message, parameters, data, getDisplayableAttribute }) => replaceMessage(message, parameters, data, getDisplayableAttribute);
|
|
1343
|
+
return true;
|
|
1344
|
+
}
|
|
1345
|
+
function registerImplicit(rule, validate, replaceMessage) {
|
|
1346
|
+
if (register(rule, validate, replaceMessage) === true) addImplicitRule(rule);
|
|
1347
|
+
}
|
|
1348
|
+
|
|
1349
|
+
//#endregion
|
|
1350
|
+
//#region src/Plugin.ts
|
|
1351
|
+
const valueInspectors = [];
|
|
1352
|
+
const api = {
|
|
1353
|
+
registerRule: register,
|
|
1354
|
+
registerImplicitRule: registerImplicit,
|
|
1355
|
+
registerValueInspector,
|
|
1356
|
+
extendTranslations: (translations) => {
|
|
1357
|
+
Lang.extendTranslationObject(translations);
|
|
1358
|
+
}
|
|
1359
|
+
};
|
|
1360
|
+
function definePlugin(plugin) {
|
|
1361
|
+
return plugin;
|
|
1362
|
+
}
|
|
1363
|
+
function usePlugin(plugin) {
|
|
1364
|
+
plugin.install(api);
|
|
1365
|
+
}
|
|
1366
|
+
function registerValueInspector(inspector) {
|
|
1367
|
+
const existing = valueInspectors.findIndex((candidate) => candidate.type === inspector.type);
|
|
1368
|
+
if (existing >= 0) {
|
|
1369
|
+
valueInspectors.splice(existing, 1, inspector);
|
|
1370
|
+
return;
|
|
1371
|
+
}
|
|
1372
|
+
valueInspectors.push(inspector);
|
|
1373
|
+
}
|
|
1374
|
+
function getValidationValueInspector(value) {
|
|
1375
|
+
return valueInspectors.find((inspector) => inspector.matches(value));
|
|
1376
|
+
}
|
|
1377
|
+
function getValidationMessageType(value, hasNumericRule = false) {
|
|
1378
|
+
if (typeof value === "number" || typeof value === "undefined" || isNaN(value) === false && hasNumericRule === true) return "number";
|
|
1379
|
+
const inspector = getValidationValueInspector(value);
|
|
1380
|
+
if (inspector) return inspector.type;
|
|
1381
|
+
if (Array.isArray(value)) return "array";
|
|
1382
|
+
return typeof value;
|
|
1383
|
+
}
|
|
1384
|
+
function getValidationSize(value, hasNumericRule = false) {
|
|
1385
|
+
if (typeof value === "number" || isNaN(value) === false && hasNumericRule === true) return Number(value);
|
|
1386
|
+
const inspector = getValidationValueInspector(value);
|
|
1387
|
+
if (inspector?.size) return inspector.size(value);
|
|
1388
|
+
if (typeof value === "string" || Array.isArray(value)) return value.length;
|
|
1389
|
+
if (typeof value === "object" && value !== null) return Object.keys(value).length;
|
|
1390
|
+
return -1;
|
|
1391
|
+
}
|
|
1392
|
+
|
|
1393
|
+
//#endregion
|
|
1394
|
+
//#region src/utilities/general.ts
|
|
1395
|
+
const implicitRues = [
|
|
1396
|
+
"accepted",
|
|
1397
|
+
"accepted_if",
|
|
1398
|
+
"declined",
|
|
1399
|
+
"declined_if",
|
|
1400
|
+
"filled",
|
|
1401
|
+
"present",
|
|
1402
|
+
"required",
|
|
1403
|
+
"required_if",
|
|
1404
|
+
"required_unless",
|
|
1405
|
+
"required_with",
|
|
1406
|
+
"required_with_all",
|
|
1407
|
+
"required_without",
|
|
1408
|
+
"required_without_all"
|
|
1409
|
+
];
|
|
1410
|
+
/**
|
|
1411
|
+
* Get the size of a value based on its type
|
|
1412
|
+
*/
|
|
1413
|
+
function getSize(value, hasNumericRule = false) {
|
|
1414
|
+
return getValidationSize(value, hasNumericRule);
|
|
1415
|
+
}
|
|
1416
|
+
/**
|
|
1417
|
+
* Check if two values are of the same type
|
|
1418
|
+
*/
|
|
1419
|
+
function sameType(value, otherValue) {
|
|
1420
|
+
return (Array.isArray(value) ? "array" : value === null ? null : typeof value) === (Array.isArray(otherValue) ? "array" : otherValue === null ? null : typeof otherValue);
|
|
1421
|
+
}
|
|
1422
|
+
/**
|
|
1423
|
+
* Check if Value is an Ineteger
|
|
1424
|
+
*/
|
|
1425
|
+
function isInteger(value) {
|
|
1426
|
+
return value !== null && isNaN(value) === false && value % 1 === 0;
|
|
1427
|
+
}
|
|
1428
|
+
/**
|
|
1429
|
+
* Check if the value can be considered as rule
|
|
1430
|
+
*/
|
|
1431
|
+
function isRule(value) {
|
|
1432
|
+
return typeof value === "string" || typeof value === "function" || value instanceof IRuleContract || value instanceof BaseRule;
|
|
1433
|
+
}
|
|
1434
|
+
/**
|
|
1435
|
+
* Check if the array contain any potentiel valid rule
|
|
1436
|
+
*/
|
|
1437
|
+
function isArrayOfRules(values) {
|
|
1438
|
+
for (let i = 0; i < values.length; i++) if (isRule(values[i])) return true;
|
|
1439
|
+
return false;
|
|
1440
|
+
}
|
|
1441
|
+
/**
|
|
1442
|
+
* Check if the rule is related to size
|
|
1443
|
+
*/
|
|
1444
|
+
function isSizeRule(rule) {
|
|
1445
|
+
return [
|
|
1446
|
+
"size",
|
|
1447
|
+
"between",
|
|
1448
|
+
"min",
|
|
1449
|
+
"max",
|
|
1450
|
+
"gt",
|
|
1451
|
+
"lt",
|
|
1452
|
+
"gte",
|
|
1453
|
+
"lte"
|
|
1454
|
+
].indexOf(rule) !== -1;
|
|
1455
|
+
}
|
|
1456
|
+
/**
|
|
1457
|
+
* Check if rule implies that the field is required
|
|
1458
|
+
*/
|
|
1459
|
+
function isImplicitRule(rule) {
|
|
1460
|
+
if (rule instanceof IRuleContract && rule.__isImplicitRule === true) return true;
|
|
1461
|
+
if (typeof rule === "string") return implicitRues.indexOf(rule) !== -1;
|
|
1462
|
+
return false;
|
|
1463
|
+
}
|
|
1464
|
+
/**
|
|
1465
|
+
* Add a new implicit rule
|
|
1466
|
+
*/
|
|
1467
|
+
function addImplicitRule(rule) {
|
|
1468
|
+
implicitRues.push(rule);
|
|
1469
|
+
}
|
|
1470
|
+
/**
|
|
1471
|
+
* Returns the numeric rules
|
|
1472
|
+
*/
|
|
1473
|
+
function getNumericRules() {
|
|
1474
|
+
return ["numeric", "integer"];
|
|
1475
|
+
}
|
|
1476
|
+
/**
|
|
1477
|
+
* Check if the rule is numeric
|
|
1478
|
+
*/
|
|
1479
|
+
function isNumericRule(rule) {
|
|
1480
|
+
return getNumericRules().indexOf(rule) !== -1;
|
|
1481
|
+
}
|
|
1482
|
+
/**
|
|
1483
|
+
* Determine if a comparison passes between the given values.
|
|
1484
|
+
*/
|
|
1485
|
+
function compare(first, second, operator, strict = false) {
|
|
1486
|
+
switch (operator) {
|
|
1487
|
+
case "<": return first < second;
|
|
1488
|
+
case ">": return first > second;
|
|
1489
|
+
case "<=": return first <= second;
|
|
1490
|
+
case ">=": return first >= second;
|
|
1491
|
+
case "=":
|
|
1492
|
+
if (strict === true) return first === second;
|
|
1493
|
+
return first == second;
|
|
1494
|
+
default: throw "Invalid operator parameter";
|
|
1495
|
+
}
|
|
1496
|
+
}
|
|
1497
|
+
/**
|
|
1498
|
+
* Convert the given values to boolean if they are string "true" / "false".
|
|
1499
|
+
*/
|
|
1500
|
+
function convertValuesToBoolean(values) {
|
|
1501
|
+
return values.map((value) => {
|
|
1502
|
+
if (value === "true") return true;
|
|
1503
|
+
else if (value === "false") return false;
|
|
1504
|
+
return value;
|
|
1505
|
+
});
|
|
1506
|
+
}
|
|
1507
|
+
/**
|
|
1508
|
+
* Convert the given values to numbers if they are numbers in a string "1", "2"
|
|
1509
|
+
*/
|
|
1510
|
+
function convertValuesToNumber(values) {
|
|
1511
|
+
return values.map((value) => {
|
|
1512
|
+
if (!isNaN(Number(value))) return Number(value);
|
|
1513
|
+
return value;
|
|
1514
|
+
});
|
|
1515
|
+
}
|
|
1516
|
+
/**
|
|
1517
|
+
* Convert the given values to null if they have null values in a string "null", "NULL"
|
|
1518
|
+
*/
|
|
1519
|
+
function convertValuesToNull(values) {
|
|
1520
|
+
return values.map((value) => {
|
|
1521
|
+
if (value.toLowerCase() === "null") return null;
|
|
1522
|
+
return value;
|
|
1523
|
+
});
|
|
1524
|
+
}
|
|
1525
|
+
|
|
1526
|
+
//#endregion
|
|
1527
|
+
//#region src/utilities/object.ts
|
|
1528
|
+
/**
|
|
1529
|
+
* Get value at path of object. If the resolved value is undifined, the returned result will be undefined
|
|
1530
|
+
*
|
|
1531
|
+
* @param obj
|
|
1532
|
+
* @param path
|
|
1533
|
+
* @returns
|
|
1534
|
+
*/
|
|
1535
|
+
function deepFind(obj, path) {
|
|
1536
|
+
const paths = path.split(".");
|
|
1537
|
+
for (let i = 0; i < paths.length; i++) {
|
|
1538
|
+
if (typeof obj[paths[i]] === "undefined") return;
|
|
1539
|
+
obj = obj[paths[i]];
|
|
1480
1540
|
}
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1541
|
+
return obj;
|
|
1542
|
+
}
|
|
1543
|
+
/**
|
|
1544
|
+
* Set value at path of object.
|
|
1545
|
+
*
|
|
1546
|
+
* @param target
|
|
1547
|
+
* @param path
|
|
1548
|
+
* @param value
|
|
1549
|
+
*/
|
|
1550
|
+
function deepSet(target, path, value) {
|
|
1551
|
+
const paths = typeof path === "string" ? path.split(".") : path;
|
|
1552
|
+
const segment = paths.shift();
|
|
1553
|
+
if (segment === "*") {
|
|
1554
|
+
target = Array.isArray(target) ? target : [];
|
|
1555
|
+
if (paths.length > 0) target.forEach((inner) => deepSet(inner, [...paths], value));
|
|
1556
|
+
else for (let i = 0; i < target.length; i++) target[i] = value;
|
|
1557
|
+
} else if (paths.length > 0 && typeof segment === "string") {
|
|
1558
|
+
if (typeof target[segment] !== "object" || target[segment] === null) target[segment] = {};
|
|
1559
|
+
deepSet(target[segment], paths, value);
|
|
1560
|
+
} else {
|
|
1561
|
+
if (typeof target !== "object" || target === null) target = {};
|
|
1562
|
+
target[segment] = value;
|
|
1494
1563
|
}
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1564
|
+
}
|
|
1565
|
+
/**
|
|
1566
|
+
* Flatten a multi-dimensional associative array with dots.
|
|
1567
|
+
*
|
|
1568
|
+
* @param obj
|
|
1569
|
+
* @param ignoreRulesArray
|
|
1570
|
+
* @param withBaseObjectType
|
|
1571
|
+
* @returns
|
|
1572
|
+
*/
|
|
1573
|
+
function dotify(obj, ignoreRulesArray = false, withBaseObjectType = false) {
|
|
1574
|
+
const res = {};
|
|
1575
|
+
(function recurse(obj, current = "") {
|
|
1576
|
+
for (const key in obj) {
|
|
1577
|
+
const value = obj[key];
|
|
1578
|
+
const newKey = current ? `${current}.${key}` : key;
|
|
1579
|
+
if (value && typeof value === "object" && !isRule(value) && !(value instanceof Date)) if (ignoreRulesArray === true && Array.isArray(value) && isArrayOfRules(value)) res[newKey] = value;
|
|
1580
|
+
else {
|
|
1581
|
+
if (withBaseObjectType) res[newKey] = Array.isArray(value) ? "array" : "object";
|
|
1582
|
+
recurse(value, newKey);
|
|
1583
|
+
}
|
|
1584
|
+
else res[newKey] = value;
|
|
1585
|
+
}
|
|
1586
|
+
})(obj);
|
|
1587
|
+
return res;
|
|
1588
|
+
}
|
|
1589
|
+
/**
|
|
1590
|
+
* Check if objects are deep equal
|
|
1591
|
+
*
|
|
1592
|
+
* @param firstParam
|
|
1593
|
+
* @param secondParam
|
|
1594
|
+
* @returns
|
|
1595
|
+
*/
|
|
1596
|
+
function deepEqual(firstParam, secondParam) {
|
|
1597
|
+
const first = dotify(firstParam, false, true);
|
|
1598
|
+
const second = dotify(secondParam, false, true);
|
|
1599
|
+
if (Object.keys(first).length !== Object.keys(second).length) return false;
|
|
1600
|
+
return Object.entries(first).every(([key, value]) => second[key] === value);
|
|
1601
|
+
}
|
|
1602
|
+
|
|
1603
|
+
//#endregion
|
|
1604
|
+
//#region src/utilities/formatMessages.ts
|
|
1605
|
+
/**
|
|
1606
|
+
* Get the message type based on the value. The message type is used essentially for size rules
|
|
1607
|
+
*/
|
|
1608
|
+
function getMesageType(value, hasNumericRule = false) {
|
|
1609
|
+
return getValidationMessageType(value, hasNumericRule);
|
|
1610
|
+
}
|
|
1611
|
+
/**
|
|
1612
|
+
* Get the custom message for a rule if exists
|
|
1613
|
+
*/
|
|
1614
|
+
function getCustomMessage(attributes, rule, customMessages, messageType, lang) {
|
|
1615
|
+
const [attribute, primaryAttribute] = attributes;
|
|
1616
|
+
const translatedMessages = dotify(Lang.get(lang)["custom"] || {});
|
|
1617
|
+
const keys = getKeyCombinations(`${attribute}.${rule}`);
|
|
1618
|
+
let allKeys = keys;
|
|
1619
|
+
if (primaryAttribute) {
|
|
1620
|
+
allKeys = [];
|
|
1621
|
+
const primaryAttributeKeys = getKeyCombinations(`${primaryAttribute}.${rule}`);
|
|
1622
|
+
for (let i = 0; i < keys.length; i++) {
|
|
1623
|
+
allKeys.push(keys[i]);
|
|
1624
|
+
if (keys[i] !== primaryAttributeKeys[i]) allKeys.push(primaryAttributeKeys[i]);
|
|
1625
|
+
}
|
|
1500
1626
|
}
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
return toDate(value) ? true : false;
|
|
1627
|
+
if (isSizeRule(rule)) {
|
|
1628
|
+
allKeys.pop();
|
|
1629
|
+
allKeys.push(`${rule}.${messageType}`);
|
|
1630
|
+
allKeys.push(rule);
|
|
1506
1631
|
}
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1632
|
+
let key = "";
|
|
1633
|
+
let message = "";
|
|
1634
|
+
for (let i = 0; i < allKeys.length; i++) {
|
|
1635
|
+
key = allKeys[i];
|
|
1636
|
+
if (Object.prototype.hasOwnProperty.call(customMessages, key)) return customMessages[key];
|
|
1637
|
+
message = translatedMessages[key];
|
|
1638
|
+
if (typeof message === "string") return message;
|
|
1639
|
+
}
|
|
1640
|
+
return null;
|
|
1641
|
+
}
|
|
1642
|
+
/**
|
|
1643
|
+
* Get the validation message for an attribute and rule.
|
|
1644
|
+
*/
|
|
1645
|
+
function getMessage(attributes, rule, value, customMessages, hasNumericRule, lang) {
|
|
1646
|
+
const inlineMessage = getCustomMessage(attributes, rule, customMessages, getMesageType(value, hasNumericRule), lang);
|
|
1647
|
+
if (inlineMessage) return inlineMessage;
|
|
1648
|
+
const validationMessages = Lang.get(lang);
|
|
1649
|
+
if (isSizeRule(rule) === true) return validationMessages[rule][getMesageType(value, hasNumericRule)];
|
|
1650
|
+
return validationMessages[rule] || "";
|
|
1651
|
+
}
|
|
1652
|
+
/**
|
|
1653
|
+
* Convert a string to snake case.
|
|
1654
|
+
*/
|
|
1655
|
+
function toSnakeCase(string) {
|
|
1656
|
+
return string.split(/ |\B(?=[A-Z])/).map((word) => word.toLowerCase()).join("_");
|
|
1657
|
+
}
|
|
1658
|
+
/**
|
|
1659
|
+
* Get the formatted name of the attribute
|
|
1660
|
+
*/
|
|
1661
|
+
function getFormattedAttribute(attribute) {
|
|
1662
|
+
return toSnakeCase(getPrimaryKeyFromPath(attribute)).replace(/_/g, " ").trim();
|
|
1663
|
+
}
|
|
1664
|
+
/**
|
|
1665
|
+
* Get the combinations of keys from a main key. For example if the main key is 'user.info.name',
|
|
1666
|
+
* the combination will be [user.info.name, info.name, name]
|
|
1667
|
+
*/
|
|
1668
|
+
function getKeyCombinations(key) {
|
|
1669
|
+
const combinations = [key];
|
|
1670
|
+
const splittedKey = key.split(".");
|
|
1671
|
+
while (splittedKey.length > 1) {
|
|
1672
|
+
splittedKey.shift();
|
|
1673
|
+
combinations.push(splittedKey.join("."));
|
|
1513
1674
|
}
|
|
1675
|
+
return combinations;
|
|
1676
|
+
}
|
|
1677
|
+
/**
|
|
1678
|
+
* The purpose of this method if to get the primary key associated with a path
|
|
1679
|
+
* For example the primary key for path 'user.info.email' will be 'email'
|
|
1680
|
+
*/
|
|
1681
|
+
function getPrimaryKeyFromPath(path) {
|
|
1682
|
+
const splittedPath = path.split(".");
|
|
1683
|
+
if (splittedPath.length <= 1) return path;
|
|
1684
|
+
const key = splittedPath.pop();
|
|
1685
|
+
if (isNaN(parseInt(key)) === false) return getPrimaryKeyFromPath(splittedPath.join("."));
|
|
1686
|
+
return key;
|
|
1687
|
+
}
|
|
1688
|
+
|
|
1689
|
+
//#endregion
|
|
1690
|
+
//#region src/validators/errorBag.ts
|
|
1691
|
+
var ErrorBag = class ErrorBag {
|
|
1514
1692
|
/**
|
|
1515
|
-
*
|
|
1516
|
-
*
|
|
1517
|
-
* This validation rule implies the attribute is "required".
|
|
1693
|
+
* All of the registered messages.
|
|
1518
1694
|
*/
|
|
1519
|
-
|
|
1520
|
-
return this.validateRequired(value) && [
|
|
1521
|
-
"no",
|
|
1522
|
-
"off",
|
|
1523
|
-
"0",
|
|
1524
|
-
0,
|
|
1525
|
-
false,
|
|
1526
|
-
"false"
|
|
1527
|
-
].indexOf(value) !== -1;
|
|
1528
|
-
}
|
|
1695
|
+
errors = {};
|
|
1529
1696
|
/**
|
|
1530
|
-
*
|
|
1697
|
+
* All Messages
|
|
1531
1698
|
*/
|
|
1532
|
-
|
|
1533
|
-
this.requireParameterCount(2, parameters, "declined_if");
|
|
1534
|
-
const other = deepFind(this.data, parameters[0]);
|
|
1535
|
-
if (!other) return true;
|
|
1536
|
-
if (parameters.slice(1).indexOf(other) !== -1) return this.validateDeclined(value);
|
|
1537
|
-
return true;
|
|
1538
|
-
}
|
|
1699
|
+
messages = {};
|
|
1539
1700
|
/**
|
|
1540
|
-
*
|
|
1701
|
+
* Stores the first error message
|
|
1541
1702
|
*/
|
|
1542
|
-
|
|
1543
|
-
this.requireParameterCount(1, parameters, "different");
|
|
1544
|
-
const other = deepFind(this.data, parameters[0]);
|
|
1545
|
-
if (!sameType(value, other)) return true;
|
|
1546
|
-
if (value !== null && typeof value === "object") return !deepEqual(value, other);
|
|
1547
|
-
return value !== other;
|
|
1548
|
-
}
|
|
1703
|
+
firstMessage = "";
|
|
1549
1704
|
/**
|
|
1550
|
-
*
|
|
1705
|
+
* Specify whether error types should be returned or no
|
|
1551
1706
|
*/
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
return /^\d+$/.test(value) && value.length === parseInt(parameters[0]);
|
|
1707
|
+
withErrorTypes = false;
|
|
1708
|
+
constructor(errors = {}, messages = {}, firstMessage = "", withErrorTypes = false) {
|
|
1709
|
+
this.errors = errors;
|
|
1710
|
+
this.messages = messages;
|
|
1711
|
+
this.firstMessage = firstMessage;
|
|
1712
|
+
this.withErrorTypes = withErrorTypes;
|
|
1559
1713
|
}
|
|
1560
1714
|
/**
|
|
1561
|
-
*
|
|
1715
|
+
* Set withErrorTypes attribute to true
|
|
1562
1716
|
*/
|
|
1563
|
-
|
|
1564
|
-
this.
|
|
1565
|
-
|
|
1566
|
-
if (isInteger(min) === false || isInteger(max) === false) throw "Validation rule digits_between requires both parameters to be integers.";
|
|
1567
|
-
min = parseInt(min);
|
|
1568
|
-
max = parseInt(max);
|
|
1569
|
-
if (min <= 0 || max <= 0) throw "Validation rule digits_between requires the parameters to be an integer greater than 0.";
|
|
1570
|
-
if (min >= max) throw "Validation rule digits_between requires the max param to be greater than the min param.";
|
|
1571
|
-
if (typeof value !== "string" && typeof value !== "number") return false;
|
|
1572
|
-
value = value.toString();
|
|
1573
|
-
const valueLength = value.length;
|
|
1574
|
-
return /^\d+$/.test(value) && valueLength >= min && valueLength <= max;
|
|
1717
|
+
addErrorTypes() {
|
|
1718
|
+
this.withErrorTypes = true;
|
|
1719
|
+
return this;
|
|
1575
1720
|
}
|
|
1576
1721
|
/**
|
|
1577
|
-
*
|
|
1722
|
+
* Add new recodrs to the errors and messages objects
|
|
1578
1723
|
*/
|
|
1579
|
-
|
|
1580
|
-
if (
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1724
|
+
add(key, error) {
|
|
1725
|
+
if (Array.isArray(this.errors[key]) && Array.isArray(this.messages[key])) {
|
|
1726
|
+
this.errors[key].push(error);
|
|
1727
|
+
this.messages[key].push(error.message);
|
|
1728
|
+
} else {
|
|
1729
|
+
this.errors[key] = [error];
|
|
1730
|
+
this.messages[key] = [error.message];
|
|
1731
|
+
}
|
|
1732
|
+
this.firstMessage = this.firstMessage || error.message;
|
|
1586
1733
|
}
|
|
1587
1734
|
/**
|
|
1588
|
-
*
|
|
1735
|
+
* Get the first error related to a specific key
|
|
1589
1736
|
*/
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
if (
|
|
1593
|
-
|
|
1594
|
-
for (let i = 0; i < parameters.length; i++) if (typeof parameters[i] === "string" && value.indexOf(parameters[i], valueLength - parameters[i].length) !== -1) return true;
|
|
1595
|
-
return false;
|
|
1737
|
+
first(key = null) {
|
|
1738
|
+
if (!key) return this.firstMessage;
|
|
1739
|
+
if (this.has(key)) return this.messages[key][0];
|
|
1740
|
+
return "";
|
|
1596
1741
|
}
|
|
1597
1742
|
/**
|
|
1598
|
-
*
|
|
1743
|
+
* Get the error messages keys
|
|
1599
1744
|
*/
|
|
1600
|
-
|
|
1601
|
-
this.
|
|
1602
|
-
const other = deepFind(this.data, paramaters[0]);
|
|
1603
|
-
if (!sameType(value, other)) return false;
|
|
1604
|
-
if (value !== null && typeof value === "object") return deepEqual(value, other);
|
|
1605
|
-
return value === other;
|
|
1745
|
+
keys() {
|
|
1746
|
+
return Object.keys(this.messages);
|
|
1606
1747
|
}
|
|
1607
1748
|
/**
|
|
1608
|
-
*
|
|
1749
|
+
* Get all the messages related to a specific key
|
|
1609
1750
|
*/
|
|
1610
|
-
|
|
1611
|
-
this.
|
|
1612
|
-
|
|
1751
|
+
get(key, withErrorTypes = this.withErrorTypes) {
|
|
1752
|
+
if (!this.has(key)) return [];
|
|
1753
|
+
if (withErrorTypes) return this.errors[key];
|
|
1754
|
+
return this.messages[key];
|
|
1613
1755
|
}
|
|
1614
1756
|
/**
|
|
1615
|
-
*
|
|
1757
|
+
* Check if key exists in messages
|
|
1616
1758
|
*/
|
|
1617
|
-
|
|
1618
|
-
return true;
|
|
1759
|
+
has(key) {
|
|
1760
|
+
return this.messages[key] && this.messages[key].length > 0 ? true : false;
|
|
1619
1761
|
}
|
|
1620
1762
|
/**
|
|
1621
|
-
*
|
|
1763
|
+
* Get all error messages
|
|
1622
1764
|
*/
|
|
1623
|
-
|
|
1624
|
-
this.
|
|
1625
|
-
if (
|
|
1626
|
-
|
|
1627
|
-
return false;
|
|
1765
|
+
all(allMessages = true, withErrorTypes = this.withErrorTypes) {
|
|
1766
|
+
const messages = withErrorTypes ? { ...this.errors } : { ...this.messages };
|
|
1767
|
+
if (!allMessages) Object.keys(messages).map((attribute) => messages[attribute] = messages[attribute][0]);
|
|
1768
|
+
return messages;
|
|
1628
1769
|
}
|
|
1629
1770
|
/**
|
|
1630
|
-
*
|
|
1771
|
+
* Remove error messages
|
|
1631
1772
|
*/
|
|
1632
|
-
|
|
1633
|
-
if (
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1773
|
+
clear(keys) {
|
|
1774
|
+
if (keys.length === 0) {
|
|
1775
|
+
this.errors = {};
|
|
1776
|
+
this.messages = {};
|
|
1777
|
+
this.firstMessage = "";
|
|
1778
|
+
return this;
|
|
1779
|
+
}
|
|
1780
|
+
keys.forEach((key) => {
|
|
1781
|
+
if (Object.prototype.hasOwnProperty.call(this.messages, key)) {
|
|
1782
|
+
delete this.messages[key];
|
|
1783
|
+
delete this.errors[key];
|
|
1784
|
+
}
|
|
1785
|
+
});
|
|
1786
|
+
this.firstMessage = "";
|
|
1787
|
+
if (this.keys().length > 0) this.firstMessage = this.messages[Object.keys(this.messages)[0]][0];
|
|
1788
|
+
return this;
|
|
1638
1789
|
}
|
|
1639
1790
|
/**
|
|
1640
|
-
*
|
|
1791
|
+
* Clone ErrorBag Instance
|
|
1641
1792
|
*/
|
|
1642
|
-
|
|
1643
|
-
this.
|
|
1644
|
-
const other = deepFind(this.data, parameters[0]);
|
|
1645
|
-
if (typeof other === "undefined") return true;
|
|
1646
|
-
if (this.parseDependentRuleParameters(other, parameters).indexOf(other) !== -1) return this.validateRequired(value);
|
|
1647
|
-
return true;
|
|
1793
|
+
clone() {
|
|
1794
|
+
return new ErrorBag({ ...this.errors }, { ...this.messages }, this.firstMessage, this.withErrorTypes);
|
|
1648
1795
|
}
|
|
1796
|
+
};
|
|
1797
|
+
|
|
1798
|
+
//#endregion
|
|
1799
|
+
//#region src/Rules/password.ts
|
|
1800
|
+
var Password$1 = class Password$1 extends IRuleContract {
|
|
1649
1801
|
/**
|
|
1650
|
-
*
|
|
1802
|
+
* The validator performing the validation.
|
|
1651
1803
|
*/
|
|
1652
|
-
|
|
1653
|
-
this.requireParameterCount(2, parameters, "required_unless");
|
|
1654
|
-
let other = deepFind(this.data, parameters[0]);
|
|
1655
|
-
other = typeof other === "undefined" ? null : other;
|
|
1656
|
-
if (this.parseDependentRuleParameters(other, parameters).indexOf(other) === -1) return this.validateRequired(value);
|
|
1657
|
-
return true;
|
|
1658
|
-
}
|
|
1804
|
+
validator;
|
|
1659
1805
|
/**
|
|
1660
|
-
*
|
|
1806
|
+
* The minimum size of the password.
|
|
1661
1807
|
*/
|
|
1662
|
-
|
|
1663
|
-
if (!this.allFailingRequired(parameters)) return this.validateRequired(value);
|
|
1664
|
-
return true;
|
|
1665
|
-
}
|
|
1808
|
+
minLength = 8;
|
|
1666
1809
|
/**
|
|
1667
|
-
*
|
|
1810
|
+
* The min amount of lower case letters required in the password
|
|
1668
1811
|
*/
|
|
1669
|
-
|
|
1670
|
-
if (!this.anyFailingRequired(parameters)) return this.validateRequired(value);
|
|
1671
|
-
return true;
|
|
1672
|
-
}
|
|
1812
|
+
minLowerCase = 0;
|
|
1673
1813
|
/**
|
|
1674
|
-
*
|
|
1814
|
+
* The min amount of uppercase letters required in the password
|
|
1675
1815
|
*/
|
|
1676
|
-
|
|
1677
|
-
if (this.anyFailingRequired(parameters)) return this.validateRequired(value);
|
|
1678
|
-
return true;
|
|
1679
|
-
}
|
|
1816
|
+
minUpperCase = 0;
|
|
1680
1817
|
/**
|
|
1681
|
-
*
|
|
1818
|
+
* The min amount of letters required in the password
|
|
1682
1819
|
*/
|
|
1683
|
-
|
|
1684
|
-
if (this.allFailingRequired(parameters)) return this.validateRequired(value);
|
|
1685
|
-
return true;
|
|
1686
|
-
}
|
|
1820
|
+
minLetters = 0;
|
|
1687
1821
|
/**
|
|
1688
|
-
*
|
|
1822
|
+
* The min amount of letters required in the password
|
|
1689
1823
|
*/
|
|
1690
|
-
|
|
1691
|
-
for (let i = 0; i < attributes.length; i++) if (!this.validateRequired(deepFind(this.data, attributes[i]))) return true;
|
|
1692
|
-
return false;
|
|
1693
|
-
}
|
|
1824
|
+
minNumbers = 0;
|
|
1694
1825
|
/**
|
|
1695
|
-
*
|
|
1826
|
+
* The min amount of symbols required in the password
|
|
1696
1827
|
*/
|
|
1697
|
-
|
|
1698
|
-
for (let i = 0; i < attributes.length; i++) if (this.validateRequired(deepFind(this.data, attributes[i]))) return false;
|
|
1699
|
-
return true;
|
|
1700
|
-
}
|
|
1828
|
+
minSymbols = 0;
|
|
1701
1829
|
/**
|
|
1702
|
-
*
|
|
1830
|
+
* Additional validation rules that should be merged into the default rules during validation.
|
|
1703
1831
|
*/
|
|
1704
|
-
|
|
1705
|
-
return typeof value === "string";
|
|
1706
|
-
}
|
|
1832
|
+
customRules = [];
|
|
1707
1833
|
/**
|
|
1708
|
-
*
|
|
1834
|
+
* The callback that will generate the "default" version of the password rule.
|
|
1709
1835
|
*/
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
|
|
1836
|
+
static defaultCallback;
|
|
1837
|
+
/**
|
|
1838
|
+
* Create a new instance of the password class
|
|
1839
|
+
*/
|
|
1840
|
+
static create() {
|
|
1841
|
+
return new Password$1();
|
|
1714
1842
|
}
|
|
1715
1843
|
/**
|
|
1716
|
-
*
|
|
1844
|
+
* Set the minimum length of the password
|
|
1717
1845
|
*/
|
|
1718
|
-
|
|
1719
|
-
this.
|
|
1720
|
-
|
|
1721
|
-
return getSize(value, validationRuleParser.hasRule(attribute, getNumericRules(), this.rules)) >= Number(parameters[0]);
|
|
1846
|
+
min(min) {
|
|
1847
|
+
this.minLength = min;
|
|
1848
|
+
return this;
|
|
1722
1849
|
}
|
|
1723
1850
|
/**
|
|
1724
|
-
*
|
|
1851
|
+
* Set the min amount of letters required in the password
|
|
1725
1852
|
*/
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
return
|
|
1853
|
+
letters(letters = 1) {
|
|
1854
|
+
this.minLetters = letters;
|
|
1855
|
+
return this;
|
|
1729
1856
|
}
|
|
1730
1857
|
/**
|
|
1731
|
-
*
|
|
1858
|
+
* Set the min amount of upper and lower case letters required in the password
|
|
1732
1859
|
*/
|
|
1733
|
-
|
|
1734
|
-
|
|
1860
|
+
mixedCase(upperCase = 1, lowerCase = 1) {
|
|
1861
|
+
this.minUpperCase = upperCase;
|
|
1862
|
+
this.minLowerCase = lowerCase;
|
|
1863
|
+
return this;
|
|
1735
1864
|
}
|
|
1736
1865
|
/**
|
|
1737
|
-
*
|
|
1866
|
+
* Set the min amount of numbers required in the password
|
|
1738
1867
|
*/
|
|
1739
|
-
|
|
1740
|
-
|
|
1868
|
+
numbers(numbers = 1) {
|
|
1869
|
+
this.minNumbers = numbers;
|
|
1870
|
+
return this;
|
|
1741
1871
|
}
|
|
1742
1872
|
/**
|
|
1743
|
-
*
|
|
1873
|
+
* Set the min amount of symbols required in the password
|
|
1744
1874
|
*/
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
return
|
|
1875
|
+
symbols(symbols = 1) {
|
|
1876
|
+
this.minSymbols = symbols;
|
|
1877
|
+
return this;
|
|
1748
1878
|
}
|
|
1749
1879
|
/**
|
|
1750
|
-
*
|
|
1880
|
+
* Determine if the validation rule passes.
|
|
1751
1881
|
*/
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
|
-
|
|
1757
|
-
|
|
1882
|
+
passes(value, attribute) {
|
|
1883
|
+
const validator = new BaseValidator(this.data, { [attribute]: [
|
|
1884
|
+
"string",
|
|
1885
|
+
`min:${this.minLength}`,
|
|
1886
|
+
...this.customRules
|
|
1887
|
+
] }, this.validator.customMessages, this.validator.customAttributes).setLang(this.lang);
|
|
1888
|
+
if (!validator.validate()) {
|
|
1889
|
+
const errors = validator.errors().addErrorTypes().get(attribute);
|
|
1890
|
+
for (const key in errors) this.validator.errors().add(attribute, errors[key]);
|
|
1891
|
+
}
|
|
1892
|
+
if (typeof value !== "string") value = "";
|
|
1893
|
+
let pattern;
|
|
1894
|
+
const formattedAttribute = this.validator.getDisplayableAttribute(attribute);
|
|
1895
|
+
if (this.minLowerCase) {
|
|
1896
|
+
pattern = this.minLowerCase === 1 ? /\p{Ll}/u : new RegExp(`(.*\\p{Ll}){${this.minLowerCase}}.*`, "u");
|
|
1897
|
+
if (!value || pattern.test(value) === false) this.validator.errors().add(attribute, {
|
|
1898
|
+
error_type: "min_lower_case",
|
|
1899
|
+
message: this.trans(`password.${this.minLowerCase === 1 ? "lower_case" : "lower_cases"}`, {
|
|
1900
|
+
attribute: formattedAttribute,
|
|
1901
|
+
amount: this.minLowerCase
|
|
1902
|
+
})
|
|
1903
|
+
});
|
|
1904
|
+
}
|
|
1905
|
+
if (this.minUpperCase) {
|
|
1906
|
+
pattern = this.minUpperCase === 1 ? /\p{Lu}/u : new RegExp(`(.*\\p{Lu}){${this.minUpperCase}}.*`, "u");
|
|
1907
|
+
if (!value || pattern.test(value) === false) this.validator.errors().add(attribute, {
|
|
1908
|
+
error_type: "min_upper_case",
|
|
1909
|
+
message: this.trans(`password.${this.minUpperCase === 1 ? "upper_case" : "upper_cases"}`, {
|
|
1910
|
+
attribute: formattedAttribute,
|
|
1911
|
+
amount: this.minUpperCase
|
|
1912
|
+
})
|
|
1913
|
+
});
|
|
1914
|
+
}
|
|
1915
|
+
if (this.minLetters) {
|
|
1916
|
+
pattern = this.minLetters === 1 ? /\p{L}/u : new RegExp(`(.*\\p{L}){${this.minLetters}}.*`, "u");
|
|
1917
|
+
if (!value || pattern.test(value) === false) this.validator.errors().add(attribute, {
|
|
1918
|
+
error_type: "min_letters",
|
|
1919
|
+
message: this.trans(`password.${this.minLetters === 1 ? "letter" : "letters"}`, {
|
|
1920
|
+
attribute: formattedAttribute,
|
|
1921
|
+
amount: this.minLetters
|
|
1922
|
+
})
|
|
1923
|
+
});
|
|
1924
|
+
}
|
|
1925
|
+
if (this.minNumbers) {
|
|
1926
|
+
pattern = this.minNumbers === 1 ? /\p{N}/u : new RegExp(`(.*\\p{N}){${this.minNumbers}}.*`, "u");
|
|
1927
|
+
if (!value || pattern.test(value) === false) this.validator.errors().add(attribute, {
|
|
1928
|
+
error_type: "min_numbers",
|
|
1929
|
+
message: this.trans(`password.${this.minNumbers === 1 ? "number" : "numbers"}`, {
|
|
1930
|
+
attribute: formattedAttribute,
|
|
1931
|
+
amount: this.minNumbers
|
|
1932
|
+
})
|
|
1933
|
+
});
|
|
1934
|
+
}
|
|
1935
|
+
if (this.minSymbols) {
|
|
1936
|
+
pattern = this.minSymbols === 1 ? /\p{Z}|\p{S}|\p{P}/u : new RegExp(`(.*(\\p{Z}|\\p{S}|\\p{P})){${this.minSymbols}}.*`, "u");
|
|
1937
|
+
if (!value || pattern.test(value) === false) this.validator.errors().add(attribute, {
|
|
1938
|
+
error_type: "min_symbols",
|
|
1939
|
+
message: this.trans(`password.${this.minSymbols === 1 ? "symbol" : "symbols"}`, {
|
|
1940
|
+
attribute: formattedAttribute,
|
|
1941
|
+
amount: this.minSymbols
|
|
1942
|
+
})
|
|
1943
|
+
});
|
|
1758
1944
|
}
|
|
1945
|
+
if (this.validator.errors().has(attribute)) return false;
|
|
1759
1946
|
return true;
|
|
1760
1947
|
}
|
|
1761
1948
|
/**
|
|
1762
|
-
*
|
|
1763
|
-
*/
|
|
1764
|
-
validateGt(value, parameters, attribute) {
|
|
1765
|
-
this.requireParameterCount(1, parameters, "gt");
|
|
1766
|
-
if (typeof value !== "number" && typeof value !== "string" && typeof value !== "object") throw "The field under validation must be a number, string, array or object";
|
|
1767
|
-
const compartedToValue = deepFind(this.data, parameters[0]) || parameters[0];
|
|
1768
|
-
if (!Array.isArray(compartedToValue) && isNaN(compartedToValue) === false) return getSize(value, validationRuleParser.hasRule(attribute, getNumericRules(), this.rules)) > compartedToValue;
|
|
1769
|
-
if (sameType(value, compartedToValue) === false) throw "The fields under validation must be of the same type";
|
|
1770
|
-
return getSize(value) > getSize(compartedToValue);
|
|
1771
|
-
}
|
|
1772
|
-
/**
|
|
1773
|
-
* Validate that an attribute is greater than or equal another attribute.
|
|
1949
|
+
* Specify additional validation rules that should be merged with the default rules during validation.
|
|
1774
1950
|
*/
|
|
1775
|
-
|
|
1776
|
-
this.
|
|
1777
|
-
|
|
1778
|
-
const compartedToValue = deepFind(this.data, parameters[0]) || parameters[0];
|
|
1779
|
-
if (!Array.isArray(compartedToValue) && isNaN(compartedToValue) === false) return getSize(value, validationRuleParser.hasRule(attribute, getNumericRules(), this.rules)) >= compartedToValue;
|
|
1780
|
-
if (sameType(value, compartedToValue) === false) throw "The fields under validation must be of the same type";
|
|
1781
|
-
return getSize(value) >= getSize(compartedToValue);
|
|
1951
|
+
rules(rules) {
|
|
1952
|
+
this.customRules = rules;
|
|
1953
|
+
return this;
|
|
1782
1954
|
}
|
|
1783
1955
|
/**
|
|
1784
|
-
*
|
|
1956
|
+
* Get all the validation error messages related to the password
|
|
1785
1957
|
*/
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
if (typeof value !== "number" && typeof value !== "string" && typeof value !== "object") throw "The field under validation must be a number, string, array or object";
|
|
1789
|
-
const compartedToValue = deepFind(this.data, parameters[0]) || parameters[0];
|
|
1790
|
-
if (!Array.isArray(compartedToValue) && isNaN(compartedToValue) === false) return getSize(value, validationRuleParser.hasRule(attribute, getNumericRules(), this.rules)) < compartedToValue;
|
|
1791
|
-
if (sameType(value, compartedToValue) === false) throw "The fields under validation must be of the same type";
|
|
1792
|
-
return getSize(value) < getSize(compartedToValue);
|
|
1958
|
+
getMessage() {
|
|
1959
|
+
return {};
|
|
1793
1960
|
}
|
|
1794
1961
|
/**
|
|
1795
|
-
*
|
|
1962
|
+
* Set the validator instance used to validate the password
|
|
1796
1963
|
*/
|
|
1797
|
-
|
|
1798
|
-
this.
|
|
1799
|
-
|
|
1800
|
-
const compartedToValue = deepFind(this.data, parameters[0]) || parameters[0];
|
|
1801
|
-
if (!Array.isArray(compartedToValue) && isNaN(compartedToValue) === false) return getSize(value, validationRuleParser.hasRule(attribute, getNumericRules(), this.rules)) <= compartedToValue;
|
|
1802
|
-
if (sameType(value, compartedToValue) === false) throw "The fields under validation must be of the same type";
|
|
1803
|
-
return getSize(value) <= getSize(compartedToValue);
|
|
1964
|
+
setValidator(validator) {
|
|
1965
|
+
this.validator = validator;
|
|
1966
|
+
return this;
|
|
1804
1967
|
}
|
|
1805
1968
|
/**
|
|
1806
|
-
*
|
|
1969
|
+
* Set the default callback to be used for determining a password's default rules.
|
|
1807
1970
|
*/
|
|
1808
|
-
|
|
1809
|
-
|
|
1810
|
-
|
|
1811
|
-
|
|
1812
|
-
return value.filter((element) => parameters.indexOf(element.toString()) === -1).length === 0;
|
|
1971
|
+
static setDefault(callback = null) {
|
|
1972
|
+
if (callback instanceof Password$1) {
|
|
1973
|
+
this.defaultCallback = callback;
|
|
1974
|
+
return;
|
|
1813
1975
|
}
|
|
1814
|
-
if (typeof
|
|
1815
|
-
|
|
1816
|
-
}
|
|
1817
|
-
/**
|
|
1818
|
-
* "Indicate" validation should pass if value is null
|
|
1819
|
-
*
|
|
1820
|
-
* Always returns true, just lets us put "nullable" in rules.
|
|
1821
|
-
*/
|
|
1822
|
-
validateNullable() {
|
|
1823
|
-
return true;
|
|
1976
|
+
if (typeof callback !== "function") throw "The given callback should be callable";
|
|
1977
|
+
this.defaultCallback = callback;
|
|
1824
1978
|
}
|
|
1825
1979
|
/**
|
|
1826
|
-
*
|
|
1980
|
+
* Get the default configuration of the password rule.
|
|
1827
1981
|
*/
|
|
1828
|
-
|
|
1829
|
-
this.
|
|
1830
|
-
|
|
1831
|
-
if (Array.isArray(value)) {
|
|
1832
|
-
for (let index = 0; index < value.length; index++) if (typeof value[index] === "number" || typeof value[index] === "string") valuesToCheck.push(value[index]);
|
|
1833
|
-
if (valuesToCheck.length === 0) return true;
|
|
1834
|
-
return valuesToCheck.filter((element) => parameters.indexOf(element.toString()) !== -1).length === 0;
|
|
1835
|
-
}
|
|
1836
|
-
if (typeof value !== "number" && typeof value !== "string") return true;
|
|
1837
|
-
return parameters.indexOf(value.toString()) === -1;
|
|
1982
|
+
static default() {
|
|
1983
|
+
const password = typeof this.defaultCallback === "function" ? this.defaultCallback() : this.defaultCallback;
|
|
1984
|
+
return password instanceof IRuleContract ? password : Password$1.create().min(8);
|
|
1838
1985
|
}
|
|
1986
|
+
};
|
|
1987
|
+
|
|
1988
|
+
//#endregion
|
|
1989
|
+
//#region src/utilities/build.ts
|
|
1990
|
+
function buildValidationMethodName(rule) {
|
|
1991
|
+
if (!rule) return rule;
|
|
1992
|
+
return rule.split("_").map((rule) => `${rule[0].toUpperCase()}${rule.slice(1)}`).join("");
|
|
1993
|
+
}
|
|
1994
|
+
|
|
1995
|
+
//#endregion
|
|
1996
|
+
//#region src/payloads/replaceAttributePayload.ts
|
|
1997
|
+
var replaceAttributePayload = class {
|
|
1839
1998
|
/**
|
|
1840
|
-
*
|
|
1999
|
+
* Stores the data object
|
|
1841
2000
|
*/
|
|
1842
|
-
|
|
1843
|
-
return true;
|
|
1844
|
-
}
|
|
2001
|
+
data;
|
|
1845
2002
|
/**
|
|
1846
|
-
*
|
|
2003
|
+
* The message in which attributes will be replaced
|
|
1847
2004
|
*/
|
|
1848
|
-
|
|
1849
|
-
if (typeof value !== "string") return false;
|
|
1850
|
-
return new RegExp("^(https?:\\/\\/)?((([a-z\\d]([a-z\\d-]*[a-z\\d])*)\\.)+[a-z]{2,}|localhost|((\\d{1,3}\\.){3}\\d{1,3}))(\\:\\d+)?(\\/[-a-z\\d%_.~+]*)*(\\?[;&a-z\\d%_.~+=-]*)?(\\#[-a-z\\d_]*)?$", "i").test(value);
|
|
1851
|
-
}
|
|
2005
|
+
message;
|
|
1852
2006
|
/**
|
|
1853
|
-
*
|
|
2007
|
+
* Parameters that will be used to replace the attributes
|
|
1854
2008
|
*/
|
|
1855
|
-
|
|
1856
|
-
value = toDate(value);
|
|
1857
|
-
if (!value) throw `Validation rule ${rule} requires the field under valation to be a date.`;
|
|
1858
|
-
const compartedToValue = toDate(deepFind(this.data, parameter) || parameter);
|
|
1859
|
-
if (!compartedToValue) throw `Validation rule ${rule} requires the parameter to be a date.`;
|
|
1860
|
-
return compare(value.getTime(), compartedToValue.getTime(), operator);
|
|
1861
|
-
}
|
|
2009
|
+
parameters;
|
|
1862
2010
|
/**
|
|
1863
|
-
*
|
|
2011
|
+
* Flag that identifies wether the numeric rule exists or not
|
|
1864
2012
|
*/
|
|
1865
|
-
|
|
1866
|
-
if (parameters.length < count) throw `Validation rule ${rule} requires at least ${count} parameters.`;
|
|
1867
|
-
}
|
|
2013
|
+
hasNumericRule;
|
|
1868
2014
|
/**
|
|
1869
|
-
*
|
|
2015
|
+
* The function that will be used to format attributes
|
|
1870
2016
|
*/
|
|
1871
|
-
|
|
1872
|
-
|
|
1873
|
-
|
|
1874
|
-
|
|
1875
|
-
|
|
1876
|
-
|
|
2017
|
+
getDisplayableAttribute;
|
|
2018
|
+
constructor(data, message, parameters, hasNumericRule, getDisplayableAttribute) {
|
|
2019
|
+
this.data = data;
|
|
2020
|
+
this.message = message;
|
|
2021
|
+
this.parameters = parameters;
|
|
2022
|
+
this.hasNumericRule = hasNumericRule;
|
|
2023
|
+
this.getDisplayableAttribute = getDisplayableAttribute;
|
|
1877
2024
|
}
|
|
1878
2025
|
};
|
|
1879
2026
|
|
|
1880
2027
|
//#endregion
|
|
1881
2028
|
//#region src/BaseValidator.ts
|
|
1882
|
-
var BaseValidator = class {
|
|
2029
|
+
var BaseValidator = class BaseValidator {
|
|
1883
2030
|
/**
|
|
1884
2031
|
* The lang used to return error messages
|
|
1885
2032
|
*/
|
|
@@ -1920,6 +2067,10 @@ var BaseValidator = class {
|
|
|
1920
2067
|
* Object of custom attribute name;
|
|
1921
2068
|
*/
|
|
1922
2069
|
customAttributes;
|
|
2070
|
+
/**
|
|
2071
|
+
* Arbitrary per-validator context for plugins.
|
|
2072
|
+
*/
|
|
2073
|
+
context = {};
|
|
1923
2074
|
constructor(data, rules, customMessages = {}, customAttributes = {}) {
|
|
1924
2075
|
this.data = data;
|
|
1925
2076
|
this.customMessages = dotify(customMessages);
|
|
@@ -1929,6 +2080,18 @@ var BaseValidator = class {
|
|
|
1929
2080
|
this.addRules(rules);
|
|
1930
2081
|
this.messages = new ErrorBag();
|
|
1931
2082
|
}
|
|
2083
|
+
static use(plugin) {
|
|
2084
|
+
usePlugin(plugin);
|
|
2085
|
+
return this;
|
|
2086
|
+
}
|
|
2087
|
+
static useContext(context = {}) {
|
|
2088
|
+
useValidatorContext(context);
|
|
2089
|
+
return this;
|
|
2090
|
+
}
|
|
2091
|
+
use(plugin) {
|
|
2092
|
+
BaseValidator.use(plugin);
|
|
2093
|
+
return this;
|
|
2094
|
+
}
|
|
1932
2095
|
setData(data) {
|
|
1933
2096
|
this.data = data;
|
|
1934
2097
|
this.addRules(this.initalRules);
|
|
@@ -1943,24 +2106,78 @@ var BaseValidator = class {
|
|
|
1943
2106
|
this.lang = lang;
|
|
1944
2107
|
return this;
|
|
1945
2108
|
}
|
|
2109
|
+
/**
|
|
2110
|
+
* Set the validator's context.
|
|
2111
|
+
*/
|
|
2112
|
+
withContext(context = {}) {
|
|
2113
|
+
this.context = context;
|
|
2114
|
+
return this;
|
|
2115
|
+
}
|
|
2116
|
+
/**
|
|
2117
|
+
* Get the validator's context.
|
|
2118
|
+
* This is useful for custom rules that need access to additional data or services.
|
|
2119
|
+
*
|
|
2120
|
+
* @returns The current context object
|
|
2121
|
+
*/
|
|
2122
|
+
getContext() {
|
|
2123
|
+
return {
|
|
2124
|
+
...getValidatorContext(),
|
|
2125
|
+
...this.context
|
|
2126
|
+
};
|
|
2127
|
+
}
|
|
2128
|
+
/**
|
|
2129
|
+
* Get the current language used by the validator.
|
|
2130
|
+
*
|
|
2131
|
+
* @returns
|
|
2132
|
+
*/
|
|
1946
2133
|
getLang() {
|
|
1947
2134
|
return this.lang;
|
|
1948
2135
|
}
|
|
2136
|
+
/**
|
|
2137
|
+
* Set custom error messages for the validator.
|
|
2138
|
+
*
|
|
2139
|
+
* @param customMessages
|
|
2140
|
+
* @returns
|
|
2141
|
+
*/
|
|
1949
2142
|
setCustomMessages(customMessages = {}) {
|
|
1950
2143
|
this.customMessages = dotify(customMessages);
|
|
1951
2144
|
return this;
|
|
1952
2145
|
}
|
|
2146
|
+
/**
|
|
2147
|
+
* Set custom attribute names for the validator.
|
|
2148
|
+
*
|
|
2149
|
+
* @param customAttributes
|
|
2150
|
+
* @returns
|
|
2151
|
+
*/
|
|
1953
2152
|
setCustomAttributes(customAttributes = {}) {
|
|
1954
2153
|
this.customAttributes = dotify(customAttributes);
|
|
1955
2154
|
return this;
|
|
1956
2155
|
}
|
|
2156
|
+
/**
|
|
2157
|
+
* Set whether the validator should stop validating after the first failure.
|
|
2158
|
+
*
|
|
2159
|
+
* @param stopOnFirstFailure
|
|
2160
|
+
* @returns
|
|
2161
|
+
*/
|
|
1957
2162
|
stopOnFirstFailure(stopOnFirstFailure = true) {
|
|
1958
2163
|
this.stopOnFirstFailureFlag = stopOnFirstFailure;
|
|
1959
2164
|
return this;
|
|
1960
2165
|
}
|
|
2166
|
+
/**
|
|
2167
|
+
* Get the error messages related to the validation.
|
|
2168
|
+
*
|
|
2169
|
+
* @returns
|
|
2170
|
+
*/
|
|
1961
2171
|
errors() {
|
|
1962
2172
|
return this.messages;
|
|
1963
2173
|
}
|
|
2174
|
+
/**
|
|
2175
|
+
* Clear the error messages for the given keys.
|
|
2176
|
+
* If no keys are provided, all error messages will be cleared.
|
|
2177
|
+
*
|
|
2178
|
+
* @param keys The keys of the error messages to clear
|
|
2179
|
+
* @returns The updated ErrorBag instance
|
|
2180
|
+
*/
|
|
1964
2181
|
clearErrors(keys = []) {
|
|
1965
2182
|
this.messages = this.messages.clear(keys).clone();
|
|
1966
2183
|
return this.messages;
|
|
@@ -1985,7 +2202,7 @@ var BaseValidator = class {
|
|
|
1985
2202
|
*/
|
|
1986
2203
|
validate(key = "", value = void 0) {
|
|
1987
2204
|
if (!isObject(this.data)) throw "The data attribute must be an object";
|
|
1988
|
-
this.validateAttributes = new validateAttributes(this.data, this.rules);
|
|
2205
|
+
this.validateAttributes = new validateAttributes(this.data, this.rules, this.getContext());
|
|
1989
2206
|
if (!key) {
|
|
1990
2207
|
this.runAllValidations();
|
|
1991
2208
|
return this.messages.keys().length === 0;
|
|
@@ -1999,7 +2216,7 @@ var BaseValidator = class {
|
|
|
1999
2216
|
*/
|
|
2000
2217
|
async validateAsync(key = "", value = void 0) {
|
|
2001
2218
|
if (!isObject(this.data)) throw "The data attribute must be an object";
|
|
2002
|
-
this.validateAttributes = new validateAttributes(this.data, this.rules);
|
|
2219
|
+
this.validateAttributes = new validateAttributes(this.data, this.rules, this.getContext());
|
|
2003
2220
|
if (!key) {
|
|
2004
2221
|
await this.runAllValidationsAsync();
|
|
2005
2222
|
return this.messages.keys().length === 0;
|
|
@@ -2010,6 +2227,9 @@ var BaseValidator = class {
|
|
|
2010
2227
|
}
|
|
2011
2228
|
/**
|
|
2012
2229
|
* Get the displayable name of the attribute.
|
|
2230
|
+
*
|
|
2231
|
+
* @param attribute
|
|
2232
|
+
* @returns
|
|
2013
2233
|
*/
|
|
2014
2234
|
getDisplayableAttribute(attribute) {
|
|
2015
2235
|
const primaryAttribute = this.getPrimaryAttribute(attribute);
|
|
@@ -2066,7 +2286,7 @@ var BaseValidator = class {
|
|
|
2066
2286
|
*/
|
|
2067
2287
|
runAllValidations() {
|
|
2068
2288
|
this.messages = new ErrorBag();
|
|
2069
|
-
this.validateAttributes = new validateAttributes(this.data, this.rules);
|
|
2289
|
+
this.validateAttributes = new validateAttributes(this.data, this.rules, this.getContext());
|
|
2070
2290
|
for (const property in this.rules) if (this.runValidation(property) === false) break;
|
|
2071
2291
|
}
|
|
2072
2292
|
/**
|
|
@@ -2074,7 +2294,7 @@ var BaseValidator = class {
|
|
|
2074
2294
|
*/
|
|
2075
2295
|
async runAllValidationsAsync() {
|
|
2076
2296
|
this.messages = new ErrorBag();
|
|
2077
|
-
this.validateAttributes = new validateAttributes(this.data, this.rules);
|
|
2297
|
+
this.validateAttributes = new validateAttributes(this.data, this.rules, this.getContext());
|
|
2078
2298
|
for (const property in this.rules) if (await this.runValidationAsync(property) === false) break;
|
|
2079
2299
|
}
|
|
2080
2300
|
/**
|
|
@@ -2256,19 +2476,6 @@ var BaseValidator = class {
|
|
|
2256
2476
|
//#region src/Contracts/IDatabaseDriver.ts
|
|
2257
2477
|
var IDatabaseDriver = class {};
|
|
2258
2478
|
|
|
2259
|
-
//#endregion
|
|
2260
|
-
//#region src/Rules/registerRule.ts
|
|
2261
|
-
function register(rule, validate, replaceMessage) {
|
|
2262
|
-
const method = buildValidationMethodName(rule);
|
|
2263
|
-
if (new validateAttributes()[`validate${method}`]) return false;
|
|
2264
|
-
validateAttributes.prototype[`validate${method}`] = validate;
|
|
2265
|
-
if (typeof replaceMessage === "function") replaceAttributes[`replace${method}`] = ({ message, parameters, data, getDisplayableAttribute }) => replaceMessage(message, parameters, data, getDisplayableAttribute);
|
|
2266
|
-
return true;
|
|
2267
|
-
}
|
|
2268
|
-
function registerImplicit(rule, validate, replaceMessage) {
|
|
2269
|
-
if (register(rule, validate, replaceMessage) === true) addImplicitRule(rule);
|
|
2270
|
-
}
|
|
2271
|
-
|
|
2272
2479
|
//#endregion
|
|
2273
2480
|
//#region src/Rules/in.ts
|
|
2274
2481
|
var In = class extends BaseRule {
|
|
@@ -2559,19 +2766,6 @@ var ExtendedRules = class extends ValidationRule {
|
|
|
2559
2766
|
validate() {}
|
|
2560
2767
|
};
|
|
2561
2768
|
|
|
2562
|
-
//#endregion
|
|
2563
|
-
//#region src/utilities/helpers.ts
|
|
2564
|
-
/**
|
|
2565
|
-
* Pluralizes a word based on the count.
|
|
2566
|
-
*
|
|
2567
|
-
* @param word
|
|
2568
|
-
* @param count
|
|
2569
|
-
* @returns
|
|
2570
|
-
*/
|
|
2571
|
-
const plural = (word, count) => {
|
|
2572
|
-
return count === 1 ? word : `${word}s`;
|
|
2573
|
-
};
|
|
2574
|
-
|
|
2575
2769
|
//#endregion
|
|
2576
2770
|
//#region src/utilities/MessageBag.ts
|
|
2577
2771
|
var MessageBag = class {
|
|
@@ -2802,6 +2996,7 @@ var Validator = class Validator {
|
|
|
2802
2996
|
errorBagName = "default";
|
|
2803
2997
|
registeredCustomRules = [new ExtendedRules()];
|
|
2804
2998
|
shouldStopOnFirstFailure = false;
|
|
2999
|
+
context = {};
|
|
2805
3000
|
constructor(data, rules, messages = {}) {
|
|
2806
3001
|
register("telephone", function(value) {
|
|
2807
3002
|
return /^\d{3}-\d{3}-\d{4}$/.test(value);
|
|
@@ -2823,6 +3018,38 @@ var Validator = class Validator {
|
|
|
2823
3018
|
return Validator;
|
|
2824
3019
|
}
|
|
2825
3020
|
/**
|
|
3021
|
+
* Set the validator's context.
|
|
3022
|
+
*
|
|
3023
|
+
* @param context
|
|
3024
|
+
* @returns
|
|
3025
|
+
*/
|
|
3026
|
+
static useContext(context = {}) {
|
|
3027
|
+
useValidatorContext(context);
|
|
3028
|
+
return this;
|
|
3029
|
+
}
|
|
3030
|
+
/**
|
|
3031
|
+
* Register a plugin with the validator.
|
|
3032
|
+
* Plugins can add rules, messages, and even override existing rules and messages.
|
|
3033
|
+
*
|
|
3034
|
+
* @param plugin The plugin to register
|
|
3035
|
+
* @returns The Validator class for chaining
|
|
3036
|
+
*/
|
|
3037
|
+
static use(plugin) {
|
|
3038
|
+
usePlugin(plugin);
|
|
3039
|
+
return this;
|
|
3040
|
+
}
|
|
3041
|
+
/**
|
|
3042
|
+
* Register a plugin with the validator.
|
|
3043
|
+
* Plugins can add rules, messages, and even override existing rules and messages.
|
|
3044
|
+
*
|
|
3045
|
+
* @param plugin The plugin to register
|
|
3046
|
+
* @returns The Validator instance for chaining
|
|
3047
|
+
*/
|
|
3048
|
+
use(plugin) {
|
|
3049
|
+
Validator.use(plugin);
|
|
3050
|
+
return this;
|
|
3051
|
+
}
|
|
3052
|
+
/**
|
|
2826
3053
|
* Run the validator and store results.
|
|
2827
3054
|
*/
|
|
2828
3055
|
async passes() {
|
|
@@ -2863,12 +3090,39 @@ var Validator = class Validator {
|
|
|
2863
3090
|
return this;
|
|
2864
3091
|
}
|
|
2865
3092
|
/**
|
|
3093
|
+
* Set the validator's context.
|
|
3094
|
+
* This is useful for custom rules that need access to additional data or services.
|
|
3095
|
+
*
|
|
3096
|
+
* @param context
|
|
3097
|
+
* @returns
|
|
3098
|
+
*/
|
|
3099
|
+
withContext(context = {}) {
|
|
3100
|
+
this.context = context;
|
|
3101
|
+
return this;
|
|
3102
|
+
}
|
|
3103
|
+
/**
|
|
3104
|
+
* Get the validator's context.
|
|
3105
|
+
* This is useful for custom rules that need access to additional data or services.
|
|
3106
|
+
*
|
|
3107
|
+
* @returns The current context object
|
|
3108
|
+
*/
|
|
3109
|
+
getContext() {
|
|
3110
|
+
return {
|
|
3111
|
+
...getValidatorContext(),
|
|
3112
|
+
...this.context
|
|
3113
|
+
};
|
|
3114
|
+
}
|
|
3115
|
+
/**
|
|
2866
3116
|
* Get the data that passed validation.
|
|
2867
3117
|
*/
|
|
2868
3118
|
validatedData() {
|
|
2869
3119
|
const validKeys = Object.keys(this.rules);
|
|
2870
3120
|
const clean = {};
|
|
2871
|
-
for (const key of validKeys)
|
|
3121
|
+
for (const key of validKeys) {
|
|
3122
|
+
const value = deepFind(this.data, key);
|
|
3123
|
+
const resolvedValue = typeof value !== "undefined" ? value : deepFind(this.getContext().requestFiles ?? {}, key);
|
|
3124
|
+
if (typeof resolvedValue !== "undefined") deepSet(clean, key, resolvedValue);
|
|
3125
|
+
}
|
|
2872
3126
|
return clean;
|
|
2873
3127
|
}
|
|
2874
3128
|
/**
|
|
@@ -2984,7 +3238,7 @@ var Validator = class Validator {
|
|
|
2984
3238
|
return this;
|
|
2985
3239
|
}
|
|
2986
3240
|
async execute() {
|
|
2987
|
-
const instance = make().setData(this.data).setRules(this.rules).setCustomMessages(this.#messages).stopOnFirstFailure(this.shouldStopOnFirstFailure);
|
|
3241
|
+
const instance = make().setData(this.data).setRules(this.rules).setCustomMessages(this.#messages).withContext(this.getContext()).stopOnFirstFailure(this.shouldStopOnFirstFailure);
|
|
2988
3242
|
this.passing = await instance.validateAsync();
|
|
2989
3243
|
this.executed = true;
|
|
2990
3244
|
this.instance = instance;
|
|
@@ -2999,6 +3253,7 @@ var ValidationException = class ValidationException extends Error {
|
|
|
2999
3253
|
validator;
|
|
3000
3254
|
response;
|
|
3001
3255
|
status = 422;
|
|
3256
|
+
statusCode = 422;
|
|
3002
3257
|
errorBag = "default";
|
|
3003
3258
|
redirectTo;
|
|
3004
3259
|
name = "ValidationException";
|
|
@@ -3101,13 +3356,19 @@ exports.convertValuesToNull = convertValuesToNull;
|
|
|
3101
3356
|
exports.convertValuesToNumber = convertValuesToNumber;
|
|
3102
3357
|
exports.deepEqual = deepEqual;
|
|
3103
3358
|
exports.deepFind = deepFind;
|
|
3359
|
+
exports.deepFindMessage = deepFindMessage;
|
|
3104
3360
|
exports.deepSet = deepSet;
|
|
3361
|
+
exports.definePlugin = definePlugin;
|
|
3105
3362
|
exports.dotify = dotify;
|
|
3106
3363
|
exports.getFormattedAttribute = getFormattedAttribute;
|
|
3107
3364
|
exports.getKeyCombinations = getKeyCombinations;
|
|
3108
3365
|
exports.getMessage = getMessage;
|
|
3109
3366
|
exports.getNumericRules = getNumericRules;
|
|
3110
3367
|
exports.getSize = getSize;
|
|
3368
|
+
exports.getValidationMessageType = getValidationMessageType;
|
|
3369
|
+
exports.getValidationSize = getValidationSize;
|
|
3370
|
+
exports.getValidationValueInspector = getValidationValueInspector;
|
|
3371
|
+
exports.getValidatorContext = getValidatorContext;
|
|
3111
3372
|
exports.isArrayOfRules = isArrayOfRules;
|
|
3112
3373
|
exports.isImplicitRule = isImplicitRule;
|
|
3113
3374
|
exports.isInteger = isInteger;
|
|
@@ -3122,9 +3383,13 @@ exports.plural = plural;
|
|
|
3122
3383
|
exports.regex = regex;
|
|
3123
3384
|
exports.register = register;
|
|
3124
3385
|
exports.registerImplicit = registerImplicit;
|
|
3386
|
+
exports.registerValueInspector = registerValueInspector;
|
|
3125
3387
|
exports.requiredIf = requiredIf;
|
|
3126
3388
|
exports.ruleIn = ruleIn;
|
|
3127
3389
|
exports.ruleNotIn = ruleNotIn;
|
|
3390
|
+
exports.runWithValidatorContext = runWithValidatorContext;
|
|
3128
3391
|
exports.sameType = sameType;
|
|
3129
3392
|
exports.toDate = toDate;
|
|
3130
|
-
exports.toSnakeCase = toSnakeCase;
|
|
3393
|
+
exports.toSnakeCase = toSnakeCase;
|
|
3394
|
+
exports.usePlugin = usePlugin;
|
|
3395
|
+
exports.useValidatorContext = useValidatorContext;
|