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
package/src/types.ts
CHANGED
|
@@ -1,239 +1,239 @@
|
|
|
1
|
-
// ---- Schema Node (interchange JSON representation) ----
|
|
2
|
-
|
|
3
|
-
export type SchemaKind =
|
|
4
|
-
| "any"
|
|
5
|
-
| "unknown"
|
|
6
|
-
| "never"
|
|
7
|
-
| "null"
|
|
8
|
-
| "bool"
|
|
9
|
-
| "string"
|
|
10
|
-
| "number"
|
|
11
|
-
| "int"
|
|
12
|
-
| "float32"
|
|
13
|
-
| "float64"
|
|
14
|
-
| "int8"
|
|
15
|
-
| "int16"
|
|
16
|
-
| "int32"
|
|
17
|
-
| "int64"
|
|
18
|
-
| "uint8"
|
|
19
|
-
| "uint16"
|
|
20
|
-
| "uint32"
|
|
21
|
-
| "uint64"
|
|
22
|
-
| "literal"
|
|
23
|
-
| "enum"
|
|
24
|
-
| "array"
|
|
25
|
-
| "tuple"
|
|
26
|
-
| "object"
|
|
27
|
-
| "record"
|
|
28
|
-
| "union"
|
|
29
|
-
| "intersection"
|
|
30
|
-
| "optional"
|
|
31
|
-
| "nullable"
|
|
32
|
-
| "ref";
|
|
33
|
-
|
|
34
|
-
export interface MetadataOptions {
|
|
35
|
-
replace?: boolean;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
export interface DescribeOptions {
|
|
39
|
-
title?: string;
|
|
40
|
-
deprecated?: boolean;
|
|
41
|
-
deprecatedMessage?: string;
|
|
42
|
-
notStable?: boolean;
|
|
43
|
-
since?: string;
|
|
44
|
-
sensitive?: boolean;
|
|
45
|
-
readonly?: boolean;
|
|
46
|
-
writeonly?: boolean;
|
|
47
|
-
examples?: unknown[];
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
export interface SchemaNodeBase {
|
|
51
|
-
kind: SchemaKind;
|
|
52
|
-
default?: unknown;
|
|
53
|
-
coerce?: CoercionConfig | string | string[];
|
|
54
|
-
metadata?: Record<string, unknown>;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
export interface CoercionConfig {
|
|
58
|
-
from?: string;
|
|
59
|
-
trim?: boolean;
|
|
60
|
-
lower?: boolean;
|
|
61
|
-
upper?: boolean;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
// String node
|
|
65
|
-
export interface StringSchemaNode extends SchemaNodeBase {
|
|
66
|
-
kind: "string";
|
|
67
|
-
minLength?: number;
|
|
68
|
-
maxLength?: number;
|
|
69
|
-
pattern?: string;
|
|
70
|
-
startsWith?: string;
|
|
71
|
-
endsWith?: string;
|
|
72
|
-
includes?: string;
|
|
73
|
-
format?: StringFormat;
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
export type StringFormat =
|
|
77
|
-
| "email"
|
|
78
|
-
| "url"
|
|
79
|
-
| "uuid"
|
|
80
|
-
| "ipv4"
|
|
81
|
-
| "ipv6"
|
|
82
|
-
| "date"
|
|
83
|
-
| "date-time";
|
|
84
|
-
|
|
85
|
-
// Numeric nodes
|
|
86
|
-
export interface NumericSchemaNode extends SchemaNodeBase {
|
|
87
|
-
kind:
|
|
88
|
-
| "number"
|
|
89
|
-
| "int"
|
|
90
|
-
| "float32"
|
|
91
|
-
| "float64"
|
|
92
|
-
| "int8"
|
|
93
|
-
| "int16"
|
|
94
|
-
| "int32"
|
|
95
|
-
| "int64"
|
|
96
|
-
| "uint8"
|
|
97
|
-
| "uint16"
|
|
98
|
-
| "uint32"
|
|
99
|
-
| "uint64";
|
|
100
|
-
min?: number;
|
|
101
|
-
max?: number;
|
|
102
|
-
exclusiveMin?: number;
|
|
103
|
-
exclusiveMax?: number;
|
|
104
|
-
multipleOf?: number;
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
// Simple nodes
|
|
108
|
-
export interface SimpleSchemaNode extends SchemaNodeBase {
|
|
109
|
-
kind: "any" | "unknown" | "never" | "null" | "bool";
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
// Literal node
|
|
113
|
-
export interface LiteralSchemaNode extends SchemaNodeBase {
|
|
114
|
-
kind: "literal";
|
|
115
|
-
value: string | number | boolean | null;
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
// Enum node
|
|
119
|
-
export interface EnumSchemaNode extends SchemaNodeBase {
|
|
120
|
-
kind: "enum";
|
|
121
|
-
values: (string | number)[];
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
// Array node
|
|
125
|
-
export interface ArraySchemaNode extends SchemaNodeBase {
|
|
126
|
-
kind: "array";
|
|
127
|
-
items: SchemaNode;
|
|
128
|
-
minItems?: number;
|
|
129
|
-
maxItems?: number;
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
// Tuple node
|
|
133
|
-
export interface TupleSchemaNode extends SchemaNodeBase {
|
|
134
|
-
kind: "tuple";
|
|
135
|
-
items: SchemaNode[];
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
// Object node
|
|
139
|
-
export interface ObjectSchemaNode extends SchemaNodeBase {
|
|
140
|
-
kind: "object";
|
|
141
|
-
properties: Record<string, SchemaNode>;
|
|
142
|
-
required: string[];
|
|
143
|
-
unknownKeys?: UnknownKeyMode;
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
// Record node
|
|
147
|
-
export interface RecordSchemaNode extends SchemaNodeBase {
|
|
148
|
-
kind: "record";
|
|
149
|
-
valueSchema: SchemaNode;
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
// Union node
|
|
153
|
-
export interface UnionSchemaNode extends SchemaNodeBase {
|
|
154
|
-
kind: "union";
|
|
155
|
-
variants: SchemaNode[];
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
// Intersection node
|
|
159
|
-
export interface IntersectionSchemaNode extends SchemaNodeBase {
|
|
160
|
-
kind: "intersection";
|
|
161
|
-
allOf: SchemaNode[];
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
// Optional node
|
|
165
|
-
export interface OptionalSchemaNode extends SchemaNodeBase {
|
|
166
|
-
kind: "optional";
|
|
167
|
-
inner: SchemaNode;
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
// Nullable node
|
|
171
|
-
export interface NullableSchemaNode extends SchemaNodeBase {
|
|
172
|
-
kind: "nullable";
|
|
173
|
-
inner: SchemaNode;
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
// Ref node
|
|
177
|
-
export interface RefSchemaNode extends SchemaNodeBase {
|
|
178
|
-
kind: "ref";
|
|
179
|
-
ref: string;
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
export type SchemaNode =
|
|
183
|
-
| StringSchemaNode
|
|
184
|
-
| NumericSchemaNode
|
|
185
|
-
| SimpleSchemaNode
|
|
186
|
-
| LiteralSchemaNode
|
|
187
|
-
| EnumSchemaNode
|
|
188
|
-
| ArraySchemaNode
|
|
189
|
-
| TupleSchemaNode
|
|
190
|
-
| ObjectSchemaNode
|
|
191
|
-
| RecordSchemaNode
|
|
192
|
-
| UnionSchemaNode
|
|
193
|
-
| IntersectionSchemaNode
|
|
194
|
-
| OptionalSchemaNode
|
|
195
|
-
| NullableSchemaNode
|
|
196
|
-
| RefSchemaNode;
|
|
197
|
-
|
|
198
|
-
// ---- Parse Result ----
|
|
199
|
-
|
|
200
|
-
export type ParseResult<T> =
|
|
201
|
-
| { success: true; data: T }
|
|
202
|
-
| { success: false; issues: ValidationIssue[] };
|
|
203
|
-
|
|
204
|
-
export interface ValidationIssue {
|
|
205
|
-
code: string;
|
|
206
|
-
message: string;
|
|
207
|
-
path: (string | number)[];
|
|
208
|
-
expected?: string;
|
|
209
|
-
received?: string;
|
|
210
|
-
meta?: Record<string, unknown>;
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
// ---- Interchange Document ----
|
|
214
|
-
|
|
215
|
-
export interface AnyValiDocument {
|
|
216
|
-
anyvaliVersion: string;
|
|
217
|
-
schemaVersion: string;
|
|
218
|
-
root: SchemaNode;
|
|
219
|
-
definitions: Record<string, SchemaNode>;
|
|
220
|
-
extensions: Record<string, Record<string, unknown>>;
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
// ---- Modes ----
|
|
224
|
-
|
|
225
|
-
export type ExportMode = "portable" | "extended";
|
|
226
|
-
|
|
227
|
-
export type UnknownKeyMode = "reject" | "strip" | "allow";
|
|
228
|
-
|
|
229
|
-
// ---- Parse Context (internal) ----
|
|
230
|
-
|
|
231
|
-
export interface ParseContext {
|
|
232
|
-
path: (string | number)[];
|
|
233
|
-
issues: ValidationIssue[];
|
|
234
|
-
definitions?: Record<string, SchemaNode>;
|
|
235
|
-
/** Tracks objects already being validated to detect circular references. */
|
|
236
|
-
seen?: WeakSet<object>;
|
|
237
|
-
/** Current recursion depth, used to bound unbounded recursion (DoS guard). */
|
|
238
|
-
depth?: number;
|
|
239
|
-
}
|
|
1
|
+
// ---- Schema Node (interchange JSON representation) ----
|
|
2
|
+
|
|
3
|
+
export type SchemaKind =
|
|
4
|
+
| "any"
|
|
5
|
+
| "unknown"
|
|
6
|
+
| "never"
|
|
7
|
+
| "null"
|
|
8
|
+
| "bool"
|
|
9
|
+
| "string"
|
|
10
|
+
| "number"
|
|
11
|
+
| "int"
|
|
12
|
+
| "float32"
|
|
13
|
+
| "float64"
|
|
14
|
+
| "int8"
|
|
15
|
+
| "int16"
|
|
16
|
+
| "int32"
|
|
17
|
+
| "int64"
|
|
18
|
+
| "uint8"
|
|
19
|
+
| "uint16"
|
|
20
|
+
| "uint32"
|
|
21
|
+
| "uint64"
|
|
22
|
+
| "literal"
|
|
23
|
+
| "enum"
|
|
24
|
+
| "array"
|
|
25
|
+
| "tuple"
|
|
26
|
+
| "object"
|
|
27
|
+
| "record"
|
|
28
|
+
| "union"
|
|
29
|
+
| "intersection"
|
|
30
|
+
| "optional"
|
|
31
|
+
| "nullable"
|
|
32
|
+
| "ref";
|
|
33
|
+
|
|
34
|
+
export interface MetadataOptions {
|
|
35
|
+
replace?: boolean;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export interface DescribeOptions {
|
|
39
|
+
title?: string;
|
|
40
|
+
deprecated?: boolean;
|
|
41
|
+
deprecatedMessage?: string;
|
|
42
|
+
notStable?: boolean;
|
|
43
|
+
since?: string;
|
|
44
|
+
sensitive?: boolean;
|
|
45
|
+
readonly?: boolean;
|
|
46
|
+
writeonly?: boolean;
|
|
47
|
+
examples?: unknown[];
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export interface SchemaNodeBase {
|
|
51
|
+
kind: SchemaKind;
|
|
52
|
+
default?: unknown;
|
|
53
|
+
coerce?: CoercionConfig | string | string[];
|
|
54
|
+
metadata?: Record<string, unknown>;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export interface CoercionConfig {
|
|
58
|
+
from?: string;
|
|
59
|
+
trim?: boolean;
|
|
60
|
+
lower?: boolean;
|
|
61
|
+
upper?: boolean;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// String node
|
|
65
|
+
export interface StringSchemaNode extends SchemaNodeBase {
|
|
66
|
+
kind: "string";
|
|
67
|
+
minLength?: number;
|
|
68
|
+
maxLength?: number;
|
|
69
|
+
pattern?: string;
|
|
70
|
+
startsWith?: string;
|
|
71
|
+
endsWith?: string;
|
|
72
|
+
includes?: string;
|
|
73
|
+
format?: StringFormat;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export type StringFormat =
|
|
77
|
+
| "email"
|
|
78
|
+
| "url"
|
|
79
|
+
| "uuid"
|
|
80
|
+
| "ipv4"
|
|
81
|
+
| "ipv6"
|
|
82
|
+
| "date"
|
|
83
|
+
| "date-time";
|
|
84
|
+
|
|
85
|
+
// Numeric nodes
|
|
86
|
+
export interface NumericSchemaNode extends SchemaNodeBase {
|
|
87
|
+
kind:
|
|
88
|
+
| "number"
|
|
89
|
+
| "int"
|
|
90
|
+
| "float32"
|
|
91
|
+
| "float64"
|
|
92
|
+
| "int8"
|
|
93
|
+
| "int16"
|
|
94
|
+
| "int32"
|
|
95
|
+
| "int64"
|
|
96
|
+
| "uint8"
|
|
97
|
+
| "uint16"
|
|
98
|
+
| "uint32"
|
|
99
|
+
| "uint64";
|
|
100
|
+
min?: number;
|
|
101
|
+
max?: number;
|
|
102
|
+
exclusiveMin?: number;
|
|
103
|
+
exclusiveMax?: number;
|
|
104
|
+
multipleOf?: number;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// Simple nodes
|
|
108
|
+
export interface SimpleSchemaNode extends SchemaNodeBase {
|
|
109
|
+
kind: "any" | "unknown" | "never" | "null" | "bool";
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Literal node
|
|
113
|
+
export interface LiteralSchemaNode extends SchemaNodeBase {
|
|
114
|
+
kind: "literal";
|
|
115
|
+
value: string | number | boolean | null;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// Enum node
|
|
119
|
+
export interface EnumSchemaNode extends SchemaNodeBase {
|
|
120
|
+
kind: "enum";
|
|
121
|
+
values: (string | number)[];
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// Array node
|
|
125
|
+
export interface ArraySchemaNode extends SchemaNodeBase {
|
|
126
|
+
kind: "array";
|
|
127
|
+
items: SchemaNode;
|
|
128
|
+
minItems?: number;
|
|
129
|
+
maxItems?: number;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// Tuple node
|
|
133
|
+
export interface TupleSchemaNode extends SchemaNodeBase {
|
|
134
|
+
kind: "tuple";
|
|
135
|
+
items: SchemaNode[];
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// Object node
|
|
139
|
+
export interface ObjectSchemaNode extends SchemaNodeBase {
|
|
140
|
+
kind: "object";
|
|
141
|
+
properties: Record<string, SchemaNode>;
|
|
142
|
+
required: string[];
|
|
143
|
+
unknownKeys?: UnknownKeyMode;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// Record node
|
|
147
|
+
export interface RecordSchemaNode extends SchemaNodeBase {
|
|
148
|
+
kind: "record";
|
|
149
|
+
valueSchema: SchemaNode;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// Union node
|
|
153
|
+
export interface UnionSchemaNode extends SchemaNodeBase {
|
|
154
|
+
kind: "union";
|
|
155
|
+
variants: SchemaNode[];
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// Intersection node
|
|
159
|
+
export interface IntersectionSchemaNode extends SchemaNodeBase {
|
|
160
|
+
kind: "intersection";
|
|
161
|
+
allOf: SchemaNode[];
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// Optional node
|
|
165
|
+
export interface OptionalSchemaNode extends SchemaNodeBase {
|
|
166
|
+
kind: "optional";
|
|
167
|
+
inner: SchemaNode;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// Nullable node
|
|
171
|
+
export interface NullableSchemaNode extends SchemaNodeBase {
|
|
172
|
+
kind: "nullable";
|
|
173
|
+
inner: SchemaNode;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// Ref node
|
|
177
|
+
export interface RefSchemaNode extends SchemaNodeBase {
|
|
178
|
+
kind: "ref";
|
|
179
|
+
ref: string;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
export type SchemaNode =
|
|
183
|
+
| StringSchemaNode
|
|
184
|
+
| NumericSchemaNode
|
|
185
|
+
| SimpleSchemaNode
|
|
186
|
+
| LiteralSchemaNode
|
|
187
|
+
| EnumSchemaNode
|
|
188
|
+
| ArraySchemaNode
|
|
189
|
+
| TupleSchemaNode
|
|
190
|
+
| ObjectSchemaNode
|
|
191
|
+
| RecordSchemaNode
|
|
192
|
+
| UnionSchemaNode
|
|
193
|
+
| IntersectionSchemaNode
|
|
194
|
+
| OptionalSchemaNode
|
|
195
|
+
| NullableSchemaNode
|
|
196
|
+
| RefSchemaNode;
|
|
197
|
+
|
|
198
|
+
// ---- Parse Result ----
|
|
199
|
+
|
|
200
|
+
export type ParseResult<T> =
|
|
201
|
+
| { success: true; data: T }
|
|
202
|
+
| { success: false; issues: ValidationIssue[] };
|
|
203
|
+
|
|
204
|
+
export interface ValidationIssue {
|
|
205
|
+
code: string;
|
|
206
|
+
message: string;
|
|
207
|
+
path: (string | number)[];
|
|
208
|
+
expected?: string;
|
|
209
|
+
received?: string;
|
|
210
|
+
meta?: Record<string, unknown>;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// ---- Interchange Document ----
|
|
214
|
+
|
|
215
|
+
export interface AnyValiDocument {
|
|
216
|
+
anyvaliVersion: string;
|
|
217
|
+
schemaVersion: string;
|
|
218
|
+
root: SchemaNode;
|
|
219
|
+
definitions: Record<string, SchemaNode>;
|
|
220
|
+
extensions: Record<string, Record<string, unknown>>;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// ---- Modes ----
|
|
224
|
+
|
|
225
|
+
export type ExportMode = "portable" | "extended";
|
|
226
|
+
|
|
227
|
+
export type UnknownKeyMode = "reject" | "strip" | "allow";
|
|
228
|
+
|
|
229
|
+
// ---- Parse Context (internal) ----
|
|
230
|
+
|
|
231
|
+
export interface ParseContext {
|
|
232
|
+
path: (string | number)[];
|
|
233
|
+
issues: ValidationIssue[];
|
|
234
|
+
definitions?: Record<string, SchemaNode>;
|
|
235
|
+
/** Tracks objects already being validated to detect circular references. */
|
|
236
|
+
seen?: WeakSet<object>;
|
|
237
|
+
/** Current recursion depth, used to bound unbounded recursion (DoS guard). */
|
|
238
|
+
depth?: number;
|
|
239
|
+
}
|
|
@@ -1,99 +1,99 @@
|
|
|
1
|
-
import { describe, it, expect } from "vitest";
|
|
2
|
-
import { array, tuple, record, string, int, bool, any } from "../../src/index.js";
|
|
3
|
-
|
|
4
|
-
describe("ArraySchema", () => {
|
|
5
|
-
it("accepts valid arrays", () => {
|
|
6
|
-
const s = array(int());
|
|
7
|
-
expect(s.parse([1, 2, 3])).toEqual([1, 2, 3]);
|
|
8
|
-
});
|
|
9
|
-
|
|
10
|
-
it("rejects non-arrays", () => {
|
|
11
|
-
const s = array(int());
|
|
12
|
-
expect(s.safeParse("not an array").success).toBe(false);
|
|
13
|
-
});
|
|
14
|
-
|
|
15
|
-
it("validates array items", () => {
|
|
16
|
-
const s = array(int());
|
|
17
|
-
const result = s.safeParse([1, "two", 3]);
|
|
18
|
-
expect(result.success).toBe(false);
|
|
19
|
-
if (!result.success) {
|
|
20
|
-
expect(result.issues[0].path).toEqual([1]);
|
|
21
|
-
}
|
|
22
|
-
});
|
|
23
|
-
|
|
24
|
-
it("validates minItems", () => {
|
|
25
|
-
const s = array(int()).minItems(2);
|
|
26
|
-
expect(s.parse([1, 2])).toEqual([1, 2]);
|
|
27
|
-
expect(s.safeParse([1]).success).toBe(false);
|
|
28
|
-
});
|
|
29
|
-
|
|
30
|
-
it("validates maxItems", () => {
|
|
31
|
-
const s = array(int()).maxItems(2);
|
|
32
|
-
expect(s.parse([1, 2])).toEqual([1, 2]);
|
|
33
|
-
expect(s.safeParse([1, 2, 3]).success).toBe(false);
|
|
34
|
-
});
|
|
35
|
-
});
|
|
36
|
-
|
|
37
|
-
describe("TupleSchema", () => {
|
|
38
|
-
it("accepts valid tuples", () => {
|
|
39
|
-
const s = tuple([string(), int(), bool()]);
|
|
40
|
-
expect(s.parse(["hello", 42, true])).toEqual(["hello", 42, true]);
|
|
41
|
-
});
|
|
42
|
-
|
|
43
|
-
it("rejects wrong length", () => {
|
|
44
|
-
const s = tuple([string(), int()]);
|
|
45
|
-
expect(s.safeParse(["hello"]).success).toBe(false);
|
|
46
|
-
expect(s.safeParse(["hello", 42, true]).success).toBe(false);
|
|
47
|
-
});
|
|
48
|
-
|
|
49
|
-
it("validates element types", () => {
|
|
50
|
-
const s = tuple([string(), int()]);
|
|
51
|
-
const result = s.safeParse([42, "hello"]);
|
|
52
|
-
expect(result.success).toBe(false);
|
|
53
|
-
if (!result.success) {
|
|
54
|
-
expect(result.issues[0].path).toEqual([0]);
|
|
55
|
-
}
|
|
56
|
-
});
|
|
57
|
-
});
|
|
58
|
-
|
|
59
|
-
describe("RecordSchema", () => {
|
|
60
|
-
it("accepts valid records", () => {
|
|
61
|
-
const s = record(int());
|
|
62
|
-
expect(s.parse({ a: 1, b: 2 })).toEqual({ a: 1, b: 2 });
|
|
63
|
-
});
|
|
64
|
-
|
|
65
|
-
it("rejects non-objects", () => {
|
|
66
|
-
const s = record(int());
|
|
67
|
-
expect(s.safeParse("not object").success).toBe(false);
|
|
68
|
-
expect(s.safeParse(null).success).toBe(false);
|
|
69
|
-
expect(s.safeParse([]).success).toBe(false);
|
|
70
|
-
});
|
|
71
|
-
|
|
72
|
-
it("validates record values", () => {
|
|
73
|
-
const s = record(int());
|
|
74
|
-
const result = s.safeParse({ a: 1, b: "two" });
|
|
75
|
-
expect(result.success).toBe(false);
|
|
76
|
-
if (!result.success) {
|
|
77
|
-
expect(result.issues[0].path).toEqual(["b"]);
|
|
78
|
-
}
|
|
79
|
-
});
|
|
80
|
-
|
|
81
|
-
it("preserves __proto__ as data in records", () => {
|
|
82
|
-
const s = record(any());
|
|
83
|
-
const input = JSON.parse(
|
|
84
|
-
'{"safe":1,"__proto__":{"polluted":"yes"}}'
|
|
85
|
-
) as Record<
|
|
86
|
-
string,
|
|
87
|
-
unknown
|
|
88
|
-
>;
|
|
89
|
-
|
|
90
|
-
const result = s.parse(input);
|
|
91
|
-
|
|
92
|
-
expect(result.safe).toBe(1);
|
|
93
|
-
expect(Object.getPrototypeOf(result)).toBe(Object.prototype);
|
|
94
|
-
expect(Object.prototype.hasOwnProperty.call(result, "__proto__")).toBe(true);
|
|
95
|
-
expect(
|
|
96
|
-
Object.getOwnPropertyDescriptor(result, "__proto__")?.value
|
|
97
|
-
).toEqual({ polluted: "yes" });
|
|
98
|
-
});
|
|
99
|
-
});
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
import { array, tuple, record, string, int, bool, any } from "../../src/index.js";
|
|
3
|
+
|
|
4
|
+
describe("ArraySchema", () => {
|
|
5
|
+
it("accepts valid arrays", () => {
|
|
6
|
+
const s = array(int());
|
|
7
|
+
expect(s.parse([1, 2, 3])).toEqual([1, 2, 3]);
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
it("rejects non-arrays", () => {
|
|
11
|
+
const s = array(int());
|
|
12
|
+
expect(s.safeParse("not an array").success).toBe(false);
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
it("validates array items", () => {
|
|
16
|
+
const s = array(int());
|
|
17
|
+
const result = s.safeParse([1, "two", 3]);
|
|
18
|
+
expect(result.success).toBe(false);
|
|
19
|
+
if (!result.success) {
|
|
20
|
+
expect(result.issues[0].path).toEqual([1]);
|
|
21
|
+
}
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it("validates minItems", () => {
|
|
25
|
+
const s = array(int()).minItems(2);
|
|
26
|
+
expect(s.parse([1, 2])).toEqual([1, 2]);
|
|
27
|
+
expect(s.safeParse([1]).success).toBe(false);
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it("validates maxItems", () => {
|
|
31
|
+
const s = array(int()).maxItems(2);
|
|
32
|
+
expect(s.parse([1, 2])).toEqual([1, 2]);
|
|
33
|
+
expect(s.safeParse([1, 2, 3]).success).toBe(false);
|
|
34
|
+
});
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
describe("TupleSchema", () => {
|
|
38
|
+
it("accepts valid tuples", () => {
|
|
39
|
+
const s = tuple([string(), int(), bool()]);
|
|
40
|
+
expect(s.parse(["hello", 42, true])).toEqual(["hello", 42, true]);
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it("rejects wrong length", () => {
|
|
44
|
+
const s = tuple([string(), int()]);
|
|
45
|
+
expect(s.safeParse(["hello"]).success).toBe(false);
|
|
46
|
+
expect(s.safeParse(["hello", 42, true]).success).toBe(false);
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it("validates element types", () => {
|
|
50
|
+
const s = tuple([string(), int()]);
|
|
51
|
+
const result = s.safeParse([42, "hello"]);
|
|
52
|
+
expect(result.success).toBe(false);
|
|
53
|
+
if (!result.success) {
|
|
54
|
+
expect(result.issues[0].path).toEqual([0]);
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
describe("RecordSchema", () => {
|
|
60
|
+
it("accepts valid records", () => {
|
|
61
|
+
const s = record(int());
|
|
62
|
+
expect(s.parse({ a: 1, b: 2 })).toEqual({ a: 1, b: 2 });
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it("rejects non-objects", () => {
|
|
66
|
+
const s = record(int());
|
|
67
|
+
expect(s.safeParse("not object").success).toBe(false);
|
|
68
|
+
expect(s.safeParse(null).success).toBe(false);
|
|
69
|
+
expect(s.safeParse([]).success).toBe(false);
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
it("validates record values", () => {
|
|
73
|
+
const s = record(int());
|
|
74
|
+
const result = s.safeParse({ a: 1, b: "two" });
|
|
75
|
+
expect(result.success).toBe(false);
|
|
76
|
+
if (!result.success) {
|
|
77
|
+
expect(result.issues[0].path).toEqual(["b"]);
|
|
78
|
+
}
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
it("preserves __proto__ as data in records", () => {
|
|
82
|
+
const s = record(any());
|
|
83
|
+
const input = JSON.parse(
|
|
84
|
+
'{"safe":1,"__proto__":{"polluted":"yes"}}'
|
|
85
|
+
) as Record<
|
|
86
|
+
string,
|
|
87
|
+
unknown
|
|
88
|
+
>;
|
|
89
|
+
|
|
90
|
+
const result = s.parse(input);
|
|
91
|
+
|
|
92
|
+
expect(result.safe).toBe(1);
|
|
93
|
+
expect(Object.getPrototypeOf(result)).toBe(Object.prototype);
|
|
94
|
+
expect(Object.prototype.hasOwnProperty.call(result, "__proto__")).toBe(true);
|
|
95
|
+
expect(
|
|
96
|
+
Object.getOwnPropertyDescriptor(result, "__proto__")?.value
|
|
97
|
+
).toEqual({ polluted: "yes" });
|
|
98
|
+
});
|
|
99
|
+
});
|