@ls-stack/utils 3.49.1 → 3.50.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 +266 -0
- package/dist/partialEqual.d.cts +28 -2
- package/dist/partialEqual.d.ts +28 -2
- package/dist/partialEqual.js +266 -0
- package/package.json +1 -1
package/dist/partialEqual.cjs
CHANGED
|
@@ -63,6 +63,18 @@ var match = {
|
|
|
63
63
|
isLessThanOrEqual: (value) => createComparison(["numIsLessThanOrEqual", value]),
|
|
64
64
|
isInRange: (value) => createComparison(["numIsInRange", value])
|
|
65
65
|
},
|
|
66
|
+
array: {
|
|
67
|
+
contains: (elements) => createComparison(["arrayContains", elements]),
|
|
68
|
+
containsInOrder: (elements) => createComparison(["arrayContainsInOrder", elements]),
|
|
69
|
+
startsWith: (elements) => createComparison(["arrayStartsWith", elements]),
|
|
70
|
+
endsWith: (elements) => createComparison(["arrayEndsWith", elements]),
|
|
71
|
+
length: (n) => createComparison(["arrayLength", n]),
|
|
72
|
+
minLength: (n) => createComparison(["arrayMinLength", n]),
|
|
73
|
+
maxLength: (n) => createComparison(["arrayMaxLength", n]),
|
|
74
|
+
includes: (element) => createComparison(["arrayIncludes", element]),
|
|
75
|
+
every: (matcher) => createComparison(["arrayEvery", matcher["~sc"]]),
|
|
76
|
+
some: (matcher) => createComparison(["arraySome", matcher["~sc"]])
|
|
77
|
+
},
|
|
66
78
|
jsonString: {
|
|
67
79
|
hasPartial: (value) => createComparison(["jsonStringHasPartial", value])
|
|
68
80
|
},
|
|
@@ -112,6 +124,18 @@ var match = {
|
|
|
112
124
|
isLessThanOrEqual: (value) => createComparison(["not", ["numIsLessThanOrEqual", value]]),
|
|
113
125
|
isInRange: (value) => createComparison(["not", ["numIsInRange", value]])
|
|
114
126
|
},
|
|
127
|
+
array: {
|
|
128
|
+
contains: (elements) => createComparison(["not", ["arrayContains", elements]]),
|
|
129
|
+
containsInOrder: (elements) => createComparison(["not", ["arrayContainsInOrder", elements]]),
|
|
130
|
+
startsWith: (elements) => createComparison(["not", ["arrayStartsWith", elements]]),
|
|
131
|
+
endsWith: (elements) => createComparison(["not", ["arrayEndsWith", elements]]),
|
|
132
|
+
length: (n) => createComparison(["not", ["arrayLength", n]]),
|
|
133
|
+
minLength: (n) => createComparison(["not", ["arrayMinLength", n]]),
|
|
134
|
+
maxLength: (n) => createComparison(["not", ["arrayMaxLength", n]]),
|
|
135
|
+
includes: (element) => createComparison(["not", ["arrayIncludes", element]]),
|
|
136
|
+
every: (matcher) => createComparison(["not", ["arrayEvery", matcher["~sc"]]]),
|
|
137
|
+
some: (matcher) => createComparison(["not", ["arraySome", matcher["~sc"]]])
|
|
138
|
+
},
|
|
115
139
|
jsonString: {
|
|
116
140
|
hasPartial: (value) => createComparison(["not", ["jsonStringHasPartial", value]])
|
|
117
141
|
},
|
|
@@ -396,6 +420,248 @@ function executeComparison(target, comparison, context) {
|
|
|
396
420
|
return checkNoExtraDefinedKeys(target, value, context, false);
|
|
397
421
|
case "deepNoExtraDefinedKeys":
|
|
398
422
|
return checkNoExtraDefinedKeys(target, value, context, true);
|
|
423
|
+
case "arrayContains": {
|
|
424
|
+
if (!Array.isArray(target)) {
|
|
425
|
+
addError(context, {
|
|
426
|
+
message: "Expected array",
|
|
427
|
+
received: target
|
|
428
|
+
});
|
|
429
|
+
return false;
|
|
430
|
+
}
|
|
431
|
+
for (const element of value) {
|
|
432
|
+
let found = false;
|
|
433
|
+
for (const targetElement of target) {
|
|
434
|
+
const tempContext = {
|
|
435
|
+
errors: [],
|
|
436
|
+
path: context.path
|
|
437
|
+
};
|
|
438
|
+
if (partialEqualInternal(targetElement, element, tempContext)) {
|
|
439
|
+
found = true;
|
|
440
|
+
break;
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
if (!found) {
|
|
444
|
+
addError(context, {
|
|
445
|
+
message: "Array does not contain expected element",
|
|
446
|
+
received: target,
|
|
447
|
+
expected: element
|
|
448
|
+
});
|
|
449
|
+
return false;
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
return true;
|
|
453
|
+
}
|
|
454
|
+
case "arrayContainsInOrder": {
|
|
455
|
+
if (!Array.isArray(target)) {
|
|
456
|
+
addError(context, {
|
|
457
|
+
message: "Expected array",
|
|
458
|
+
received: target
|
|
459
|
+
});
|
|
460
|
+
return false;
|
|
461
|
+
}
|
|
462
|
+
let targetIndex = 0;
|
|
463
|
+
for (const element of value) {
|
|
464
|
+
let found = false;
|
|
465
|
+
for (let i = targetIndex; i < target.length; i++) {
|
|
466
|
+
const tempContext = {
|
|
467
|
+
errors: [],
|
|
468
|
+
path: context.path
|
|
469
|
+
};
|
|
470
|
+
if (partialEqualInternal(target[i], element, tempContext)) {
|
|
471
|
+
targetIndex = i + 1;
|
|
472
|
+
found = true;
|
|
473
|
+
break;
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
if (!found) {
|
|
477
|
+
addError(context, {
|
|
478
|
+
message: "Array does not contain expected elements in order",
|
|
479
|
+
received: target,
|
|
480
|
+
expected: value
|
|
481
|
+
});
|
|
482
|
+
return false;
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
return true;
|
|
486
|
+
}
|
|
487
|
+
case "arrayStartsWith": {
|
|
488
|
+
if (!Array.isArray(target)) {
|
|
489
|
+
addError(context, {
|
|
490
|
+
message: "Expected array",
|
|
491
|
+
received: target
|
|
492
|
+
});
|
|
493
|
+
return false;
|
|
494
|
+
}
|
|
495
|
+
if (target.length < value.length) {
|
|
496
|
+
addError(context, {
|
|
497
|
+
message: `Array too short: expected to start with ${value.length} elements, got ${target.length}`,
|
|
498
|
+
received: target,
|
|
499
|
+
expected: value
|
|
500
|
+
});
|
|
501
|
+
return false;
|
|
502
|
+
}
|
|
503
|
+
let allMatch = true;
|
|
504
|
+
for (let i = 0; i < value.length; i++) {
|
|
505
|
+
const oldPath = context.path;
|
|
506
|
+
context.path = [...oldPath, `[${i}]`];
|
|
507
|
+
const result = partialEqualInternal(target[i], value[i], context);
|
|
508
|
+
context.path = oldPath;
|
|
509
|
+
if (!result) allMatch = false;
|
|
510
|
+
}
|
|
511
|
+
return allMatch;
|
|
512
|
+
}
|
|
513
|
+
case "arrayEndsWith": {
|
|
514
|
+
if (!Array.isArray(target)) {
|
|
515
|
+
addError(context, {
|
|
516
|
+
message: "Expected array",
|
|
517
|
+
received: target
|
|
518
|
+
});
|
|
519
|
+
return false;
|
|
520
|
+
}
|
|
521
|
+
if (target.length < value.length) {
|
|
522
|
+
addError(context, {
|
|
523
|
+
message: `Array too short: expected to end with ${value.length} elements, got ${target.length}`,
|
|
524
|
+
received: target,
|
|
525
|
+
expected: value
|
|
526
|
+
});
|
|
527
|
+
return false;
|
|
528
|
+
}
|
|
529
|
+
let allMatch = true;
|
|
530
|
+
const startIndex = target.length - value.length;
|
|
531
|
+
for (let i = 0; i < value.length; i++) {
|
|
532
|
+
const oldPath = context.path;
|
|
533
|
+
context.path = [...oldPath, `[${startIndex + i}]`];
|
|
534
|
+
const result = partialEqualInternal(
|
|
535
|
+
target[startIndex + i],
|
|
536
|
+
value[i],
|
|
537
|
+
context
|
|
538
|
+
);
|
|
539
|
+
context.path = oldPath;
|
|
540
|
+
if (!result) allMatch = false;
|
|
541
|
+
}
|
|
542
|
+
return allMatch;
|
|
543
|
+
}
|
|
544
|
+
case "arrayLength": {
|
|
545
|
+
if (!Array.isArray(target)) {
|
|
546
|
+
addError(context, {
|
|
547
|
+
message: "Expected array",
|
|
548
|
+
received: target
|
|
549
|
+
});
|
|
550
|
+
return false;
|
|
551
|
+
}
|
|
552
|
+
if (target.length !== value) {
|
|
553
|
+
addError(context, {
|
|
554
|
+
message: `Expected array length ${value}, got ${target.length}`,
|
|
555
|
+
received: target
|
|
556
|
+
});
|
|
557
|
+
return false;
|
|
558
|
+
}
|
|
559
|
+
return true;
|
|
560
|
+
}
|
|
561
|
+
case "arrayMinLength": {
|
|
562
|
+
if (!Array.isArray(target)) {
|
|
563
|
+
addError(context, {
|
|
564
|
+
message: "Expected array",
|
|
565
|
+
received: target
|
|
566
|
+
});
|
|
567
|
+
return false;
|
|
568
|
+
}
|
|
569
|
+
if (target.length < value) {
|
|
570
|
+
addError(context, {
|
|
571
|
+
message: `Expected array with at least ${value} elements, got ${target.length}`,
|
|
572
|
+
received: target
|
|
573
|
+
});
|
|
574
|
+
return false;
|
|
575
|
+
}
|
|
576
|
+
return true;
|
|
577
|
+
}
|
|
578
|
+
case "arrayMaxLength": {
|
|
579
|
+
if (!Array.isArray(target)) {
|
|
580
|
+
addError(context, {
|
|
581
|
+
message: "Expected array",
|
|
582
|
+
received: target
|
|
583
|
+
});
|
|
584
|
+
return false;
|
|
585
|
+
}
|
|
586
|
+
if (target.length > value) {
|
|
587
|
+
addError(context, {
|
|
588
|
+
message: `Expected array with at most ${value} elements, got ${target.length}`,
|
|
589
|
+
received: target
|
|
590
|
+
});
|
|
591
|
+
return false;
|
|
592
|
+
}
|
|
593
|
+
return true;
|
|
594
|
+
}
|
|
595
|
+
case "arrayIncludes": {
|
|
596
|
+
if (!Array.isArray(target)) {
|
|
597
|
+
addError(context, {
|
|
598
|
+
message: "Expected array",
|
|
599
|
+
received: target
|
|
600
|
+
});
|
|
601
|
+
return false;
|
|
602
|
+
}
|
|
603
|
+
let found = false;
|
|
604
|
+
for (const targetElement of target) {
|
|
605
|
+
const tempContext = {
|
|
606
|
+
errors: [],
|
|
607
|
+
path: context.path
|
|
608
|
+
};
|
|
609
|
+
if (partialEqualInternal(targetElement, value, tempContext)) {
|
|
610
|
+
found = true;
|
|
611
|
+
break;
|
|
612
|
+
}
|
|
613
|
+
}
|
|
614
|
+
if (!found) {
|
|
615
|
+
addError(context, {
|
|
616
|
+
message: "Array does not include expected element",
|
|
617
|
+
received: target,
|
|
618
|
+
expected: value
|
|
619
|
+
});
|
|
620
|
+
return false;
|
|
621
|
+
}
|
|
622
|
+
return true;
|
|
623
|
+
}
|
|
624
|
+
case "arrayEvery": {
|
|
625
|
+
if (!Array.isArray(target)) {
|
|
626
|
+
addError(context, {
|
|
627
|
+
message: "Expected array",
|
|
628
|
+
received: target
|
|
629
|
+
});
|
|
630
|
+
return false;
|
|
631
|
+
}
|
|
632
|
+
let allMatch = true;
|
|
633
|
+
for (let i = 0; i < target.length; i++) {
|
|
634
|
+
const oldPath = context.path;
|
|
635
|
+
context.path = [...oldPath, `[${i}]`];
|
|
636
|
+
const result = executeComparison(target[i], value, context);
|
|
637
|
+
context.path = oldPath;
|
|
638
|
+
if (!result) allMatch = false;
|
|
639
|
+
}
|
|
640
|
+
return allMatch;
|
|
641
|
+
}
|
|
642
|
+
case "arraySome": {
|
|
643
|
+
if (!Array.isArray(target)) {
|
|
644
|
+
addError(context, {
|
|
645
|
+
message: "Expected array",
|
|
646
|
+
received: target
|
|
647
|
+
});
|
|
648
|
+
return false;
|
|
649
|
+
}
|
|
650
|
+
for (let i = 0; i < target.length; i++) {
|
|
651
|
+
const tempContext = {
|
|
652
|
+
errors: [],
|
|
653
|
+
path: [...context.path, `[${i}]`]
|
|
654
|
+
};
|
|
655
|
+
if (executeComparison(target[i], value, tempContext)) {
|
|
656
|
+
return true;
|
|
657
|
+
}
|
|
658
|
+
}
|
|
659
|
+
addError(context, {
|
|
660
|
+
message: "No array element matches the condition",
|
|
661
|
+
received: target
|
|
662
|
+
});
|
|
663
|
+
return false;
|
|
664
|
+
}
|
|
399
665
|
default:
|
|
400
666
|
throw exhaustiveCheck(type);
|
|
401
667
|
}
|
package/dist/partialEqual.d.cts
CHANGED
|
@@ -3,7 +3,7 @@ import { Result } from 't-result';
|
|
|
3
3
|
type ComparisonsType = [type: 'strStartsWith', value: string] | [type: 'strEndsWith', value: string] | [
|
|
4
4
|
type: 'hasType',
|
|
5
5
|
value: 'string' | 'number' | 'boolean' | 'object' | 'array' | 'function'
|
|
6
|
-
] | [type: 'strContains', value: string] | [type: 'strMatchesRegex', value: RegExp] | [type: 'deepEqual', value: any] | [type: 'numIsGreaterThan', value: number] | [type: 'numIsGreaterThanOrEqual', value: number] | [type: 'numIsLessThan', value: number] | [type: 'numIsLessThanOrEqual', value: number] | [type: 'numIsInRange', value: [number, number]] | [type: 'jsonStringHasPartial', value: any] | [type: 'partialEqual', value: any] | [type: 'custom', value: (target: unknown) => boolean | {
|
|
6
|
+
] | [type: 'strContains', value: string] | [type: 'strMatchesRegex', value: RegExp] | [type: 'deepEqual', value: any] | [type: 'numIsGreaterThan', value: number] | [type: 'numIsGreaterThanOrEqual', value: number] | [type: 'numIsLessThan', value: number] | [type: 'numIsLessThanOrEqual', value: number] | [type: 'numIsInRange', value: [number, number]] | [type: 'arrayContains', value: any[]] | [type: 'arrayContainsInOrder', value: any[]] | [type: 'arrayStartsWith', value: any[]] | [type: 'arrayEndsWith', value: any[]] | [type: 'arrayLength', value: number] | [type: 'arrayMinLength', value: number] | [type: 'arrayMaxLength', value: number] | [type: 'arrayIncludes', value: any] | [type: 'arrayEvery', value: ComparisonsType] | [type: 'arraySome', value: ComparisonsType] | [type: 'jsonStringHasPartial', value: any] | [type: 'partialEqual', value: any] | [type: 'custom', value: (target: unknown) => boolean | {
|
|
7
7
|
error: string;
|
|
8
8
|
}] | [type: 'isInstanceOf', value: new (...args: any[]) => any] | [type: 'keyNotBePresent', value: null] | [type: 'not', value: ComparisonsType] | [type: 'any', value: ComparisonsType[]] | [type: 'all', value: ComparisonsType[]] | [type: 'withNoExtraKeys', partialShape: any] | [type: 'withDeepNoExtraKeys', partialShape: any] | [type: 'noExtraDefinedKeys', partialShape: any] | [type: 'deepNoExtraDefinedKeys', partialShape: any];
|
|
9
9
|
type Comparison = {
|
|
@@ -36,6 +36,18 @@ type BaseMatch = {
|
|
|
36
36
|
isLessThanOrEqual: (value: number) => Comparison;
|
|
37
37
|
isInRange: (value: [number, number]) => Comparison;
|
|
38
38
|
};
|
|
39
|
+
array: {
|
|
40
|
+
contains: (elements: any[]) => Comparison;
|
|
41
|
+
containsInOrder: (elements: any[]) => Comparison;
|
|
42
|
+
startsWith: (elements: any[]) => Comparison;
|
|
43
|
+
endsWith: (elements: any[]) => Comparison;
|
|
44
|
+
length: (n: number) => Comparison;
|
|
45
|
+
minLength: (n: number) => Comparison;
|
|
46
|
+
maxLength: (n: number) => Comparison;
|
|
47
|
+
includes: (element: any) => Comparison;
|
|
48
|
+
every: (matcher: Comparison) => Comparison;
|
|
49
|
+
some: (matcher: Comparison) => Comparison;
|
|
50
|
+
};
|
|
39
51
|
jsonString: {
|
|
40
52
|
hasPartial: (value: any) => Comparison;
|
|
41
53
|
};
|
|
@@ -66,7 +78,21 @@ type PartialError = {
|
|
|
66
78
|
* @example
|
|
67
79
|
* // Basic partial matching
|
|
68
80
|
* partialEqual({ a: 1, b: 2 }, { a: 1 }); // true - sub is subset of target
|
|
69
|
-
* partialEqual([1, 2, 3], [1, 2]); // true - sub array is prefix of target
|
|
81
|
+
* partialEqual([1, 2, 3], [1, 2]); // true - sub array is prefix of target (default behavior)
|
|
82
|
+
*
|
|
83
|
+
* // Array matching (default behavior: prefix matching)
|
|
84
|
+
* partialEqual([1, 2, 3, 4], [1, 2]); // true - checks first 2 elements
|
|
85
|
+
* partialEqual([1, 3, 4], [1, 2]); // false - second element doesn't match
|
|
86
|
+
*
|
|
87
|
+
* // Advanced array matchers for flexible matching
|
|
88
|
+
* partialEqual([1, 2, 3, 4, 5], match.array.contains([3, 1])); // true - contains elements anywhere
|
|
89
|
+
* partialEqual([1, 2, 3, 4, 5], match.array.containsInOrder([2, 4])); // true - contains in order (non-consecutive)
|
|
90
|
+
* partialEqual([1, 2, 3], match.array.startsWith([1, 2])); // true - explicit prefix matching
|
|
91
|
+
* partialEqual([1, 2, 3], match.array.endsWith([2, 3])); // true - suffix matching
|
|
92
|
+
* partialEqual([1, 2, 3], match.array.length(3)); // true - exact length
|
|
93
|
+
* partialEqual([1, 2, 3], match.array.includes(2)); // true - includes element
|
|
94
|
+
* partialEqual([10, 20, 30], match.array.every(match.num.isGreaterThan(5))); // true - all elements match
|
|
95
|
+
* partialEqual([1, 10, 3], match.array.some(match.num.isGreaterThan(8))); // true - at least one matches
|
|
70
96
|
*
|
|
71
97
|
* // Special comparisons
|
|
72
98
|
* partialEqual('hello world', match.str.contains('world')); // true
|
package/dist/partialEqual.d.ts
CHANGED
|
@@ -3,7 +3,7 @@ import { Result } from 't-result';
|
|
|
3
3
|
type ComparisonsType = [type: 'strStartsWith', value: string] | [type: 'strEndsWith', value: string] | [
|
|
4
4
|
type: 'hasType',
|
|
5
5
|
value: 'string' | 'number' | 'boolean' | 'object' | 'array' | 'function'
|
|
6
|
-
] | [type: 'strContains', value: string] | [type: 'strMatchesRegex', value: RegExp] | [type: 'deepEqual', value: any] | [type: 'numIsGreaterThan', value: number] | [type: 'numIsGreaterThanOrEqual', value: number] | [type: 'numIsLessThan', value: number] | [type: 'numIsLessThanOrEqual', value: number] | [type: 'numIsInRange', value: [number, number]] | [type: 'jsonStringHasPartial', value: any] | [type: 'partialEqual', value: any] | [type: 'custom', value: (target: unknown) => boolean | {
|
|
6
|
+
] | [type: 'strContains', value: string] | [type: 'strMatchesRegex', value: RegExp] | [type: 'deepEqual', value: any] | [type: 'numIsGreaterThan', value: number] | [type: 'numIsGreaterThanOrEqual', value: number] | [type: 'numIsLessThan', value: number] | [type: 'numIsLessThanOrEqual', value: number] | [type: 'numIsInRange', value: [number, number]] | [type: 'arrayContains', value: any[]] | [type: 'arrayContainsInOrder', value: any[]] | [type: 'arrayStartsWith', value: any[]] | [type: 'arrayEndsWith', value: any[]] | [type: 'arrayLength', value: number] | [type: 'arrayMinLength', value: number] | [type: 'arrayMaxLength', value: number] | [type: 'arrayIncludes', value: any] | [type: 'arrayEvery', value: ComparisonsType] | [type: 'arraySome', value: ComparisonsType] | [type: 'jsonStringHasPartial', value: any] | [type: 'partialEqual', value: any] | [type: 'custom', value: (target: unknown) => boolean | {
|
|
7
7
|
error: string;
|
|
8
8
|
}] | [type: 'isInstanceOf', value: new (...args: any[]) => any] | [type: 'keyNotBePresent', value: null] | [type: 'not', value: ComparisonsType] | [type: 'any', value: ComparisonsType[]] | [type: 'all', value: ComparisonsType[]] | [type: 'withNoExtraKeys', partialShape: any] | [type: 'withDeepNoExtraKeys', partialShape: any] | [type: 'noExtraDefinedKeys', partialShape: any] | [type: 'deepNoExtraDefinedKeys', partialShape: any];
|
|
9
9
|
type Comparison = {
|
|
@@ -36,6 +36,18 @@ type BaseMatch = {
|
|
|
36
36
|
isLessThanOrEqual: (value: number) => Comparison;
|
|
37
37
|
isInRange: (value: [number, number]) => Comparison;
|
|
38
38
|
};
|
|
39
|
+
array: {
|
|
40
|
+
contains: (elements: any[]) => Comparison;
|
|
41
|
+
containsInOrder: (elements: any[]) => Comparison;
|
|
42
|
+
startsWith: (elements: any[]) => Comparison;
|
|
43
|
+
endsWith: (elements: any[]) => Comparison;
|
|
44
|
+
length: (n: number) => Comparison;
|
|
45
|
+
minLength: (n: number) => Comparison;
|
|
46
|
+
maxLength: (n: number) => Comparison;
|
|
47
|
+
includes: (element: any) => Comparison;
|
|
48
|
+
every: (matcher: Comparison) => Comparison;
|
|
49
|
+
some: (matcher: Comparison) => Comparison;
|
|
50
|
+
};
|
|
39
51
|
jsonString: {
|
|
40
52
|
hasPartial: (value: any) => Comparison;
|
|
41
53
|
};
|
|
@@ -66,7 +78,21 @@ type PartialError = {
|
|
|
66
78
|
* @example
|
|
67
79
|
* // Basic partial matching
|
|
68
80
|
* partialEqual({ a: 1, b: 2 }, { a: 1 }); // true - sub is subset of target
|
|
69
|
-
* partialEqual([1, 2, 3], [1, 2]); // true - sub array is prefix of target
|
|
81
|
+
* partialEqual([1, 2, 3], [1, 2]); // true - sub array is prefix of target (default behavior)
|
|
82
|
+
*
|
|
83
|
+
* // Array matching (default behavior: prefix matching)
|
|
84
|
+
* partialEqual([1, 2, 3, 4], [1, 2]); // true - checks first 2 elements
|
|
85
|
+
* partialEqual([1, 3, 4], [1, 2]); // false - second element doesn't match
|
|
86
|
+
*
|
|
87
|
+
* // Advanced array matchers for flexible matching
|
|
88
|
+
* partialEqual([1, 2, 3, 4, 5], match.array.contains([3, 1])); // true - contains elements anywhere
|
|
89
|
+
* partialEqual([1, 2, 3, 4, 5], match.array.containsInOrder([2, 4])); // true - contains in order (non-consecutive)
|
|
90
|
+
* partialEqual([1, 2, 3], match.array.startsWith([1, 2])); // true - explicit prefix matching
|
|
91
|
+
* partialEqual([1, 2, 3], match.array.endsWith([2, 3])); // true - suffix matching
|
|
92
|
+
* partialEqual([1, 2, 3], match.array.length(3)); // true - exact length
|
|
93
|
+
* partialEqual([1, 2, 3], match.array.includes(2)); // true - includes element
|
|
94
|
+
* partialEqual([10, 20, 30], match.array.every(match.num.isGreaterThan(5))); // true - all elements match
|
|
95
|
+
* partialEqual([1, 10, 3], match.array.some(match.num.isGreaterThan(8))); // true - at least one matches
|
|
70
96
|
*
|
|
71
97
|
* // Special comparisons
|
|
72
98
|
* partialEqual('hello world', match.str.contains('world')); // true
|
package/dist/partialEqual.js
CHANGED
|
@@ -36,6 +36,18 @@ var match = {
|
|
|
36
36
|
isLessThanOrEqual: (value) => createComparison(["numIsLessThanOrEqual", value]),
|
|
37
37
|
isInRange: (value) => createComparison(["numIsInRange", value])
|
|
38
38
|
},
|
|
39
|
+
array: {
|
|
40
|
+
contains: (elements) => createComparison(["arrayContains", elements]),
|
|
41
|
+
containsInOrder: (elements) => createComparison(["arrayContainsInOrder", elements]),
|
|
42
|
+
startsWith: (elements) => createComparison(["arrayStartsWith", elements]),
|
|
43
|
+
endsWith: (elements) => createComparison(["arrayEndsWith", elements]),
|
|
44
|
+
length: (n) => createComparison(["arrayLength", n]),
|
|
45
|
+
minLength: (n) => createComparison(["arrayMinLength", n]),
|
|
46
|
+
maxLength: (n) => createComparison(["arrayMaxLength", n]),
|
|
47
|
+
includes: (element) => createComparison(["arrayIncludes", element]),
|
|
48
|
+
every: (matcher) => createComparison(["arrayEvery", matcher["~sc"]]),
|
|
49
|
+
some: (matcher) => createComparison(["arraySome", matcher["~sc"]])
|
|
50
|
+
},
|
|
39
51
|
jsonString: {
|
|
40
52
|
hasPartial: (value) => createComparison(["jsonStringHasPartial", value])
|
|
41
53
|
},
|
|
@@ -85,6 +97,18 @@ var match = {
|
|
|
85
97
|
isLessThanOrEqual: (value) => createComparison(["not", ["numIsLessThanOrEqual", value]]),
|
|
86
98
|
isInRange: (value) => createComparison(["not", ["numIsInRange", value]])
|
|
87
99
|
},
|
|
100
|
+
array: {
|
|
101
|
+
contains: (elements) => createComparison(["not", ["arrayContains", elements]]),
|
|
102
|
+
containsInOrder: (elements) => createComparison(["not", ["arrayContainsInOrder", elements]]),
|
|
103
|
+
startsWith: (elements) => createComparison(["not", ["arrayStartsWith", elements]]),
|
|
104
|
+
endsWith: (elements) => createComparison(["not", ["arrayEndsWith", elements]]),
|
|
105
|
+
length: (n) => createComparison(["not", ["arrayLength", n]]),
|
|
106
|
+
minLength: (n) => createComparison(["not", ["arrayMinLength", n]]),
|
|
107
|
+
maxLength: (n) => createComparison(["not", ["arrayMaxLength", n]]),
|
|
108
|
+
includes: (element) => createComparison(["not", ["arrayIncludes", element]]),
|
|
109
|
+
every: (matcher) => createComparison(["not", ["arrayEvery", matcher["~sc"]]]),
|
|
110
|
+
some: (matcher) => createComparison(["not", ["arraySome", matcher["~sc"]]])
|
|
111
|
+
},
|
|
88
112
|
jsonString: {
|
|
89
113
|
hasPartial: (value) => createComparison(["not", ["jsonStringHasPartial", value]])
|
|
90
114
|
},
|
|
@@ -369,6 +393,248 @@ function executeComparison(target, comparison, context) {
|
|
|
369
393
|
return checkNoExtraDefinedKeys(target, value, context, false);
|
|
370
394
|
case "deepNoExtraDefinedKeys":
|
|
371
395
|
return checkNoExtraDefinedKeys(target, value, context, true);
|
|
396
|
+
case "arrayContains": {
|
|
397
|
+
if (!Array.isArray(target)) {
|
|
398
|
+
addError(context, {
|
|
399
|
+
message: "Expected array",
|
|
400
|
+
received: target
|
|
401
|
+
});
|
|
402
|
+
return false;
|
|
403
|
+
}
|
|
404
|
+
for (const element of value) {
|
|
405
|
+
let found = false;
|
|
406
|
+
for (const targetElement of target) {
|
|
407
|
+
const tempContext = {
|
|
408
|
+
errors: [],
|
|
409
|
+
path: context.path
|
|
410
|
+
};
|
|
411
|
+
if (partialEqualInternal(targetElement, element, tempContext)) {
|
|
412
|
+
found = true;
|
|
413
|
+
break;
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
if (!found) {
|
|
417
|
+
addError(context, {
|
|
418
|
+
message: "Array does not contain expected element",
|
|
419
|
+
received: target,
|
|
420
|
+
expected: element
|
|
421
|
+
});
|
|
422
|
+
return false;
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
return true;
|
|
426
|
+
}
|
|
427
|
+
case "arrayContainsInOrder": {
|
|
428
|
+
if (!Array.isArray(target)) {
|
|
429
|
+
addError(context, {
|
|
430
|
+
message: "Expected array",
|
|
431
|
+
received: target
|
|
432
|
+
});
|
|
433
|
+
return false;
|
|
434
|
+
}
|
|
435
|
+
let targetIndex = 0;
|
|
436
|
+
for (const element of value) {
|
|
437
|
+
let found = false;
|
|
438
|
+
for (let i = targetIndex; i < target.length; i++) {
|
|
439
|
+
const tempContext = {
|
|
440
|
+
errors: [],
|
|
441
|
+
path: context.path
|
|
442
|
+
};
|
|
443
|
+
if (partialEqualInternal(target[i], element, tempContext)) {
|
|
444
|
+
targetIndex = i + 1;
|
|
445
|
+
found = true;
|
|
446
|
+
break;
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
if (!found) {
|
|
450
|
+
addError(context, {
|
|
451
|
+
message: "Array does not contain expected elements in order",
|
|
452
|
+
received: target,
|
|
453
|
+
expected: value
|
|
454
|
+
});
|
|
455
|
+
return false;
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
return true;
|
|
459
|
+
}
|
|
460
|
+
case "arrayStartsWith": {
|
|
461
|
+
if (!Array.isArray(target)) {
|
|
462
|
+
addError(context, {
|
|
463
|
+
message: "Expected array",
|
|
464
|
+
received: target
|
|
465
|
+
});
|
|
466
|
+
return false;
|
|
467
|
+
}
|
|
468
|
+
if (target.length < value.length) {
|
|
469
|
+
addError(context, {
|
|
470
|
+
message: `Array too short: expected to start with ${value.length} elements, got ${target.length}`,
|
|
471
|
+
received: target,
|
|
472
|
+
expected: value
|
|
473
|
+
});
|
|
474
|
+
return false;
|
|
475
|
+
}
|
|
476
|
+
let allMatch = true;
|
|
477
|
+
for (let i = 0; i < value.length; i++) {
|
|
478
|
+
const oldPath = context.path;
|
|
479
|
+
context.path = [...oldPath, `[${i}]`];
|
|
480
|
+
const result = partialEqualInternal(target[i], value[i], context);
|
|
481
|
+
context.path = oldPath;
|
|
482
|
+
if (!result) allMatch = false;
|
|
483
|
+
}
|
|
484
|
+
return allMatch;
|
|
485
|
+
}
|
|
486
|
+
case "arrayEndsWith": {
|
|
487
|
+
if (!Array.isArray(target)) {
|
|
488
|
+
addError(context, {
|
|
489
|
+
message: "Expected array",
|
|
490
|
+
received: target
|
|
491
|
+
});
|
|
492
|
+
return false;
|
|
493
|
+
}
|
|
494
|
+
if (target.length < value.length) {
|
|
495
|
+
addError(context, {
|
|
496
|
+
message: `Array too short: expected to end with ${value.length} elements, got ${target.length}`,
|
|
497
|
+
received: target,
|
|
498
|
+
expected: value
|
|
499
|
+
});
|
|
500
|
+
return false;
|
|
501
|
+
}
|
|
502
|
+
let allMatch = true;
|
|
503
|
+
const startIndex = target.length - value.length;
|
|
504
|
+
for (let i = 0; i < value.length; i++) {
|
|
505
|
+
const oldPath = context.path;
|
|
506
|
+
context.path = [...oldPath, `[${startIndex + i}]`];
|
|
507
|
+
const result = partialEqualInternal(
|
|
508
|
+
target[startIndex + i],
|
|
509
|
+
value[i],
|
|
510
|
+
context
|
|
511
|
+
);
|
|
512
|
+
context.path = oldPath;
|
|
513
|
+
if (!result) allMatch = false;
|
|
514
|
+
}
|
|
515
|
+
return allMatch;
|
|
516
|
+
}
|
|
517
|
+
case "arrayLength": {
|
|
518
|
+
if (!Array.isArray(target)) {
|
|
519
|
+
addError(context, {
|
|
520
|
+
message: "Expected array",
|
|
521
|
+
received: target
|
|
522
|
+
});
|
|
523
|
+
return false;
|
|
524
|
+
}
|
|
525
|
+
if (target.length !== value) {
|
|
526
|
+
addError(context, {
|
|
527
|
+
message: `Expected array length ${value}, got ${target.length}`,
|
|
528
|
+
received: target
|
|
529
|
+
});
|
|
530
|
+
return false;
|
|
531
|
+
}
|
|
532
|
+
return true;
|
|
533
|
+
}
|
|
534
|
+
case "arrayMinLength": {
|
|
535
|
+
if (!Array.isArray(target)) {
|
|
536
|
+
addError(context, {
|
|
537
|
+
message: "Expected array",
|
|
538
|
+
received: target
|
|
539
|
+
});
|
|
540
|
+
return false;
|
|
541
|
+
}
|
|
542
|
+
if (target.length < value) {
|
|
543
|
+
addError(context, {
|
|
544
|
+
message: `Expected array with at least ${value} elements, got ${target.length}`,
|
|
545
|
+
received: target
|
|
546
|
+
});
|
|
547
|
+
return false;
|
|
548
|
+
}
|
|
549
|
+
return true;
|
|
550
|
+
}
|
|
551
|
+
case "arrayMaxLength": {
|
|
552
|
+
if (!Array.isArray(target)) {
|
|
553
|
+
addError(context, {
|
|
554
|
+
message: "Expected array",
|
|
555
|
+
received: target
|
|
556
|
+
});
|
|
557
|
+
return false;
|
|
558
|
+
}
|
|
559
|
+
if (target.length > value) {
|
|
560
|
+
addError(context, {
|
|
561
|
+
message: `Expected array with at most ${value} elements, got ${target.length}`,
|
|
562
|
+
received: target
|
|
563
|
+
});
|
|
564
|
+
return false;
|
|
565
|
+
}
|
|
566
|
+
return true;
|
|
567
|
+
}
|
|
568
|
+
case "arrayIncludes": {
|
|
569
|
+
if (!Array.isArray(target)) {
|
|
570
|
+
addError(context, {
|
|
571
|
+
message: "Expected array",
|
|
572
|
+
received: target
|
|
573
|
+
});
|
|
574
|
+
return false;
|
|
575
|
+
}
|
|
576
|
+
let found = false;
|
|
577
|
+
for (const targetElement of target) {
|
|
578
|
+
const tempContext = {
|
|
579
|
+
errors: [],
|
|
580
|
+
path: context.path
|
|
581
|
+
};
|
|
582
|
+
if (partialEqualInternal(targetElement, value, tempContext)) {
|
|
583
|
+
found = true;
|
|
584
|
+
break;
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
if (!found) {
|
|
588
|
+
addError(context, {
|
|
589
|
+
message: "Array does not include expected element",
|
|
590
|
+
received: target,
|
|
591
|
+
expected: value
|
|
592
|
+
});
|
|
593
|
+
return false;
|
|
594
|
+
}
|
|
595
|
+
return true;
|
|
596
|
+
}
|
|
597
|
+
case "arrayEvery": {
|
|
598
|
+
if (!Array.isArray(target)) {
|
|
599
|
+
addError(context, {
|
|
600
|
+
message: "Expected array",
|
|
601
|
+
received: target
|
|
602
|
+
});
|
|
603
|
+
return false;
|
|
604
|
+
}
|
|
605
|
+
let allMatch = true;
|
|
606
|
+
for (let i = 0; i < target.length; i++) {
|
|
607
|
+
const oldPath = context.path;
|
|
608
|
+
context.path = [...oldPath, `[${i}]`];
|
|
609
|
+
const result = executeComparison(target[i], value, context);
|
|
610
|
+
context.path = oldPath;
|
|
611
|
+
if (!result) allMatch = false;
|
|
612
|
+
}
|
|
613
|
+
return allMatch;
|
|
614
|
+
}
|
|
615
|
+
case "arraySome": {
|
|
616
|
+
if (!Array.isArray(target)) {
|
|
617
|
+
addError(context, {
|
|
618
|
+
message: "Expected array",
|
|
619
|
+
received: target
|
|
620
|
+
});
|
|
621
|
+
return false;
|
|
622
|
+
}
|
|
623
|
+
for (let i = 0; i < target.length; i++) {
|
|
624
|
+
const tempContext = {
|
|
625
|
+
errors: [],
|
|
626
|
+
path: [...context.path, `[${i}]`]
|
|
627
|
+
};
|
|
628
|
+
if (executeComparison(target[i], value, tempContext)) {
|
|
629
|
+
return true;
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
addError(context, {
|
|
633
|
+
message: "No array element matches the condition",
|
|
634
|
+
received: target
|
|
635
|
+
});
|
|
636
|
+
return false;
|
|
637
|
+
}
|
|
372
638
|
default:
|
|
373
639
|
throw exhaustiveCheck(type);
|
|
374
640
|
}
|