@ls-stack/utils 3.47.0 → 3.48.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/partialEqual.cjs +432 -1
- package/dist/partialEqual.d.cts +9 -0
- package/dist/partialEqual.d.ts +9 -0
- package/dist/partialEqual.js +432 -1
- package/package.json +1 -1
package/dist/partialEqual.cjs
CHANGED
|
@@ -24,6 +24,7 @@ __export(partialEqual_exports, {
|
|
|
24
24
|
partialEqual: () => partialEqual
|
|
25
25
|
});
|
|
26
26
|
module.exports = __toCommonJS(partialEqual_exports);
|
|
27
|
+
var import_t_result = require("t-result");
|
|
27
28
|
|
|
28
29
|
// src/deepEqual.ts
|
|
29
30
|
var has = Object.prototype.hasOwnProperty;
|
|
@@ -389,7 +390,437 @@ function executeComparison(target, comparison) {
|
|
|
389
390
|
return false;
|
|
390
391
|
}
|
|
391
392
|
}
|
|
392
|
-
function
|
|
393
|
+
function formatPath(path) {
|
|
394
|
+
if (path.length === 0) return "";
|
|
395
|
+
let result = path[0] || "";
|
|
396
|
+
for (let i = 1; i < path.length; i++) {
|
|
397
|
+
const segment = path[i];
|
|
398
|
+
if (segment && segment.startsWith("[") && segment.endsWith("]")) {
|
|
399
|
+
result += segment;
|
|
400
|
+
} else if (segment) {
|
|
401
|
+
result += `.${segment}`;
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
return result;
|
|
405
|
+
}
|
|
406
|
+
function addError(context, message, received, expected) {
|
|
407
|
+
const error = {
|
|
408
|
+
path: formatPath(context.path),
|
|
409
|
+
message,
|
|
410
|
+
received
|
|
411
|
+
};
|
|
412
|
+
if (expected !== void 0) {
|
|
413
|
+
error.expected = expected;
|
|
414
|
+
}
|
|
415
|
+
context.errors.push(error);
|
|
416
|
+
}
|
|
417
|
+
function executeComparisonWithErrorCollection(target, comparison, context) {
|
|
418
|
+
const [type, value] = comparison;
|
|
419
|
+
switch (type) {
|
|
420
|
+
case "hasType":
|
|
421
|
+
switch (value) {
|
|
422
|
+
case "string":
|
|
423
|
+
if (typeof target !== "string") {
|
|
424
|
+
addError(context, `Expected type string`, target);
|
|
425
|
+
}
|
|
426
|
+
break;
|
|
427
|
+
case "number":
|
|
428
|
+
if (typeof target !== "number") {
|
|
429
|
+
addError(context, `Expected type number`, target);
|
|
430
|
+
}
|
|
431
|
+
break;
|
|
432
|
+
case "boolean":
|
|
433
|
+
if (typeof target !== "boolean") {
|
|
434
|
+
addError(context, `Expected type boolean`, target);
|
|
435
|
+
}
|
|
436
|
+
break;
|
|
437
|
+
case "function":
|
|
438
|
+
if (typeof target !== "function") {
|
|
439
|
+
addError(context, `Expected type function`, target);
|
|
440
|
+
}
|
|
441
|
+
break;
|
|
442
|
+
case "array":
|
|
443
|
+
if (!Array.isArray(target)) {
|
|
444
|
+
addError(context, `Expected array`, target);
|
|
445
|
+
}
|
|
446
|
+
break;
|
|
447
|
+
case "object":
|
|
448
|
+
if (typeof target !== "object" || target === null || Array.isArray(target)) {
|
|
449
|
+
addError(context, `Expected object`, target);
|
|
450
|
+
}
|
|
451
|
+
break;
|
|
452
|
+
}
|
|
453
|
+
break;
|
|
454
|
+
case "isInstanceOf":
|
|
455
|
+
if (!(target instanceof value)) {
|
|
456
|
+
addError(
|
|
457
|
+
context,
|
|
458
|
+
`Expected instance of ${value.name}`,
|
|
459
|
+
target
|
|
460
|
+
);
|
|
461
|
+
}
|
|
462
|
+
break;
|
|
463
|
+
case "strStartsWith":
|
|
464
|
+
if (typeof target !== "string" || !target.startsWith(value)) {
|
|
465
|
+
addError(
|
|
466
|
+
context,
|
|
467
|
+
`Expected string starting with "${value}"`,
|
|
468
|
+
target
|
|
469
|
+
);
|
|
470
|
+
}
|
|
471
|
+
break;
|
|
472
|
+
case "strEndsWith":
|
|
473
|
+
if (typeof target !== "string" || !target.endsWith(value)) {
|
|
474
|
+
addError(
|
|
475
|
+
context,
|
|
476
|
+
`Expected string ending with "${value}"`,
|
|
477
|
+
target
|
|
478
|
+
);
|
|
479
|
+
}
|
|
480
|
+
break;
|
|
481
|
+
case "strContains":
|
|
482
|
+
if (typeof target !== "string" || !target.includes(value)) {
|
|
483
|
+
addError(
|
|
484
|
+
context,
|
|
485
|
+
`Expected string containing "${value}"`,
|
|
486
|
+
target
|
|
487
|
+
);
|
|
488
|
+
}
|
|
489
|
+
break;
|
|
490
|
+
case "strMatchesRegex":
|
|
491
|
+
if (typeof target !== "string" || !value.test(target)) {
|
|
492
|
+
addError(
|
|
493
|
+
context,
|
|
494
|
+
`Expected string matching regex ${value}`,
|
|
495
|
+
target
|
|
496
|
+
);
|
|
497
|
+
}
|
|
498
|
+
break;
|
|
499
|
+
case "numIsGreaterThan":
|
|
500
|
+
if (typeof target !== "number" || target <= value) {
|
|
501
|
+
addError(
|
|
502
|
+
context,
|
|
503
|
+
`Expected number greater than ${value}`,
|
|
504
|
+
target
|
|
505
|
+
);
|
|
506
|
+
}
|
|
507
|
+
break;
|
|
508
|
+
case "numIsGreaterThanOrEqual":
|
|
509
|
+
if (typeof target !== "number" || target < value) {
|
|
510
|
+
addError(
|
|
511
|
+
context,
|
|
512
|
+
`Expected number greater than or equal to ${value}`,
|
|
513
|
+
target
|
|
514
|
+
);
|
|
515
|
+
}
|
|
516
|
+
break;
|
|
517
|
+
case "numIsLessThan":
|
|
518
|
+
if (typeof target !== "number" || target >= value) {
|
|
519
|
+
addError(
|
|
520
|
+
context,
|
|
521
|
+
`Expected number less than ${value}`,
|
|
522
|
+
target
|
|
523
|
+
);
|
|
524
|
+
}
|
|
525
|
+
break;
|
|
526
|
+
case "numIsLessThanOrEqual":
|
|
527
|
+
if (typeof target !== "number" || target > value) {
|
|
528
|
+
addError(
|
|
529
|
+
context,
|
|
530
|
+
`Expected number less than or equal to ${value}`,
|
|
531
|
+
target
|
|
532
|
+
);
|
|
533
|
+
}
|
|
534
|
+
break;
|
|
535
|
+
case "numIsInRange":
|
|
536
|
+
if (typeof target !== "number" || target < value[0] || target > value[1]) {
|
|
537
|
+
addError(
|
|
538
|
+
context,
|
|
539
|
+
`Expected number in range [${value[0]}, ${value[1]}]`,
|
|
540
|
+
target
|
|
541
|
+
);
|
|
542
|
+
}
|
|
543
|
+
break;
|
|
544
|
+
case "jsonStringHasPartial":
|
|
545
|
+
if (typeof target !== "string") {
|
|
546
|
+
addError(context, `Expected JSON string`, target);
|
|
547
|
+
} else {
|
|
548
|
+
try {
|
|
549
|
+
const parsed = JSON.parse(target);
|
|
550
|
+
partialEqualWithErrorCollection(parsed, value, context);
|
|
551
|
+
} catch {
|
|
552
|
+
addError(
|
|
553
|
+
context,
|
|
554
|
+
`Expected valid JSON string`,
|
|
555
|
+
target
|
|
556
|
+
);
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
break;
|
|
560
|
+
case "deepEqual":
|
|
561
|
+
if (!deepEqual(target, value)) {
|
|
562
|
+
addError(context, `Expected deep equal`, target, value);
|
|
563
|
+
}
|
|
564
|
+
break;
|
|
565
|
+
case "partialEqual":
|
|
566
|
+
partialEqualWithErrorCollection(target, value, context);
|
|
567
|
+
break;
|
|
568
|
+
case "custom":
|
|
569
|
+
if (!value(target)) {
|
|
570
|
+
addError(context, `Custom validation failed`, target, "valid value");
|
|
571
|
+
}
|
|
572
|
+
break;
|
|
573
|
+
case "keyNotBePresent":
|
|
574
|
+
addError(context, `Key should not be present`, target);
|
|
575
|
+
break;
|
|
576
|
+
case "any": {
|
|
577
|
+
for (const comp of value) {
|
|
578
|
+
const subContext = {
|
|
579
|
+
errors: [],
|
|
580
|
+
path: [...context.path]
|
|
581
|
+
};
|
|
582
|
+
executeComparisonWithErrorCollection(target, comp, subContext);
|
|
583
|
+
if (subContext.errors.length === 0) {
|
|
584
|
+
return;
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
addError(
|
|
588
|
+
context,
|
|
589
|
+
`None of the alternative comparisons matched`,
|
|
590
|
+
target
|
|
591
|
+
);
|
|
592
|
+
break;
|
|
593
|
+
}
|
|
594
|
+
case "all":
|
|
595
|
+
for (const comp of value) {
|
|
596
|
+
executeComparisonWithErrorCollection(target, comp, context);
|
|
597
|
+
}
|
|
598
|
+
break;
|
|
599
|
+
case "not": {
|
|
600
|
+
const subContext = {
|
|
601
|
+
errors: [],
|
|
602
|
+
path: [...context.path]
|
|
603
|
+
};
|
|
604
|
+
executeComparisonWithErrorCollection(target, value, subContext);
|
|
605
|
+
if (subContext.errors.length === 0) {
|
|
606
|
+
addError(
|
|
607
|
+
context,
|
|
608
|
+
`Expected negated condition to fail`,
|
|
609
|
+
target
|
|
610
|
+
);
|
|
611
|
+
}
|
|
612
|
+
break;
|
|
613
|
+
}
|
|
614
|
+
case "withNoExtraKeys":
|
|
615
|
+
case "withDeepNoExtraKeys":
|
|
616
|
+
case "noExtraDefinedKeys":
|
|
617
|
+
case "deepNoExtraDefinedKeys":
|
|
618
|
+
addError(
|
|
619
|
+
context,
|
|
620
|
+
`Complex validation not supported in error collection mode yet`,
|
|
621
|
+
target
|
|
622
|
+
);
|
|
623
|
+
break;
|
|
624
|
+
}
|
|
625
|
+
}
|
|
626
|
+
function executeComparisonWithKeyContextAndErrorCollection(target, comp, keyExists, context) {
|
|
627
|
+
const [type, value] = comp;
|
|
628
|
+
if (type === "keyNotBePresent") {
|
|
629
|
+
if (keyExists) {
|
|
630
|
+
addError(context, `Key should not be present`, target);
|
|
631
|
+
}
|
|
632
|
+
return;
|
|
633
|
+
}
|
|
634
|
+
if (type === "any") {
|
|
635
|
+
for (const childComp of value) {
|
|
636
|
+
const subContext = {
|
|
637
|
+
errors: [],
|
|
638
|
+
path: [...context.path]
|
|
639
|
+
};
|
|
640
|
+
executeComparisonWithKeyContextAndErrorCollection(
|
|
641
|
+
target,
|
|
642
|
+
childComp,
|
|
643
|
+
keyExists,
|
|
644
|
+
subContext
|
|
645
|
+
);
|
|
646
|
+
if (subContext.errors.length === 0) {
|
|
647
|
+
return;
|
|
648
|
+
}
|
|
649
|
+
}
|
|
650
|
+
addError(
|
|
651
|
+
context,
|
|
652
|
+
`None of the alternative comparisons matched`,
|
|
653
|
+
target,
|
|
654
|
+
"any alternative match"
|
|
655
|
+
);
|
|
656
|
+
return;
|
|
657
|
+
}
|
|
658
|
+
if (type === "not") {
|
|
659
|
+
const subContext = {
|
|
660
|
+
errors: [],
|
|
661
|
+
path: [...context.path]
|
|
662
|
+
};
|
|
663
|
+
executeComparisonWithKeyContextAndErrorCollection(
|
|
664
|
+
target,
|
|
665
|
+
value,
|
|
666
|
+
keyExists,
|
|
667
|
+
subContext
|
|
668
|
+
);
|
|
669
|
+
if (subContext.errors.length === 0) {
|
|
670
|
+
addError(
|
|
671
|
+
context,
|
|
672
|
+
`Expected negated condition to fail`,
|
|
673
|
+
target,
|
|
674
|
+
"negated condition"
|
|
675
|
+
);
|
|
676
|
+
}
|
|
677
|
+
return;
|
|
678
|
+
}
|
|
679
|
+
executeComparisonWithErrorCollection(target, comp, context);
|
|
680
|
+
}
|
|
681
|
+
function partialEqualWithErrorCollection(target, sub, context) {
|
|
682
|
+
if (sub === target) return;
|
|
683
|
+
if (sub && typeof sub === "object" && "~sc" in sub) {
|
|
684
|
+
executeComparisonWithErrorCollection(target, sub["~sc"], context);
|
|
685
|
+
return;
|
|
686
|
+
}
|
|
687
|
+
if (sub && target && sub.constructor === target.constructor) {
|
|
688
|
+
const ctor = sub.constructor;
|
|
689
|
+
if (ctor === Date) {
|
|
690
|
+
if (sub.getTime() !== target.getTime()) {
|
|
691
|
+
addError(context, `Date mismatch`, target, sub);
|
|
692
|
+
}
|
|
693
|
+
return;
|
|
694
|
+
}
|
|
695
|
+
if (ctor === RegExp) {
|
|
696
|
+
if (sub.toString() !== target.toString()) {
|
|
697
|
+
addError(context, `RegExp mismatch`, target, sub);
|
|
698
|
+
}
|
|
699
|
+
return;
|
|
700
|
+
}
|
|
701
|
+
if (ctor === Array) {
|
|
702
|
+
if (sub.length > target.length) {
|
|
703
|
+
addError(
|
|
704
|
+
context,
|
|
705
|
+
`Array too short: expected at least ${sub.length} elements, got ${target.length}`,
|
|
706
|
+
target,
|
|
707
|
+
sub
|
|
708
|
+
);
|
|
709
|
+
return;
|
|
710
|
+
}
|
|
711
|
+
for (let i = 0; i < sub.length; i++) {
|
|
712
|
+
context.path.push(`[${i}]`);
|
|
713
|
+
partialEqualWithErrorCollection(target[i], sub[i], context);
|
|
714
|
+
context.path.pop();
|
|
715
|
+
}
|
|
716
|
+
return;
|
|
717
|
+
}
|
|
718
|
+
if (ctor === Set) {
|
|
719
|
+
if (sub.size > target.size) {
|
|
720
|
+
addError(
|
|
721
|
+
context,
|
|
722
|
+
`Set too small: expected at least ${sub.size} elements, got ${target.size}`,
|
|
723
|
+
target,
|
|
724
|
+
sub
|
|
725
|
+
);
|
|
726
|
+
return;
|
|
727
|
+
}
|
|
728
|
+
for (const value of sub) {
|
|
729
|
+
let found = false;
|
|
730
|
+
if (value && typeof value === "object") {
|
|
731
|
+
found = !!find2(target, value);
|
|
732
|
+
} else {
|
|
733
|
+
found = target.has(value);
|
|
734
|
+
}
|
|
735
|
+
if (!found) {
|
|
736
|
+
addError(context, `Set missing value`, target, value);
|
|
737
|
+
}
|
|
738
|
+
}
|
|
739
|
+
return;
|
|
740
|
+
}
|
|
741
|
+
if (ctor === Map) {
|
|
742
|
+
if (sub.size > target.size) {
|
|
743
|
+
addError(
|
|
744
|
+
context,
|
|
745
|
+
`Map too small: expected at least ${sub.size} entries, got ${target.size}`,
|
|
746
|
+
target,
|
|
747
|
+
sub
|
|
748
|
+
);
|
|
749
|
+
return;
|
|
750
|
+
}
|
|
751
|
+
for (const [key, value] of sub) {
|
|
752
|
+
let targetKey = key;
|
|
753
|
+
if (key && typeof key === "object") {
|
|
754
|
+
targetKey = find2(target, key);
|
|
755
|
+
if (!targetKey) {
|
|
756
|
+
addError(context, `Map missing key`, target, key);
|
|
757
|
+
continue;
|
|
758
|
+
}
|
|
759
|
+
}
|
|
760
|
+
if (!target.has(targetKey)) {
|
|
761
|
+
addError(context, `Map missing key`, target, key);
|
|
762
|
+
continue;
|
|
763
|
+
}
|
|
764
|
+
context.path.push(`[${key}]`);
|
|
765
|
+
partialEqualWithErrorCollection(target.get(targetKey), value, context);
|
|
766
|
+
context.path.pop();
|
|
767
|
+
}
|
|
768
|
+
return;
|
|
769
|
+
}
|
|
770
|
+
if (!ctor || typeof sub === "object") {
|
|
771
|
+
for (const key in sub) {
|
|
772
|
+
if (has2.call(sub, key)) {
|
|
773
|
+
const subValue = sub[key];
|
|
774
|
+
context.path.push(key);
|
|
775
|
+
if (subValue && typeof subValue === "object" && "~sc" in subValue && subValue["~sc"][0] === "keyNotBePresent") {
|
|
776
|
+
if (has2.call(target, key)) {
|
|
777
|
+
addError(
|
|
778
|
+
context,
|
|
779
|
+
`Key should not be present`,
|
|
780
|
+
target[key],
|
|
781
|
+
"key not present"
|
|
782
|
+
);
|
|
783
|
+
}
|
|
784
|
+
} else if (subValue && typeof subValue === "object" && "~sc" in subValue && subValue["~sc"][0] === "any") {
|
|
785
|
+
const targetHasKey = has2.call(target, key);
|
|
786
|
+
const targetValue = targetHasKey ? target[key] : void 0;
|
|
787
|
+
executeComparisonWithKeyContextAndErrorCollection(
|
|
788
|
+
targetValue,
|
|
789
|
+
subValue["~sc"],
|
|
790
|
+
targetHasKey,
|
|
791
|
+
context
|
|
792
|
+
);
|
|
793
|
+
} else {
|
|
794
|
+
if (!has2.call(target, key)) {
|
|
795
|
+
addError(context, `Missing property`, void 0, subValue);
|
|
796
|
+
} else {
|
|
797
|
+
partialEqualWithErrorCollection(target[key], subValue, context);
|
|
798
|
+
}
|
|
799
|
+
}
|
|
800
|
+
context.path.pop();
|
|
801
|
+
}
|
|
802
|
+
}
|
|
803
|
+
return;
|
|
804
|
+
}
|
|
805
|
+
}
|
|
806
|
+
if (sub !== sub && target !== target) {
|
|
807
|
+
return;
|
|
808
|
+
}
|
|
809
|
+
addError(context, `Value mismatch`, target, sub);
|
|
810
|
+
}
|
|
811
|
+
function partialEqual(target, sub, returnErrors) {
|
|
812
|
+
if (returnErrors === true) {
|
|
813
|
+
const context = {
|
|
814
|
+
errors: [],
|
|
815
|
+
path: []
|
|
816
|
+
};
|
|
817
|
+
partialEqualWithErrorCollection(target, sub, context);
|
|
818
|
+
if (context.errors.length === 0) {
|
|
819
|
+
return (0, import_t_result.ok)(void 0);
|
|
820
|
+
} else {
|
|
821
|
+
return (0, import_t_result.err)(context.errors);
|
|
822
|
+
}
|
|
823
|
+
}
|
|
393
824
|
if (sub === target) return true;
|
|
394
825
|
if (sub && typeof sub === "object" && "~sc" in sub) {
|
|
395
826
|
return executeComparison(target, sub["~sc"]);
|
package/dist/partialEqual.d.cts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { Result } from 't-result';
|
|
2
|
+
|
|
1
3
|
type ComparisonsType = [type: 'strStartsWith', value: string] | [type: 'strEndsWith', value: string] | [
|
|
2
4
|
type: 'hasType',
|
|
3
5
|
value: 'string' | 'number' | 'boolean' | 'object' | 'array' | 'function'
|
|
@@ -46,6 +48,13 @@ type Match = BaseMatch & {
|
|
|
46
48
|
not: BaseMatch;
|
|
47
49
|
};
|
|
48
50
|
declare const match: Match;
|
|
51
|
+
type PartialError = {
|
|
52
|
+
path: string;
|
|
53
|
+
message: string;
|
|
54
|
+
received: any;
|
|
55
|
+
expected?: any;
|
|
56
|
+
};
|
|
57
|
+
declare function partialEqual(target: any, sub: any, returnErrors: true): Result<void, PartialError[]>;
|
|
49
58
|
declare function partialEqual(target: any, sub: any): boolean;
|
|
50
59
|
|
|
51
60
|
export { match, partialEqual };
|
package/dist/partialEqual.d.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { Result } from 't-result';
|
|
2
|
+
|
|
1
3
|
type ComparisonsType = [type: 'strStartsWith', value: string] | [type: 'strEndsWith', value: string] | [
|
|
2
4
|
type: 'hasType',
|
|
3
5
|
value: 'string' | 'number' | 'boolean' | 'object' | 'array' | 'function'
|
|
@@ -46,6 +48,13 @@ type Match = BaseMatch & {
|
|
|
46
48
|
not: BaseMatch;
|
|
47
49
|
};
|
|
48
50
|
declare const match: Match;
|
|
51
|
+
type PartialError = {
|
|
52
|
+
path: string;
|
|
53
|
+
message: string;
|
|
54
|
+
received: any;
|
|
55
|
+
expected?: any;
|
|
56
|
+
};
|
|
57
|
+
declare function partialEqual(target: any, sub: any, returnErrors: true): Result<void, PartialError[]>;
|
|
49
58
|
declare function partialEqual(target: any, sub: any): boolean;
|
|
50
59
|
|
|
51
60
|
export { match, partialEqual };
|
package/dist/partialEqual.js
CHANGED
|
@@ -3,6 +3,7 @@ import {
|
|
|
3
3
|
} from "./chunk-JQFUKJU5.js";
|
|
4
4
|
|
|
5
5
|
// src/partialEqual.ts
|
|
6
|
+
import { err, ok } from "t-result";
|
|
6
7
|
var has = Object.prototype.hasOwnProperty;
|
|
7
8
|
function createComparison(type) {
|
|
8
9
|
return { "~sc": type };
|
|
@@ -302,7 +303,437 @@ function executeComparison(target, comparison) {
|
|
|
302
303
|
return false;
|
|
303
304
|
}
|
|
304
305
|
}
|
|
305
|
-
function
|
|
306
|
+
function formatPath(path) {
|
|
307
|
+
if (path.length === 0) return "";
|
|
308
|
+
let result = path[0] || "";
|
|
309
|
+
for (let i = 1; i < path.length; i++) {
|
|
310
|
+
const segment = path[i];
|
|
311
|
+
if (segment && segment.startsWith("[") && segment.endsWith("]")) {
|
|
312
|
+
result += segment;
|
|
313
|
+
} else if (segment) {
|
|
314
|
+
result += `.${segment}`;
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
return result;
|
|
318
|
+
}
|
|
319
|
+
function addError(context, message, received, expected) {
|
|
320
|
+
const error = {
|
|
321
|
+
path: formatPath(context.path),
|
|
322
|
+
message,
|
|
323
|
+
received
|
|
324
|
+
};
|
|
325
|
+
if (expected !== void 0) {
|
|
326
|
+
error.expected = expected;
|
|
327
|
+
}
|
|
328
|
+
context.errors.push(error);
|
|
329
|
+
}
|
|
330
|
+
function executeComparisonWithErrorCollection(target, comparison, context) {
|
|
331
|
+
const [type, value] = comparison;
|
|
332
|
+
switch (type) {
|
|
333
|
+
case "hasType":
|
|
334
|
+
switch (value) {
|
|
335
|
+
case "string":
|
|
336
|
+
if (typeof target !== "string") {
|
|
337
|
+
addError(context, `Expected type string`, target);
|
|
338
|
+
}
|
|
339
|
+
break;
|
|
340
|
+
case "number":
|
|
341
|
+
if (typeof target !== "number") {
|
|
342
|
+
addError(context, `Expected type number`, target);
|
|
343
|
+
}
|
|
344
|
+
break;
|
|
345
|
+
case "boolean":
|
|
346
|
+
if (typeof target !== "boolean") {
|
|
347
|
+
addError(context, `Expected type boolean`, target);
|
|
348
|
+
}
|
|
349
|
+
break;
|
|
350
|
+
case "function":
|
|
351
|
+
if (typeof target !== "function") {
|
|
352
|
+
addError(context, `Expected type function`, target);
|
|
353
|
+
}
|
|
354
|
+
break;
|
|
355
|
+
case "array":
|
|
356
|
+
if (!Array.isArray(target)) {
|
|
357
|
+
addError(context, `Expected array`, target);
|
|
358
|
+
}
|
|
359
|
+
break;
|
|
360
|
+
case "object":
|
|
361
|
+
if (typeof target !== "object" || target === null || Array.isArray(target)) {
|
|
362
|
+
addError(context, `Expected object`, target);
|
|
363
|
+
}
|
|
364
|
+
break;
|
|
365
|
+
}
|
|
366
|
+
break;
|
|
367
|
+
case "isInstanceOf":
|
|
368
|
+
if (!(target instanceof value)) {
|
|
369
|
+
addError(
|
|
370
|
+
context,
|
|
371
|
+
`Expected instance of ${value.name}`,
|
|
372
|
+
target
|
|
373
|
+
);
|
|
374
|
+
}
|
|
375
|
+
break;
|
|
376
|
+
case "strStartsWith":
|
|
377
|
+
if (typeof target !== "string" || !target.startsWith(value)) {
|
|
378
|
+
addError(
|
|
379
|
+
context,
|
|
380
|
+
`Expected string starting with "${value}"`,
|
|
381
|
+
target
|
|
382
|
+
);
|
|
383
|
+
}
|
|
384
|
+
break;
|
|
385
|
+
case "strEndsWith":
|
|
386
|
+
if (typeof target !== "string" || !target.endsWith(value)) {
|
|
387
|
+
addError(
|
|
388
|
+
context,
|
|
389
|
+
`Expected string ending with "${value}"`,
|
|
390
|
+
target
|
|
391
|
+
);
|
|
392
|
+
}
|
|
393
|
+
break;
|
|
394
|
+
case "strContains":
|
|
395
|
+
if (typeof target !== "string" || !target.includes(value)) {
|
|
396
|
+
addError(
|
|
397
|
+
context,
|
|
398
|
+
`Expected string containing "${value}"`,
|
|
399
|
+
target
|
|
400
|
+
);
|
|
401
|
+
}
|
|
402
|
+
break;
|
|
403
|
+
case "strMatchesRegex":
|
|
404
|
+
if (typeof target !== "string" || !value.test(target)) {
|
|
405
|
+
addError(
|
|
406
|
+
context,
|
|
407
|
+
`Expected string matching regex ${value}`,
|
|
408
|
+
target
|
|
409
|
+
);
|
|
410
|
+
}
|
|
411
|
+
break;
|
|
412
|
+
case "numIsGreaterThan":
|
|
413
|
+
if (typeof target !== "number" || target <= value) {
|
|
414
|
+
addError(
|
|
415
|
+
context,
|
|
416
|
+
`Expected number greater than ${value}`,
|
|
417
|
+
target
|
|
418
|
+
);
|
|
419
|
+
}
|
|
420
|
+
break;
|
|
421
|
+
case "numIsGreaterThanOrEqual":
|
|
422
|
+
if (typeof target !== "number" || target < value) {
|
|
423
|
+
addError(
|
|
424
|
+
context,
|
|
425
|
+
`Expected number greater than or equal to ${value}`,
|
|
426
|
+
target
|
|
427
|
+
);
|
|
428
|
+
}
|
|
429
|
+
break;
|
|
430
|
+
case "numIsLessThan":
|
|
431
|
+
if (typeof target !== "number" || target >= value) {
|
|
432
|
+
addError(
|
|
433
|
+
context,
|
|
434
|
+
`Expected number less than ${value}`,
|
|
435
|
+
target
|
|
436
|
+
);
|
|
437
|
+
}
|
|
438
|
+
break;
|
|
439
|
+
case "numIsLessThanOrEqual":
|
|
440
|
+
if (typeof target !== "number" || target > value) {
|
|
441
|
+
addError(
|
|
442
|
+
context,
|
|
443
|
+
`Expected number less than or equal to ${value}`,
|
|
444
|
+
target
|
|
445
|
+
);
|
|
446
|
+
}
|
|
447
|
+
break;
|
|
448
|
+
case "numIsInRange":
|
|
449
|
+
if (typeof target !== "number" || target < value[0] || target > value[1]) {
|
|
450
|
+
addError(
|
|
451
|
+
context,
|
|
452
|
+
`Expected number in range [${value[0]}, ${value[1]}]`,
|
|
453
|
+
target
|
|
454
|
+
);
|
|
455
|
+
}
|
|
456
|
+
break;
|
|
457
|
+
case "jsonStringHasPartial":
|
|
458
|
+
if (typeof target !== "string") {
|
|
459
|
+
addError(context, `Expected JSON string`, target);
|
|
460
|
+
} else {
|
|
461
|
+
try {
|
|
462
|
+
const parsed = JSON.parse(target);
|
|
463
|
+
partialEqualWithErrorCollection(parsed, value, context);
|
|
464
|
+
} catch {
|
|
465
|
+
addError(
|
|
466
|
+
context,
|
|
467
|
+
`Expected valid JSON string`,
|
|
468
|
+
target
|
|
469
|
+
);
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
break;
|
|
473
|
+
case "deepEqual":
|
|
474
|
+
if (!deepEqual(target, value)) {
|
|
475
|
+
addError(context, `Expected deep equal`, target, value);
|
|
476
|
+
}
|
|
477
|
+
break;
|
|
478
|
+
case "partialEqual":
|
|
479
|
+
partialEqualWithErrorCollection(target, value, context);
|
|
480
|
+
break;
|
|
481
|
+
case "custom":
|
|
482
|
+
if (!value(target)) {
|
|
483
|
+
addError(context, `Custom validation failed`, target, "valid value");
|
|
484
|
+
}
|
|
485
|
+
break;
|
|
486
|
+
case "keyNotBePresent":
|
|
487
|
+
addError(context, `Key should not be present`, target);
|
|
488
|
+
break;
|
|
489
|
+
case "any": {
|
|
490
|
+
for (const comp of value) {
|
|
491
|
+
const subContext = {
|
|
492
|
+
errors: [],
|
|
493
|
+
path: [...context.path]
|
|
494
|
+
};
|
|
495
|
+
executeComparisonWithErrorCollection(target, comp, subContext);
|
|
496
|
+
if (subContext.errors.length === 0) {
|
|
497
|
+
return;
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
addError(
|
|
501
|
+
context,
|
|
502
|
+
`None of the alternative comparisons matched`,
|
|
503
|
+
target
|
|
504
|
+
);
|
|
505
|
+
break;
|
|
506
|
+
}
|
|
507
|
+
case "all":
|
|
508
|
+
for (const comp of value) {
|
|
509
|
+
executeComparisonWithErrorCollection(target, comp, context);
|
|
510
|
+
}
|
|
511
|
+
break;
|
|
512
|
+
case "not": {
|
|
513
|
+
const subContext = {
|
|
514
|
+
errors: [],
|
|
515
|
+
path: [...context.path]
|
|
516
|
+
};
|
|
517
|
+
executeComparisonWithErrorCollection(target, value, subContext);
|
|
518
|
+
if (subContext.errors.length === 0) {
|
|
519
|
+
addError(
|
|
520
|
+
context,
|
|
521
|
+
`Expected negated condition to fail`,
|
|
522
|
+
target
|
|
523
|
+
);
|
|
524
|
+
}
|
|
525
|
+
break;
|
|
526
|
+
}
|
|
527
|
+
case "withNoExtraKeys":
|
|
528
|
+
case "withDeepNoExtraKeys":
|
|
529
|
+
case "noExtraDefinedKeys":
|
|
530
|
+
case "deepNoExtraDefinedKeys":
|
|
531
|
+
addError(
|
|
532
|
+
context,
|
|
533
|
+
`Complex validation not supported in error collection mode yet`,
|
|
534
|
+
target
|
|
535
|
+
);
|
|
536
|
+
break;
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
function executeComparisonWithKeyContextAndErrorCollection(target, comp, keyExists, context) {
|
|
540
|
+
const [type, value] = comp;
|
|
541
|
+
if (type === "keyNotBePresent") {
|
|
542
|
+
if (keyExists) {
|
|
543
|
+
addError(context, `Key should not be present`, target);
|
|
544
|
+
}
|
|
545
|
+
return;
|
|
546
|
+
}
|
|
547
|
+
if (type === "any") {
|
|
548
|
+
for (const childComp of value) {
|
|
549
|
+
const subContext = {
|
|
550
|
+
errors: [],
|
|
551
|
+
path: [...context.path]
|
|
552
|
+
};
|
|
553
|
+
executeComparisonWithKeyContextAndErrorCollection(
|
|
554
|
+
target,
|
|
555
|
+
childComp,
|
|
556
|
+
keyExists,
|
|
557
|
+
subContext
|
|
558
|
+
);
|
|
559
|
+
if (subContext.errors.length === 0) {
|
|
560
|
+
return;
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
addError(
|
|
564
|
+
context,
|
|
565
|
+
`None of the alternative comparisons matched`,
|
|
566
|
+
target,
|
|
567
|
+
"any alternative match"
|
|
568
|
+
);
|
|
569
|
+
return;
|
|
570
|
+
}
|
|
571
|
+
if (type === "not") {
|
|
572
|
+
const subContext = {
|
|
573
|
+
errors: [],
|
|
574
|
+
path: [...context.path]
|
|
575
|
+
};
|
|
576
|
+
executeComparisonWithKeyContextAndErrorCollection(
|
|
577
|
+
target,
|
|
578
|
+
value,
|
|
579
|
+
keyExists,
|
|
580
|
+
subContext
|
|
581
|
+
);
|
|
582
|
+
if (subContext.errors.length === 0) {
|
|
583
|
+
addError(
|
|
584
|
+
context,
|
|
585
|
+
`Expected negated condition to fail`,
|
|
586
|
+
target,
|
|
587
|
+
"negated condition"
|
|
588
|
+
);
|
|
589
|
+
}
|
|
590
|
+
return;
|
|
591
|
+
}
|
|
592
|
+
executeComparisonWithErrorCollection(target, comp, context);
|
|
593
|
+
}
|
|
594
|
+
function partialEqualWithErrorCollection(target, sub, context) {
|
|
595
|
+
if (sub === target) return;
|
|
596
|
+
if (sub && typeof sub === "object" && "~sc" in sub) {
|
|
597
|
+
executeComparisonWithErrorCollection(target, sub["~sc"], context);
|
|
598
|
+
return;
|
|
599
|
+
}
|
|
600
|
+
if (sub && target && sub.constructor === target.constructor) {
|
|
601
|
+
const ctor = sub.constructor;
|
|
602
|
+
if (ctor === Date) {
|
|
603
|
+
if (sub.getTime() !== target.getTime()) {
|
|
604
|
+
addError(context, `Date mismatch`, target, sub);
|
|
605
|
+
}
|
|
606
|
+
return;
|
|
607
|
+
}
|
|
608
|
+
if (ctor === RegExp) {
|
|
609
|
+
if (sub.toString() !== target.toString()) {
|
|
610
|
+
addError(context, `RegExp mismatch`, target, sub);
|
|
611
|
+
}
|
|
612
|
+
return;
|
|
613
|
+
}
|
|
614
|
+
if (ctor === Array) {
|
|
615
|
+
if (sub.length > target.length) {
|
|
616
|
+
addError(
|
|
617
|
+
context,
|
|
618
|
+
`Array too short: expected at least ${sub.length} elements, got ${target.length}`,
|
|
619
|
+
target,
|
|
620
|
+
sub
|
|
621
|
+
);
|
|
622
|
+
return;
|
|
623
|
+
}
|
|
624
|
+
for (let i = 0; i < sub.length; i++) {
|
|
625
|
+
context.path.push(`[${i}]`);
|
|
626
|
+
partialEqualWithErrorCollection(target[i], sub[i], context);
|
|
627
|
+
context.path.pop();
|
|
628
|
+
}
|
|
629
|
+
return;
|
|
630
|
+
}
|
|
631
|
+
if (ctor === Set) {
|
|
632
|
+
if (sub.size > target.size) {
|
|
633
|
+
addError(
|
|
634
|
+
context,
|
|
635
|
+
`Set too small: expected at least ${sub.size} elements, got ${target.size}`,
|
|
636
|
+
target,
|
|
637
|
+
sub
|
|
638
|
+
);
|
|
639
|
+
return;
|
|
640
|
+
}
|
|
641
|
+
for (const value of sub) {
|
|
642
|
+
let found = false;
|
|
643
|
+
if (value && typeof value === "object") {
|
|
644
|
+
found = !!find(target, value);
|
|
645
|
+
} else {
|
|
646
|
+
found = target.has(value);
|
|
647
|
+
}
|
|
648
|
+
if (!found) {
|
|
649
|
+
addError(context, `Set missing value`, target, value);
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
return;
|
|
653
|
+
}
|
|
654
|
+
if (ctor === Map) {
|
|
655
|
+
if (sub.size > target.size) {
|
|
656
|
+
addError(
|
|
657
|
+
context,
|
|
658
|
+
`Map too small: expected at least ${sub.size} entries, got ${target.size}`,
|
|
659
|
+
target,
|
|
660
|
+
sub
|
|
661
|
+
);
|
|
662
|
+
return;
|
|
663
|
+
}
|
|
664
|
+
for (const [key, value] of sub) {
|
|
665
|
+
let targetKey = key;
|
|
666
|
+
if (key && typeof key === "object") {
|
|
667
|
+
targetKey = find(target, key);
|
|
668
|
+
if (!targetKey) {
|
|
669
|
+
addError(context, `Map missing key`, target, key);
|
|
670
|
+
continue;
|
|
671
|
+
}
|
|
672
|
+
}
|
|
673
|
+
if (!target.has(targetKey)) {
|
|
674
|
+
addError(context, `Map missing key`, target, key);
|
|
675
|
+
continue;
|
|
676
|
+
}
|
|
677
|
+
context.path.push(`[${key}]`);
|
|
678
|
+
partialEqualWithErrorCollection(target.get(targetKey), value, context);
|
|
679
|
+
context.path.pop();
|
|
680
|
+
}
|
|
681
|
+
return;
|
|
682
|
+
}
|
|
683
|
+
if (!ctor || typeof sub === "object") {
|
|
684
|
+
for (const key in sub) {
|
|
685
|
+
if (has.call(sub, key)) {
|
|
686
|
+
const subValue = sub[key];
|
|
687
|
+
context.path.push(key);
|
|
688
|
+
if (subValue && typeof subValue === "object" && "~sc" in subValue && subValue["~sc"][0] === "keyNotBePresent") {
|
|
689
|
+
if (has.call(target, key)) {
|
|
690
|
+
addError(
|
|
691
|
+
context,
|
|
692
|
+
`Key should not be present`,
|
|
693
|
+
target[key],
|
|
694
|
+
"key not present"
|
|
695
|
+
);
|
|
696
|
+
}
|
|
697
|
+
} else if (subValue && typeof subValue === "object" && "~sc" in subValue && subValue["~sc"][0] === "any") {
|
|
698
|
+
const targetHasKey = has.call(target, key);
|
|
699
|
+
const targetValue = targetHasKey ? target[key] : void 0;
|
|
700
|
+
executeComparisonWithKeyContextAndErrorCollection(
|
|
701
|
+
targetValue,
|
|
702
|
+
subValue["~sc"],
|
|
703
|
+
targetHasKey,
|
|
704
|
+
context
|
|
705
|
+
);
|
|
706
|
+
} else {
|
|
707
|
+
if (!has.call(target, key)) {
|
|
708
|
+
addError(context, `Missing property`, void 0, subValue);
|
|
709
|
+
} else {
|
|
710
|
+
partialEqualWithErrorCollection(target[key], subValue, context);
|
|
711
|
+
}
|
|
712
|
+
}
|
|
713
|
+
context.path.pop();
|
|
714
|
+
}
|
|
715
|
+
}
|
|
716
|
+
return;
|
|
717
|
+
}
|
|
718
|
+
}
|
|
719
|
+
if (sub !== sub && target !== target) {
|
|
720
|
+
return;
|
|
721
|
+
}
|
|
722
|
+
addError(context, `Value mismatch`, target, sub);
|
|
723
|
+
}
|
|
724
|
+
function partialEqual(target, sub, returnErrors) {
|
|
725
|
+
if (returnErrors === true) {
|
|
726
|
+
const context = {
|
|
727
|
+
errors: [],
|
|
728
|
+
path: []
|
|
729
|
+
};
|
|
730
|
+
partialEqualWithErrorCollection(target, sub, context);
|
|
731
|
+
if (context.errors.length === 0) {
|
|
732
|
+
return ok(void 0);
|
|
733
|
+
} else {
|
|
734
|
+
return err(context.errors);
|
|
735
|
+
}
|
|
736
|
+
}
|
|
306
737
|
if (sub === target) return true;
|
|
307
738
|
if (sub && typeof sub === "object" && "~sc" in sub) {
|
|
308
739
|
return executeComparison(target, sub["~sc"]);
|