@rimbu/deep 0.11.2 → 0.12.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/main/deep.js +211 -0
- package/dist/main/deep.js.map +1 -0
- package/dist/main/index.js +7 -9
- package/dist/main/index.js.map +1 -1
- package/dist/main/internal.js +6 -4
- package/dist/main/internal.js.map +1 -1
- package/dist/main/match.js +160 -199
- package/dist/main/match.js.map +1 -1
- package/dist/main/patch.js +101 -125
- package/dist/main/patch.js.map +1 -1
- package/dist/main/path.js +109 -62
- package/dist/main/path.js.map +1 -1
- package/dist/main/protected.js +0 -19
- package/dist/main/protected.js.map +1 -1
- package/dist/main/selector.js +40 -0
- package/dist/main/selector.js.map +1 -0
- package/dist/main/tuple.js.map +1 -1
- package/dist/module/deep.js +192 -0
- package/dist/module/deep.js.map +1 -0
- package/dist/module/index.js +9 -1
- package/dist/module/index.js.map +1 -1
- package/dist/module/internal.js +4 -4
- package/dist/module/internal.js.map +1 -1
- package/dist/module/match.js +159 -179
- package/dist/module/match.js.map +1 -1
- package/dist/module/patch.js +91 -112
- package/dist/module/patch.js.map +1 -1
- package/dist/module/path.js +99 -44
- package/dist/module/path.js.map +1 -1
- package/dist/module/protected.js +1 -17
- package/dist/module/protected.js.map +1 -1
- package/dist/module/selector.js +36 -0
- package/dist/module/selector.js.map +1 -0
- package/dist/module/tuple.js.map +1 -1
- package/dist/types/deep.d.ts +284 -0
- package/dist/types/index.d.ts +10 -1
- package/dist/types/internal.d.ts +7 -4
- package/dist/types/match.d.ts +74 -80
- package/dist/types/patch.d.ts +57 -50
- package/dist/types/path.d.ts +177 -34
- package/dist/types/protected.d.ts +1 -16
- package/dist/types/selector.d.ts +47 -0
- package/dist/types/tuple.d.ts +10 -0
- package/package.json +4 -4
- package/src/deep.ts +362 -0
- package/src/index.ts +14 -10
- package/src/internal.ts +7 -4
- package/src/match.ts +396 -212
- package/src/patch.ts +173 -176
- package/src/path.ts +400 -74
- package/src/protected.ts +14 -25
- package/src/selector.ts +90 -0
- package/src/tuple.ts +12 -0
package/src/match.ts
CHANGED
|
@@ -1,318 +1,502 @@
|
|
|
1
1
|
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
type PlainObj,
|
|
2
|
+
IsAnyFunc,
|
|
3
|
+
IsArray,
|
|
5
4
|
isPlainObj,
|
|
6
|
-
|
|
5
|
+
IsPlainObj,
|
|
6
|
+
NotIterable,
|
|
7
7
|
} from '@rimbu/base';
|
|
8
|
-
|
|
9
8
|
import type { Protected } from './internal';
|
|
9
|
+
import type { Tuple } from './tuple';
|
|
10
10
|
|
|
11
11
|
/**
|
|
12
|
-
* The type to determine the allowed input values for the `match`
|
|
12
|
+
* The type to determine the allowed input values for the `match` function.
|
|
13
13
|
* @typeparam T - the type of value to match
|
|
14
|
+
* @typeparam C - utility type
|
|
14
15
|
*/
|
|
15
|
-
export type Match<T> = Match.
|
|
16
|
+
export type Match<T, C extends Partial<T> = Partial<T>> = Match.Entry<
|
|
17
|
+
T,
|
|
18
|
+
C,
|
|
19
|
+
T,
|
|
20
|
+
T
|
|
21
|
+
>;
|
|
16
22
|
|
|
17
23
|
export namespace Match {
|
|
18
24
|
/**
|
|
19
|
-
*
|
|
20
|
-
* @typeparam T - the
|
|
25
|
+
* Determines the various allowed match types for given type `T`.
|
|
26
|
+
* @typeparam T - the input value type
|
|
27
|
+
* @typeparam C - utility type
|
|
28
|
+
* @typeparam P - the parant type
|
|
21
29
|
* @typeparam R - the root object type
|
|
22
30
|
*/
|
|
23
|
-
export type
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
31
|
+
export type Entry<T, C, P, R> = IsAnyFunc<T> extends true
|
|
32
|
+
? // function can only be directly matched
|
|
33
|
+
T
|
|
34
|
+
: IsPlainObj<T> extends true
|
|
35
|
+
? // determine allowed match values for object
|
|
36
|
+
Match.WithResult<T, P, R, Match.Obj<T, C, P, R>>
|
|
37
|
+
: IsArray<T> extends true
|
|
38
|
+
? // determine allowed match values for array or tuple
|
|
39
|
+
Match.Arr<T, C, P, R> | Match.Func<T, P, R, Match.Arr<T, C, P, R>>
|
|
40
|
+
: // only accept values with same interface
|
|
41
|
+
Match.WithResult<T, P, R, { [K in keyof C]: C[K & keyof T] }>;
|
|
29
42
|
|
|
30
43
|
/**
|
|
31
|
-
* The type
|
|
32
|
-
* @typeparam T - the
|
|
44
|
+
* The type that determines allowed matchers for objects.
|
|
45
|
+
* @typeparam T - the input value type
|
|
46
|
+
* @typeparam C - utility type
|
|
47
|
+
* @typeparam P - the parant type
|
|
33
48
|
* @typeparam R - the root object type
|
|
34
49
|
*/
|
|
35
|
-
export type Obj<T, R> =
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
| ((
|
|
39
|
-
current: Protected<T[K]>,
|
|
40
|
-
parent: Protected<T>,
|
|
41
|
-
root: Protected<R>
|
|
42
|
-
) => boolean | Match.ObjItem<T[K], R>);
|
|
43
|
-
};
|
|
50
|
+
export type Obj<T, C, P, R> =
|
|
51
|
+
| Match.ObjProps<T, C, R>
|
|
52
|
+
| Match.CompoundForObj<T, C, P, R>;
|
|
44
53
|
|
|
45
54
|
/**
|
|
46
55
|
* The type to determine allowed matchers for object properties.
|
|
47
|
-
* @typeparam T - the
|
|
56
|
+
* @typeparam T - the input value type
|
|
57
|
+
* @typeparam C - utility type
|
|
48
58
|
* @typeparam R - the root object type
|
|
49
59
|
*/
|
|
50
|
-
export type
|
|
51
|
-
? Match.
|
|
52
|
-
|
|
53
|
-
? T | Iterable<U>
|
|
54
|
-
: T;
|
|
60
|
+
export type ObjProps<T, C, R> = {
|
|
61
|
+
[K in keyof C]?: K extends keyof T ? Match.Entry<T[K], C[K], T, R> : never;
|
|
62
|
+
};
|
|
55
63
|
|
|
56
64
|
/**
|
|
57
|
-
*
|
|
58
|
-
* @typeparam T - the
|
|
65
|
+
* The type that determines allowed matchers for arrays/tuples.
|
|
66
|
+
* @typeparam T - the input value type
|
|
67
|
+
* @typeparam C - utility type
|
|
68
|
+
* @typeparam P - the parant type
|
|
59
69
|
* @typeparam R - the root object type
|
|
60
|
-
* @typeparam Q - a utility type for the matcher
|
|
61
|
-
* @param matchItems - the match specifications to test
|
|
62
|
-
* @example
|
|
63
|
-
* ```ts
|
|
64
|
-
* const input = { a: 1, b: { c: true, d: 'a' } }
|
|
65
|
-
* match(input, Match.every({ a: 1, { c: true } } )) // => true
|
|
66
|
-
* match(input, Match.every({ a: 1, { c: false } } )) // => false
|
|
67
|
-
* ```
|
|
68
70
|
*/
|
|
69
|
-
export
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
71
|
+
export type Arr<T, C, P, R> =
|
|
72
|
+
| C
|
|
73
|
+
| Match.CompoundForArr<T, C, P, R>
|
|
74
|
+
| (Match.TupIndices<T, C, R> & { [K in Match.CompoundType]?: never });
|
|
75
|
+
|
|
76
|
+
export type WithResult<T, P, R, S> = S | Match.Func<T, P, R, S>;
|
|
77
|
+
|
|
74
78
|
/**
|
|
75
|
-
*
|
|
76
|
-
* @typeparam T - the
|
|
79
|
+
* Type used to determine the allowed function types. Always includes booleans.
|
|
80
|
+
* @typeparam T - the input value type
|
|
81
|
+
* @typeparam P - the parant type
|
|
77
82
|
* @typeparam R - the root object type
|
|
78
|
-
* @typeparam
|
|
79
|
-
* @param matchItems - the match specifications to test
|
|
80
|
-
* @example
|
|
81
|
-
* ```ts
|
|
82
|
-
* const input = { a: 1, b: { c: true, d: 'a' } }
|
|
83
|
-
* match(input, Match.some({ a: 5, { c: true } } )) // => true
|
|
84
|
-
* match(input, Match.some({ a: 5, { c: false } } )) // => false
|
|
85
|
-
* ```
|
|
83
|
+
* @typeparam S - the allowed return value type
|
|
86
84
|
*/
|
|
87
|
-
export
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
85
|
+
export type Func<T, P, R, S> = (
|
|
86
|
+
current: Protected<T>,
|
|
87
|
+
parent: Protected<P>,
|
|
88
|
+
root: Protected<R>
|
|
89
|
+
) => boolean | S;
|
|
90
|
+
|
|
92
91
|
/**
|
|
93
|
-
*
|
|
94
|
-
* @typeparam T - the
|
|
92
|
+
* Type used to indicate an object containing matches for tuple indices.
|
|
93
|
+
* @typeparam T - the input value type
|
|
94
|
+
* @typeparam C - utility type
|
|
95
95
|
* @typeparam R - the root object type
|
|
96
|
-
* @typeparam Q - a utility type for the matcher
|
|
97
|
-
* @param matchItems - the match specifications to test
|
|
98
|
-
* @example
|
|
99
|
-
* ```ts
|
|
100
|
-
* const input = { a: 1, b: { c: true, d: 'a' } }
|
|
101
|
-
* match(input, Match.none({ a: 5, { c: true } } )) // => false
|
|
102
|
-
* match(input, Match.none({ a: 5, { c: false } } )) // => true
|
|
103
|
-
* ```
|
|
104
96
|
*/
|
|
105
|
-
export
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
}
|
|
97
|
+
export type TupIndices<T, C, R> = {
|
|
98
|
+
[K in Tuple.KeysOf<C>]?: Match.Entry<T[K & keyof T], C[K], T, R>;
|
|
99
|
+
} & NotIterable;
|
|
100
|
+
|
|
110
101
|
/**
|
|
111
|
-
*
|
|
112
|
-
* @typeparam T - the type of value to match
|
|
113
|
-
* @typeparam R - the root object type
|
|
114
|
-
* @typeparam Q - a utility type for the matcher
|
|
115
|
-
* @param matchItems - the match specifications to test
|
|
116
|
-
* @example
|
|
117
|
-
* ```ts
|
|
118
|
-
* const input = { a: 1, b: { c: true, d: 'a' } }
|
|
119
|
-
* match(input, Match.single({ a: 1, { c: true } } )) // => false
|
|
120
|
-
* match(input, Match.single({ a: 1, { c: false } } )) // => true
|
|
121
|
-
* ```
|
|
102
|
+
* Compound keys used to indicate the type of compound.
|
|
122
103
|
*/
|
|
123
|
-
export
|
|
124
|
-
...matchItems: Match.Options<Q, R>[]
|
|
125
|
-
): Single<T, R, Q> {
|
|
126
|
-
return new Single(matchItems);
|
|
127
|
-
}
|
|
104
|
+
export type CompoundType = 'every' | 'some' | 'none' | 'single';
|
|
128
105
|
|
|
129
106
|
/**
|
|
130
|
-
*
|
|
107
|
+
* Compount matcher for objects, can only be an array staring with a compound type keyword.
|
|
108
|
+
* @typeparam T - the input value type
|
|
109
|
+
* @typeparam C - utility type
|
|
110
|
+
* @typeparam P - the parent type
|
|
111
|
+
* @typeparam R - the root object type
|
|
131
112
|
*/
|
|
132
|
-
export type
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
constructor(readonly matchItems: Match.Options<Q, R>[]) {}
|
|
137
|
-
}
|
|
113
|
+
export type CompoundForObj<T, C, P, R> = [
|
|
114
|
+
Match.CompoundType,
|
|
115
|
+
...Match.Entry<T, C, P, R>[]
|
|
116
|
+
];
|
|
138
117
|
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
118
|
+
/**
|
|
119
|
+
* Defines an object containing exactly one `CompoundType` key, having an array of matchers.
|
|
120
|
+
* @typeparam T - the input value type
|
|
121
|
+
* @typeparam C - utility type
|
|
122
|
+
* @typeparam P - the parent type
|
|
123
|
+
* @typeparam R - the root object type
|
|
124
|
+
*/
|
|
125
|
+
export type CompoundForArr<T, C, P, R> = {
|
|
126
|
+
[K in CompoundType]: {
|
|
127
|
+
[K2 in CompoundType]?: K2 extends K ? Match.Entry<T, C, P, R>[] : never;
|
|
128
|
+
};
|
|
129
|
+
}[CompoundType];
|
|
146
130
|
|
|
147
|
-
|
|
148
|
-
|
|
131
|
+
/**
|
|
132
|
+
* Utility type for collecting errors
|
|
133
|
+
*/
|
|
134
|
+
export type ErrorCollector = string[] | undefined;
|
|
149
135
|
}
|
|
150
136
|
|
|
151
137
|
/**
|
|
152
138
|
* Returns true if the given `value` object matches the given `matcher`, false otherwise.
|
|
153
139
|
* @typeparam T - the input value type
|
|
154
|
-
* @
|
|
140
|
+
* @typeparam C - utility type
|
|
141
|
+
* @param source - the value to match (should be a plain object)
|
|
155
142
|
* @param matcher - a matcher object or a function taking the matcher API and returning a match object
|
|
143
|
+
* @param errorCollector - (optional) a string array that can be passed to collect reasons why the match failed
|
|
156
144
|
* @example
|
|
157
145
|
* ```ts
|
|
158
146
|
* const input = { a: 1, b: { c: true, d: 'a' } }
|
|
159
147
|
* match(input, { a: 1 }) // => true
|
|
160
148
|
* match(input, { a: 2 }) // => false
|
|
161
|
-
* match(input, { a: v => v > 10 }) // => false
|
|
149
|
+
* match(input, { a: (v) => v > 10 }) // => false
|
|
162
150
|
* match(input, { b: { c: true }}) // => true
|
|
163
|
-
* match(input, (
|
|
151
|
+
* match(input, (['every', { a: (v) => v > 0 }, { b: { c: true } }]) // => true
|
|
164
152
|
* match(input, { b: { c: (v, parent, root) => v && parent.d.length > 0 && root.a > 0 } })
|
|
165
153
|
* // => true
|
|
166
154
|
* ```
|
|
167
155
|
*/
|
|
168
|
-
export function match<T>(
|
|
169
|
-
|
|
170
|
-
matcher: Match<T
|
|
156
|
+
export function match<T, C extends Partial<T> = Partial<T>>(
|
|
157
|
+
source: T,
|
|
158
|
+
matcher: Match<T, C>,
|
|
159
|
+
errorCollector: Match.ErrorCollector = undefined
|
|
171
160
|
): boolean {
|
|
172
|
-
|
|
173
|
-
return matchOptions(value, value, matcher(Match));
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
return matchOptions(value, value, matcher);
|
|
161
|
+
return matchEntry(source, source, source, matcher as any, errorCollector);
|
|
177
162
|
}
|
|
178
163
|
|
|
179
|
-
|
|
180
|
-
|
|
164
|
+
/**
|
|
165
|
+
* Match a generic match entry against the given source.
|
|
166
|
+
*/
|
|
167
|
+
function matchEntry<T, C, P, R>(
|
|
168
|
+
source: T,
|
|
169
|
+
parent: P,
|
|
181
170
|
root: R,
|
|
182
|
-
matcher: Match.
|
|
171
|
+
matcher: Match.Entry<T, C, P, R>,
|
|
172
|
+
errorCollector: Match.ErrorCollector
|
|
183
173
|
): boolean {
|
|
184
|
-
if (matcher
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
174
|
+
if (Object.is(source, matcher)) {
|
|
175
|
+
// value and target are exactly the same, always will be true
|
|
176
|
+
return true;
|
|
177
|
+
}
|
|
188
178
|
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
179
|
+
if (matcher === null || matcher === undefined) {
|
|
180
|
+
// these matchers can only be direct matches, and previously it was determined that
|
|
181
|
+
// they are not equal
|
|
182
|
+
errorCollector?.push(
|
|
183
|
+
`value ${JSON.stringify(source)} did not match matcher ${matcher}`
|
|
184
|
+
);
|
|
185
|
+
|
|
186
|
+
return false;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
if (typeof source === 'function') {
|
|
190
|
+
// function source values can only be directly matched
|
|
191
|
+
const result = Object.is(source, matcher);
|
|
192
|
+
|
|
193
|
+
if (!result) {
|
|
194
|
+
errorCollector?.push(
|
|
195
|
+
`both value and matcher are functions, but they do not have the same reference`
|
|
196
|
+
);
|
|
193
197
|
}
|
|
194
198
|
|
|
195
|
-
return
|
|
199
|
+
return result;
|
|
196
200
|
}
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
const
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
201
|
+
|
|
202
|
+
if (typeof matcher === 'function') {
|
|
203
|
+
// resolve match function first
|
|
204
|
+
const matcherResult = matcher(source, parent, root);
|
|
205
|
+
|
|
206
|
+
if (typeof matcherResult === 'boolean') {
|
|
207
|
+
// function resulted in a direct match result
|
|
208
|
+
|
|
209
|
+
if (!matcherResult) {
|
|
210
|
+
errorCollector?.push(
|
|
211
|
+
`function matcher returned false for value ${JSON.stringify(source)}`
|
|
212
|
+
);
|
|
204
213
|
}
|
|
214
|
+
|
|
215
|
+
return matcherResult;
|
|
205
216
|
}
|
|
206
217
|
|
|
207
|
-
|
|
218
|
+
// function resulted in a value that needs to be further matched
|
|
219
|
+
return matchEntry(source, parent, root, matcherResult, errorCollector);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
if (isPlainObj(source)) {
|
|
223
|
+
// source ia a plain object, can be partially matched
|
|
224
|
+
return matchPlainObj(source, parent, root, matcher as any, errorCollector);
|
|
208
225
|
}
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
226
|
+
|
|
227
|
+
if (Array.isArray(source)) {
|
|
228
|
+
// source is an array
|
|
229
|
+
return matchArr(source, root, matcher as any, errorCollector);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// already determined above that the source and matcher are not equal
|
|
233
|
+
|
|
234
|
+
errorCollector?.push(
|
|
235
|
+
`value ${JSON.stringify(
|
|
236
|
+
source
|
|
237
|
+
)} does not match given matcher ${JSON.stringify(matcher)}`
|
|
238
|
+
);
|
|
239
|
+
|
|
240
|
+
return false;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/**
|
|
244
|
+
* Match an array matcher against the given source.
|
|
245
|
+
*/
|
|
246
|
+
function matchArr<T extends any[], C, P, R>(
|
|
247
|
+
source: T,
|
|
248
|
+
root: R,
|
|
249
|
+
matcher: Match.Arr<T, C, P, R>,
|
|
250
|
+
errorCollector: Match.ErrorCollector
|
|
251
|
+
): boolean {
|
|
252
|
+
if (Array.isArray(matcher)) {
|
|
253
|
+
// directly compare array contents
|
|
254
|
+
const length = source.length;
|
|
255
|
+
|
|
256
|
+
if (length !== matcher.length) {
|
|
257
|
+
// if lengths not equal, arrays are not equal
|
|
258
|
+
|
|
259
|
+
errorCollector?.push(
|
|
260
|
+
`array lengths are not equal: value length ${source.length} !== matcher length ${matcher.length}`
|
|
261
|
+
);
|
|
262
|
+
|
|
263
|
+
return false;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
// loop over arrays, matching every value
|
|
267
|
+
let index = -1;
|
|
268
|
+
while (++index < length) {
|
|
269
|
+
if (!Object.is(source[index], matcher[index])) {
|
|
270
|
+
// item did not match, return false
|
|
271
|
+
|
|
272
|
+
errorCollector?.push(
|
|
273
|
+
`index ${index} does not match with value ${JSON.stringify(
|
|
274
|
+
source[index]
|
|
275
|
+
)} and matcher ${matcher[index]}`
|
|
276
|
+
);
|
|
277
|
+
|
|
215
278
|
return false;
|
|
216
279
|
}
|
|
217
280
|
}
|
|
218
281
|
|
|
282
|
+
// all items are equal
|
|
219
283
|
return true;
|
|
220
284
|
}
|
|
221
|
-
if (matcher instanceof Single) {
|
|
222
|
-
let i = -1;
|
|
223
|
-
const { matchItems } = matcher;
|
|
224
|
-
const len = matchItems.length;
|
|
225
|
-
let matched = false;
|
|
226
|
-
|
|
227
|
-
while (++i < len) {
|
|
228
|
-
if (matchOptions(value, root, matchItems[i])) {
|
|
229
|
-
if (matched) {
|
|
230
|
-
return false;
|
|
231
|
-
}
|
|
232
285
|
|
|
233
|
-
|
|
234
|
-
|
|
286
|
+
// matcher is plain object with index keys
|
|
287
|
+
|
|
288
|
+
for (const index in matcher as any) {
|
|
289
|
+
const matcherAtIndex = (matcher as any)[index];
|
|
290
|
+
|
|
291
|
+
if (!(index in source)) {
|
|
292
|
+
// source does not have item at given index
|
|
293
|
+
|
|
294
|
+
errorCollector?.push(
|
|
295
|
+
`index ${index} does not exist in source ${JSON.stringify(
|
|
296
|
+
source
|
|
297
|
+
)} but should match matcher ${JSON.stringify(matcherAtIndex)}`
|
|
298
|
+
);
|
|
299
|
+
|
|
300
|
+
return false;
|
|
235
301
|
}
|
|
236
302
|
|
|
237
|
-
|
|
238
|
-
|
|
303
|
+
// match the source item at the given index
|
|
304
|
+
const result = matchEntry(
|
|
305
|
+
(source as any)[index],
|
|
306
|
+
source,
|
|
307
|
+
root,
|
|
308
|
+
matcherAtIndex,
|
|
309
|
+
errorCollector
|
|
310
|
+
);
|
|
311
|
+
|
|
312
|
+
if (!result) {
|
|
313
|
+
// item did not match
|
|
314
|
+
|
|
315
|
+
errorCollector?.push(
|
|
316
|
+
`index ${index} does not match with value ${JSON.stringify(
|
|
317
|
+
(source as any)[index]
|
|
318
|
+
)} and matcher ${JSON.stringify(matcherAtIndex)}`
|
|
319
|
+
);
|
|
239
320
|
|
|
240
|
-
|
|
241
|
-
|
|
321
|
+
return false;
|
|
322
|
+
}
|
|
242
323
|
}
|
|
243
324
|
|
|
244
|
-
|
|
325
|
+
// all items match
|
|
326
|
+
|
|
327
|
+
return true;
|
|
245
328
|
}
|
|
246
329
|
|
|
247
|
-
|
|
248
|
-
|
|
330
|
+
/**
|
|
331
|
+
* Match an object matcher against the given source.
|
|
332
|
+
*/
|
|
333
|
+
function matchPlainObj<T, C, P, R>(
|
|
334
|
+
source: T,
|
|
335
|
+
parent: P,
|
|
249
336
|
root: R,
|
|
250
|
-
matcher: Match.Obj<T, R
|
|
337
|
+
matcher: Match.Obj<T, C, P, R>,
|
|
338
|
+
errorCollector: Match.ErrorCollector
|
|
251
339
|
): boolean {
|
|
252
|
-
if (
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
);
|
|
340
|
+
if (Array.isArray(matcher)) {
|
|
341
|
+
// the matcher is of compound type
|
|
342
|
+
return matchCompound(source, parent, root, matcher as any, errorCollector);
|
|
256
343
|
}
|
|
257
344
|
|
|
258
|
-
|
|
259
|
-
if (!(key in value)) return false;
|
|
260
|
-
|
|
261
|
-
const matchValue = matcher[key];
|
|
262
|
-
const target = value[key];
|
|
345
|
+
// partial object props matcher
|
|
263
346
|
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
}
|
|
347
|
+
for (const key in matcher) {
|
|
348
|
+
if (!(key in source)) {
|
|
349
|
+
// the source does not have the given key
|
|
268
350
|
|
|
269
|
-
|
|
351
|
+
errorCollector?.push(
|
|
352
|
+
`key ${key} is specified in matcher but not present in value ${JSON.stringify(
|
|
353
|
+
source
|
|
354
|
+
)}`
|
|
355
|
+
);
|
|
270
356
|
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
continue;
|
|
274
|
-
}
|
|
357
|
+
return false;
|
|
358
|
+
}
|
|
275
359
|
|
|
276
|
-
|
|
277
|
-
|
|
360
|
+
// match the source value at the given key with the matcher at given key
|
|
361
|
+
const result = matchEntry(
|
|
362
|
+
(source as any)[key],
|
|
363
|
+
source,
|
|
364
|
+
root,
|
|
365
|
+
matcher[key],
|
|
366
|
+
errorCollector
|
|
367
|
+
);
|
|
278
368
|
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
369
|
+
if (!result) {
|
|
370
|
+
errorCollector?.push(
|
|
371
|
+
`key ${key} does not match in value ${JSON.stringify(
|
|
372
|
+
(source as any)[key]
|
|
373
|
+
)} with matcher ${JSON.stringify(matcher[key])}`
|
|
374
|
+
);
|
|
375
|
+
return false;
|
|
286
376
|
}
|
|
287
377
|
}
|
|
288
378
|
|
|
379
|
+
// all properties match
|
|
380
|
+
|
|
289
381
|
return true;
|
|
290
382
|
}
|
|
291
383
|
|
|
292
|
-
|
|
293
|
-
|
|
384
|
+
/**
|
|
385
|
+
* Match a compound matcher against the given source.
|
|
386
|
+
*/
|
|
387
|
+
function matchCompound<T, C, P, R>(
|
|
388
|
+
source: T,
|
|
389
|
+
parent: P,
|
|
294
390
|
root: R,
|
|
295
|
-
|
|
391
|
+
compound: [Match.CompoundType, ...Match.Entry<T, C, P, R>[]],
|
|
392
|
+
errorCollector: string[] | undefined
|
|
296
393
|
): boolean {
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
const it2 = (matcher as any)[Symbol.iterator]() as Iterator<unknown>;
|
|
394
|
+
// first item indicates compound match type
|
|
395
|
+
const matchType = compound[0];
|
|
300
396
|
|
|
301
|
-
|
|
302
|
-
const v1 = it1.next();
|
|
303
|
-
const v2 = it2.next();
|
|
397
|
+
const length = compound.length;
|
|
304
398
|
|
|
305
|
-
|
|
306
|
-
|
|
399
|
+
// start at index 1
|
|
400
|
+
let index = 0;
|
|
401
|
+
|
|
402
|
+
type Entry = Match.Entry<T, C, P, R>;
|
|
403
|
+
|
|
404
|
+
switch (matchType) {
|
|
405
|
+
case 'every': {
|
|
406
|
+
while (++index < length) {
|
|
407
|
+
// if any item does not match, return false
|
|
408
|
+
const result = matchEntry(
|
|
409
|
+
source,
|
|
410
|
+
parent,
|
|
411
|
+
root,
|
|
412
|
+
compound[index] as Entry,
|
|
413
|
+
errorCollector
|
|
414
|
+
);
|
|
415
|
+
|
|
416
|
+
if (!result) {
|
|
417
|
+
errorCollector?.push(
|
|
418
|
+
`in compound "every": match at index ${index} failed`
|
|
419
|
+
);
|
|
420
|
+
|
|
421
|
+
return false;
|
|
422
|
+
}
|
|
307
423
|
}
|
|
308
|
-
|
|
309
|
-
|
|
424
|
+
|
|
425
|
+
return true;
|
|
426
|
+
}
|
|
427
|
+
case 'none': {
|
|
428
|
+
// if any item matches, return false
|
|
429
|
+
while (++index < length) {
|
|
430
|
+
const result = matchEntry(
|
|
431
|
+
source,
|
|
432
|
+
parent,
|
|
433
|
+
root,
|
|
434
|
+
compound[index] as Entry,
|
|
435
|
+
errorCollector
|
|
436
|
+
);
|
|
437
|
+
|
|
438
|
+
if (result) {
|
|
439
|
+
errorCollector?.push(
|
|
440
|
+
`in compound "none": match at index ${index} succeeded`
|
|
441
|
+
);
|
|
442
|
+
|
|
443
|
+
return false;
|
|
444
|
+
}
|
|
310
445
|
}
|
|
446
|
+
|
|
447
|
+
return true;
|
|
311
448
|
}
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
449
|
+
case 'single': {
|
|
450
|
+
// if not exactly one item matches, return false
|
|
451
|
+
let onePassed = false;
|
|
452
|
+
|
|
453
|
+
while (++index < length) {
|
|
454
|
+
const result = matchEntry(
|
|
455
|
+
source,
|
|
456
|
+
parent,
|
|
457
|
+
root,
|
|
458
|
+
compound[index] as Entry,
|
|
459
|
+
errorCollector
|
|
460
|
+
);
|
|
316
461
|
|
|
317
|
-
|
|
462
|
+
if (result) {
|
|
463
|
+
if (onePassed) {
|
|
464
|
+
errorCollector?.push(
|
|
465
|
+
`in compound "single": multiple matches succeeded`
|
|
466
|
+
);
|
|
467
|
+
|
|
468
|
+
return false;
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
onePassed = true;
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
if (!onePassed) {
|
|
476
|
+
errorCollector?.push(`in compound "single": no matches succeeded`);
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
return onePassed;
|
|
480
|
+
}
|
|
481
|
+
case 'some': {
|
|
482
|
+
// if any item matches, return true
|
|
483
|
+
while (++index < length) {
|
|
484
|
+
const result = matchEntry(
|
|
485
|
+
source,
|
|
486
|
+
parent,
|
|
487
|
+
root,
|
|
488
|
+
compound[index] as Entry,
|
|
489
|
+
errorCollector
|
|
490
|
+
);
|
|
491
|
+
|
|
492
|
+
if (result) {
|
|
493
|
+
return true;
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
errorCollector?.push(`in compound "some": no matches succeeded`);
|
|
498
|
+
|
|
499
|
+
return false;
|
|
500
|
+
}
|
|
501
|
+
}
|
|
318
502
|
}
|