anyvali 0.3.1 → 0.3.3
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 +60 -44
- package/README.md +370 -370
- package/dist/schemas/optional.d.ts.map +1 -1
- package/dist/schemas/optional.js +4 -3
- package/dist/schemas/optional.js.map +1 -1
- package/package.json +40 -40
- package/sdk/js/CHANGELOG.md +13 -13
- package/src/format/validators.ts +71 -71
- package/src/index.ts +285 -285
- package/src/infer.ts +12 -12
- package/src/interchange/importer.ts +285 -285
- package/src/issue-codes.ts +19 -19
- package/src/schemas/base.ts +322 -322
- package/src/schemas/intersection.ts +81 -81
- package/src/schemas/object.ts +203 -203
- package/src/schemas/optional.ts +4 -3
- package/src/schemas/record.ts +55 -55
- package/src/schemas/string.ts +192 -192
- package/src/schemas/union.ts +53 -53
- package/src/types.ts +239 -239
- package/tests/unit/collections.test.ts +99 -99
- package/tests/unit/date-format.test.ts +18 -18
- package/tests/unit/default-mutation.test.ts +32 -32
- package/tests/unit/defaults.test.ts +70 -1
- package/tests/unit/inference.test.ts +306 -306
- package/tests/unit/interchange.test.ts +191 -191
- package/tests/unit/object.test.ts +208 -208
- package/tests/unit/security-recursion.test.ts +105 -105
- package/tests/unit/security.test.ts +945 -945
- package/tests/unit/shared-ref-falsepos.test.ts +33 -33
- package/tests/unit/string-pattern-redos.test.ts +46 -46
- package/tests/unit/string.test.ts +147 -147
|
@@ -1,285 +1,285 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
AnyValiDocument,
|
|
3
|
-
SchemaNode,
|
|
4
|
-
} from "../types.js";
|
|
5
|
-
import { BaseSchema } from "../schemas/base.js";
|
|
6
|
-
import { StringSchema } from "../schemas/string.js";
|
|
7
|
-
import {
|
|
8
|
-
NumberSchema,
|
|
9
|
-
Float32Schema,
|
|
10
|
-
Float64Schema,
|
|
11
|
-
} from "../schemas/number.js";
|
|
12
|
-
import {
|
|
13
|
-
IntSchema,
|
|
14
|
-
Int8Schema,
|
|
15
|
-
Int16Schema,
|
|
16
|
-
Int32Schema,
|
|
17
|
-
Int64Schema,
|
|
18
|
-
Uint8Schema,
|
|
19
|
-
Uint16Schema,
|
|
20
|
-
Uint32Schema,
|
|
21
|
-
Uint64Schema,
|
|
22
|
-
} from "../schemas/int.js";
|
|
23
|
-
import { BoolSchema } from "../schemas/bool.js";
|
|
24
|
-
import { NullSchema } from "../schemas/null.js";
|
|
25
|
-
import { AnySchema } from "../schemas/any.js";
|
|
26
|
-
import { UnknownSchema } from "../schemas/unknown.js";
|
|
27
|
-
import { NeverSchema } from "../schemas/never.js";
|
|
28
|
-
import { LiteralSchema } from "../schemas/literal.js";
|
|
29
|
-
import { EnumSchema } from "../schemas/enum.js";
|
|
30
|
-
import { ArraySchema } from "../schemas/array.js";
|
|
31
|
-
import { TupleSchema } from "../schemas/tuple.js";
|
|
32
|
-
import { ObjectSchema } from "../schemas/object.js";
|
|
33
|
-
import { RecordSchema } from "../schemas/record.js";
|
|
34
|
-
import { UnionSchema } from "../schemas/union.js";
|
|
35
|
-
import { IntersectionSchema } from "../schemas/intersection.js";
|
|
36
|
-
import { OptionalSchema } from "../schemas/optional.js";
|
|
37
|
-
import { NullableSchema } from "../schemas/nullable.js";
|
|
38
|
-
import { RefSchema } from "../schemas/ref.js";
|
|
39
|
-
import { normalizeCoercionConfig } from "../parse/coerce.js";
|
|
40
|
-
import type { StringFormat, UnknownKeyMode } from "../types.js";
|
|
41
|
-
|
|
42
|
-
/**
|
|
43
|
-
* Import an AnyValiDocument back into a live Schema.
|
|
44
|
-
*/
|
|
45
|
-
/**
|
|
46
|
-
* Maximum schema-document nesting depth accepted by importSchema. Bounds the
|
|
47
|
-
* recursive importNode walk so an untrusted, deeply nested document cannot
|
|
48
|
-
* exhaust the call stack (DoS). Throws a controlled error instead of a
|
|
49
|
-
* RangeError stack overflow.
|
|
50
|
-
*/
|
|
51
|
-
const MAX_IMPORT_DEPTH = 512;
|
|
52
|
-
|
|
53
|
-
export function importSchema(doc: AnyValiDocument): BaseSchema {
|
|
54
|
-
const definitions = doc.definitions ?? {};
|
|
55
|
-
const resolvedDefs = new Map<string, BaseSchema>();
|
|
56
|
-
|
|
57
|
-
function importNode(node: any, depth: number = 0): BaseSchema {
|
|
58
|
-
if (depth > MAX_IMPORT_DEPTH) {
|
|
59
|
-
throw new Error(
|
|
60
|
-
`Schema document too deeply nested (max depth ${MAX_IMPORT_DEPTH} exceeded)`
|
|
61
|
-
);
|
|
62
|
-
}
|
|
63
|
-
const d = depth + 1;
|
|
64
|
-
let schema: BaseSchema;
|
|
65
|
-
|
|
66
|
-
switch (node.kind) {
|
|
67
|
-
case "string": {
|
|
68
|
-
let s = new StringSchema();
|
|
69
|
-
if (node.minLength !== undefined) s = s.minLength(node.minLength);
|
|
70
|
-
if (node.maxLength !== undefined) s = s.maxLength(node.maxLength);
|
|
71
|
-
if (node.pattern !== undefined) s = s.pattern(node.pattern);
|
|
72
|
-
if (node.startsWith !== undefined) s = s.startsWith(node.startsWith);
|
|
73
|
-
if (node.endsWith !== undefined) s = s.endsWith(node.endsWith);
|
|
74
|
-
if (node.includes !== undefined) s = s.includes(node.includes);
|
|
75
|
-
if (node.format !== undefined)
|
|
76
|
-
s = s.format(node.format as StringFormat);
|
|
77
|
-
schema = s;
|
|
78
|
-
break;
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
case "number":
|
|
82
|
-
case "float64": {
|
|
83
|
-
let s =
|
|
84
|
-
node.kind === "float64" ? new Float64Schema() : new NumberSchema();
|
|
85
|
-
schema = applyNumericConstraints(s, node);
|
|
86
|
-
break;
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
case "float32": {
|
|
90
|
-
schema = applyNumericConstraints(new Float32Schema(), node);
|
|
91
|
-
break;
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
case "int":
|
|
95
|
-
case "int64": {
|
|
96
|
-
schema = applyNumericConstraints(
|
|
97
|
-
node.kind === "int64" ? new Int64Schema() : new IntSchema(),
|
|
98
|
-
node
|
|
99
|
-
);
|
|
100
|
-
break;
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
case "int8":
|
|
104
|
-
schema = applyNumericConstraints(new Int8Schema(), node);
|
|
105
|
-
break;
|
|
106
|
-
case "int16":
|
|
107
|
-
schema = applyNumericConstraints(new Int16Schema(), node);
|
|
108
|
-
break;
|
|
109
|
-
case "int32":
|
|
110
|
-
schema = applyNumericConstraints(new Int32Schema(), node);
|
|
111
|
-
break;
|
|
112
|
-
case "uint8":
|
|
113
|
-
schema = applyNumericConstraints(new Uint8Schema(), node);
|
|
114
|
-
break;
|
|
115
|
-
case "uint16":
|
|
116
|
-
schema = applyNumericConstraints(new Uint16Schema(), node);
|
|
117
|
-
break;
|
|
118
|
-
case "uint32":
|
|
119
|
-
schema = applyNumericConstraints(new Uint32Schema(), node);
|
|
120
|
-
break;
|
|
121
|
-
case "uint64":
|
|
122
|
-
schema = applyNumericConstraints(new Uint64Schema(), node);
|
|
123
|
-
break;
|
|
124
|
-
|
|
125
|
-
case "bool":
|
|
126
|
-
schema = new BoolSchema();
|
|
127
|
-
break;
|
|
128
|
-
|
|
129
|
-
case "null":
|
|
130
|
-
schema = new NullSchema();
|
|
131
|
-
break;
|
|
132
|
-
|
|
133
|
-
case "any":
|
|
134
|
-
schema = new AnySchema();
|
|
135
|
-
break;
|
|
136
|
-
|
|
137
|
-
case "unknown":
|
|
138
|
-
schema = new UnknownSchema();
|
|
139
|
-
break;
|
|
140
|
-
|
|
141
|
-
case "never":
|
|
142
|
-
schema = new NeverSchema();
|
|
143
|
-
break;
|
|
144
|
-
|
|
145
|
-
case "literal": {
|
|
146
|
-
schema = new LiteralSchema(node.value);
|
|
147
|
-
break;
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
case "enum": {
|
|
151
|
-
schema = new EnumSchema(node.values);
|
|
152
|
-
break;
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
case "array": {
|
|
156
|
-
let s = new ArraySchema(importNode(node.items, d));
|
|
157
|
-
if (node.minItems !== undefined) s = s.minItems(node.minItems);
|
|
158
|
-
if (node.maxItems !== undefined) s = s.maxItems(node.maxItems);
|
|
159
|
-
schema = s;
|
|
160
|
-
break;
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
case "tuple": {
|
|
164
|
-
// Corpus uses "elements", our export uses "items"
|
|
165
|
-
const elements = node.elements ?? node.items;
|
|
166
|
-
schema = new TupleSchema(
|
|
167
|
-
elements.map((i: any) => importNode(i, d))
|
|
168
|
-
);
|
|
169
|
-
break;
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
case "object": {
|
|
173
|
-
const shape: Record<string, BaseSchema> = Object.create(null);
|
|
174
|
-
const requiredSet = new Set<string>(node.required ?? []);
|
|
175
|
-
for (const [key, propNode] of Object.entries(
|
|
176
|
-
node.properties ?? {}
|
|
177
|
-
)) {
|
|
178
|
-
let propSchema = importNode(propNode, d);
|
|
179
|
-
if (!requiredSet.has(key)) {
|
|
180
|
-
propSchema = new OptionalSchema(propSchema);
|
|
181
|
-
}
|
|
182
|
-
// Use defineProperty to safely handle __proto__ and other special keys
|
|
183
|
-
Object.defineProperty(shape, key, {
|
|
184
|
-
value: propSchema,
|
|
185
|
-
writable: true,
|
|
186
|
-
enumerable: true,
|
|
187
|
-
configurable: true,
|
|
188
|
-
});
|
|
189
|
-
}
|
|
190
|
-
Object.setPrototypeOf(shape, Object.prototype);
|
|
191
|
-
schema = new ObjectSchema(shape, {
|
|
192
|
-
unknownKeys:
|
|
193
|
-
(node.unknownKeys as UnknownKeyMode) ?? "strip",
|
|
194
|
-
});
|
|
195
|
-
break;
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
case "record": {
|
|
199
|
-
// Corpus uses "values", our export uses "valueSchema"
|
|
200
|
-
const valueNode = node.values ?? node.valueSchema;
|
|
201
|
-
schema = new RecordSchema(importNode(valueNode, d));
|
|
202
|
-
break;
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
case "union": {
|
|
206
|
-
schema = new UnionSchema(
|
|
207
|
-
node.variants.map((v: any) => importNode(v, d))
|
|
208
|
-
);
|
|
209
|
-
break;
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
case "intersection": {
|
|
213
|
-
schema = new IntersectionSchema(
|
|
214
|
-
node.allOf.map((s: any) => importNode(s, d))
|
|
215
|
-
);
|
|
216
|
-
break;
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
case "optional": {
|
|
220
|
-
// Corpus uses "schema", our export uses "inner"
|
|
221
|
-
const innerNode = node.schema ?? node.inner;
|
|
222
|
-
schema = new OptionalSchema(importNode(innerNode, d));
|
|
223
|
-
break;
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
case "nullable": {
|
|
227
|
-
// Corpus uses "schema", our export uses "inner"
|
|
228
|
-
const innerNode = node.schema ?? node.inner;
|
|
229
|
-
schema = new NullableSchema(importNode(innerNode, d));
|
|
230
|
-
break;
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
case "ref": {
|
|
234
|
-
const refPath = node.ref as string;
|
|
235
|
-
const defName = refPath.replace("#/definitions/", "");
|
|
236
|
-
schema = new RefSchema(refPath, () => {
|
|
237
|
-
if (resolvedDefs.has(defName)) {
|
|
238
|
-
return resolvedDefs.get(defName)!;
|
|
239
|
-
}
|
|
240
|
-
const defNode = definitions[defName];
|
|
241
|
-
if (!defNode) {
|
|
242
|
-
throw new Error(`Unresolved definition: ${defName}`);
|
|
243
|
-
}
|
|
244
|
-
const resolved = importNode(defNode);
|
|
245
|
-
resolvedDefs.set(defName, resolved);
|
|
246
|
-
return resolved;
|
|
247
|
-
});
|
|
248
|
-
break;
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
default:
|
|
252
|
-
throw new Error(`Unsupported schema kind: ${node.kind}`);
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
// Apply default
|
|
256
|
-
if (node.default !== undefined) {
|
|
257
|
-
schema = schema.default(node.default as any);
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
// Apply coercion config - handle both string and object formats
|
|
261
|
-
if (node.coerce !== undefined) {
|
|
262
|
-
const config = normalizeCoercionConfig(node.coerce);
|
|
263
|
-
schema = schema.coerce(config);
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
return schema;
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
return importNode(doc.root);
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
function applyNumericConstraints<T extends NumberSchema>(
|
|
273
|
-
schema: T,
|
|
274
|
-
node: any
|
|
275
|
-
): T {
|
|
276
|
-
let s = schema;
|
|
277
|
-
if (node.min !== undefined) s = s.min(node.min) as T;
|
|
278
|
-
if (node.max !== undefined) s = s.max(node.max) as T;
|
|
279
|
-
if (node.exclusiveMin !== undefined)
|
|
280
|
-
s = s.exclusiveMin(node.exclusiveMin) as T;
|
|
281
|
-
if (node.exclusiveMax !== undefined)
|
|
282
|
-
s = s.exclusiveMax(node.exclusiveMax) as T;
|
|
283
|
-
if (node.multipleOf !== undefined) s = s.multipleOf(node.multipleOf) as T;
|
|
284
|
-
return s;
|
|
285
|
-
}
|
|
1
|
+
import type {
|
|
2
|
+
AnyValiDocument,
|
|
3
|
+
SchemaNode,
|
|
4
|
+
} from "../types.js";
|
|
5
|
+
import { BaseSchema } from "../schemas/base.js";
|
|
6
|
+
import { StringSchema } from "../schemas/string.js";
|
|
7
|
+
import {
|
|
8
|
+
NumberSchema,
|
|
9
|
+
Float32Schema,
|
|
10
|
+
Float64Schema,
|
|
11
|
+
} from "../schemas/number.js";
|
|
12
|
+
import {
|
|
13
|
+
IntSchema,
|
|
14
|
+
Int8Schema,
|
|
15
|
+
Int16Schema,
|
|
16
|
+
Int32Schema,
|
|
17
|
+
Int64Schema,
|
|
18
|
+
Uint8Schema,
|
|
19
|
+
Uint16Schema,
|
|
20
|
+
Uint32Schema,
|
|
21
|
+
Uint64Schema,
|
|
22
|
+
} from "../schemas/int.js";
|
|
23
|
+
import { BoolSchema } from "../schemas/bool.js";
|
|
24
|
+
import { NullSchema } from "../schemas/null.js";
|
|
25
|
+
import { AnySchema } from "../schemas/any.js";
|
|
26
|
+
import { UnknownSchema } from "../schemas/unknown.js";
|
|
27
|
+
import { NeverSchema } from "../schemas/never.js";
|
|
28
|
+
import { LiteralSchema } from "../schemas/literal.js";
|
|
29
|
+
import { EnumSchema } from "../schemas/enum.js";
|
|
30
|
+
import { ArraySchema } from "../schemas/array.js";
|
|
31
|
+
import { TupleSchema } from "../schemas/tuple.js";
|
|
32
|
+
import { ObjectSchema } from "../schemas/object.js";
|
|
33
|
+
import { RecordSchema } from "../schemas/record.js";
|
|
34
|
+
import { UnionSchema } from "../schemas/union.js";
|
|
35
|
+
import { IntersectionSchema } from "../schemas/intersection.js";
|
|
36
|
+
import { OptionalSchema } from "../schemas/optional.js";
|
|
37
|
+
import { NullableSchema } from "../schemas/nullable.js";
|
|
38
|
+
import { RefSchema } from "../schemas/ref.js";
|
|
39
|
+
import { normalizeCoercionConfig } from "../parse/coerce.js";
|
|
40
|
+
import type { StringFormat, UnknownKeyMode } from "../types.js";
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Import an AnyValiDocument back into a live Schema.
|
|
44
|
+
*/
|
|
45
|
+
/**
|
|
46
|
+
* Maximum schema-document nesting depth accepted by importSchema. Bounds the
|
|
47
|
+
* recursive importNode walk so an untrusted, deeply nested document cannot
|
|
48
|
+
* exhaust the call stack (DoS). Throws a controlled error instead of a
|
|
49
|
+
* RangeError stack overflow.
|
|
50
|
+
*/
|
|
51
|
+
const MAX_IMPORT_DEPTH = 512;
|
|
52
|
+
|
|
53
|
+
export function importSchema(doc: AnyValiDocument): BaseSchema {
|
|
54
|
+
const definitions = doc.definitions ?? {};
|
|
55
|
+
const resolvedDefs = new Map<string, BaseSchema>();
|
|
56
|
+
|
|
57
|
+
function importNode(node: any, depth: number = 0): BaseSchema {
|
|
58
|
+
if (depth > MAX_IMPORT_DEPTH) {
|
|
59
|
+
throw new Error(
|
|
60
|
+
`Schema document too deeply nested (max depth ${MAX_IMPORT_DEPTH} exceeded)`
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
const d = depth + 1;
|
|
64
|
+
let schema: BaseSchema;
|
|
65
|
+
|
|
66
|
+
switch (node.kind) {
|
|
67
|
+
case "string": {
|
|
68
|
+
let s = new StringSchema();
|
|
69
|
+
if (node.minLength !== undefined) s = s.minLength(node.minLength);
|
|
70
|
+
if (node.maxLength !== undefined) s = s.maxLength(node.maxLength);
|
|
71
|
+
if (node.pattern !== undefined) s = s.pattern(node.pattern);
|
|
72
|
+
if (node.startsWith !== undefined) s = s.startsWith(node.startsWith);
|
|
73
|
+
if (node.endsWith !== undefined) s = s.endsWith(node.endsWith);
|
|
74
|
+
if (node.includes !== undefined) s = s.includes(node.includes);
|
|
75
|
+
if (node.format !== undefined)
|
|
76
|
+
s = s.format(node.format as StringFormat);
|
|
77
|
+
schema = s;
|
|
78
|
+
break;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
case "number":
|
|
82
|
+
case "float64": {
|
|
83
|
+
let s =
|
|
84
|
+
node.kind === "float64" ? new Float64Schema() : new NumberSchema();
|
|
85
|
+
schema = applyNumericConstraints(s, node);
|
|
86
|
+
break;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
case "float32": {
|
|
90
|
+
schema = applyNumericConstraints(new Float32Schema(), node);
|
|
91
|
+
break;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
case "int":
|
|
95
|
+
case "int64": {
|
|
96
|
+
schema = applyNumericConstraints(
|
|
97
|
+
node.kind === "int64" ? new Int64Schema() : new IntSchema(),
|
|
98
|
+
node
|
|
99
|
+
);
|
|
100
|
+
break;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
case "int8":
|
|
104
|
+
schema = applyNumericConstraints(new Int8Schema(), node);
|
|
105
|
+
break;
|
|
106
|
+
case "int16":
|
|
107
|
+
schema = applyNumericConstraints(new Int16Schema(), node);
|
|
108
|
+
break;
|
|
109
|
+
case "int32":
|
|
110
|
+
schema = applyNumericConstraints(new Int32Schema(), node);
|
|
111
|
+
break;
|
|
112
|
+
case "uint8":
|
|
113
|
+
schema = applyNumericConstraints(new Uint8Schema(), node);
|
|
114
|
+
break;
|
|
115
|
+
case "uint16":
|
|
116
|
+
schema = applyNumericConstraints(new Uint16Schema(), node);
|
|
117
|
+
break;
|
|
118
|
+
case "uint32":
|
|
119
|
+
schema = applyNumericConstraints(new Uint32Schema(), node);
|
|
120
|
+
break;
|
|
121
|
+
case "uint64":
|
|
122
|
+
schema = applyNumericConstraints(new Uint64Schema(), node);
|
|
123
|
+
break;
|
|
124
|
+
|
|
125
|
+
case "bool":
|
|
126
|
+
schema = new BoolSchema();
|
|
127
|
+
break;
|
|
128
|
+
|
|
129
|
+
case "null":
|
|
130
|
+
schema = new NullSchema();
|
|
131
|
+
break;
|
|
132
|
+
|
|
133
|
+
case "any":
|
|
134
|
+
schema = new AnySchema();
|
|
135
|
+
break;
|
|
136
|
+
|
|
137
|
+
case "unknown":
|
|
138
|
+
schema = new UnknownSchema();
|
|
139
|
+
break;
|
|
140
|
+
|
|
141
|
+
case "never":
|
|
142
|
+
schema = new NeverSchema();
|
|
143
|
+
break;
|
|
144
|
+
|
|
145
|
+
case "literal": {
|
|
146
|
+
schema = new LiteralSchema(node.value);
|
|
147
|
+
break;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
case "enum": {
|
|
151
|
+
schema = new EnumSchema(node.values);
|
|
152
|
+
break;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
case "array": {
|
|
156
|
+
let s = new ArraySchema(importNode(node.items, d));
|
|
157
|
+
if (node.minItems !== undefined) s = s.minItems(node.minItems);
|
|
158
|
+
if (node.maxItems !== undefined) s = s.maxItems(node.maxItems);
|
|
159
|
+
schema = s;
|
|
160
|
+
break;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
case "tuple": {
|
|
164
|
+
// Corpus uses "elements", our export uses "items"
|
|
165
|
+
const elements = node.elements ?? node.items;
|
|
166
|
+
schema = new TupleSchema(
|
|
167
|
+
elements.map((i: any) => importNode(i, d))
|
|
168
|
+
);
|
|
169
|
+
break;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
case "object": {
|
|
173
|
+
const shape: Record<string, BaseSchema> = Object.create(null);
|
|
174
|
+
const requiredSet = new Set<string>(node.required ?? []);
|
|
175
|
+
for (const [key, propNode] of Object.entries(
|
|
176
|
+
node.properties ?? {}
|
|
177
|
+
)) {
|
|
178
|
+
let propSchema = importNode(propNode, d);
|
|
179
|
+
if (!requiredSet.has(key)) {
|
|
180
|
+
propSchema = new OptionalSchema(propSchema);
|
|
181
|
+
}
|
|
182
|
+
// Use defineProperty to safely handle __proto__ and other special keys
|
|
183
|
+
Object.defineProperty(shape, key, {
|
|
184
|
+
value: propSchema,
|
|
185
|
+
writable: true,
|
|
186
|
+
enumerable: true,
|
|
187
|
+
configurable: true,
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
Object.setPrototypeOf(shape, Object.prototype);
|
|
191
|
+
schema = new ObjectSchema(shape, {
|
|
192
|
+
unknownKeys:
|
|
193
|
+
(node.unknownKeys as UnknownKeyMode) ?? "strip",
|
|
194
|
+
});
|
|
195
|
+
break;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
case "record": {
|
|
199
|
+
// Corpus uses "values", our export uses "valueSchema"
|
|
200
|
+
const valueNode = node.values ?? node.valueSchema;
|
|
201
|
+
schema = new RecordSchema(importNode(valueNode, d));
|
|
202
|
+
break;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
case "union": {
|
|
206
|
+
schema = new UnionSchema(
|
|
207
|
+
node.variants.map((v: any) => importNode(v, d))
|
|
208
|
+
);
|
|
209
|
+
break;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
case "intersection": {
|
|
213
|
+
schema = new IntersectionSchema(
|
|
214
|
+
node.allOf.map((s: any) => importNode(s, d))
|
|
215
|
+
);
|
|
216
|
+
break;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
case "optional": {
|
|
220
|
+
// Corpus uses "schema", our export uses "inner"
|
|
221
|
+
const innerNode = node.schema ?? node.inner;
|
|
222
|
+
schema = new OptionalSchema(importNode(innerNode, d));
|
|
223
|
+
break;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
case "nullable": {
|
|
227
|
+
// Corpus uses "schema", our export uses "inner"
|
|
228
|
+
const innerNode = node.schema ?? node.inner;
|
|
229
|
+
schema = new NullableSchema(importNode(innerNode, d));
|
|
230
|
+
break;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
case "ref": {
|
|
234
|
+
const refPath = node.ref as string;
|
|
235
|
+
const defName = refPath.replace("#/definitions/", "");
|
|
236
|
+
schema = new RefSchema(refPath, () => {
|
|
237
|
+
if (resolvedDefs.has(defName)) {
|
|
238
|
+
return resolvedDefs.get(defName)!;
|
|
239
|
+
}
|
|
240
|
+
const defNode = definitions[defName];
|
|
241
|
+
if (!defNode) {
|
|
242
|
+
throw new Error(`Unresolved definition: ${defName}`);
|
|
243
|
+
}
|
|
244
|
+
const resolved = importNode(defNode);
|
|
245
|
+
resolvedDefs.set(defName, resolved);
|
|
246
|
+
return resolved;
|
|
247
|
+
});
|
|
248
|
+
break;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
default:
|
|
252
|
+
throw new Error(`Unsupported schema kind: ${node.kind}`);
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
// Apply default
|
|
256
|
+
if (node.default !== undefined) {
|
|
257
|
+
schema = schema.default(node.default as any);
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
// Apply coercion config - handle both string and object formats
|
|
261
|
+
if (node.coerce !== undefined) {
|
|
262
|
+
const config = normalizeCoercionConfig(node.coerce);
|
|
263
|
+
schema = schema.coerce(config);
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
return schema;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
return importNode(doc.root);
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
function applyNumericConstraints<T extends NumberSchema>(
|
|
273
|
+
schema: T,
|
|
274
|
+
node: any
|
|
275
|
+
): T {
|
|
276
|
+
let s = schema;
|
|
277
|
+
if (node.min !== undefined) s = s.min(node.min) as T;
|
|
278
|
+
if (node.max !== undefined) s = s.max(node.max) as T;
|
|
279
|
+
if (node.exclusiveMin !== undefined)
|
|
280
|
+
s = s.exclusiveMin(node.exclusiveMin) as T;
|
|
281
|
+
if (node.exclusiveMax !== undefined)
|
|
282
|
+
s = s.exclusiveMax(node.exclusiveMax) as T;
|
|
283
|
+
if (node.multipleOf !== undefined) s = s.multipleOf(node.multipleOf) as T;
|
|
284
|
+
return s;
|
|
285
|
+
}
|
package/src/issue-codes.ts
CHANGED
|
@@ -1,19 +1,19 @@
|
|
|
1
|
-
export const ISSUE_CODES = {
|
|
2
|
-
INVALID_TYPE: "invalid_type",
|
|
3
|
-
REQUIRED: "required",
|
|
4
|
-
UNKNOWN_KEY: "unknown_key",
|
|
5
|
-
TOO_SMALL: "too_small",
|
|
6
|
-
TOO_LARGE: "too_large",
|
|
7
|
-
INVALID_STRING: "invalid_string",
|
|
8
|
-
INVALID_NUMBER: "invalid_number",
|
|
9
|
-
INVALID_LITERAL: "invalid_literal",
|
|
10
|
-
INVALID_UNION: "invalid_union",
|
|
11
|
-
CUSTOM_VALIDATION_NOT_PORTABLE: "custom_validation_not_portable",
|
|
12
|
-
UNSUPPORTED_EXTENSION: "unsupported_extension",
|
|
13
|
-
UNSUPPORTED_SCHEMA_KIND: "unsupported_schema_kind",
|
|
14
|
-
COERCION_FAILED: "coercion_failed",
|
|
15
|
-
DEFAULT_INVALID: "default_invalid",
|
|
16
|
-
TOO_DEEP: "too_deep",
|
|
17
|
-
} as const;
|
|
18
|
-
|
|
19
|
-
export type IssueCode = (typeof ISSUE_CODES)[keyof typeof ISSUE_CODES];
|
|
1
|
+
export const ISSUE_CODES = {
|
|
2
|
+
INVALID_TYPE: "invalid_type",
|
|
3
|
+
REQUIRED: "required",
|
|
4
|
+
UNKNOWN_KEY: "unknown_key",
|
|
5
|
+
TOO_SMALL: "too_small",
|
|
6
|
+
TOO_LARGE: "too_large",
|
|
7
|
+
INVALID_STRING: "invalid_string",
|
|
8
|
+
INVALID_NUMBER: "invalid_number",
|
|
9
|
+
INVALID_LITERAL: "invalid_literal",
|
|
10
|
+
INVALID_UNION: "invalid_union",
|
|
11
|
+
CUSTOM_VALIDATION_NOT_PORTABLE: "custom_validation_not_portable",
|
|
12
|
+
UNSUPPORTED_EXTENSION: "unsupported_extension",
|
|
13
|
+
UNSUPPORTED_SCHEMA_KIND: "unsupported_schema_kind",
|
|
14
|
+
COERCION_FAILED: "coercion_failed",
|
|
15
|
+
DEFAULT_INVALID: "default_invalid",
|
|
16
|
+
TOO_DEEP: "too_deep",
|
|
17
|
+
} as const;
|
|
18
|
+
|
|
19
|
+
export type IssueCode = (typeof ISSUE_CODES)[keyof typeof ISSUE_CODES];
|