@modulify/validator 0.1.0 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +31 -0
- package/README.md +324 -107
- package/dist/assert.cjs +66 -0
- package/dist/assert.d.ts +16 -0
- package/dist/assert.mjs +66 -0
- package/dist/assertions.cjs +190 -92
- package/dist/assertions.d.ts +58 -2
- package/dist/assertions.mjs +191 -93
- package/dist/checkers.d.ts +8 -0
- package/dist/combinators.cjs +341 -0
- package/dist/combinators.d.ts +17 -0
- package/dist/combinators.mjs +341 -0
- package/dist/constraints.d.ts +4 -0
- package/dist/extractors.d.ts +2 -0
- package/dist/index.cjs +172 -61
- package/dist/index.d.ts +10 -4
- package/dist/index.mjs +176 -64
- package/dist/json-schema.cjs +514 -0
- package/dist/json-schema.d.ts +14 -0
- package/dist/json-schema.mjs +514 -0
- package/dist/metadata.cjs +8 -0
- package/dist/metadata.cjs.js +130 -0
- package/dist/metadata.d.ts +8 -0
- package/dist/metadata.es.js +131 -0
- package/dist/metadata.mjs +8 -0
- package/dist/predicates.cjs +40 -5
- package/dist/predicates.d.ts +25 -3
- package/dist/predicates.mjs +40 -5
- package/dist/violations.d.ts +29 -0
- package/docs/en/00-index.md +14 -0
- package/docs/en/01-shape-api.md +348 -0
- package/docs/en/02-metadata-and-introspection.md +276 -0
- package/docs/en/03-violations.md +267 -0
- package/docs/en/04-json-schema-export.md +264 -0
- package/docs/en/05-public-api.md +123 -0
- package/docs/en/06-common-recipes.md +273 -0
- package/docs/en/07-ai-reference.md +215 -0
- package/docs/en/08-violation-code-types.md +241 -0
- package/docs/ru/00-index.md +15 -0
- package/docs/ru/01-shape-api.md +348 -0
- package/docs/ru/02-metadata-and-introspection.md +276 -0
- package/docs/ru/03-violations.md +267 -0
- package/docs/ru/04-json-schema-export.md +264 -0
- package/docs/ru/05-public-api.md +123 -0
- package/docs/ru/06-common-recipes.md +273 -0
- package/docs/ru/07-ai-reference.md +215 -0
- package/docs/ru/08-violation-code-types.md +241 -0
- package/docs/ru/README.md +371 -0
- package/package.json +51 -33
- package/types/index.d.ts +789 -30
- package/types/json-schema.d.ts +75 -0
- package/dist/assertions/Assert.d.ts +0 -2
- package/dist/assertions/HasLength.d.ts +0 -7
- package/dist/assertions/check.d.ts +0 -3
- package/dist/assertions/index.d.ts +0 -16
- package/dist/runners/Each.d.ts +0 -3
- package/dist/runners/HasProperties.d.ts +0 -6
- package/dist/runners/index.d.ts +0 -2
- package/dist/runners.cjs +0 -32
- package/dist/runners.d.ts +0 -2
- package/dist/runners.mjs +0 -32
|
@@ -0,0 +1,341 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
3
|
+
const assert = require("./assert.cjs");
|
|
4
|
+
const metadata = require("./metadata.cjs.js");
|
|
5
|
+
const predicates = require("./predicates.cjs");
|
|
6
|
+
const describeConstraintMap = (constraints) => {
|
|
7
|
+
const described = {};
|
|
8
|
+
Reflect.ownKeys(constraints).forEach((key) => {
|
|
9
|
+
described[key] = metadata.describeConstraints(constraints[key]);
|
|
10
|
+
});
|
|
11
|
+
return described;
|
|
12
|
+
};
|
|
13
|
+
const normalizeRuleDescriptor = (descriptor) => {
|
|
14
|
+
if (!descriptor.metadata) {
|
|
15
|
+
return descriptor;
|
|
16
|
+
}
|
|
17
|
+
return {
|
|
18
|
+
...descriptor,
|
|
19
|
+
metadata: Object.freeze({ ...descriptor.metadata })
|
|
20
|
+
};
|
|
21
|
+
};
|
|
22
|
+
const passthrough = (kind, accepts, constraints) => metadata.attachConstraintDescriptor({
|
|
23
|
+
check(value) {
|
|
24
|
+
return accepts(value) || metadata.matchesConstraints(value, constraints);
|
|
25
|
+
},
|
|
26
|
+
run(validate, value, path) {
|
|
27
|
+
return accepts(value) ? [] : [validate(value, constraints, path)];
|
|
28
|
+
}
|
|
29
|
+
}, () => ({
|
|
30
|
+
kind,
|
|
31
|
+
child: metadata.describeConstraints(constraints)
|
|
32
|
+
}));
|
|
33
|
+
const keysOf = (value) => Object.keys(value);
|
|
34
|
+
const hasOwn = (value, key) => Object.prototype.hasOwnProperty.call(value, key);
|
|
35
|
+
const hasUnknownKeys = (value, descriptor) => Object.keys(value).some((key) => !hasOwn(descriptor, key));
|
|
36
|
+
const collectUnknownKeyViolations = (value, path, descriptor) => Object.keys(value).filter((key) => !hasOwn(descriptor, key)).map((key) => [{
|
|
37
|
+
value: value[key],
|
|
38
|
+
path: [...path, key],
|
|
39
|
+
violates: { kind: "validator", name: "shape", code: "shape.unknown-key", args: [] }
|
|
40
|
+
}]);
|
|
41
|
+
const pickDescriptor = (descriptor, keys) => {
|
|
42
|
+
const picked = {};
|
|
43
|
+
keys.forEach((key) => {
|
|
44
|
+
if (hasOwn(descriptor, key)) {
|
|
45
|
+
picked[key] = descriptor[key];
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
return picked;
|
|
49
|
+
};
|
|
50
|
+
const omitDescriptor = (descriptor, keys) => {
|
|
51
|
+
const omitted = { ...descriptor };
|
|
52
|
+
keys.forEach((key) => {
|
|
53
|
+
delete omitted[key];
|
|
54
|
+
});
|
|
55
|
+
return omitted;
|
|
56
|
+
};
|
|
57
|
+
const extendDescriptor = (descriptor, extension) => ({
|
|
58
|
+
...descriptor,
|
|
59
|
+
...extension
|
|
60
|
+
});
|
|
61
|
+
const partialDescriptor = (descriptor) => {
|
|
62
|
+
const partial = {};
|
|
63
|
+
keysOf(descriptor).forEach((key) => {
|
|
64
|
+
partial[key] = optional(descriptor[key]);
|
|
65
|
+
});
|
|
66
|
+
return partial;
|
|
67
|
+
};
|
|
68
|
+
const getPathValue = (value, path) => path.reduce((current, key) => {
|
|
69
|
+
if (current === null || current === void 0) {
|
|
70
|
+
return void 0;
|
|
71
|
+
}
|
|
72
|
+
return Reflect.get(Object(current), key);
|
|
73
|
+
}, value);
|
|
74
|
+
const toPath = (selector) => Array.isArray(selector) ? [...selector] : [selector];
|
|
75
|
+
const collectShapeRefinementViolations = (value, path, refinements) => refinements.flatMap((refinement) => {
|
|
76
|
+
const result = refinement(value);
|
|
77
|
+
if (result === null || result === void 0) {
|
|
78
|
+
return [];
|
|
79
|
+
}
|
|
80
|
+
return metadata.arrayify(result).filter((issue) => issue !== null && issue !== void 0).map((issue) => {
|
|
81
|
+
const issuePath = issue.path ? [...issue.path] : [];
|
|
82
|
+
return {
|
|
83
|
+
value: issue.value ?? getPathValue(value, issuePath),
|
|
84
|
+
path: [...path, ...issuePath],
|
|
85
|
+
violates: {
|
|
86
|
+
kind: "validator",
|
|
87
|
+
name: "shape",
|
|
88
|
+
code: issue.code,
|
|
89
|
+
args: issue.args ?? []
|
|
90
|
+
}
|
|
91
|
+
};
|
|
92
|
+
});
|
|
93
|
+
});
|
|
94
|
+
const createObjectShape = (descriptor, unknownKeys, refinements, rules) => {
|
|
95
|
+
const keys = keysOf(descriptor);
|
|
96
|
+
return metadata.attachConstraintDescriptor({
|
|
97
|
+
descriptor,
|
|
98
|
+
unknownKeys,
|
|
99
|
+
check(value) {
|
|
100
|
+
return predicates.isRecord(value) && keys.every((key) => metadata.matchesConstraints(value[key], descriptor[key])) && (unknownKeys === "passthrough" || !hasUnknownKeys(value, descriptor)) && collectShapeRefinementViolations(value, [], refinements).length === 0;
|
|
101
|
+
},
|
|
102
|
+
run(validate, value, path) {
|
|
103
|
+
if (!predicates.isRecord(value)) {
|
|
104
|
+
return [[{
|
|
105
|
+
value,
|
|
106
|
+
path,
|
|
107
|
+
violates: { kind: "validator", name: "shape", code: "type.record", args: [] }
|
|
108
|
+
}]];
|
|
109
|
+
}
|
|
110
|
+
const validations = keys.reduce((all, key) => [
|
|
111
|
+
...all,
|
|
112
|
+
validate(value[key], descriptor[key], [...path, key])
|
|
113
|
+
], []);
|
|
114
|
+
if (unknownKeys === "strict") {
|
|
115
|
+
validations.push(...collectUnknownKeyViolations(value, path, descriptor));
|
|
116
|
+
}
|
|
117
|
+
if (validations.some((validation) => validation instanceof Promise)) {
|
|
118
|
+
return [Promise.all(validations.map((validation) => Promise.resolve(validation))).then((results) => {
|
|
119
|
+
const structuralViolations2 = results.flat();
|
|
120
|
+
return structuralViolations2.length > 0 ? structuralViolations2 : collectShapeRefinementViolations(value, path, refinements);
|
|
121
|
+
})];
|
|
122
|
+
}
|
|
123
|
+
const structuralViolations = validations.flat();
|
|
124
|
+
return structuralViolations.length > 0 ? validations : [collectShapeRefinementViolations(value, path, refinements)];
|
|
125
|
+
},
|
|
126
|
+
refine(refinement, ruleDescriptor = { kind: "refine" }) {
|
|
127
|
+
return createObjectShape(
|
|
128
|
+
descriptor,
|
|
129
|
+
unknownKeys,
|
|
130
|
+
[...refinements, refinement],
|
|
131
|
+
[...rules, normalizeRuleDescriptor(ruleDescriptor)]
|
|
132
|
+
);
|
|
133
|
+
},
|
|
134
|
+
fieldsMatch(selectedKeys) {
|
|
135
|
+
const [left, right] = selectedKeys;
|
|
136
|
+
const leftPath = toPath(left);
|
|
137
|
+
const rightPath = toPath(right);
|
|
138
|
+
const ruleDescriptor = {
|
|
139
|
+
kind: "fieldsMatch",
|
|
140
|
+
selectors: [left, right]
|
|
141
|
+
};
|
|
142
|
+
return createObjectShape(
|
|
143
|
+
descriptor,
|
|
144
|
+
unknownKeys,
|
|
145
|
+
[...refinements, (value) => {
|
|
146
|
+
return getPathValue(value, leftPath) === getPathValue(value, rightPath) ? [] : [{
|
|
147
|
+
path: rightPath,
|
|
148
|
+
code: "shape.fields.mismatch",
|
|
149
|
+
args: [selectedKeys]
|
|
150
|
+
}];
|
|
151
|
+
}],
|
|
152
|
+
[...rules, ruleDescriptor]
|
|
153
|
+
);
|
|
154
|
+
},
|
|
155
|
+
strict() {
|
|
156
|
+
return createObjectShape(descriptor, "strict", refinements, rules);
|
|
157
|
+
},
|
|
158
|
+
passthrough() {
|
|
159
|
+
return createObjectShape(descriptor, "passthrough", refinements, rules);
|
|
160
|
+
},
|
|
161
|
+
pick(selectedKeys) {
|
|
162
|
+
return createObjectShape(pickDescriptor(descriptor, selectedKeys), unknownKeys, [], []);
|
|
163
|
+
},
|
|
164
|
+
omit(selectedKeys) {
|
|
165
|
+
return createObjectShape(omitDescriptor(descriptor, selectedKeys), unknownKeys, [], []);
|
|
166
|
+
},
|
|
167
|
+
partial() {
|
|
168
|
+
return createObjectShape(partialDescriptor(descriptor), unknownKeys, [], []);
|
|
169
|
+
},
|
|
170
|
+
extend(extension) {
|
|
171
|
+
return createObjectShape(extendDescriptor(descriptor, extension), unknownKeys, [], []);
|
|
172
|
+
},
|
|
173
|
+
merge(shape2) {
|
|
174
|
+
return createObjectShape(extendDescriptor(descriptor, shape2.descriptor), unknownKeys, [], []);
|
|
175
|
+
}
|
|
176
|
+
}, () => ({
|
|
177
|
+
kind: "shape",
|
|
178
|
+
unknownKeys,
|
|
179
|
+
fields: describeConstraintMap(descriptor),
|
|
180
|
+
rules
|
|
181
|
+
}));
|
|
182
|
+
};
|
|
183
|
+
const toUnionFailure = (branches, value, path, branchResults) => {
|
|
184
|
+
const matched = branchResults.find((result) => result.length === 0);
|
|
185
|
+
if (matched) {
|
|
186
|
+
return matched;
|
|
187
|
+
}
|
|
188
|
+
return [{
|
|
189
|
+
value,
|
|
190
|
+
path,
|
|
191
|
+
violates: { kind: "validator", name: "union", code: "union.no-match", args: [branches.length] }
|
|
192
|
+
}, ...branchResults.flat()];
|
|
193
|
+
};
|
|
194
|
+
const collectUnionViolations = (branches, validate, value, path) => {
|
|
195
|
+
const branchResults = branches.map((branch) => validate(value, branch, path));
|
|
196
|
+
if (branchResults.some((result) => result instanceof Promise)) {
|
|
197
|
+
return Promise.all(branchResults.map((result) => Promise.resolve(result))).then((results) => {
|
|
198
|
+
return toUnionFailure(branches, value, path, results);
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
return toUnionFailure(branches, value, path, branchResults);
|
|
202
|
+
};
|
|
203
|
+
const each = (constraints) => metadata.attachConstraintDescriptor({
|
|
204
|
+
check(value) {
|
|
205
|
+
return predicates.isArray(value) && value.every((item) => metadata.matchesConstraints(item, constraints));
|
|
206
|
+
},
|
|
207
|
+
run(validate, value, path) {
|
|
208
|
+
return predicates.isArray(value) ? value.map((item, index) => validate(item, constraints, [...path, index])) : [[{
|
|
209
|
+
value,
|
|
210
|
+
path,
|
|
211
|
+
violates: { kind: "validator", name: "each", code: "type.array", args: [] }
|
|
212
|
+
}]];
|
|
213
|
+
}
|
|
214
|
+
}, () => ({
|
|
215
|
+
kind: "each",
|
|
216
|
+
item: metadata.describeConstraints(constraints)
|
|
217
|
+
}));
|
|
218
|
+
const tuple = (constraints) => metadata.attachConstraintDescriptor({
|
|
219
|
+
check(value) {
|
|
220
|
+
return predicates.isArray(value) && value.length === constraints.length && constraints.every((constraint, index) => metadata.matchesConstraints(value[index], constraint));
|
|
221
|
+
},
|
|
222
|
+
run(validate, value, path) {
|
|
223
|
+
if (!predicates.isArray(value)) {
|
|
224
|
+
return [[{
|
|
225
|
+
value,
|
|
226
|
+
path,
|
|
227
|
+
violates: { kind: "validator", name: "tuple", code: "type.array", args: [] }
|
|
228
|
+
}]];
|
|
229
|
+
}
|
|
230
|
+
if (value.length !== constraints.length) {
|
|
231
|
+
return [[{
|
|
232
|
+
value,
|
|
233
|
+
path,
|
|
234
|
+
violates: { kind: "validator", name: "tuple", code: "tuple.length", args: [constraints.length] }
|
|
235
|
+
}]];
|
|
236
|
+
}
|
|
237
|
+
return constraints.map((constraint, index) => validate(value[index], constraint, [...path, index]));
|
|
238
|
+
}
|
|
239
|
+
}, () => ({
|
|
240
|
+
kind: "tuple",
|
|
241
|
+
items: constraints.map((constraint) => metadata.describeConstraints(constraint))
|
|
242
|
+
}));
|
|
243
|
+
const union = (constraints) => metadata.attachConstraintDescriptor({
|
|
244
|
+
check(value) {
|
|
245
|
+
return constraints.some((constraint) => metadata.matchesConstraints(value, constraint));
|
|
246
|
+
},
|
|
247
|
+
run(validate, value, path) {
|
|
248
|
+
return [collectUnionViolations(constraints, validate, value, path)];
|
|
249
|
+
}
|
|
250
|
+
}, () => ({
|
|
251
|
+
kind: "union",
|
|
252
|
+
branches: constraints.map((constraint) => metadata.describeConstraints(constraint))
|
|
253
|
+
}));
|
|
254
|
+
const discriminatedUnion = (key, variants) => metadata.attachConstraintDescriptor({
|
|
255
|
+
check(value) {
|
|
256
|
+
if (!predicates.isRecord(value)) {
|
|
257
|
+
return false;
|
|
258
|
+
}
|
|
259
|
+
const discriminator = value[key];
|
|
260
|
+
return Object.prototype.hasOwnProperty.call(variants, discriminator) && metadata.matchesConstraints(value, variants[discriminator]);
|
|
261
|
+
},
|
|
262
|
+
run(validate, value, path) {
|
|
263
|
+
if (!predicates.isRecord(value)) {
|
|
264
|
+
return [[{
|
|
265
|
+
value,
|
|
266
|
+
path,
|
|
267
|
+
violates: { kind: "validator", name: "discriminatedUnion", code: "type.record", args: [] }
|
|
268
|
+
}]];
|
|
269
|
+
}
|
|
270
|
+
const discriminator = value[key];
|
|
271
|
+
if (!Object.prototype.hasOwnProperty.call(variants, discriminator)) {
|
|
272
|
+
return [[{
|
|
273
|
+
value: discriminator,
|
|
274
|
+
path: [...path, key],
|
|
275
|
+
violates: {
|
|
276
|
+
kind: "validator",
|
|
277
|
+
name: "discriminatedUnion",
|
|
278
|
+
code: "union.invalid-discriminator",
|
|
279
|
+
args: [Reflect.ownKeys(variants)]
|
|
280
|
+
}
|
|
281
|
+
}]];
|
|
282
|
+
}
|
|
283
|
+
return [validate(value, variants[discriminator], path)];
|
|
284
|
+
}
|
|
285
|
+
}, () => ({
|
|
286
|
+
kind: "discriminatedUnion",
|
|
287
|
+
key,
|
|
288
|
+
variants: describeConstraintMap(variants)
|
|
289
|
+
}));
|
|
290
|
+
const record = (constraints) => metadata.attachConstraintDescriptor({
|
|
291
|
+
check(value) {
|
|
292
|
+
return predicates.isRecord(value) && Object.values(value).every((item) => metadata.matchesConstraints(item, constraints));
|
|
293
|
+
},
|
|
294
|
+
run(validate, value, path) {
|
|
295
|
+
if (!predicates.isRecord(value)) {
|
|
296
|
+
return [[{
|
|
297
|
+
value,
|
|
298
|
+
path,
|
|
299
|
+
violates: { kind: "validator", name: "record", code: "type.record", args: [] }
|
|
300
|
+
}]];
|
|
301
|
+
}
|
|
302
|
+
return Object.keys(value).map((key) => validate(value[key], constraints, [...path, key]));
|
|
303
|
+
}
|
|
304
|
+
}, () => ({
|
|
305
|
+
kind: "record",
|
|
306
|
+
values: metadata.describeConstraints(constraints)
|
|
307
|
+
}));
|
|
308
|
+
const shape = (descriptor) => {
|
|
309
|
+
return createObjectShape(descriptor, "passthrough", [], []);
|
|
310
|
+
};
|
|
311
|
+
const exact = (value) => assert.assert(predicates.isExact(value), {
|
|
312
|
+
name: "exact",
|
|
313
|
+
bail: true,
|
|
314
|
+
code: "value.exact",
|
|
315
|
+
args: [value]
|
|
316
|
+
});
|
|
317
|
+
const optional = (constraints) => passthrough(
|
|
318
|
+
"optional",
|
|
319
|
+
predicates.isUndefined,
|
|
320
|
+
constraints
|
|
321
|
+
);
|
|
322
|
+
const nullable = (constraints) => passthrough(
|
|
323
|
+
"nullable",
|
|
324
|
+
predicates.isNull,
|
|
325
|
+
constraints
|
|
326
|
+
);
|
|
327
|
+
const nullish = (constraints) => passthrough(
|
|
328
|
+
"nullish",
|
|
329
|
+
(value) => predicates.isNull(value) || predicates.isUndefined(value),
|
|
330
|
+
constraints
|
|
331
|
+
);
|
|
332
|
+
exports.discriminatedUnion = discriminatedUnion;
|
|
333
|
+
exports.each = each;
|
|
334
|
+
exports.exact = exact;
|
|
335
|
+
exports.nullable = nullable;
|
|
336
|
+
exports.nullish = nullish;
|
|
337
|
+
exports.optional = optional;
|
|
338
|
+
exports.record = record;
|
|
339
|
+
exports.shape = shape;
|
|
340
|
+
exports.tuple = tuple;
|
|
341
|
+
exports.union = union;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { Constraint, DiscriminatedUnionValidator, EachValidator, InferConstraints, MaybeMany, NullableValidator, NullishValidator, ObjectShape, OptionalValidator, RecordValidator, TupleValidator, UnionValidator, Assertion } from '../types';
|
|
2
|
+
export type ShapeDescriptor = Record<PropertyKey, MaybeMany<Constraint>>;
|
|
3
|
+
export type InferShape<D extends ShapeDescriptor> = {
|
|
4
|
+
[K in keyof D]: InferConstraints<D[K]>;
|
|
5
|
+
};
|
|
6
|
+
export type { ObjectShapeFieldSelector, ObjectShape, ObjectShapeRefinement, ObjectShapeRefinementIssue, UnknownKeysMode, } from '../types';
|
|
7
|
+
type VariantMap = Record<PropertyKey, MaybeMany<Constraint>>;
|
|
8
|
+
export declare const each: <const C extends MaybeMany<Constraint>>(constraints: C) => EachValidator<C>;
|
|
9
|
+
export declare const tuple: <const T extends readonly MaybeMany<Constraint>[]>(constraints: T) => TupleValidator<T>;
|
|
10
|
+
export declare const union: <const T extends readonly MaybeMany<Constraint>[]>(constraints: T) => UnionValidator<T>;
|
|
11
|
+
export declare const discriminatedUnion: <const K extends PropertyKey, const T extends VariantMap>(key: K, variants: T) => DiscriminatedUnionValidator<K, T>;
|
|
12
|
+
export declare const record: <const C extends MaybeMany<Constraint>>(constraints: C) => RecordValidator<C>;
|
|
13
|
+
export declare const shape: <const D extends ShapeDescriptor>(descriptor: D) => ObjectShape<D, "passthrough", [], never>;
|
|
14
|
+
export declare const exact: <const T>(value: T) => Assertion<T, [], "value.exact", readonly [T], "exact">;
|
|
15
|
+
export declare const optional: <const C extends MaybeMany<Constraint>>(constraints: C) => OptionalValidator<C>;
|
|
16
|
+
export declare const nullable: <const C extends MaybeMany<Constraint>>(constraints: C) => NullableValidator<C>;
|
|
17
|
+
export declare const nullish: <const C extends MaybeMany<Constraint>>(constraints: C) => NullishValidator<C>;
|
|
@@ -0,0 +1,341 @@
|
|
|
1
|
+
import { assert } from "./assert.mjs";
|
|
2
|
+
import { e as attachConstraintDescriptor, f as matchesConstraints, d as describeConstraints, a as arrayify } from "./metadata.es.js";
|
|
3
|
+
import { isRecord, isArray, isExact, isUndefined, isNull } from "./predicates.mjs";
|
|
4
|
+
const describeConstraintMap = (constraints) => {
|
|
5
|
+
const described = {};
|
|
6
|
+
Reflect.ownKeys(constraints).forEach((key) => {
|
|
7
|
+
described[key] = describeConstraints(constraints[key]);
|
|
8
|
+
});
|
|
9
|
+
return described;
|
|
10
|
+
};
|
|
11
|
+
const normalizeRuleDescriptor = (descriptor) => {
|
|
12
|
+
if (!descriptor.metadata) {
|
|
13
|
+
return descriptor;
|
|
14
|
+
}
|
|
15
|
+
return {
|
|
16
|
+
...descriptor,
|
|
17
|
+
metadata: Object.freeze({ ...descriptor.metadata })
|
|
18
|
+
};
|
|
19
|
+
};
|
|
20
|
+
const passthrough = (kind, accepts, constraints) => attachConstraintDescriptor({
|
|
21
|
+
check(value) {
|
|
22
|
+
return accepts(value) || matchesConstraints(value, constraints);
|
|
23
|
+
},
|
|
24
|
+
run(validate, value, path) {
|
|
25
|
+
return accepts(value) ? [] : [validate(value, constraints, path)];
|
|
26
|
+
}
|
|
27
|
+
}, () => ({
|
|
28
|
+
kind,
|
|
29
|
+
child: describeConstraints(constraints)
|
|
30
|
+
}));
|
|
31
|
+
const keysOf = (value) => Object.keys(value);
|
|
32
|
+
const hasOwn = (value, key) => Object.prototype.hasOwnProperty.call(value, key);
|
|
33
|
+
const hasUnknownKeys = (value, descriptor) => Object.keys(value).some((key) => !hasOwn(descriptor, key));
|
|
34
|
+
const collectUnknownKeyViolations = (value, path, descriptor) => Object.keys(value).filter((key) => !hasOwn(descriptor, key)).map((key) => [{
|
|
35
|
+
value: value[key],
|
|
36
|
+
path: [...path, key],
|
|
37
|
+
violates: { kind: "validator", name: "shape", code: "shape.unknown-key", args: [] }
|
|
38
|
+
}]);
|
|
39
|
+
const pickDescriptor = (descriptor, keys) => {
|
|
40
|
+
const picked = {};
|
|
41
|
+
keys.forEach((key) => {
|
|
42
|
+
if (hasOwn(descriptor, key)) {
|
|
43
|
+
picked[key] = descriptor[key];
|
|
44
|
+
}
|
|
45
|
+
});
|
|
46
|
+
return picked;
|
|
47
|
+
};
|
|
48
|
+
const omitDescriptor = (descriptor, keys) => {
|
|
49
|
+
const omitted = { ...descriptor };
|
|
50
|
+
keys.forEach((key) => {
|
|
51
|
+
delete omitted[key];
|
|
52
|
+
});
|
|
53
|
+
return omitted;
|
|
54
|
+
};
|
|
55
|
+
const extendDescriptor = (descriptor, extension) => ({
|
|
56
|
+
...descriptor,
|
|
57
|
+
...extension
|
|
58
|
+
});
|
|
59
|
+
const partialDescriptor = (descriptor) => {
|
|
60
|
+
const partial = {};
|
|
61
|
+
keysOf(descriptor).forEach((key) => {
|
|
62
|
+
partial[key] = optional(descriptor[key]);
|
|
63
|
+
});
|
|
64
|
+
return partial;
|
|
65
|
+
};
|
|
66
|
+
const getPathValue = (value, path) => path.reduce((current, key) => {
|
|
67
|
+
if (current === null || current === void 0) {
|
|
68
|
+
return void 0;
|
|
69
|
+
}
|
|
70
|
+
return Reflect.get(Object(current), key);
|
|
71
|
+
}, value);
|
|
72
|
+
const toPath = (selector) => Array.isArray(selector) ? [...selector] : [selector];
|
|
73
|
+
const collectShapeRefinementViolations = (value, path, refinements) => refinements.flatMap((refinement) => {
|
|
74
|
+
const result = refinement(value);
|
|
75
|
+
if (result === null || result === void 0) {
|
|
76
|
+
return [];
|
|
77
|
+
}
|
|
78
|
+
return arrayify(result).filter((issue) => issue !== null && issue !== void 0).map((issue) => {
|
|
79
|
+
const issuePath = issue.path ? [...issue.path] : [];
|
|
80
|
+
return {
|
|
81
|
+
value: issue.value ?? getPathValue(value, issuePath),
|
|
82
|
+
path: [...path, ...issuePath],
|
|
83
|
+
violates: {
|
|
84
|
+
kind: "validator",
|
|
85
|
+
name: "shape",
|
|
86
|
+
code: issue.code,
|
|
87
|
+
args: issue.args ?? []
|
|
88
|
+
}
|
|
89
|
+
};
|
|
90
|
+
});
|
|
91
|
+
});
|
|
92
|
+
const createObjectShape = (descriptor, unknownKeys, refinements, rules) => {
|
|
93
|
+
const keys = keysOf(descriptor);
|
|
94
|
+
return attachConstraintDescriptor({
|
|
95
|
+
descriptor,
|
|
96
|
+
unknownKeys,
|
|
97
|
+
check(value) {
|
|
98
|
+
return isRecord(value) && keys.every((key) => matchesConstraints(value[key], descriptor[key])) && (unknownKeys === "passthrough" || !hasUnknownKeys(value, descriptor)) && collectShapeRefinementViolations(value, [], refinements).length === 0;
|
|
99
|
+
},
|
|
100
|
+
run(validate, value, path) {
|
|
101
|
+
if (!isRecord(value)) {
|
|
102
|
+
return [[{
|
|
103
|
+
value,
|
|
104
|
+
path,
|
|
105
|
+
violates: { kind: "validator", name: "shape", code: "type.record", args: [] }
|
|
106
|
+
}]];
|
|
107
|
+
}
|
|
108
|
+
const validations = keys.reduce((all, key) => [
|
|
109
|
+
...all,
|
|
110
|
+
validate(value[key], descriptor[key], [...path, key])
|
|
111
|
+
], []);
|
|
112
|
+
if (unknownKeys === "strict") {
|
|
113
|
+
validations.push(...collectUnknownKeyViolations(value, path, descriptor));
|
|
114
|
+
}
|
|
115
|
+
if (validations.some((validation) => validation instanceof Promise)) {
|
|
116
|
+
return [Promise.all(validations.map((validation) => Promise.resolve(validation))).then((results) => {
|
|
117
|
+
const structuralViolations2 = results.flat();
|
|
118
|
+
return structuralViolations2.length > 0 ? structuralViolations2 : collectShapeRefinementViolations(value, path, refinements);
|
|
119
|
+
})];
|
|
120
|
+
}
|
|
121
|
+
const structuralViolations = validations.flat();
|
|
122
|
+
return structuralViolations.length > 0 ? validations : [collectShapeRefinementViolations(value, path, refinements)];
|
|
123
|
+
},
|
|
124
|
+
refine(refinement, ruleDescriptor = { kind: "refine" }) {
|
|
125
|
+
return createObjectShape(
|
|
126
|
+
descriptor,
|
|
127
|
+
unknownKeys,
|
|
128
|
+
[...refinements, refinement],
|
|
129
|
+
[...rules, normalizeRuleDescriptor(ruleDescriptor)]
|
|
130
|
+
);
|
|
131
|
+
},
|
|
132
|
+
fieldsMatch(selectedKeys) {
|
|
133
|
+
const [left, right] = selectedKeys;
|
|
134
|
+
const leftPath = toPath(left);
|
|
135
|
+
const rightPath = toPath(right);
|
|
136
|
+
const ruleDescriptor = {
|
|
137
|
+
kind: "fieldsMatch",
|
|
138
|
+
selectors: [left, right]
|
|
139
|
+
};
|
|
140
|
+
return createObjectShape(
|
|
141
|
+
descriptor,
|
|
142
|
+
unknownKeys,
|
|
143
|
+
[...refinements, (value) => {
|
|
144
|
+
return getPathValue(value, leftPath) === getPathValue(value, rightPath) ? [] : [{
|
|
145
|
+
path: rightPath,
|
|
146
|
+
code: "shape.fields.mismatch",
|
|
147
|
+
args: [selectedKeys]
|
|
148
|
+
}];
|
|
149
|
+
}],
|
|
150
|
+
[...rules, ruleDescriptor]
|
|
151
|
+
);
|
|
152
|
+
},
|
|
153
|
+
strict() {
|
|
154
|
+
return createObjectShape(descriptor, "strict", refinements, rules);
|
|
155
|
+
},
|
|
156
|
+
passthrough() {
|
|
157
|
+
return createObjectShape(descriptor, "passthrough", refinements, rules);
|
|
158
|
+
},
|
|
159
|
+
pick(selectedKeys) {
|
|
160
|
+
return createObjectShape(pickDescriptor(descriptor, selectedKeys), unknownKeys, [], []);
|
|
161
|
+
},
|
|
162
|
+
omit(selectedKeys) {
|
|
163
|
+
return createObjectShape(omitDescriptor(descriptor, selectedKeys), unknownKeys, [], []);
|
|
164
|
+
},
|
|
165
|
+
partial() {
|
|
166
|
+
return createObjectShape(partialDescriptor(descriptor), unknownKeys, [], []);
|
|
167
|
+
},
|
|
168
|
+
extend(extension) {
|
|
169
|
+
return createObjectShape(extendDescriptor(descriptor, extension), unknownKeys, [], []);
|
|
170
|
+
},
|
|
171
|
+
merge(shape2) {
|
|
172
|
+
return createObjectShape(extendDescriptor(descriptor, shape2.descriptor), unknownKeys, [], []);
|
|
173
|
+
}
|
|
174
|
+
}, () => ({
|
|
175
|
+
kind: "shape",
|
|
176
|
+
unknownKeys,
|
|
177
|
+
fields: describeConstraintMap(descriptor),
|
|
178
|
+
rules
|
|
179
|
+
}));
|
|
180
|
+
};
|
|
181
|
+
const toUnionFailure = (branches, value, path, branchResults) => {
|
|
182
|
+
const matched = branchResults.find((result) => result.length === 0);
|
|
183
|
+
if (matched) {
|
|
184
|
+
return matched;
|
|
185
|
+
}
|
|
186
|
+
return [{
|
|
187
|
+
value,
|
|
188
|
+
path,
|
|
189
|
+
violates: { kind: "validator", name: "union", code: "union.no-match", args: [branches.length] }
|
|
190
|
+
}, ...branchResults.flat()];
|
|
191
|
+
};
|
|
192
|
+
const collectUnionViolations = (branches, validate, value, path) => {
|
|
193
|
+
const branchResults = branches.map((branch) => validate(value, branch, path));
|
|
194
|
+
if (branchResults.some((result) => result instanceof Promise)) {
|
|
195
|
+
return Promise.all(branchResults.map((result) => Promise.resolve(result))).then((results) => {
|
|
196
|
+
return toUnionFailure(branches, value, path, results);
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
return toUnionFailure(branches, value, path, branchResults);
|
|
200
|
+
};
|
|
201
|
+
const each = (constraints) => attachConstraintDescriptor({
|
|
202
|
+
check(value) {
|
|
203
|
+
return isArray(value) && value.every((item) => matchesConstraints(item, constraints));
|
|
204
|
+
},
|
|
205
|
+
run(validate, value, path) {
|
|
206
|
+
return isArray(value) ? value.map((item, index) => validate(item, constraints, [...path, index])) : [[{
|
|
207
|
+
value,
|
|
208
|
+
path,
|
|
209
|
+
violates: { kind: "validator", name: "each", code: "type.array", args: [] }
|
|
210
|
+
}]];
|
|
211
|
+
}
|
|
212
|
+
}, () => ({
|
|
213
|
+
kind: "each",
|
|
214
|
+
item: describeConstraints(constraints)
|
|
215
|
+
}));
|
|
216
|
+
const tuple = (constraints) => attachConstraintDescriptor({
|
|
217
|
+
check(value) {
|
|
218
|
+
return isArray(value) && value.length === constraints.length && constraints.every((constraint, index) => matchesConstraints(value[index], constraint));
|
|
219
|
+
},
|
|
220
|
+
run(validate, value, path) {
|
|
221
|
+
if (!isArray(value)) {
|
|
222
|
+
return [[{
|
|
223
|
+
value,
|
|
224
|
+
path,
|
|
225
|
+
violates: { kind: "validator", name: "tuple", code: "type.array", args: [] }
|
|
226
|
+
}]];
|
|
227
|
+
}
|
|
228
|
+
if (value.length !== constraints.length) {
|
|
229
|
+
return [[{
|
|
230
|
+
value,
|
|
231
|
+
path,
|
|
232
|
+
violates: { kind: "validator", name: "tuple", code: "tuple.length", args: [constraints.length] }
|
|
233
|
+
}]];
|
|
234
|
+
}
|
|
235
|
+
return constraints.map((constraint, index) => validate(value[index], constraint, [...path, index]));
|
|
236
|
+
}
|
|
237
|
+
}, () => ({
|
|
238
|
+
kind: "tuple",
|
|
239
|
+
items: constraints.map((constraint) => describeConstraints(constraint))
|
|
240
|
+
}));
|
|
241
|
+
const union = (constraints) => attachConstraintDescriptor({
|
|
242
|
+
check(value) {
|
|
243
|
+
return constraints.some((constraint) => matchesConstraints(value, constraint));
|
|
244
|
+
},
|
|
245
|
+
run(validate, value, path) {
|
|
246
|
+
return [collectUnionViolations(constraints, validate, value, path)];
|
|
247
|
+
}
|
|
248
|
+
}, () => ({
|
|
249
|
+
kind: "union",
|
|
250
|
+
branches: constraints.map((constraint) => describeConstraints(constraint))
|
|
251
|
+
}));
|
|
252
|
+
const discriminatedUnion = (key, variants) => attachConstraintDescriptor({
|
|
253
|
+
check(value) {
|
|
254
|
+
if (!isRecord(value)) {
|
|
255
|
+
return false;
|
|
256
|
+
}
|
|
257
|
+
const discriminator = value[key];
|
|
258
|
+
return Object.prototype.hasOwnProperty.call(variants, discriminator) && matchesConstraints(value, variants[discriminator]);
|
|
259
|
+
},
|
|
260
|
+
run(validate, value, path) {
|
|
261
|
+
if (!isRecord(value)) {
|
|
262
|
+
return [[{
|
|
263
|
+
value,
|
|
264
|
+
path,
|
|
265
|
+
violates: { kind: "validator", name: "discriminatedUnion", code: "type.record", args: [] }
|
|
266
|
+
}]];
|
|
267
|
+
}
|
|
268
|
+
const discriminator = value[key];
|
|
269
|
+
if (!Object.prototype.hasOwnProperty.call(variants, discriminator)) {
|
|
270
|
+
return [[{
|
|
271
|
+
value: discriminator,
|
|
272
|
+
path: [...path, key],
|
|
273
|
+
violates: {
|
|
274
|
+
kind: "validator",
|
|
275
|
+
name: "discriminatedUnion",
|
|
276
|
+
code: "union.invalid-discriminator",
|
|
277
|
+
args: [Reflect.ownKeys(variants)]
|
|
278
|
+
}
|
|
279
|
+
}]];
|
|
280
|
+
}
|
|
281
|
+
return [validate(value, variants[discriminator], path)];
|
|
282
|
+
}
|
|
283
|
+
}, () => ({
|
|
284
|
+
kind: "discriminatedUnion",
|
|
285
|
+
key,
|
|
286
|
+
variants: describeConstraintMap(variants)
|
|
287
|
+
}));
|
|
288
|
+
const record = (constraints) => attachConstraintDescriptor({
|
|
289
|
+
check(value) {
|
|
290
|
+
return isRecord(value) && Object.values(value).every((item) => matchesConstraints(item, constraints));
|
|
291
|
+
},
|
|
292
|
+
run(validate, value, path) {
|
|
293
|
+
if (!isRecord(value)) {
|
|
294
|
+
return [[{
|
|
295
|
+
value,
|
|
296
|
+
path,
|
|
297
|
+
violates: { kind: "validator", name: "record", code: "type.record", args: [] }
|
|
298
|
+
}]];
|
|
299
|
+
}
|
|
300
|
+
return Object.keys(value).map((key) => validate(value[key], constraints, [...path, key]));
|
|
301
|
+
}
|
|
302
|
+
}, () => ({
|
|
303
|
+
kind: "record",
|
|
304
|
+
values: describeConstraints(constraints)
|
|
305
|
+
}));
|
|
306
|
+
const shape = (descriptor) => {
|
|
307
|
+
return createObjectShape(descriptor, "passthrough", [], []);
|
|
308
|
+
};
|
|
309
|
+
const exact = (value) => assert(isExact(value), {
|
|
310
|
+
name: "exact",
|
|
311
|
+
bail: true,
|
|
312
|
+
code: "value.exact",
|
|
313
|
+
args: [value]
|
|
314
|
+
});
|
|
315
|
+
const optional = (constraints) => passthrough(
|
|
316
|
+
"optional",
|
|
317
|
+
isUndefined,
|
|
318
|
+
constraints
|
|
319
|
+
);
|
|
320
|
+
const nullable = (constraints) => passthrough(
|
|
321
|
+
"nullable",
|
|
322
|
+
isNull,
|
|
323
|
+
constraints
|
|
324
|
+
);
|
|
325
|
+
const nullish = (constraints) => passthrough(
|
|
326
|
+
"nullish",
|
|
327
|
+
(value) => isNull(value) || isUndefined(value),
|
|
328
|
+
constraints
|
|
329
|
+
);
|
|
330
|
+
export {
|
|
331
|
+
discriminatedUnion,
|
|
332
|
+
each,
|
|
333
|
+
exact,
|
|
334
|
+
nullable,
|
|
335
|
+
nullish,
|
|
336
|
+
optional,
|
|
337
|
+
record,
|
|
338
|
+
shape,
|
|
339
|
+
tuple,
|
|
340
|
+
union
|
|
341
|
+
};
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { Constraint, InferConstraints, MaybeMany, Validator } from '../types';
|
|
2
|
+
export declare const isValidator: <T = unknown>(constraint: Constraint<T>) => constraint is Validator<T>;
|
|
3
|
+
export declare function arrayify<T>(value: MaybeMany<T>): T[];
|
|
4
|
+
export declare function matchesConstraints<C extends MaybeMany<Constraint>>(value: unknown, constraints: C): value is InferConstraints<C>;
|