@ls-stack/utils 3.47.0 → 3.49.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 +590 -303
- package/dist/partialEqual.d.cts +46 -4
- package/dist/partialEqual.d.ts +46 -4
- package/dist/partialEqual.js +589 -242
- package/package.json +1 -1
package/dist/partialEqual.js
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import {
|
|
2
|
-
|
|
3
|
-
} from "./chunk-
|
|
2
|
+
exhaustiveCheck
|
|
3
|
+
} from "./chunk-C2SVCIWE.js";
|
|
4
|
+
import "./chunk-JF2MDHOJ.js";
|
|
4
5
|
|
|
5
6
|
// src/partialEqual.ts
|
|
7
|
+
import { err, ok } from "t-result";
|
|
6
8
|
var has = Object.prototype.hasOwnProperty;
|
|
7
9
|
function createComparison(type) {
|
|
8
10
|
return { "~sc": type };
|
|
@@ -41,8 +43,24 @@ var match = {
|
|
|
41
43
|
partialEqual: (value) => createComparison(["partialEqual", value]),
|
|
42
44
|
custom: (isEqual) => createComparison(["custom", isEqual]),
|
|
43
45
|
keyNotBePresent: createComparison(["keyNotBePresent", null]),
|
|
44
|
-
any: (...
|
|
45
|
-
|
|
46
|
+
any: (...values) => createComparison([
|
|
47
|
+
"any",
|
|
48
|
+
values.map((v) => {
|
|
49
|
+
if (isComparison(v)) return v["~sc"];
|
|
50
|
+
if (typeof v === "object" && v !== null)
|
|
51
|
+
return ["partialEqual", v];
|
|
52
|
+
return ["deepEqual", v];
|
|
53
|
+
})
|
|
54
|
+
]),
|
|
55
|
+
all: (...values) => createComparison([
|
|
56
|
+
"all",
|
|
57
|
+
values.map((v) => {
|
|
58
|
+
if (isComparison(v)) return v["~sc"];
|
|
59
|
+
if (typeof v === "object" && v !== null)
|
|
60
|
+
return ["partialEqual", v];
|
|
61
|
+
return ["deepEqual", v];
|
|
62
|
+
})
|
|
63
|
+
]),
|
|
46
64
|
not: {
|
|
47
65
|
hasType: {
|
|
48
66
|
string: createComparison(["not", ["hasType", "string"]]),
|
|
@@ -73,311 +91,640 @@ var match = {
|
|
|
73
91
|
equal: (value) => createComparison(["not", ["deepEqual", value]]),
|
|
74
92
|
partialEqual: (value) => createComparison(["not", ["partialEqual", value]]),
|
|
75
93
|
custom: (value) => createComparison(["not", ["custom", value]]),
|
|
76
|
-
any: (...
|
|
77
|
-
|
|
94
|
+
any: (...values) => createComparison([
|
|
95
|
+
"not",
|
|
96
|
+
[
|
|
97
|
+
"any",
|
|
98
|
+
values.map((v) => {
|
|
99
|
+
if (isComparison(v)) return v["~sc"];
|
|
100
|
+
if (typeof v === "object" && v !== null)
|
|
101
|
+
return ["partialEqual", v];
|
|
102
|
+
return ["deepEqual", v];
|
|
103
|
+
})
|
|
104
|
+
]
|
|
105
|
+
]),
|
|
106
|
+
all: (...values) => createComparison([
|
|
107
|
+
"not",
|
|
108
|
+
[
|
|
109
|
+
"all",
|
|
110
|
+
values.map((v) => {
|
|
111
|
+
if (isComparison(v)) return v["~sc"];
|
|
112
|
+
if (typeof v === "object" && v !== null)
|
|
113
|
+
return ["partialEqual", v];
|
|
114
|
+
return ["deepEqual", v];
|
|
115
|
+
})
|
|
116
|
+
]
|
|
117
|
+
]),
|
|
78
118
|
noExtraKeys: (partialShape) => createComparison(["not", ["withNoExtraKeys", partialShape]]),
|
|
79
119
|
deepNoExtraKeys: (partialShape) => createComparison(["not", ["withDeepNoExtraKeys", partialShape]]),
|
|
80
120
|
noExtraDefinedKeys: (partialShape) => createComparison(["not", ["noExtraDefinedKeys", partialShape]]),
|
|
81
121
|
deepNoExtraDefinedKeys: (partialShape) => createComparison(["not", ["deepNoExtraDefinedKeys", partialShape]])
|
|
82
122
|
}
|
|
83
123
|
};
|
|
84
|
-
function
|
|
85
|
-
|
|
86
|
-
if (partialEqual(key, tar)) return key;
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
function executeComparisonWithKeyContext(target, comp, keyExists) {
|
|
90
|
-
const [type, value] = comp;
|
|
91
|
-
if (type === "keyNotBePresent") {
|
|
92
|
-
return !keyExists;
|
|
93
|
-
}
|
|
94
|
-
if (type === "any") {
|
|
95
|
-
for (const childComp of value) {
|
|
96
|
-
if (executeComparisonWithKeyContext(target, childComp, keyExists)) {
|
|
97
|
-
return true;
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
return false;
|
|
101
|
-
}
|
|
102
|
-
if (type === "not") {
|
|
103
|
-
return !executeComparisonWithKeyContext(target, value, keyExists);
|
|
104
|
-
}
|
|
105
|
-
return executeComparison(target, comp);
|
|
124
|
+
function isComparison(value) {
|
|
125
|
+
return value && typeof value === "object" && "~sc" in value;
|
|
106
126
|
}
|
|
107
|
-
function executeComparison(target, comparison) {
|
|
127
|
+
function executeComparison(target, comparison, context) {
|
|
108
128
|
const [type, value] = comparison;
|
|
109
129
|
switch (type) {
|
|
110
|
-
case "hasType":
|
|
111
|
-
switch (value) {
|
|
112
|
-
case "string":
|
|
113
|
-
return typeof target === "string";
|
|
114
|
-
case "number":
|
|
115
|
-
return typeof target === "number";
|
|
116
|
-
case "boolean":
|
|
117
|
-
return typeof target === "boolean";
|
|
118
|
-
case "function":
|
|
119
|
-
return typeof target === "function";
|
|
120
|
-
case "array":
|
|
121
|
-
return Array.isArray(target);
|
|
122
|
-
case "object":
|
|
123
|
-
return typeof target === "object" && target !== null && !Array.isArray(target);
|
|
124
|
-
default:
|
|
125
|
-
return false;
|
|
126
|
-
}
|
|
127
|
-
case "isInstanceOf":
|
|
128
|
-
return target instanceof value;
|
|
129
130
|
case "strStartsWith":
|
|
130
|
-
|
|
131
|
+
if (typeof target !== "string") {
|
|
132
|
+
addError(context, {
|
|
133
|
+
message: `Expected string starting with "${value}"`,
|
|
134
|
+
received: target
|
|
135
|
+
});
|
|
136
|
+
return false;
|
|
137
|
+
}
|
|
138
|
+
if (!target.startsWith(value)) {
|
|
139
|
+
addError(context, {
|
|
140
|
+
message: `Expected string starting with "${value}"`,
|
|
141
|
+
received: target
|
|
142
|
+
});
|
|
143
|
+
return false;
|
|
144
|
+
}
|
|
145
|
+
return true;
|
|
131
146
|
case "strEndsWith":
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
case "numIsGreaterThan":
|
|
138
|
-
return typeof target === "number" && target > value;
|
|
139
|
-
case "numIsGreaterThanOrEqual":
|
|
140
|
-
return typeof target === "number" && target >= value;
|
|
141
|
-
case "numIsLessThan":
|
|
142
|
-
return typeof target === "number" && target < value;
|
|
143
|
-
case "numIsLessThanOrEqual":
|
|
144
|
-
return typeof target === "number" && target <= value;
|
|
145
|
-
case "numIsInRange":
|
|
146
|
-
return typeof target === "number" && target >= value[0] && target <= value[1];
|
|
147
|
-
case "jsonStringHasPartial":
|
|
148
|
-
if (typeof target !== "string") return false;
|
|
149
|
-
try {
|
|
150
|
-
const parsed = JSON.parse(target);
|
|
151
|
-
return partialEqual(parsed, value);
|
|
152
|
-
} catch {
|
|
147
|
+
if (typeof target !== "string") {
|
|
148
|
+
addError(context, {
|
|
149
|
+
message: `Expected string ending with "${value}"`,
|
|
150
|
+
received: target
|
|
151
|
+
});
|
|
153
152
|
return false;
|
|
154
153
|
}
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
case "keyNotBePresent":
|
|
162
|
-
return false;
|
|
163
|
-
case "any":
|
|
164
|
-
for (const comp of value) {
|
|
165
|
-
if (executeComparison(target, comp)) {
|
|
166
|
-
return true;
|
|
167
|
-
}
|
|
154
|
+
if (!target.endsWith(value)) {
|
|
155
|
+
addError(context, {
|
|
156
|
+
message: `Expected string ending with "${value}"`,
|
|
157
|
+
received: target
|
|
158
|
+
});
|
|
159
|
+
return false;
|
|
168
160
|
}
|
|
169
|
-
return
|
|
170
|
-
case "
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
161
|
+
return true;
|
|
162
|
+
case "strContains":
|
|
163
|
+
if (typeof target !== "string") {
|
|
164
|
+
addError(context, {
|
|
165
|
+
message: `Expected string containing "${value}"`,
|
|
166
|
+
received: target
|
|
167
|
+
});
|
|
168
|
+
return false;
|
|
169
|
+
}
|
|
170
|
+
if (!target.includes(value)) {
|
|
171
|
+
addError(context, {
|
|
172
|
+
message: `Expected string containing "${value}"`,
|
|
173
|
+
received: target
|
|
174
|
+
});
|
|
175
|
+
return false;
|
|
175
176
|
}
|
|
176
177
|
return true;
|
|
177
|
-
case "
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
178
|
+
case "strMatchesRegex":
|
|
179
|
+
if (typeof target !== "string") {
|
|
180
|
+
addError(context, {
|
|
181
|
+
message: `Expected string matching regex ${value}`,
|
|
182
|
+
received: target
|
|
183
|
+
});
|
|
181
184
|
return false;
|
|
182
185
|
}
|
|
183
|
-
if (
|
|
186
|
+
if (!value.test(target)) {
|
|
187
|
+
addError(context, {
|
|
188
|
+
message: `Expected string matching regex ${value}`,
|
|
189
|
+
received: target
|
|
190
|
+
});
|
|
184
191
|
return false;
|
|
185
192
|
}
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
193
|
+
return true;
|
|
194
|
+
case "hasType": {
|
|
195
|
+
let actualType;
|
|
196
|
+
if (value === "array") {
|
|
197
|
+
actualType = Array.isArray(target) ? "array" : typeof target;
|
|
198
|
+
} else if (value === "object") {
|
|
199
|
+
if (target === null || Array.isArray(target)) {
|
|
200
|
+
actualType = "not-object";
|
|
201
|
+
} else {
|
|
202
|
+
actualType = typeof target;
|
|
189
203
|
}
|
|
204
|
+
} else {
|
|
205
|
+
actualType = typeof target;
|
|
190
206
|
}
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
return false;
|
|
198
|
-
}
|
|
199
|
-
}
|
|
207
|
+
if (actualType !== value) {
|
|
208
|
+
addError(context, {
|
|
209
|
+
message: `Expected type ${value}`,
|
|
210
|
+
received: target
|
|
211
|
+
});
|
|
212
|
+
return false;
|
|
200
213
|
}
|
|
201
214
|
return true;
|
|
202
|
-
|
|
203
|
-
|
|
215
|
+
}
|
|
216
|
+
case "deepEqual":
|
|
217
|
+
if (!deepEqual(target, value)) {
|
|
218
|
+
addError(context, {
|
|
219
|
+
message: "Values are not deeply equal",
|
|
220
|
+
received: target,
|
|
221
|
+
expected: value
|
|
222
|
+
});
|
|
204
223
|
return false;
|
|
205
224
|
}
|
|
206
|
-
|
|
225
|
+
return true;
|
|
226
|
+
case "numIsGreaterThan":
|
|
227
|
+
if (typeof target !== "number" || target <= value) {
|
|
228
|
+
addError(context, {
|
|
229
|
+
message: `Expected number greater than ${value}`,
|
|
230
|
+
received: target
|
|
231
|
+
});
|
|
207
232
|
return false;
|
|
208
233
|
}
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
234
|
+
return true;
|
|
235
|
+
case "numIsGreaterThanOrEqual":
|
|
236
|
+
if (typeof target !== "number" || target < value) {
|
|
237
|
+
addError(context, {
|
|
238
|
+
message: `Expected number greater than or equal to ${value}`,
|
|
239
|
+
received: target
|
|
240
|
+
});
|
|
241
|
+
return false;
|
|
213
242
|
}
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
if (!executeComparison(targetValue, partialValue["~sc"])) {
|
|
223
|
-
return false;
|
|
224
|
-
}
|
|
225
|
-
} else if (partialValue && typeof partialValue === "object" && !Array.isArray(partialValue) && partialValue.constructor === Object && targetValue && typeof targetValue === "object" && !Array.isArray(targetValue) && targetValue.constructor === Object) {
|
|
226
|
-
if (!executeComparison(targetValue, [
|
|
227
|
-
"withDeepNoExtraKeys",
|
|
228
|
-
partialValue
|
|
229
|
-
])) {
|
|
230
|
-
return false;
|
|
231
|
-
}
|
|
232
|
-
} else {
|
|
233
|
-
if (!partialEqual(targetValue, partialValue)) {
|
|
234
|
-
return false;
|
|
235
|
-
}
|
|
236
|
-
}
|
|
237
|
-
}
|
|
243
|
+
return true;
|
|
244
|
+
case "numIsLessThan":
|
|
245
|
+
if (typeof target !== "number" || target >= value) {
|
|
246
|
+
addError(context, {
|
|
247
|
+
message: `Expected number less than ${value}`,
|
|
248
|
+
received: target
|
|
249
|
+
});
|
|
250
|
+
return false;
|
|
238
251
|
}
|
|
239
252
|
return true;
|
|
240
|
-
case "
|
|
241
|
-
if (typeof target !== "
|
|
253
|
+
case "numIsLessThanOrEqual":
|
|
254
|
+
if (typeof target !== "number" || target > value) {
|
|
255
|
+
addError(context, {
|
|
256
|
+
message: `Expected number less than or equal to ${value}`,
|
|
257
|
+
received: target
|
|
258
|
+
});
|
|
242
259
|
return false;
|
|
243
260
|
}
|
|
244
|
-
|
|
261
|
+
return true;
|
|
262
|
+
case "numIsInRange":
|
|
263
|
+
if (typeof target !== "number" || target < value[0] || target > value[1]) {
|
|
264
|
+
addError(context, {
|
|
265
|
+
message: `Expected number in range [${value[0]}, ${value[1]}]`,
|
|
266
|
+
received: target
|
|
267
|
+
});
|
|
268
|
+
return false;
|
|
269
|
+
}
|
|
270
|
+
return true;
|
|
271
|
+
case "jsonStringHasPartial":
|
|
272
|
+
if (typeof target !== "string") {
|
|
273
|
+
addError(context, {
|
|
274
|
+
message: "Expected JSON string",
|
|
275
|
+
received: target
|
|
276
|
+
});
|
|
245
277
|
return false;
|
|
246
278
|
}
|
|
247
|
-
|
|
248
|
-
|
|
279
|
+
try {
|
|
280
|
+
const parsed = JSON.parse(target);
|
|
281
|
+
if (!partialEqualInternal(parsed, value, context)) {
|
|
249
282
|
return false;
|
|
250
283
|
}
|
|
284
|
+
} catch {
|
|
285
|
+
addError(context, {
|
|
286
|
+
message: "Expected valid JSON string",
|
|
287
|
+
received: target
|
|
288
|
+
});
|
|
289
|
+
return false;
|
|
251
290
|
}
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
}
|
|
260
|
-
|
|
291
|
+
return true;
|
|
292
|
+
case "partialEqual":
|
|
293
|
+
return partialEqualInternal(target, value, context);
|
|
294
|
+
case "custom": {
|
|
295
|
+
const result = value(target);
|
|
296
|
+
if (result !== true) {
|
|
297
|
+
addError(context, {
|
|
298
|
+
message: `Custom validation failed ${typeof result === "object" ? `: ${result.error}` : ""}`,
|
|
299
|
+
received: target
|
|
300
|
+
});
|
|
301
|
+
return false;
|
|
261
302
|
}
|
|
262
303
|
return true;
|
|
263
|
-
|
|
264
|
-
|
|
304
|
+
}
|
|
305
|
+
case "isInstanceOf":
|
|
306
|
+
if (!(target instanceof value)) {
|
|
307
|
+
addError(context, {
|
|
308
|
+
message: `Expected instance of ${value.name}`,
|
|
309
|
+
received: target
|
|
310
|
+
});
|
|
265
311
|
return false;
|
|
266
312
|
}
|
|
267
|
-
|
|
313
|
+
return true;
|
|
314
|
+
case "keyNotBePresent":
|
|
315
|
+
addError(context, {
|
|
316
|
+
message: "This property should not be present",
|
|
317
|
+
received: target
|
|
318
|
+
});
|
|
319
|
+
return false;
|
|
320
|
+
case "not": {
|
|
321
|
+
const tempContext = {
|
|
322
|
+
errors: [],
|
|
323
|
+
path: context.path
|
|
324
|
+
};
|
|
325
|
+
const result = executeComparison(target, value, tempContext);
|
|
326
|
+
if (result) {
|
|
327
|
+
addError(context, {
|
|
328
|
+
message: "Expected negated condition to fail",
|
|
329
|
+
received: target,
|
|
330
|
+
expected: { "not match": value }
|
|
331
|
+
});
|
|
268
332
|
return false;
|
|
269
333
|
}
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
334
|
+
return true;
|
|
335
|
+
}
|
|
336
|
+
case "any": {
|
|
337
|
+
for (const subComparison of value) {
|
|
338
|
+
const anyTempContext = {
|
|
339
|
+
errors: [],
|
|
340
|
+
path: context.path
|
|
341
|
+
};
|
|
342
|
+
if (executeComparison(target, subComparison, anyTempContext)) {
|
|
343
|
+
return true;
|
|
273
344
|
}
|
|
274
345
|
}
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
partialValue
|
|
290
|
-
])) {
|
|
291
|
-
return false;
|
|
292
|
-
}
|
|
293
|
-
} else {
|
|
294
|
-
if (!partialEqual(targetValue, partialValue)) {
|
|
295
|
-
return false;
|
|
296
|
-
}
|
|
297
|
-
}
|
|
346
|
+
addError(context, {
|
|
347
|
+
message: "None of the alternative comparisons matched",
|
|
348
|
+
received: target,
|
|
349
|
+
expected: {
|
|
350
|
+
matchAny: value
|
|
351
|
+
}
|
|
352
|
+
});
|
|
353
|
+
return false;
|
|
354
|
+
}
|
|
355
|
+
case "all": {
|
|
356
|
+
let allMatch = true;
|
|
357
|
+
for (const subComparison of value) {
|
|
358
|
+
if (!executeComparison(target, subComparison, context)) {
|
|
359
|
+
allMatch = false;
|
|
298
360
|
}
|
|
299
361
|
}
|
|
300
|
-
return
|
|
362
|
+
return allMatch;
|
|
363
|
+
}
|
|
364
|
+
case "withNoExtraKeys":
|
|
365
|
+
return checkNoExtraKeys(target, value, context, false);
|
|
366
|
+
case "withDeepNoExtraKeys":
|
|
367
|
+
return checkNoExtraKeys(target, value, context, true);
|
|
368
|
+
case "noExtraDefinedKeys":
|
|
369
|
+
return checkNoExtraDefinedKeys(target, value, context, false);
|
|
370
|
+
case "deepNoExtraDefinedKeys":
|
|
371
|
+
return checkNoExtraDefinedKeys(target, value, context, true);
|
|
301
372
|
default:
|
|
302
|
-
|
|
373
|
+
throw exhaustiveCheck(type);
|
|
303
374
|
}
|
|
304
375
|
}
|
|
305
|
-
function
|
|
306
|
-
if (
|
|
307
|
-
|
|
308
|
-
|
|
376
|
+
function formatPath(path) {
|
|
377
|
+
if (path.length === 0) return "";
|
|
378
|
+
let result = path[0] || "";
|
|
379
|
+
for (let i = 1; i < path.length; i++) {
|
|
380
|
+
const segment = path[i];
|
|
381
|
+
if (segment && segment.startsWith("[") && segment.endsWith("]")) {
|
|
382
|
+
result += segment;
|
|
383
|
+
} else if (segment) {
|
|
384
|
+
if (result) {
|
|
385
|
+
result += `.${segment}`;
|
|
386
|
+
} else {
|
|
387
|
+
result += segment;
|
|
388
|
+
}
|
|
389
|
+
}
|
|
309
390
|
}
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
391
|
+
return result;
|
|
392
|
+
}
|
|
393
|
+
function addError(context, error) {
|
|
394
|
+
context.errors.push({
|
|
395
|
+
path: formatPath(context.path),
|
|
396
|
+
...error
|
|
397
|
+
});
|
|
398
|
+
}
|
|
399
|
+
function deepEqual(a, b) {
|
|
400
|
+
if (a === b) return true;
|
|
401
|
+
if (Number.isNaN(a) && Number.isNaN(b)) return true;
|
|
402
|
+
if (a === null || b === null) return false;
|
|
403
|
+
if (typeof a !== typeof b) return false;
|
|
404
|
+
if (Array.isArray(a) && Array.isArray(b)) {
|
|
405
|
+
if (a.length !== b.length) return false;
|
|
406
|
+
for (let i = 0; i < a.length; i++) {
|
|
407
|
+
if (!deepEqual(a[i], b[i])) return false;
|
|
314
408
|
}
|
|
315
|
-
|
|
316
|
-
|
|
409
|
+
return true;
|
|
410
|
+
}
|
|
411
|
+
if (typeof a === "object") {
|
|
412
|
+
const keysA = Object.keys(a);
|
|
413
|
+
const keysB = Object.keys(b);
|
|
414
|
+
if (keysA.length !== keysB.length) return false;
|
|
415
|
+
for (const key of keysA) {
|
|
416
|
+
if (!has.call(b, key) || !deepEqual(a[key], b[key])) return false;
|
|
317
417
|
}
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
418
|
+
return true;
|
|
419
|
+
}
|
|
420
|
+
return false;
|
|
421
|
+
}
|
|
422
|
+
function partialEqualInternal(target, sub, context) {
|
|
423
|
+
if (isComparison(sub)) {
|
|
424
|
+
return executeComparison(target, sub["~sc"], context);
|
|
425
|
+
}
|
|
426
|
+
if (isComparison(sub) && sub["~sc"][0] === "keyNotBePresent") {
|
|
427
|
+
addError(context, {
|
|
428
|
+
message: "Key should not be present",
|
|
429
|
+
received: target,
|
|
430
|
+
expected: "key not present"
|
|
431
|
+
});
|
|
432
|
+
return false;
|
|
433
|
+
}
|
|
434
|
+
if (target === sub) return true;
|
|
435
|
+
if (Number.isNaN(target) && Number.isNaN(sub)) return true;
|
|
436
|
+
if (target === null || sub === null || target === void 0 || sub === void 0) {
|
|
437
|
+
if (target !== sub) {
|
|
438
|
+
addError(context, {
|
|
439
|
+
message: "Value mismatch",
|
|
440
|
+
received: target,
|
|
441
|
+
expected: sub
|
|
442
|
+
});
|
|
443
|
+
return false;
|
|
324
444
|
}
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
445
|
+
return true;
|
|
446
|
+
}
|
|
447
|
+
if (target instanceof Date && sub instanceof Date) {
|
|
448
|
+
if (target.getTime() !== sub.getTime()) {
|
|
449
|
+
addError(context, {
|
|
450
|
+
message: "Date mismatch",
|
|
451
|
+
received: target,
|
|
452
|
+
expected: sub
|
|
453
|
+
});
|
|
454
|
+
return false;
|
|
455
|
+
}
|
|
456
|
+
return true;
|
|
457
|
+
}
|
|
458
|
+
if (target instanceof RegExp && sub instanceof RegExp) {
|
|
459
|
+
if (target.source !== sub.source || target.flags !== sub.flags) {
|
|
460
|
+
addError(context, {
|
|
461
|
+
message: "RegExp mismatch",
|
|
462
|
+
received: target,
|
|
463
|
+
expected: sub
|
|
464
|
+
});
|
|
465
|
+
return false;
|
|
466
|
+
}
|
|
467
|
+
return true;
|
|
468
|
+
}
|
|
469
|
+
if (target instanceof Set && sub instanceof Set) {
|
|
470
|
+
if (sub.size > target.size) {
|
|
471
|
+
addError(context, {
|
|
472
|
+
message: "Set too small",
|
|
473
|
+
received: target,
|
|
474
|
+
expected: sub
|
|
475
|
+
});
|
|
476
|
+
return false;
|
|
477
|
+
}
|
|
478
|
+
for (const subValue of sub) {
|
|
479
|
+
let found = false;
|
|
480
|
+
for (const targetValue of target) {
|
|
481
|
+
const tempContext = {
|
|
482
|
+
errors: [],
|
|
483
|
+
path: context.path
|
|
484
|
+
};
|
|
485
|
+
if (partialEqualInternal(targetValue, subValue, tempContext)) {
|
|
486
|
+
found = true;
|
|
487
|
+
break;
|
|
333
488
|
}
|
|
334
|
-
if (!found) return false;
|
|
335
489
|
}
|
|
336
|
-
|
|
490
|
+
if (!found) {
|
|
491
|
+
addError(context, {
|
|
492
|
+
message: "Set element not found",
|
|
493
|
+
received: target,
|
|
494
|
+
expected: sub
|
|
495
|
+
});
|
|
496
|
+
return false;
|
|
497
|
+
}
|
|
337
498
|
}
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
499
|
+
return true;
|
|
500
|
+
}
|
|
501
|
+
if (target instanceof Map && sub instanceof Map) {
|
|
502
|
+
if (sub.size > target.size) {
|
|
503
|
+
addError(context, {
|
|
504
|
+
message: "Map too small",
|
|
505
|
+
received: target,
|
|
506
|
+
expected: sub
|
|
507
|
+
});
|
|
508
|
+
return false;
|
|
509
|
+
}
|
|
510
|
+
for (const [subKey, subValue] of sub) {
|
|
511
|
+
let found = false;
|
|
512
|
+
for (const [targetKey, targetValue] of target) {
|
|
513
|
+
const tempContextKey = {
|
|
514
|
+
errors: [],
|
|
515
|
+
path: context.path
|
|
516
|
+
};
|
|
517
|
+
const tempContextValue = {
|
|
518
|
+
errors: [],
|
|
519
|
+
path: context.path
|
|
520
|
+
};
|
|
521
|
+
if (partialEqualInternal(targetKey, subKey, tempContextKey) && partialEqualInternal(targetValue, subValue, tempContextValue)) {
|
|
522
|
+
found = true;
|
|
523
|
+
break;
|
|
348
524
|
}
|
|
349
525
|
}
|
|
350
|
-
|
|
526
|
+
if (!found) {
|
|
527
|
+
addError(context, {
|
|
528
|
+
message: "Map entry not found",
|
|
529
|
+
received: target,
|
|
530
|
+
expected: sub
|
|
531
|
+
});
|
|
532
|
+
return false;
|
|
533
|
+
}
|
|
351
534
|
}
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
535
|
+
return true;
|
|
536
|
+
}
|
|
537
|
+
if (typeof target !== typeof sub) {
|
|
538
|
+
addError(context, {
|
|
539
|
+
message: "Value mismatch",
|
|
540
|
+
received: target,
|
|
541
|
+
expected: sub
|
|
542
|
+
});
|
|
543
|
+
return false;
|
|
544
|
+
}
|
|
545
|
+
if (Array.isArray(sub)) {
|
|
546
|
+
if (!Array.isArray(target)) {
|
|
547
|
+
addError(context, {
|
|
548
|
+
message: "Expected array",
|
|
549
|
+
received: target,
|
|
550
|
+
expected: sub
|
|
551
|
+
});
|
|
552
|
+
return false;
|
|
553
|
+
}
|
|
554
|
+
if (target.length < sub.length) {
|
|
555
|
+
addError(context, {
|
|
556
|
+
message: `Array too short: expected at least ${sub.length} elements, got ${target.length}`,
|
|
557
|
+
received: target,
|
|
558
|
+
expected: sub
|
|
559
|
+
});
|
|
560
|
+
return false;
|
|
561
|
+
}
|
|
562
|
+
let allMatch = true;
|
|
563
|
+
for (let i = 0; i < sub.length; i++) {
|
|
564
|
+
const oldPath = context.path;
|
|
565
|
+
context.path = [...oldPath, `[${i}]`];
|
|
566
|
+
const result = partialEqualInternal(target[i], sub[i], context);
|
|
567
|
+
context.path = oldPath;
|
|
568
|
+
if (!result) allMatch = false;
|
|
569
|
+
}
|
|
570
|
+
return allMatch;
|
|
571
|
+
}
|
|
572
|
+
if (typeof sub === "object") {
|
|
573
|
+
if (typeof target !== "object" || Array.isArray(target)) {
|
|
574
|
+
addError(context, {
|
|
575
|
+
message: "Expected object",
|
|
576
|
+
received: target,
|
|
577
|
+
expected: sub
|
|
578
|
+
});
|
|
579
|
+
return false;
|
|
580
|
+
}
|
|
581
|
+
let allMatch = true;
|
|
582
|
+
for (const key of Object.keys(sub)) {
|
|
583
|
+
if (isComparison(sub[key]) && sub[key]["~sc"][0] === "keyNotBePresent") {
|
|
584
|
+
if (has.call(target, key)) {
|
|
585
|
+
const oldPath2 = context.path;
|
|
586
|
+
context.path = [...oldPath2, key];
|
|
587
|
+
addError(context, {
|
|
588
|
+
message: "Key should not be present",
|
|
589
|
+
received: target[key],
|
|
590
|
+
expected: "key not present"
|
|
591
|
+
});
|
|
592
|
+
context.path = oldPath2;
|
|
593
|
+
allMatch = false;
|
|
594
|
+
}
|
|
595
|
+
continue;
|
|
596
|
+
}
|
|
597
|
+
if (!has.call(target, key)) {
|
|
598
|
+
if (isComparison(sub[key])) {
|
|
599
|
+
const comparison = sub[key]["~sc"];
|
|
600
|
+
if (comparison[0] === "any") {
|
|
601
|
+
const anyComparisons = comparison[1];
|
|
602
|
+
const hasKeyNotBePresent = anyComparisons.some(
|
|
603
|
+
(comp) => comp[0] === "keyNotBePresent"
|
|
604
|
+
);
|
|
605
|
+
if (hasKeyNotBePresent) {
|
|
606
|
+
continue;
|
|
373
607
|
}
|
|
374
608
|
}
|
|
375
609
|
}
|
|
610
|
+
const oldPath2 = context.path;
|
|
611
|
+
context.path = [...oldPath2, key];
|
|
612
|
+
addError(context, {
|
|
613
|
+
message: "Missing property",
|
|
614
|
+
received: void 0,
|
|
615
|
+
expected: sub[key]
|
|
616
|
+
});
|
|
617
|
+
context.path = oldPath2;
|
|
618
|
+
allMatch = false;
|
|
619
|
+
continue;
|
|
376
620
|
}
|
|
377
|
-
|
|
621
|
+
const oldPath = context.path;
|
|
622
|
+
context.path = [...oldPath, key];
|
|
623
|
+
const result = partialEqualInternal(target[key], sub[key], context);
|
|
624
|
+
context.path = oldPath;
|
|
625
|
+
if (!result) allMatch = false;
|
|
378
626
|
}
|
|
627
|
+
return allMatch;
|
|
628
|
+
}
|
|
629
|
+
if (target !== sub) {
|
|
630
|
+
addError(context, {
|
|
631
|
+
message: "Value mismatch",
|
|
632
|
+
received: target,
|
|
633
|
+
expected: sub
|
|
634
|
+
});
|
|
635
|
+
return false;
|
|
636
|
+
}
|
|
637
|
+
return true;
|
|
638
|
+
}
|
|
639
|
+
function checkNoExtraKeys(target, partialShape, context, deep) {
|
|
640
|
+
if (typeof target !== "object" || target === null || Array.isArray(target)) {
|
|
641
|
+
addError(context, {
|
|
642
|
+
message: "Expected object for key validation",
|
|
643
|
+
received: target
|
|
644
|
+
});
|
|
645
|
+
return false;
|
|
646
|
+
}
|
|
647
|
+
if (!partialEqualInternal(target, partialShape, context)) {
|
|
648
|
+
return false;
|
|
649
|
+
}
|
|
650
|
+
const allowedKeys = new Set(Object.keys(partialShape));
|
|
651
|
+
for (const key of Object.keys(target)) {
|
|
652
|
+
if (!allowedKeys.has(key)) {
|
|
653
|
+
const oldPath = context.path;
|
|
654
|
+
context.path = [...oldPath, key];
|
|
655
|
+
addError(context, {
|
|
656
|
+
message: `Extra key "${key}" not expected`
|
|
657
|
+
});
|
|
658
|
+
context.path = oldPath;
|
|
659
|
+
return false;
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
if (deep) {
|
|
663
|
+
for (const key of Object.keys(partialShape)) {
|
|
664
|
+
if (typeof partialShape[key] === "object" && partialShape[key] !== null && !Array.isArray(partialShape[key]) && !isComparison(partialShape[key])) {
|
|
665
|
+
const oldPath = context.path;
|
|
666
|
+
context.path = [...oldPath, key];
|
|
667
|
+
const result = checkNoExtraKeys(
|
|
668
|
+
target[key],
|
|
669
|
+
partialShape[key],
|
|
670
|
+
context,
|
|
671
|
+
true
|
|
672
|
+
);
|
|
673
|
+
context.path = oldPath;
|
|
674
|
+
if (!result) return false;
|
|
675
|
+
}
|
|
676
|
+
}
|
|
677
|
+
}
|
|
678
|
+
return true;
|
|
679
|
+
}
|
|
680
|
+
function checkNoExtraDefinedKeys(target, partialShape, context, deep) {
|
|
681
|
+
if (typeof target !== "object" || target === null || Array.isArray(target)) {
|
|
682
|
+
addError(context, {
|
|
683
|
+
message: "Expected object for key validation",
|
|
684
|
+
received: target
|
|
685
|
+
});
|
|
686
|
+
return false;
|
|
687
|
+
}
|
|
688
|
+
if (!partialEqualInternal(target, partialShape, context)) {
|
|
689
|
+
return false;
|
|
690
|
+
}
|
|
691
|
+
const allowedKeys = new Set(Object.keys(partialShape));
|
|
692
|
+
for (const key of Object.keys(target)) {
|
|
693
|
+
if (!allowedKeys.has(key) && target[key] !== void 0) {
|
|
694
|
+
const oldPath = context.path;
|
|
695
|
+
context.path = [...oldPath, key];
|
|
696
|
+
addError(context, {
|
|
697
|
+
message: `Extra defined key "${key}" not expected`
|
|
698
|
+
});
|
|
699
|
+
context.path = oldPath;
|
|
700
|
+
return false;
|
|
701
|
+
}
|
|
702
|
+
}
|
|
703
|
+
if (deep) {
|
|
704
|
+
for (const key of Object.keys(partialShape)) {
|
|
705
|
+
if (typeof partialShape[key] === "object" && partialShape[key] !== null && !Array.isArray(partialShape[key]) && !isComparison(partialShape[key])) {
|
|
706
|
+
const oldPath = context.path;
|
|
707
|
+
context.path = [...oldPath, key];
|
|
708
|
+
const result = checkNoExtraDefinedKeys(
|
|
709
|
+
target[key],
|
|
710
|
+
partialShape[key],
|
|
711
|
+
context,
|
|
712
|
+
true
|
|
713
|
+
);
|
|
714
|
+
context.path = oldPath;
|
|
715
|
+
if (!result) return false;
|
|
716
|
+
}
|
|
717
|
+
}
|
|
718
|
+
}
|
|
719
|
+
return true;
|
|
720
|
+
}
|
|
721
|
+
function partialEqual(target, sub, returnErrors) {
|
|
722
|
+
const context = { errors: [], path: [] };
|
|
723
|
+
const result = partialEqualInternal(target, sub, context);
|
|
724
|
+
if (returnErrors) {
|
|
725
|
+
return result ? ok(void 0) : err(context.errors);
|
|
379
726
|
}
|
|
380
|
-
return
|
|
727
|
+
return result;
|
|
381
728
|
}
|
|
382
729
|
export {
|
|
383
730
|
match,
|