anyvali 0.3.1 → 0.3.4
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 +67 -44
- package/README.md +370 -370
- package/dist/parse/coerce.d.ts.map +1 -1
- package/dist/parse/coerce.js +14 -0
- package/dist/parse/coerce.js.map +1 -1
- package/dist/schemas/number.d.ts.map +1 -1
- package/dist/schemas/number.js +15 -0
- package/dist/schemas/number.js.map +1 -1
- 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/parse/coerce.ts +15 -0
- package/src/schemas/base.ts +322 -322
- package/src/schemas/intersection.ts +81 -81
- package/src/schemas/number.ts +17 -0
- 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 +1067 -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,33 +1,33 @@
|
|
|
1
|
-
import { describe, it, expect } from "vitest";
|
|
2
|
-
import { object, int, array } from "../../src/index.js";
|
|
3
|
-
|
|
4
|
-
// A shared (but NON-circular) object reference appearing in sibling positions
|
|
5
|
-
// must validate. The circular-reference guard must only trip on true cycles
|
|
6
|
-
// (an ancestor reappearing), not on a DAG / repeated sibling.
|
|
7
|
-
describe("Shared non-circular references (false-positive guard)", () => {
|
|
8
|
-
it("same object used in two object fields validates", () => {
|
|
9
|
-
const inner = object({ a: int() });
|
|
10
|
-
const s = object({ x: inner, y: inner });
|
|
11
|
-
const shared = { a: 1 };
|
|
12
|
-
const r = s.safeParse({ x: shared, y: shared });
|
|
13
|
-
expect(r.success).toBe(true);
|
|
14
|
-
});
|
|
15
|
-
|
|
16
|
-
it("same object repeated in an array validates", () => {
|
|
17
|
-
const s = array(object({ a: int() }));
|
|
18
|
-
const shared = { a: 1 };
|
|
19
|
-
const r = s.safeParse([shared, shared, shared]);
|
|
20
|
-
expect(r.success).toBe(true);
|
|
21
|
-
});
|
|
22
|
-
|
|
23
|
-
it("a genuinely circular input is still rejected (not an infinite loop)", () => {
|
|
24
|
-
const s = object({ self: object({ a: int() }) });
|
|
25
|
-
const cyclic: any = {};
|
|
26
|
-
cyclic.self = cyclic; // true cycle
|
|
27
|
-
let r: ReturnType<typeof s.safeParse>;
|
|
28
|
-
expect(() => {
|
|
29
|
-
r = s.safeParse(cyclic);
|
|
30
|
-
}).not.toThrow();
|
|
31
|
-
expect(r!.success).toBe(false);
|
|
32
|
-
});
|
|
33
|
-
});
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
import { object, int, array } from "../../src/index.js";
|
|
3
|
+
|
|
4
|
+
// A shared (but NON-circular) object reference appearing in sibling positions
|
|
5
|
+
// must validate. The circular-reference guard must only trip on true cycles
|
|
6
|
+
// (an ancestor reappearing), not on a DAG / repeated sibling.
|
|
7
|
+
describe("Shared non-circular references (false-positive guard)", () => {
|
|
8
|
+
it("same object used in two object fields validates", () => {
|
|
9
|
+
const inner = object({ a: int() });
|
|
10
|
+
const s = object({ x: inner, y: inner });
|
|
11
|
+
const shared = { a: 1 };
|
|
12
|
+
const r = s.safeParse({ x: shared, y: shared });
|
|
13
|
+
expect(r.success).toBe(true);
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
it("same object repeated in an array validates", () => {
|
|
17
|
+
const s = array(object({ a: int() }));
|
|
18
|
+
const shared = { a: 1 };
|
|
19
|
+
const r = s.safeParse([shared, shared, shared]);
|
|
20
|
+
expect(r.success).toBe(true);
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it("a genuinely circular input is still rejected (not an infinite loop)", () => {
|
|
24
|
+
const s = object({ self: object({ a: int() }) });
|
|
25
|
+
const cyclic: any = {};
|
|
26
|
+
cyclic.self = cyclic; // true cycle
|
|
27
|
+
let r: ReturnType<typeof s.safeParse>;
|
|
28
|
+
expect(() => {
|
|
29
|
+
r = s.safeParse(cyclic);
|
|
30
|
+
}).not.toThrow();
|
|
31
|
+
expect(r!.success).toBe(false);
|
|
32
|
+
});
|
|
33
|
+
});
|
|
@@ -1,46 +1,46 @@
|
|
|
1
|
-
import { describe, it, expect } from "vitest";
|
|
2
|
-
import { string, array } from "../../src/index.js";
|
|
3
|
-
|
|
4
|
-
describe("String pattern: caching + ReDoS defense-in-depth", () => {
|
|
5
|
-
it("matches and rejects correctly (behavior preserved by caching)", () => {
|
|
6
|
-
const s = string().pattern("^[a-z]+$");
|
|
7
|
-
expect(s.safeParse("abc").success).toBe(true);
|
|
8
|
-
const bad = s.safeParse("ABC");
|
|
9
|
-
expect(bad.success).toBe(false);
|
|
10
|
-
if (!bad.success) expect(bad.issues[0].code).toBe("invalid_string");
|
|
11
|
-
});
|
|
12
|
-
|
|
13
|
-
it("cached regex stays correct when reused across many values", () => {
|
|
14
|
-
const s = array(string().pattern("^\\d{3}$"));
|
|
15
|
-
const r = s.safeParse(["123", "456", "789"]);
|
|
16
|
-
expect(r.success).toBe(true);
|
|
17
|
-
const r2 = s.safeParse(["123", "xx", "789"]);
|
|
18
|
-
expect(r2.success).toBe(false);
|
|
19
|
-
});
|
|
20
|
-
|
|
21
|
-
it("invalid pattern is reported as invalid_string", () => {
|
|
22
|
-
const s = string().pattern("(");
|
|
23
|
-
const r = s.safeParse("anything");
|
|
24
|
-
expect(r.success).toBe(false);
|
|
25
|
-
if (!r.success) {
|
|
26
|
-
expect(r.issues[0].code).toBe("invalid_string");
|
|
27
|
-
expect(r.issues[0].message).toContain("Invalid regex pattern");
|
|
28
|
-
}
|
|
29
|
-
});
|
|
30
|
-
|
|
31
|
-
it("re-patterning resets the compiled cache", () => {
|
|
32
|
-
const base = string().pattern("^a$");
|
|
33
|
-
const repat = base.pattern("^b$");
|
|
34
|
-
expect(base.safeParse("a").success).toBe(true);
|
|
35
|
-
expect(repat.safeParse("b").success).toBe(true);
|
|
36
|
-
expect(repat.safeParse("a").success).toBe(false);
|
|
37
|
-
});
|
|
38
|
-
|
|
39
|
-
it("long input with a safe (linear) pattern still validates", () => {
|
|
40
|
-
// Caching must not change behavior for legitimate large inputs.
|
|
41
|
-
const s = string().pattern("^x+$");
|
|
42
|
-
const big = "x".repeat(1_000_000);
|
|
43
|
-
const r = s.safeParse(big);
|
|
44
|
-
expect(r.success).toBe(true);
|
|
45
|
-
});
|
|
46
|
-
});
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
import { string, array } from "../../src/index.js";
|
|
3
|
+
|
|
4
|
+
describe("String pattern: caching + ReDoS defense-in-depth", () => {
|
|
5
|
+
it("matches and rejects correctly (behavior preserved by caching)", () => {
|
|
6
|
+
const s = string().pattern("^[a-z]+$");
|
|
7
|
+
expect(s.safeParse("abc").success).toBe(true);
|
|
8
|
+
const bad = s.safeParse("ABC");
|
|
9
|
+
expect(bad.success).toBe(false);
|
|
10
|
+
if (!bad.success) expect(bad.issues[0].code).toBe("invalid_string");
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
it("cached regex stays correct when reused across many values", () => {
|
|
14
|
+
const s = array(string().pattern("^\\d{3}$"));
|
|
15
|
+
const r = s.safeParse(["123", "456", "789"]);
|
|
16
|
+
expect(r.success).toBe(true);
|
|
17
|
+
const r2 = s.safeParse(["123", "xx", "789"]);
|
|
18
|
+
expect(r2.success).toBe(false);
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
it("invalid pattern is reported as invalid_string", () => {
|
|
22
|
+
const s = string().pattern("(");
|
|
23
|
+
const r = s.safeParse("anything");
|
|
24
|
+
expect(r.success).toBe(false);
|
|
25
|
+
if (!r.success) {
|
|
26
|
+
expect(r.issues[0].code).toBe("invalid_string");
|
|
27
|
+
expect(r.issues[0].message).toContain("Invalid regex pattern");
|
|
28
|
+
}
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it("re-patterning resets the compiled cache", () => {
|
|
32
|
+
const base = string().pattern("^a$");
|
|
33
|
+
const repat = base.pattern("^b$");
|
|
34
|
+
expect(base.safeParse("a").success).toBe(true);
|
|
35
|
+
expect(repat.safeParse("b").success).toBe(true);
|
|
36
|
+
expect(repat.safeParse("a").success).toBe(false);
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it("long input with a safe (linear) pattern still validates", () => {
|
|
40
|
+
// Caching must not change behavior for legitimate large inputs.
|
|
41
|
+
const s = string().pattern("^x+$");
|
|
42
|
+
const big = "x".repeat(1_000_000);
|
|
43
|
+
const r = s.safeParse(big);
|
|
44
|
+
expect(r.success).toBe(true);
|
|
45
|
+
});
|
|
46
|
+
});
|
|
@@ -1,147 +1,147 @@
|
|
|
1
|
-
import { describe, it, expect } from "vitest";
|
|
2
|
-
import { string } from "../../src/index.js";
|
|
3
|
-
|
|
4
|
-
describe("StringSchema", () => {
|
|
5
|
-
it("accepts valid strings", () => {
|
|
6
|
-
const s = string();
|
|
7
|
-
expect(s.parse("hello")).toBe("hello");
|
|
8
|
-
expect(s.parse("")).toBe("");
|
|
9
|
-
});
|
|
10
|
-
|
|
11
|
-
it("rejects non-strings", () => {
|
|
12
|
-
const s = string();
|
|
13
|
-
const result = s.safeParse(42);
|
|
14
|
-
expect(result.success).toBe(false);
|
|
15
|
-
if (!result.success) {
|
|
16
|
-
expect(result.issues[0].code).toBe("invalid_type");
|
|
17
|
-
}
|
|
18
|
-
});
|
|
19
|
-
|
|
20
|
-
it("validates minLength", () => {
|
|
21
|
-
const s = string().minLength(3);
|
|
22
|
-
expect(s.parse("abc")).toBe("abc");
|
|
23
|
-
const result = s.safeParse("ab");
|
|
24
|
-
expect(result.success).toBe(false);
|
|
25
|
-
if (!result.success) {
|
|
26
|
-
expect(result.issues[0].code).toBe("too_small");
|
|
27
|
-
}
|
|
28
|
-
});
|
|
29
|
-
|
|
30
|
-
it("validates maxLength", () => {
|
|
31
|
-
const s = string().maxLength(3);
|
|
32
|
-
expect(s.parse("abc")).toBe("abc");
|
|
33
|
-
const result = s.safeParse("abcd");
|
|
34
|
-
expect(result.success).toBe(false);
|
|
35
|
-
if (!result.success) {
|
|
36
|
-
expect(result.issues[0].code).toBe("too_large");
|
|
37
|
-
}
|
|
38
|
-
});
|
|
39
|
-
|
|
40
|
-
it("validates pattern", () => {
|
|
41
|
-
const s = string().pattern("^[a-z]+$");
|
|
42
|
-
expect(s.parse("abc")).toBe("abc");
|
|
43
|
-
const result = s.safeParse("ABC");
|
|
44
|
-
expect(result.success).toBe(false);
|
|
45
|
-
if (!result.success) {
|
|
46
|
-
expect(result.issues[0].code).toBe("invalid_string");
|
|
47
|
-
}
|
|
48
|
-
});
|
|
49
|
-
|
|
50
|
-
it("fails invalid regex patterns without throwing", () => {
|
|
51
|
-
const s = string().pattern("(");
|
|
52
|
-
expect(() => s.safeParse("abc")).not.toThrow();
|
|
53
|
-
const result = s.safeParse("abc");
|
|
54
|
-
expect(result.success).toBe(false);
|
|
55
|
-
if (!result.success) {
|
|
56
|
-
expect(result.issues[0].code).toBe("invalid_string");
|
|
57
|
-
}
|
|
58
|
-
});
|
|
59
|
-
|
|
60
|
-
it("validates startsWith", () => {
|
|
61
|
-
const s = string().startsWith("hello");
|
|
62
|
-
expect(s.parse("hello world")).toBe("hello world");
|
|
63
|
-
const result = s.safeParse("world hello");
|
|
64
|
-
expect(result.success).toBe(false);
|
|
65
|
-
});
|
|
66
|
-
|
|
67
|
-
it("validates endsWith", () => {
|
|
68
|
-
const s = string().endsWith("world");
|
|
69
|
-
expect(s.parse("hello world")).toBe("hello world");
|
|
70
|
-
const result = s.safeParse("world hello");
|
|
71
|
-
expect(result.success).toBe(false);
|
|
72
|
-
});
|
|
73
|
-
|
|
74
|
-
it("validates includes", () => {
|
|
75
|
-
const s = string().includes("mid");
|
|
76
|
-
expect(s.parse("a mid b")).toBe("a mid b");
|
|
77
|
-
const result = s.safeParse("no match");
|
|
78
|
-
expect(result.success).toBe(false);
|
|
79
|
-
});
|
|
80
|
-
|
|
81
|
-
it("validates email format", () => {
|
|
82
|
-
const s = string().format("email");
|
|
83
|
-
expect(s.parse("user@example.com")).toBe("user@example.com");
|
|
84
|
-
const result = s.safeParse("notanemail");
|
|
85
|
-
expect(result.success).toBe(false);
|
|
86
|
-
});
|
|
87
|
-
|
|
88
|
-
it("validates uuid format", () => {
|
|
89
|
-
const s = string().format("uuid");
|
|
90
|
-
expect(s.parse("550e8400-e29b-41d4-a716-446655440000")).toBe(
|
|
91
|
-
"550e8400-e29b-41d4-a716-446655440000"
|
|
92
|
-
);
|
|
93
|
-
const result = s.safeParse("not-a-uuid");
|
|
94
|
-
expect(result.success).toBe(false);
|
|
95
|
-
});
|
|
96
|
-
|
|
97
|
-
it("validates url format", () => {
|
|
98
|
-
const s = string().format("url");
|
|
99
|
-
expect(s.parse("https://example.com")).toBe("https://example.com");
|
|
100
|
-
const result = s.safeParse("not a url");
|
|
101
|
-
expect(result.success).toBe(false);
|
|
102
|
-
});
|
|
103
|
-
|
|
104
|
-
it("validates ipv4 format", () => {
|
|
105
|
-
const s = string().format("ipv4");
|
|
106
|
-
expect(s.parse("192.168.1.1")).toBe("192.168.1.1");
|
|
107
|
-
const result = s.safeParse("999.999.999.999");
|
|
108
|
-
expect(result.success).toBe(false);
|
|
109
|
-
});
|
|
110
|
-
|
|
111
|
-
it("validates date format", () => {
|
|
112
|
-
const s = string().format("date");
|
|
113
|
-
expect(s.parse("2024-01-15")).toBe("2024-01-15");
|
|
114
|
-
const result = s.safeParse("2024-13-01");
|
|
115
|
-
expect(result.success).toBe(false);
|
|
116
|
-
});
|
|
117
|
-
|
|
118
|
-
it("validates date-time format", () => {
|
|
119
|
-
const s = string().format("date-time");
|
|
120
|
-
expect(s.parse("2024-01-15T10:30:00Z")).toBe("2024-01-15T10:30:00Z");
|
|
121
|
-
const result = s.safeParse("not-a-date-time");
|
|
122
|
-
expect(result.success).toBe(false);
|
|
123
|
-
});
|
|
124
|
-
|
|
125
|
-
it("chains constraints immutably", () => {
|
|
126
|
-
const base = string();
|
|
127
|
-
const constrained = base.minLength(1).maxLength(10);
|
|
128
|
-
// base should not be affected
|
|
129
|
-
expect(base.safeParse("").success).toBe(true);
|
|
130
|
-
expect(constrained.safeParse("").success).toBe(false);
|
|
131
|
-
});
|
|
132
|
-
|
|
133
|
-
it("applies coercion: trim", () => {
|
|
134
|
-
const s = string().coerce({ trim: true });
|
|
135
|
-
expect(s.parse(" hello ")).toBe("hello");
|
|
136
|
-
});
|
|
137
|
-
|
|
138
|
-
it("applies coercion: lower", () => {
|
|
139
|
-
const s = string().coerce({ lower: true });
|
|
140
|
-
expect(s.parse("HELLO")).toBe("hello");
|
|
141
|
-
});
|
|
142
|
-
|
|
143
|
-
it("applies coercion: upper", () => {
|
|
144
|
-
const s = string().coerce({ upper: true });
|
|
145
|
-
expect(s.parse("hello")).toBe("HELLO");
|
|
146
|
-
});
|
|
147
|
-
});
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
import { string } from "../../src/index.js";
|
|
3
|
+
|
|
4
|
+
describe("StringSchema", () => {
|
|
5
|
+
it("accepts valid strings", () => {
|
|
6
|
+
const s = string();
|
|
7
|
+
expect(s.parse("hello")).toBe("hello");
|
|
8
|
+
expect(s.parse("")).toBe("");
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
it("rejects non-strings", () => {
|
|
12
|
+
const s = string();
|
|
13
|
+
const result = s.safeParse(42);
|
|
14
|
+
expect(result.success).toBe(false);
|
|
15
|
+
if (!result.success) {
|
|
16
|
+
expect(result.issues[0].code).toBe("invalid_type");
|
|
17
|
+
}
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
it("validates minLength", () => {
|
|
21
|
+
const s = string().minLength(3);
|
|
22
|
+
expect(s.parse("abc")).toBe("abc");
|
|
23
|
+
const result = s.safeParse("ab");
|
|
24
|
+
expect(result.success).toBe(false);
|
|
25
|
+
if (!result.success) {
|
|
26
|
+
expect(result.issues[0].code).toBe("too_small");
|
|
27
|
+
}
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it("validates maxLength", () => {
|
|
31
|
+
const s = string().maxLength(3);
|
|
32
|
+
expect(s.parse("abc")).toBe("abc");
|
|
33
|
+
const result = s.safeParse("abcd");
|
|
34
|
+
expect(result.success).toBe(false);
|
|
35
|
+
if (!result.success) {
|
|
36
|
+
expect(result.issues[0].code).toBe("too_large");
|
|
37
|
+
}
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it("validates pattern", () => {
|
|
41
|
+
const s = string().pattern("^[a-z]+$");
|
|
42
|
+
expect(s.parse("abc")).toBe("abc");
|
|
43
|
+
const result = s.safeParse("ABC");
|
|
44
|
+
expect(result.success).toBe(false);
|
|
45
|
+
if (!result.success) {
|
|
46
|
+
expect(result.issues[0].code).toBe("invalid_string");
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it("fails invalid regex patterns without throwing", () => {
|
|
51
|
+
const s = string().pattern("(");
|
|
52
|
+
expect(() => s.safeParse("abc")).not.toThrow();
|
|
53
|
+
const result = s.safeParse("abc");
|
|
54
|
+
expect(result.success).toBe(false);
|
|
55
|
+
if (!result.success) {
|
|
56
|
+
expect(result.issues[0].code).toBe("invalid_string");
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it("validates startsWith", () => {
|
|
61
|
+
const s = string().startsWith("hello");
|
|
62
|
+
expect(s.parse("hello world")).toBe("hello world");
|
|
63
|
+
const result = s.safeParse("world hello");
|
|
64
|
+
expect(result.success).toBe(false);
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
it("validates endsWith", () => {
|
|
68
|
+
const s = string().endsWith("world");
|
|
69
|
+
expect(s.parse("hello world")).toBe("hello world");
|
|
70
|
+
const result = s.safeParse("world hello");
|
|
71
|
+
expect(result.success).toBe(false);
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
it("validates includes", () => {
|
|
75
|
+
const s = string().includes("mid");
|
|
76
|
+
expect(s.parse("a mid b")).toBe("a mid b");
|
|
77
|
+
const result = s.safeParse("no match");
|
|
78
|
+
expect(result.success).toBe(false);
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
it("validates email format", () => {
|
|
82
|
+
const s = string().format("email");
|
|
83
|
+
expect(s.parse("user@example.com")).toBe("user@example.com");
|
|
84
|
+
const result = s.safeParse("notanemail");
|
|
85
|
+
expect(result.success).toBe(false);
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
it("validates uuid format", () => {
|
|
89
|
+
const s = string().format("uuid");
|
|
90
|
+
expect(s.parse("550e8400-e29b-41d4-a716-446655440000")).toBe(
|
|
91
|
+
"550e8400-e29b-41d4-a716-446655440000"
|
|
92
|
+
);
|
|
93
|
+
const result = s.safeParse("not-a-uuid");
|
|
94
|
+
expect(result.success).toBe(false);
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
it("validates url format", () => {
|
|
98
|
+
const s = string().format("url");
|
|
99
|
+
expect(s.parse("https://example.com")).toBe("https://example.com");
|
|
100
|
+
const result = s.safeParse("not a url");
|
|
101
|
+
expect(result.success).toBe(false);
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
it("validates ipv4 format", () => {
|
|
105
|
+
const s = string().format("ipv4");
|
|
106
|
+
expect(s.parse("192.168.1.1")).toBe("192.168.1.1");
|
|
107
|
+
const result = s.safeParse("999.999.999.999");
|
|
108
|
+
expect(result.success).toBe(false);
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
it("validates date format", () => {
|
|
112
|
+
const s = string().format("date");
|
|
113
|
+
expect(s.parse("2024-01-15")).toBe("2024-01-15");
|
|
114
|
+
const result = s.safeParse("2024-13-01");
|
|
115
|
+
expect(result.success).toBe(false);
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
it("validates date-time format", () => {
|
|
119
|
+
const s = string().format("date-time");
|
|
120
|
+
expect(s.parse("2024-01-15T10:30:00Z")).toBe("2024-01-15T10:30:00Z");
|
|
121
|
+
const result = s.safeParse("not-a-date-time");
|
|
122
|
+
expect(result.success).toBe(false);
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
it("chains constraints immutably", () => {
|
|
126
|
+
const base = string();
|
|
127
|
+
const constrained = base.minLength(1).maxLength(10);
|
|
128
|
+
// base should not be affected
|
|
129
|
+
expect(base.safeParse("").success).toBe(true);
|
|
130
|
+
expect(constrained.safeParse("").success).toBe(false);
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
it("applies coercion: trim", () => {
|
|
134
|
+
const s = string().coerce({ trim: true });
|
|
135
|
+
expect(s.parse(" hello ")).toBe("hello");
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
it("applies coercion: lower", () => {
|
|
139
|
+
const s = string().coerce({ lower: true });
|
|
140
|
+
expect(s.parse("HELLO")).toBe("hello");
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
it("applies coercion: upper", () => {
|
|
144
|
+
const s = string().coerce({ upper: true });
|
|
145
|
+
expect(s.parse("hello")).toBe("HELLO");
|
|
146
|
+
});
|
|
147
|
+
});
|