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,191 +1,191 @@
|
|
|
1
|
-
import { describe, it, expect } from "vitest";
|
|
2
|
-
import {
|
|
3
|
-
string,
|
|
4
|
-
int,
|
|
5
|
-
object,
|
|
6
|
-
array,
|
|
7
|
-
optional,
|
|
8
|
-
nullable,
|
|
9
|
-
bool,
|
|
10
|
-
exportSchema,
|
|
11
|
-
importSchema,
|
|
12
|
-
} from "../../src/index.js";
|
|
13
|
-
|
|
14
|
-
describe("Export", () => {
|
|
15
|
-
it("exports a simple string schema", () => {
|
|
16
|
-
const doc = exportSchema(string().minLength(1).maxLength(100));
|
|
17
|
-
expect(doc.anyvaliVersion).toBe("1.0");
|
|
18
|
-
expect(doc.schemaVersion).toBe("1.1");
|
|
19
|
-
expect(doc.root.kind).toBe("string");
|
|
20
|
-
expect((doc.root as any).minLength).toBe(1);
|
|
21
|
-
expect((doc.root as any).maxLength).toBe(100);
|
|
22
|
-
});
|
|
23
|
-
|
|
24
|
-
it("exports an object schema", () => {
|
|
25
|
-
const s = object({
|
|
26
|
-
name: string(),
|
|
27
|
-
age: optional(int()),
|
|
28
|
-
});
|
|
29
|
-
const doc = exportSchema(s);
|
|
30
|
-
expect(doc.root.kind).toBe("object");
|
|
31
|
-
const root = doc.root as any;
|
|
32
|
-
expect(root.properties.name.kind).toBe("string");
|
|
33
|
-
expect(root.required).toContain("name");
|
|
34
|
-
expect(root.required).not.toContain("age");
|
|
35
|
-
});
|
|
36
|
-
|
|
37
|
-
it("exports defaults", () => {
|
|
38
|
-
const s = string().default("hello");
|
|
39
|
-
const doc = exportSchema(s);
|
|
40
|
-
expect(doc.root.default).toBe("hello");
|
|
41
|
-
});
|
|
42
|
-
|
|
43
|
-
it("exports coercion config", () => {
|
|
44
|
-
const s = int().coerce({ from: "string" });
|
|
45
|
-
const doc = exportSchema(s);
|
|
46
|
-
expect(doc.root.coerce).toEqual({ from: "string" });
|
|
47
|
-
});
|
|
48
|
-
});
|
|
49
|
-
|
|
50
|
-
describe("Import", () => {
|
|
51
|
-
it("round-trips a string schema", () => {
|
|
52
|
-
const original = string().minLength(1).maxLength(100);
|
|
53
|
-
const doc = exportSchema(original);
|
|
54
|
-
const imported = importSchema(doc);
|
|
55
|
-
expect(imported.parse("hello")).toBe("hello");
|
|
56
|
-
expect(imported.safeParse("").success).toBe(false);
|
|
57
|
-
});
|
|
58
|
-
|
|
59
|
-
it("round-trips an object schema", () => {
|
|
60
|
-
const original = object({
|
|
61
|
-
name: string(),
|
|
62
|
-
active: bool().default(true),
|
|
63
|
-
});
|
|
64
|
-
const doc = exportSchema(original);
|
|
65
|
-
const imported = importSchema(doc);
|
|
66
|
-
const result = imported.parse({ name: "Alice" });
|
|
67
|
-
expect(result).toEqual({ name: "Alice", active: true });
|
|
68
|
-
});
|
|
69
|
-
|
|
70
|
-
it("round-trips an array schema", () => {
|
|
71
|
-
const original = array(int()).minItems(1);
|
|
72
|
-
const doc = exportSchema(original);
|
|
73
|
-
const imported = importSchema(doc);
|
|
74
|
-
expect(imported.parse([1, 2, 3])).toEqual([1, 2, 3]);
|
|
75
|
-
expect(imported.safeParse([]).success).toBe(false);
|
|
76
|
-
});
|
|
77
|
-
|
|
78
|
-
it("round-trips nullable", () => {
|
|
79
|
-
const original = nullable(string());
|
|
80
|
-
const doc = exportSchema(original);
|
|
81
|
-
const imported = importSchema(doc);
|
|
82
|
-
expect(imported.parse(null)).toBe(null);
|
|
83
|
-
expect(imported.parse("hello")).toBe("hello");
|
|
84
|
-
});
|
|
85
|
-
|
|
86
|
-
it("round-trips coercion", () => {
|
|
87
|
-
const original = int().coerce({ from: "string" });
|
|
88
|
-
const doc = exportSchema(original);
|
|
89
|
-
const imported = importSchema(doc);
|
|
90
|
-
expect(imported.parse("42")).toBe(42);
|
|
91
|
-
});
|
|
92
|
-
|
|
93
|
-
it("throws on missing kind field", () => {
|
|
94
|
-
const doc = {
|
|
95
|
-
anyvaliVersion: "1.0",
|
|
96
|
-
schemaVersion: "1",
|
|
97
|
-
root: {},
|
|
98
|
-
definitions: {},
|
|
99
|
-
extensions: {},
|
|
100
|
-
};
|
|
101
|
-
expect(() => importSchema(doc as any)).toThrow();
|
|
102
|
-
});
|
|
103
|
-
|
|
104
|
-
it("throws on null/empty root", () => {
|
|
105
|
-
expect(() =>
|
|
106
|
-
importSchema({ anyvaliVersion: "1.0", schemaVersion: "1", root: null } as any)
|
|
107
|
-
).toThrow();
|
|
108
|
-
expect(() =>
|
|
109
|
-
importSchema({ anyvaliVersion: "1.0", schemaVersion: "1" } as any)
|
|
110
|
-
).toThrow();
|
|
111
|
-
});
|
|
112
|
-
|
|
113
|
-
it("imports invalid regex patterns without throwing", () => {
|
|
114
|
-
const doc = {
|
|
115
|
-
anyvaliVersion: "1.0",
|
|
116
|
-
schemaVersion: "1.1",
|
|
117
|
-
root: { kind: "string", pattern: "(" },
|
|
118
|
-
definitions: {},
|
|
119
|
-
extensions: {},
|
|
120
|
-
};
|
|
121
|
-
|
|
122
|
-
const imported = importSchema(doc as any);
|
|
123
|
-
|
|
124
|
-
expect(() => imported.safeParse("abc")).not.toThrow();
|
|
125
|
-
expect(imported.safeParse("abc").success).toBe(false);
|
|
126
|
-
});
|
|
127
|
-
|
|
128
|
-
it("imports array schemas with canonical items keys", () => {
|
|
129
|
-
const doc = {
|
|
130
|
-
anyvaliVersion: "1.0",
|
|
131
|
-
schemaVersion: "1.1",
|
|
132
|
-
root: {
|
|
133
|
-
kind: "array",
|
|
134
|
-
items: { kind: "int" },
|
|
135
|
-
},
|
|
136
|
-
definitions: {},
|
|
137
|
-
extensions: {},
|
|
138
|
-
};
|
|
139
|
-
|
|
140
|
-
const imported = importSchema(doc as any);
|
|
141
|
-
expect(imported.parse([1, 2, 3])).toEqual([1, 2, 3]);
|
|
142
|
-
expect(imported.safeParse(["a"]).success).toBe(false);
|
|
143
|
-
});
|
|
144
|
-
|
|
145
|
-
it("imports union schemas with canonical variants keys", () => {
|
|
146
|
-
const doc = {
|
|
147
|
-
anyvaliVersion: "1.0",
|
|
148
|
-
schemaVersion: "1.1",
|
|
149
|
-
root: {
|
|
150
|
-
kind: "union",
|
|
151
|
-
variants: [{ kind: "string" }, { kind: "int" }],
|
|
152
|
-
},
|
|
153
|
-
definitions: {},
|
|
154
|
-
extensions: {},
|
|
155
|
-
};
|
|
156
|
-
|
|
157
|
-
const imported = importSchema(doc as any);
|
|
158
|
-
expect(imported.parse("hello")).toBe("hello");
|
|
159
|
-
expect(imported.parse(42)).toBe(42);
|
|
160
|
-
expect(imported.safeParse(true).success).toBe(false);
|
|
161
|
-
});
|
|
162
|
-
|
|
163
|
-
it("imports __proto__ property names without prototype pollution", () => {
|
|
164
|
-
const doc = JSON.parse(`{
|
|
165
|
-
"anyvaliVersion": "1.0",
|
|
166
|
-
"schemaVersion": "1.1",
|
|
167
|
-
"root": {
|
|
168
|
-
"kind": "object",
|
|
169
|
-
"properties": {
|
|
170
|
-
"__proto__": { "kind": "string" }
|
|
171
|
-
},
|
|
172
|
-
"required": ["__proto__"],
|
|
173
|
-
"unknownKeys": "reject"
|
|
174
|
-
},
|
|
175
|
-
"definitions": {},
|
|
176
|
-
"extensions": {}
|
|
177
|
-
}`);
|
|
178
|
-
const input = JSON.parse('{"__proto__":"safe"}') as Record<string, unknown>;
|
|
179
|
-
|
|
180
|
-
const imported = importSchema(doc as any);
|
|
181
|
-
|
|
182
|
-
expect(() => imported.parse(input)).not.toThrow();
|
|
183
|
-
const result = imported.parse(input) as Record<string, unknown>;
|
|
184
|
-
expect(Object.getPrototypeOf(result)).toBe(Object.prototype);
|
|
185
|
-
expect(Object.prototype.hasOwnProperty.call(result, "__proto__")).toBe(true);
|
|
186
|
-
expect(Object.getOwnPropertyDescriptor(result, "__proto__")?.value).toBe(
|
|
187
|
-
"safe"
|
|
188
|
-
);
|
|
189
|
-
expect(({} as Record<string, unknown>).safe).toBeUndefined();
|
|
190
|
-
});
|
|
191
|
-
});
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
import {
|
|
3
|
+
string,
|
|
4
|
+
int,
|
|
5
|
+
object,
|
|
6
|
+
array,
|
|
7
|
+
optional,
|
|
8
|
+
nullable,
|
|
9
|
+
bool,
|
|
10
|
+
exportSchema,
|
|
11
|
+
importSchema,
|
|
12
|
+
} from "../../src/index.js";
|
|
13
|
+
|
|
14
|
+
describe("Export", () => {
|
|
15
|
+
it("exports a simple string schema", () => {
|
|
16
|
+
const doc = exportSchema(string().minLength(1).maxLength(100));
|
|
17
|
+
expect(doc.anyvaliVersion).toBe("1.0");
|
|
18
|
+
expect(doc.schemaVersion).toBe("1.1");
|
|
19
|
+
expect(doc.root.kind).toBe("string");
|
|
20
|
+
expect((doc.root as any).minLength).toBe(1);
|
|
21
|
+
expect((doc.root as any).maxLength).toBe(100);
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it("exports an object schema", () => {
|
|
25
|
+
const s = object({
|
|
26
|
+
name: string(),
|
|
27
|
+
age: optional(int()),
|
|
28
|
+
});
|
|
29
|
+
const doc = exportSchema(s);
|
|
30
|
+
expect(doc.root.kind).toBe("object");
|
|
31
|
+
const root = doc.root as any;
|
|
32
|
+
expect(root.properties.name.kind).toBe("string");
|
|
33
|
+
expect(root.required).toContain("name");
|
|
34
|
+
expect(root.required).not.toContain("age");
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it("exports defaults", () => {
|
|
38
|
+
const s = string().default("hello");
|
|
39
|
+
const doc = exportSchema(s);
|
|
40
|
+
expect(doc.root.default).toBe("hello");
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it("exports coercion config", () => {
|
|
44
|
+
const s = int().coerce({ from: "string" });
|
|
45
|
+
const doc = exportSchema(s);
|
|
46
|
+
expect(doc.root.coerce).toEqual({ from: "string" });
|
|
47
|
+
});
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
describe("Import", () => {
|
|
51
|
+
it("round-trips a string schema", () => {
|
|
52
|
+
const original = string().minLength(1).maxLength(100);
|
|
53
|
+
const doc = exportSchema(original);
|
|
54
|
+
const imported = importSchema(doc);
|
|
55
|
+
expect(imported.parse("hello")).toBe("hello");
|
|
56
|
+
expect(imported.safeParse("").success).toBe(false);
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it("round-trips an object schema", () => {
|
|
60
|
+
const original = object({
|
|
61
|
+
name: string(),
|
|
62
|
+
active: bool().default(true),
|
|
63
|
+
});
|
|
64
|
+
const doc = exportSchema(original);
|
|
65
|
+
const imported = importSchema(doc);
|
|
66
|
+
const result = imported.parse({ name: "Alice" });
|
|
67
|
+
expect(result).toEqual({ name: "Alice", active: true });
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
it("round-trips an array schema", () => {
|
|
71
|
+
const original = array(int()).minItems(1);
|
|
72
|
+
const doc = exportSchema(original);
|
|
73
|
+
const imported = importSchema(doc);
|
|
74
|
+
expect(imported.parse([1, 2, 3])).toEqual([1, 2, 3]);
|
|
75
|
+
expect(imported.safeParse([]).success).toBe(false);
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
it("round-trips nullable", () => {
|
|
79
|
+
const original = nullable(string());
|
|
80
|
+
const doc = exportSchema(original);
|
|
81
|
+
const imported = importSchema(doc);
|
|
82
|
+
expect(imported.parse(null)).toBe(null);
|
|
83
|
+
expect(imported.parse("hello")).toBe("hello");
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
it("round-trips coercion", () => {
|
|
87
|
+
const original = int().coerce({ from: "string" });
|
|
88
|
+
const doc = exportSchema(original);
|
|
89
|
+
const imported = importSchema(doc);
|
|
90
|
+
expect(imported.parse("42")).toBe(42);
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
it("throws on missing kind field", () => {
|
|
94
|
+
const doc = {
|
|
95
|
+
anyvaliVersion: "1.0",
|
|
96
|
+
schemaVersion: "1",
|
|
97
|
+
root: {},
|
|
98
|
+
definitions: {},
|
|
99
|
+
extensions: {},
|
|
100
|
+
};
|
|
101
|
+
expect(() => importSchema(doc as any)).toThrow();
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
it("throws on null/empty root", () => {
|
|
105
|
+
expect(() =>
|
|
106
|
+
importSchema({ anyvaliVersion: "1.0", schemaVersion: "1", root: null } as any)
|
|
107
|
+
).toThrow();
|
|
108
|
+
expect(() =>
|
|
109
|
+
importSchema({ anyvaliVersion: "1.0", schemaVersion: "1" } as any)
|
|
110
|
+
).toThrow();
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
it("imports invalid regex patterns without throwing", () => {
|
|
114
|
+
const doc = {
|
|
115
|
+
anyvaliVersion: "1.0",
|
|
116
|
+
schemaVersion: "1.1",
|
|
117
|
+
root: { kind: "string", pattern: "(" },
|
|
118
|
+
definitions: {},
|
|
119
|
+
extensions: {},
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
const imported = importSchema(doc as any);
|
|
123
|
+
|
|
124
|
+
expect(() => imported.safeParse("abc")).not.toThrow();
|
|
125
|
+
expect(imported.safeParse("abc").success).toBe(false);
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
it("imports array schemas with canonical items keys", () => {
|
|
129
|
+
const doc = {
|
|
130
|
+
anyvaliVersion: "1.0",
|
|
131
|
+
schemaVersion: "1.1",
|
|
132
|
+
root: {
|
|
133
|
+
kind: "array",
|
|
134
|
+
items: { kind: "int" },
|
|
135
|
+
},
|
|
136
|
+
definitions: {},
|
|
137
|
+
extensions: {},
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
const imported = importSchema(doc as any);
|
|
141
|
+
expect(imported.parse([1, 2, 3])).toEqual([1, 2, 3]);
|
|
142
|
+
expect(imported.safeParse(["a"]).success).toBe(false);
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
it("imports union schemas with canonical variants keys", () => {
|
|
146
|
+
const doc = {
|
|
147
|
+
anyvaliVersion: "1.0",
|
|
148
|
+
schemaVersion: "1.1",
|
|
149
|
+
root: {
|
|
150
|
+
kind: "union",
|
|
151
|
+
variants: [{ kind: "string" }, { kind: "int" }],
|
|
152
|
+
},
|
|
153
|
+
definitions: {},
|
|
154
|
+
extensions: {},
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
const imported = importSchema(doc as any);
|
|
158
|
+
expect(imported.parse("hello")).toBe("hello");
|
|
159
|
+
expect(imported.parse(42)).toBe(42);
|
|
160
|
+
expect(imported.safeParse(true).success).toBe(false);
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
it("imports __proto__ property names without prototype pollution", () => {
|
|
164
|
+
const doc = JSON.parse(`{
|
|
165
|
+
"anyvaliVersion": "1.0",
|
|
166
|
+
"schemaVersion": "1.1",
|
|
167
|
+
"root": {
|
|
168
|
+
"kind": "object",
|
|
169
|
+
"properties": {
|
|
170
|
+
"__proto__": { "kind": "string" }
|
|
171
|
+
},
|
|
172
|
+
"required": ["__proto__"],
|
|
173
|
+
"unknownKeys": "reject"
|
|
174
|
+
},
|
|
175
|
+
"definitions": {},
|
|
176
|
+
"extensions": {}
|
|
177
|
+
}`);
|
|
178
|
+
const input = JSON.parse('{"__proto__":"safe"}') as Record<string, unknown>;
|
|
179
|
+
|
|
180
|
+
const imported = importSchema(doc as any);
|
|
181
|
+
|
|
182
|
+
expect(() => imported.parse(input)).not.toThrow();
|
|
183
|
+
const result = imported.parse(input) as Record<string, unknown>;
|
|
184
|
+
expect(Object.getPrototypeOf(result)).toBe(Object.prototype);
|
|
185
|
+
expect(Object.prototype.hasOwnProperty.call(result, "__proto__")).toBe(true);
|
|
186
|
+
expect(Object.getOwnPropertyDescriptor(result, "__proto__")?.value).toBe(
|
|
187
|
+
"safe"
|
|
188
|
+
);
|
|
189
|
+
expect(({} as Record<string, unknown>).safe).toBeUndefined();
|
|
190
|
+
});
|
|
191
|
+
});
|