@hy_ong/zod-kit 0.2.0 → 0.2.2
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/.github/workflows/ci.yml +24 -0
- package/CLAUDE.md +64 -22
- package/dist/chunk-2SWEVDFZ.js +134 -0
- package/dist/chunk-32JI34CV.cjs +146 -0
- package/dist/chunk-42C5OHRK.js +71 -0
- package/dist/chunk-46VAH2BJ.js +160 -0
- package/dist/chunk-5JGTDL3Y.js +87 -0
- package/dist/chunk-5LEXCVLX.js +257 -0
- package/dist/chunk-6AAP4LPF.js +2606 -0
- package/dist/chunk-B4EZYZOK.cjs +215 -0
- package/dist/chunk-COYKBWTI.js +161 -0
- package/dist/chunk-DFJZ3NS2.cjs +151 -0
- package/dist/chunk-EDHT4LPO.js +118 -0
- package/dist/chunk-EGHL277K.cjs +165 -0
- package/dist/chunk-ERH4NIMU.cjs +69 -0
- package/dist/chunk-FM3EZ72O.js +165 -0
- package/dist/chunk-GJIRDBZJ.cjs +90 -0
- package/dist/chunk-H2XTEM4M.js +696 -0
- package/dist/chunk-HMSM6FFA.cjs +181 -0
- package/dist/chunk-HTEHINI7.cjs +177 -0
- package/dist/chunk-JOLSGZGN.cjs +696 -0
- package/dist/chunk-JXY7APBU.js +69 -0
- package/dist/chunk-K2UOY6TB.js +136 -0
- package/dist/chunk-KFOHKTFD.js +61 -0
- package/dist/chunk-L4HSIKTU.cjs +135 -0
- package/dist/chunk-LH7ZB4BK.js +124 -0
- package/dist/chunk-LL4ZWLGO.js +90 -0
- package/dist/chunk-M6MTP3NY.cjs +99 -0
- package/dist/chunk-MHJFYYGV.js +215 -0
- package/dist/chunk-MINMXGW3.js +135 -0
- package/dist/chunk-MM7IL2RG.js +181 -0
- package/dist/chunk-OPQJWHXN.cjs +301 -0
- package/dist/chunk-ORFHDJII.cjs +136 -0
- package/dist/chunk-ORVV4MCF.cjs +87 -0
- package/dist/chunk-QICQ6YEY.js +75 -0
- package/dist/chunk-RKUQREMW.js +127 -0
- package/dist/chunk-RO47DKQG.js +146 -0
- package/dist/chunk-RRPXIRTQ.cjs +257 -0
- package/dist/chunk-RYFG2GKM.cjs +118 -0
- package/dist/chunk-STNHTRG7.cjs +124 -0
- package/dist/chunk-TFGS34VD.cjs +71 -0
- package/dist/chunk-TQXDUMML.cjs +61 -0
- package/dist/chunk-UBK3VCVH.cjs +134 -0
- package/dist/chunk-UCOXAZJF.cjs +2606 -0
- package/dist/chunk-UQZKFAFX.js +130 -0
- package/dist/chunk-VB2KV2ZM.cjs +130 -0
- package/dist/chunk-WABKPFPK.js +151 -0
- package/dist/chunk-WDI4QJMQ.js +177 -0
- package/dist/chunk-YDH3L27K.cjs +127 -0
- package/dist/chunk-YIM3D2AD.js +99 -0
- package/dist/chunk-YPSEIDUR.cjs +160 -0
- package/dist/chunk-ZNJLWJX3.cjs +75 -0
- package/dist/chunk-ZTFCJCPO.cjs +161 -0
- package/dist/chunk-ZXUMK2RR.js +301 -0
- package/dist/common/boolean.cjs +7 -0
- package/dist/common/boolean.d.cts +119 -0
- package/dist/common/boolean.d.ts +119 -0
- package/dist/common/boolean.js +7 -0
- package/dist/common/color.cjs +9 -0
- package/dist/common/color.d.cts +26 -0
- package/dist/common/color.d.ts +26 -0
- package/dist/common/color.js +9 -0
- package/dist/common/coordinate.cjs +11 -0
- package/dist/common/coordinate.d.cts +23 -0
- package/dist/common/coordinate.d.ts +23 -0
- package/dist/common/coordinate.js +11 -0
- package/dist/common/credit-card.cjs +11 -0
- package/dist/common/credit-card.d.cts +22 -0
- package/dist/common/credit-card.d.ts +22 -0
- package/dist/common/credit-card.js +11 -0
- package/dist/common/date.cjs +7 -0
- package/dist/common/date.d.cts +174 -0
- package/dist/common/date.d.ts +174 -0
- package/dist/common/date.js +7 -0
- package/dist/common/datetime.cjs +15 -0
- package/dist/common/datetime.d.cts +301 -0
- package/dist/common/datetime.d.ts +301 -0
- package/dist/common/datetime.js +15 -0
- package/dist/common/email.cjs +7 -0
- package/dist/common/email.d.cts +149 -0
- package/dist/common/email.d.ts +149 -0
- package/dist/common/email.js +7 -0
- package/dist/common/file.cjs +7 -0
- package/dist/common/file.d.cts +178 -0
- package/dist/common/file.d.ts +178 -0
- package/dist/common/file.js +7 -0
- package/dist/common/id.cjs +13 -0
- package/dist/common/id.d.cts +288 -0
- package/dist/common/id.d.ts +288 -0
- package/dist/common/id.js +13 -0
- package/dist/common/ip.cjs +11 -0
- package/dist/common/ip.d.cts +25 -0
- package/dist/common/ip.d.ts +25 -0
- package/dist/common/ip.js +11 -0
- package/dist/common/number.cjs +7 -0
- package/dist/common/number.d.cts +167 -0
- package/dist/common/number.d.ts +167 -0
- package/dist/common/number.js +7 -0
- package/dist/common/password.cjs +7 -0
- package/dist/common/password.d.cts +192 -0
- package/dist/common/password.d.ts +192 -0
- package/dist/common/password.js +7 -0
- package/dist/common/text.cjs +7 -0
- package/dist/common/text.d.cts +156 -0
- package/dist/common/text.d.ts +156 -0
- package/dist/common/text.js +7 -0
- package/dist/common/time.cjs +15 -0
- package/dist/common/time.d.cts +268 -0
- package/dist/common/time.d.ts +268 -0
- package/dist/common/time.js +15 -0
- package/dist/common/url.cjs +7 -0
- package/dist/common/url.d.cts +196 -0
- package/dist/common/url.d.ts +196 -0
- package/dist/common/url.js +7 -0
- package/dist/config-CABSSvAp.d.cts +5 -0
- package/dist/config-CABSSvAp.d.ts +5 -0
- package/dist/index.cjs +180 -5255
- package/dist/index.d.cts +28 -3150
- package/dist/index.d.ts +28 -3150
- package/dist/index.js +135 -5131
- package/dist/taiwan/bank-account.cjs +11 -0
- package/dist/taiwan/bank-account.d.cts +22 -0
- package/dist/taiwan/bank-account.d.ts +22 -0
- package/dist/taiwan/bank-account.js +11 -0
- package/dist/taiwan/business-id.cjs +9 -0
- package/dist/taiwan/business-id.d.cts +133 -0
- package/dist/taiwan/business-id.d.ts +133 -0
- package/dist/taiwan/business-id.js +9 -0
- package/dist/taiwan/fax.cjs +9 -0
- package/dist/taiwan/fax.d.cts +157 -0
- package/dist/taiwan/fax.d.ts +157 -0
- package/dist/taiwan/fax.js +9 -0
- package/dist/taiwan/invoice.cjs +9 -0
- package/dist/taiwan/invoice.d.cts +17 -0
- package/dist/taiwan/invoice.d.ts +17 -0
- package/dist/taiwan/invoice.js +9 -0
- package/dist/taiwan/license-plate.cjs +9 -0
- package/dist/taiwan/license-plate.d.cts +19 -0
- package/dist/taiwan/license-plate.d.ts +19 -0
- package/dist/taiwan/license-plate.js +9 -0
- package/dist/taiwan/mobile.cjs +9 -0
- package/dist/taiwan/mobile.d.cts +146 -0
- package/dist/taiwan/mobile.d.ts +146 -0
- package/dist/taiwan/mobile.js +9 -0
- package/dist/taiwan/national-id.cjs +15 -0
- package/dist/taiwan/national-id.d.cts +214 -0
- package/dist/taiwan/national-id.d.ts +214 -0
- package/dist/taiwan/national-id.js +15 -0
- package/dist/taiwan/passport.cjs +9 -0
- package/dist/taiwan/passport.d.cts +19 -0
- package/dist/taiwan/passport.d.ts +19 -0
- package/dist/taiwan/passport.js +9 -0
- package/dist/taiwan/postal-code.cjs +17 -0
- package/dist/taiwan/postal-code.d.cts +237 -0
- package/dist/taiwan/postal-code.d.ts +237 -0
- package/dist/taiwan/postal-code.js +17 -0
- package/dist/taiwan/tel.cjs +9 -0
- package/dist/taiwan/tel.d.cts +162 -0
- package/dist/taiwan/tel.d.ts +162 -0
- package/dist/taiwan/tel.js +9 -0
- package/package.json +132 -6
- package/src/i18n/locales/en-GB.json +51 -0
- package/src/i18n/locales/en-US.json +52 -1
- package/src/i18n/locales/id-ID.json +51 -0
- package/src/i18n/locales/ja-JP.json +51 -0
- package/src/i18n/locales/ko-KR.json +51 -0
- package/src/i18n/locales/ms-MY.json +51 -0
- package/src/i18n/locales/th-TH.json +51 -0
- package/src/i18n/locales/vi-VN.json +51 -0
- package/src/i18n/locales/zh-CN.json +51 -0
- package/src/i18n/locales/zh-TW.json +51 -0
- package/src/index.ts +10 -2
- package/src/validators/common/color.ts +192 -0
- package/src/validators/common/coordinate.ts +159 -0
- package/src/validators/common/credit-card.ts +134 -0
- package/src/validators/common/id.ts +45 -3
- package/src/validators/common/ip.ts +210 -0
- package/src/validators/taiwan/bank-account.ts +176 -0
- package/src/validators/taiwan/invoice.ts +84 -0
- package/src/validators/taiwan/license-plate.ts +110 -0
- package/src/validators/taiwan/passport.ts +103 -0
- package/tests/common/color.test.ts +587 -0
- package/tests/common/coordinate.test.ts +345 -0
- package/tests/common/credit-card.test.ts +378 -0
- package/tests/common/id.test.ts +68 -3
- package/tests/common/ip.test.ts +419 -0
- package/tests/taiwan/bank-account.test.ts +286 -0
- package/tests/taiwan/invoice.test.ts +227 -0
- package/tests/taiwan/license-plate.test.ts +280 -0
- package/tests/taiwan/passport.test.ts +277 -0
- package/tsup.config.ts +36 -0
|
@@ -0,0 +1,419 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach } from "vitest"
|
|
2
|
+
import { ip, setLocale, validateIPv4, validateIPv6 } from "../../src"
|
|
3
|
+
|
|
4
|
+
describe("ip(true) validator", () => {
|
|
5
|
+
beforeEach(() => setLocale("en-US"))
|
|
6
|
+
|
|
7
|
+
describe("IPv4 validation", () => {
|
|
8
|
+
it("should accept valid IPv4 addresses", () => {
|
|
9
|
+
const schema = ip(true)
|
|
10
|
+
|
|
11
|
+
expect(schema.parse("192.168.1.1")).toBe("192.168.1.1")
|
|
12
|
+
expect(schema.parse("0.0.0.0")).toBe("0.0.0.0")
|
|
13
|
+
expect(schema.parse("255.255.255.255")).toBe("255.255.255.255")
|
|
14
|
+
expect(schema.parse("10.0.0.1")).toBe("10.0.0.1")
|
|
15
|
+
expect(schema.parse("127.0.0.1")).toBe("127.0.0.1")
|
|
16
|
+
expect(schema.parse("172.16.0.1")).toBe("172.16.0.1")
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
it("should reject invalid IPv4 addresses", () => {
|
|
20
|
+
const schema = ip(true)
|
|
21
|
+
|
|
22
|
+
expect(() => schema.parse("256.1.1.1")).toThrow("Invalid IP address")
|
|
23
|
+
expect(() => schema.parse("1.1.1")).toThrow("Invalid IP address")
|
|
24
|
+
expect(() => schema.parse("1.2.3.4.5")).toThrow("Invalid IP address")
|
|
25
|
+
expect(() => schema.parse("999.999.999.999")).toThrow("Invalid IP address")
|
|
26
|
+
expect(() => schema.parse("-1.0.0.0")).toThrow("Invalid IP address")
|
|
27
|
+
expect(() => schema.parse("abc.def.ghi.jkl")).toThrow("Invalid IP address")
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
it("should reject IPv4 addresses with leading zeros", () => {
|
|
31
|
+
const schema = ip(true)
|
|
32
|
+
|
|
33
|
+
expect(() => schema.parse("01.01.01.01")).toThrow("Invalid IP address")
|
|
34
|
+
expect(() => schema.parse("192.168.01.1")).toThrow("Invalid IP address")
|
|
35
|
+
expect(() => schema.parse("00.0.0.0")).toThrow("Invalid IP address")
|
|
36
|
+
})
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
describe("IPv6 validation", () => {
|
|
40
|
+
it("should accept valid full-form IPv6 addresses", () => {
|
|
41
|
+
const schema = ip(true)
|
|
42
|
+
|
|
43
|
+
expect(schema.parse("2001:0db8:85a3:0000:0000:8a2e:0370:7334")).toBe("2001:0db8:85a3:0000:0000:8a2e:0370:7334")
|
|
44
|
+
expect(schema.parse("fe80:0000:0000:0000:0000:0000:0000:0001")).toBe("fe80:0000:0000:0000:0000:0000:0000:0001")
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
it("should accept valid compressed IPv6 addresses", () => {
|
|
48
|
+
const schema = ip(true)
|
|
49
|
+
|
|
50
|
+
expect(schema.parse("::1")).toBe("::1")
|
|
51
|
+
expect(schema.parse("fe80::")).toBe("fe80::")
|
|
52
|
+
expect(schema.parse("2001:db8::1")).toBe("2001:db8::1")
|
|
53
|
+
expect(schema.parse("::")).toBe("::")
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
it("should accept valid mixed IPv6/IPv4 addresses", () => {
|
|
57
|
+
const schema = ip(true)
|
|
58
|
+
|
|
59
|
+
expect(schema.parse("::ffff:192.0.2.1")).toBe("::ffff:192.0.2.1")
|
|
60
|
+
expect(schema.parse("::ffff:10.0.0.1")).toBe("::ffff:10.0.0.1")
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
it("should reject invalid IPv6 addresses", () => {
|
|
64
|
+
const schema = ip(true)
|
|
65
|
+
|
|
66
|
+
expect(() => schema.parse("gggg::1")).toThrow("Invalid IP address")
|
|
67
|
+
expect(() => schema.parse("2001:db8::85a3::7334")).toThrow("Invalid IP address")
|
|
68
|
+
expect(() => schema.parse("12345::1")).toThrow("Invalid IP address")
|
|
69
|
+
expect(() => schema.parse("2001:db8:85a3:0000:0000:8a2e:0370")).toThrow("Invalid IP address")
|
|
70
|
+
})
|
|
71
|
+
})
|
|
72
|
+
|
|
73
|
+
describe("version filtering", () => {
|
|
74
|
+
it("should only accept IPv4 when version is 'v4'", () => {
|
|
75
|
+
const schema = ip(true, { version: "v4" })
|
|
76
|
+
|
|
77
|
+
expect(schema.parse("192.168.1.1")).toBe("192.168.1.1")
|
|
78
|
+
expect(schema.parse("10.0.0.1")).toBe("10.0.0.1")
|
|
79
|
+
|
|
80
|
+
expect(() => schema.parse("::1")).toThrow("Must be a valid IPv4 address")
|
|
81
|
+
expect(() => schema.parse("2001:db8::1")).toThrow("Must be a valid IPv4 address")
|
|
82
|
+
})
|
|
83
|
+
|
|
84
|
+
it("should only accept IPv6 when version is 'v6'", () => {
|
|
85
|
+
const schema = ip(true, { version: "v6" })
|
|
86
|
+
|
|
87
|
+
expect(schema.parse("::1")).toBe("::1")
|
|
88
|
+
expect(schema.parse("2001:0db8:85a3:0000:0000:8a2e:0370:7334")).toBe("2001:0db8:85a3:0000:0000:8a2e:0370:7334")
|
|
89
|
+
|
|
90
|
+
expect(() => schema.parse("192.168.1.1")).toThrow("Must be a valid IPv6 address")
|
|
91
|
+
expect(() => schema.parse("10.0.0.1")).toThrow("Must be a valid IPv6 address")
|
|
92
|
+
})
|
|
93
|
+
|
|
94
|
+
it("should accept both IPv4 and IPv6 when version is 'any'", () => {
|
|
95
|
+
const schema = ip(true, { version: "any" })
|
|
96
|
+
|
|
97
|
+
expect(schema.parse("192.168.1.1")).toBe("192.168.1.1")
|
|
98
|
+
expect(schema.parse("::1")).toBe("::1")
|
|
99
|
+
expect(schema.parse("2001:db8::1")).toBe("2001:db8::1")
|
|
100
|
+
})
|
|
101
|
+
|
|
102
|
+
it("should default to 'any' when version is not specified", () => {
|
|
103
|
+
const schema = ip(true)
|
|
104
|
+
|
|
105
|
+
expect(schema.parse("192.168.1.1")).toBe("192.168.1.1")
|
|
106
|
+
expect(schema.parse("::1")).toBe("::1")
|
|
107
|
+
})
|
|
108
|
+
})
|
|
109
|
+
|
|
110
|
+
describe("CIDR support", () => {
|
|
111
|
+
it("should reject CIDR notation when allowCIDR is false (default)", () => {
|
|
112
|
+
const schema = ip(true)
|
|
113
|
+
|
|
114
|
+
expect(() => schema.parse("192.168.1.0/24")).toThrow("Invalid IP address")
|
|
115
|
+
expect(() => schema.parse("10.0.0.0/8")).toThrow("Invalid IP address")
|
|
116
|
+
expect(() => schema.parse("2001:db8::/32")).toThrow("Invalid IP address")
|
|
117
|
+
})
|
|
118
|
+
|
|
119
|
+
it("should accept valid IPv4 CIDR when allowCIDR is true", () => {
|
|
120
|
+
const schema = ip(true, { allowCIDR: true })
|
|
121
|
+
|
|
122
|
+
expect(schema.parse("192.168.1.0/24")).toBe("192.168.1.0/24")
|
|
123
|
+
expect(schema.parse("10.0.0.0/8")).toBe("10.0.0.0/8")
|
|
124
|
+
expect(schema.parse("0.0.0.0/0")).toBe("0.0.0.0/0")
|
|
125
|
+
expect(schema.parse("255.255.255.255/32")).toBe("255.255.255.255/32")
|
|
126
|
+
})
|
|
127
|
+
|
|
128
|
+
it("should accept valid IPv6 CIDR when allowCIDR is true", () => {
|
|
129
|
+
const schema = ip(true, { allowCIDR: true })
|
|
130
|
+
|
|
131
|
+
expect(schema.parse("2001:db8::/32")).toBe("2001:db8::/32")
|
|
132
|
+
expect(schema.parse("::1/128")).toBe("::1/128")
|
|
133
|
+
expect(schema.parse("::/0")).toBe("::/0")
|
|
134
|
+
})
|
|
135
|
+
|
|
136
|
+
it("should reject invalid CIDR prefix lengths", () => {
|
|
137
|
+
const schema = ip(true, { allowCIDR: true })
|
|
138
|
+
|
|
139
|
+
expect(() => schema.parse("192.168.1.0/33")).toThrow("Invalid IP address")
|
|
140
|
+
expect(() => schema.parse("192.168.1.0/-1")).toThrow("Invalid IP address")
|
|
141
|
+
expect(() => schema.parse("2001:db8::/129")).toThrow("Invalid IP address")
|
|
142
|
+
expect(() => schema.parse("192.168.1.0/abc")).toThrow("Invalid IP address")
|
|
143
|
+
})
|
|
144
|
+
|
|
145
|
+
it("should still accept plain IPs when allowCIDR is true", () => {
|
|
146
|
+
const schema = ip(true, { allowCIDR: true })
|
|
147
|
+
|
|
148
|
+
expect(schema.parse("192.168.1.1")).toBe("192.168.1.1")
|
|
149
|
+
expect(schema.parse("::1")).toBe("::1")
|
|
150
|
+
})
|
|
151
|
+
})
|
|
152
|
+
|
|
153
|
+
describe("whitelist", () => {
|
|
154
|
+
it("should accept IPs in the whitelist", () => {
|
|
155
|
+
const schema = ip(true, { whitelist: ["192.168.1.1", "10.0.0.1", "::1"] })
|
|
156
|
+
|
|
157
|
+
expect(schema.parse("192.168.1.1")).toBe("192.168.1.1")
|
|
158
|
+
expect(schema.parse("10.0.0.1")).toBe("10.0.0.1")
|
|
159
|
+
expect(schema.parse("::1")).toBe("::1")
|
|
160
|
+
})
|
|
161
|
+
|
|
162
|
+
it("should reject IPs not in the whitelist", () => {
|
|
163
|
+
const schema = ip(true, { whitelist: ["192.168.1.1", "10.0.0.1"] })
|
|
164
|
+
|
|
165
|
+
expect(() => schema.parse("192.168.1.2")).toThrow("IP address is not in the allowed list")
|
|
166
|
+
expect(() => schema.parse("172.16.0.1")).toThrow("IP address is not in the allowed list")
|
|
167
|
+
expect(() => schema.parse("::1")).toThrow("IP address is not in the allowed list")
|
|
168
|
+
})
|
|
169
|
+
|
|
170
|
+
it("should not apply whitelist when the array is empty", () => {
|
|
171
|
+
const schema = ip(true, { whitelist: [] })
|
|
172
|
+
|
|
173
|
+
expect(schema.parse("192.168.1.1")).toBe("192.168.1.1")
|
|
174
|
+
expect(schema.parse("::1")).toBe("::1")
|
|
175
|
+
})
|
|
176
|
+
})
|
|
177
|
+
|
|
178
|
+
describe("required/optional behavior", () => {
|
|
179
|
+
it("should reject empty values when required=true", () => {
|
|
180
|
+
const schema = ip(true)
|
|
181
|
+
|
|
182
|
+
expect(() => schema.parse("")).toThrow("Required")
|
|
183
|
+
expect(() => schema.parse(null)).toThrow()
|
|
184
|
+
expect(() => schema.parse(undefined)).toThrow()
|
|
185
|
+
})
|
|
186
|
+
|
|
187
|
+
it("should accept empty values when required=false", () => {
|
|
188
|
+
const schema = ip(false)
|
|
189
|
+
|
|
190
|
+
expect(schema.parse("")).toBe(null)
|
|
191
|
+
expect(schema.parse(null)).toBe(null)
|
|
192
|
+
expect(schema.parse(undefined)).toBe(null)
|
|
193
|
+
})
|
|
194
|
+
|
|
195
|
+
it("should still validate non-empty values when required=false", () => {
|
|
196
|
+
const schema = ip(false)
|
|
197
|
+
|
|
198
|
+
expect(schema.parse("192.168.1.1")).toBe("192.168.1.1")
|
|
199
|
+
expect(() => schema.parse("invalid-ip")).toThrow("Invalid IP address")
|
|
200
|
+
})
|
|
201
|
+
|
|
202
|
+
it("should use default values", () => {
|
|
203
|
+
const requiredSchema = ip(true, { defaultValue: "127.0.0.1" })
|
|
204
|
+
const optionalSchema = ip(false, { defaultValue: "127.0.0.1" })
|
|
205
|
+
|
|
206
|
+
expect(requiredSchema.parse("")).toBe("127.0.0.1")
|
|
207
|
+
expect(optionalSchema.parse("")).toBe("127.0.0.1")
|
|
208
|
+
})
|
|
209
|
+
})
|
|
210
|
+
|
|
211
|
+
describe("transform function", () => {
|
|
212
|
+
it("should apply custom transform before validation", () => {
|
|
213
|
+
const schema = ip(true, {
|
|
214
|
+
transform: (val) => val.toLowerCase(),
|
|
215
|
+
})
|
|
216
|
+
|
|
217
|
+
expect(schema.parse("192.168.1.1")).toBe("192.168.1.1")
|
|
218
|
+
})
|
|
219
|
+
|
|
220
|
+
it("should apply transform to the trimmed value", () => {
|
|
221
|
+
const schema = ip(true, {
|
|
222
|
+
transform: (val) => val.replace(/\s+/g, ""),
|
|
223
|
+
})
|
|
224
|
+
|
|
225
|
+
expect(schema.parse("192. 168.1.1")).toBe("192.168.1.1")
|
|
226
|
+
})
|
|
227
|
+
})
|
|
228
|
+
|
|
229
|
+
describe("i18n support", () => {
|
|
230
|
+
it("should use English messages by default", () => {
|
|
231
|
+
setLocale("en-US")
|
|
232
|
+
const schema = ip(true)
|
|
233
|
+
|
|
234
|
+
expect(() => schema.parse("")).toThrow("Required")
|
|
235
|
+
expect(() => schema.parse("invalid")).toThrow("Invalid IP address")
|
|
236
|
+
})
|
|
237
|
+
|
|
238
|
+
it("should use Chinese messages when locale is zh-TW", () => {
|
|
239
|
+
setLocale("zh-TW")
|
|
240
|
+
const schema = ip(true)
|
|
241
|
+
|
|
242
|
+
expect(() => schema.parse("")).toThrow("必填")
|
|
243
|
+
expect(() => schema.parse("invalid")).toThrow("無效的 IP 位址")
|
|
244
|
+
})
|
|
245
|
+
|
|
246
|
+
it("should use version-specific messages", () => {
|
|
247
|
+
setLocale("en-US")
|
|
248
|
+
const v4Schema = ip(true, { version: "v4" })
|
|
249
|
+
const v6Schema = ip(true, { version: "v6" })
|
|
250
|
+
|
|
251
|
+
expect(() => v4Schema.parse("::1")).toThrow("Must be a valid IPv4 address")
|
|
252
|
+
expect(() => v6Schema.parse("192.168.1.1")).toThrow("Must be a valid IPv6 address")
|
|
253
|
+
|
|
254
|
+
setLocale("zh-TW")
|
|
255
|
+
expect(() => v4Schema.parse("::1")).toThrow("必須為有效的 IPv4 位址")
|
|
256
|
+
expect(() => v6Schema.parse("192.168.1.1")).toThrow("必須為有效的 IPv6 位址")
|
|
257
|
+
})
|
|
258
|
+
|
|
259
|
+
it("should support custom i18n messages", () => {
|
|
260
|
+
const schema = ip(true, {
|
|
261
|
+
i18n: {
|
|
262
|
+
"en-US": {
|
|
263
|
+
required: "IP is required",
|
|
264
|
+
invalid: "Bad IP",
|
|
265
|
+
notIPv4: "Need IPv4",
|
|
266
|
+
notIPv6: "Need IPv6",
|
|
267
|
+
notInWhitelist: "Not allowed",
|
|
268
|
+
},
|
|
269
|
+
"zh-TW": {
|
|
270
|
+
required: "請輸入 IP",
|
|
271
|
+
invalid: "IP 錯誤",
|
|
272
|
+
},
|
|
273
|
+
},
|
|
274
|
+
})
|
|
275
|
+
|
|
276
|
+
setLocale("en-US")
|
|
277
|
+
expect(() => schema.parse("")).toThrow("IP is required")
|
|
278
|
+
expect(() => schema.parse("invalid")).toThrow("Bad IP")
|
|
279
|
+
|
|
280
|
+
setLocale("zh-TW")
|
|
281
|
+
expect(() => schema.parse("")).toThrow("請輸入 IP")
|
|
282
|
+
expect(() => schema.parse("invalid")).toThrow("IP 錯誤")
|
|
283
|
+
})
|
|
284
|
+
|
|
285
|
+
it("should support partial custom i18n messages with fallback to defaults", () => {
|
|
286
|
+
const schema = ip(true, {
|
|
287
|
+
version: "v4",
|
|
288
|
+
i18n: {
|
|
289
|
+
"en-US": {
|
|
290
|
+
required: "Custom required",
|
|
291
|
+
},
|
|
292
|
+
},
|
|
293
|
+
})
|
|
294
|
+
|
|
295
|
+
setLocale("en-US")
|
|
296
|
+
expect(() => schema.parse("")).toThrow("Custom required")
|
|
297
|
+
expect(() => schema.parse("::1")).toThrow("Must be a valid IPv4 address")
|
|
298
|
+
})
|
|
299
|
+
|
|
300
|
+
it("should support whitelist error messages in custom i18n", () => {
|
|
301
|
+
const schema = ip(true, {
|
|
302
|
+
whitelist: ["192.168.1.1"],
|
|
303
|
+
i18n: {
|
|
304
|
+
"en-US": {
|
|
305
|
+
notInWhitelist: "Not allowed",
|
|
306
|
+
},
|
|
307
|
+
},
|
|
308
|
+
})
|
|
309
|
+
|
|
310
|
+
setLocale("en-US")
|
|
311
|
+
expect(() => schema.parse("10.0.0.1")).toThrow("Not allowed")
|
|
312
|
+
})
|
|
313
|
+
})
|
|
314
|
+
|
|
315
|
+
describe("utility functions", () => {
|
|
316
|
+
describe("validateIPv4", () => {
|
|
317
|
+
it("should return true for valid IPv4 addresses", () => {
|
|
318
|
+
expect(validateIPv4("192.168.1.1")).toBe(true)
|
|
319
|
+
expect(validateIPv4("0.0.0.0")).toBe(true)
|
|
320
|
+
expect(validateIPv4("255.255.255.255")).toBe(true)
|
|
321
|
+
expect(validateIPv4("10.0.0.1")).toBe(true)
|
|
322
|
+
expect(validateIPv4("127.0.0.1")).toBe(true)
|
|
323
|
+
})
|
|
324
|
+
|
|
325
|
+
it("should return false for invalid IPv4 addresses", () => {
|
|
326
|
+
expect(validateIPv4("256.1.1.1")).toBe(false)
|
|
327
|
+
expect(validateIPv4("1.1.1")).toBe(false)
|
|
328
|
+
expect(validateIPv4("1.2.3.4.5")).toBe(false)
|
|
329
|
+
expect(validateIPv4("01.01.01.01")).toBe(false)
|
|
330
|
+
expect(validateIPv4("abc.def.ghi.jkl")).toBe(false)
|
|
331
|
+
expect(validateIPv4("")).toBe(false)
|
|
332
|
+
expect(validateIPv4("...")).toBe(false)
|
|
333
|
+
expect(validateIPv4("1.2.3.")).toBe(false)
|
|
334
|
+
expect(validateIPv4(".1.2.3")).toBe(false)
|
|
335
|
+
})
|
|
336
|
+
})
|
|
337
|
+
|
|
338
|
+
describe("validateIPv6", () => {
|
|
339
|
+
it("should return true for valid IPv6 addresses", () => {
|
|
340
|
+
expect(validateIPv6("2001:0db8:85a3:0000:0000:8a2e:0370:7334")).toBe(true)
|
|
341
|
+
expect(validateIPv6("::1")).toBe(true)
|
|
342
|
+
expect(validateIPv6("fe80::")).toBe(true)
|
|
343
|
+
expect(validateIPv6("::")).toBe(true)
|
|
344
|
+
expect(validateIPv6("2001:db8::1")).toBe(true)
|
|
345
|
+
})
|
|
346
|
+
|
|
347
|
+
it("should return true for mixed IPv6/IPv4 addresses", () => {
|
|
348
|
+
expect(validateIPv6("::ffff:192.0.2.1")).toBe(true)
|
|
349
|
+
expect(validateIPv6("::ffff:10.0.0.1")).toBe(true)
|
|
350
|
+
})
|
|
351
|
+
|
|
352
|
+
it("should return false for invalid IPv6 addresses", () => {
|
|
353
|
+
expect(validateIPv6("gggg::1")).toBe(false)
|
|
354
|
+
expect(validateIPv6("2001:db8::85a3::7334")).toBe(false)
|
|
355
|
+
expect(validateIPv6("12345::1")).toBe(false)
|
|
356
|
+
expect(validateIPv6("")).toBe(false)
|
|
357
|
+
})
|
|
358
|
+
|
|
359
|
+
it("should return false for mixed IPv6 with invalid IPv4 portion", () => {
|
|
360
|
+
expect(validateIPv6("::ffff:256.0.0.1")).toBe(false)
|
|
361
|
+
expect(validateIPv6("::ffff:01.02.03.04")).toBe(false)
|
|
362
|
+
})
|
|
363
|
+
})
|
|
364
|
+
})
|
|
365
|
+
|
|
366
|
+
describe("edge cases", () => {
|
|
367
|
+
it("should handle leading zeros in IPv4 as invalid", () => {
|
|
368
|
+
const schema = ip(true)
|
|
369
|
+
|
|
370
|
+
expect(() => schema.parse("01.01.01.01")).toThrow("Invalid IP address")
|
|
371
|
+
expect(() => schema.parse("001.001.001.001")).toThrow("Invalid IP address")
|
|
372
|
+
expect(() => schema.parse("192.168.01.1")).toThrow("Invalid IP address")
|
|
373
|
+
})
|
|
374
|
+
|
|
375
|
+
it("should handle empty octets in IPv4 as invalid", () => {
|
|
376
|
+
const schema = ip(true)
|
|
377
|
+
|
|
378
|
+
expect(() => schema.parse("1..1.1")).toThrow("Invalid IP address")
|
|
379
|
+
expect(() => schema.parse("...")).toThrow("Invalid IP address")
|
|
380
|
+
expect(() => schema.parse("1.2.3.")).toThrow("Invalid IP address")
|
|
381
|
+
expect(() => schema.parse(".1.2.3")).toThrow("Invalid IP address")
|
|
382
|
+
})
|
|
383
|
+
|
|
384
|
+
it("should handle whitespace trimming", () => {
|
|
385
|
+
const schema = ip(true)
|
|
386
|
+
|
|
387
|
+
expect(schema.parse(" 192.168.1.1 ")).toBe("192.168.1.1")
|
|
388
|
+
expect(schema.parse("\t::1\n")).toBe("::1")
|
|
389
|
+
})
|
|
390
|
+
|
|
391
|
+
it("should handle string conversion from other types", () => {
|
|
392
|
+
const schema = ip(true)
|
|
393
|
+
|
|
394
|
+
expect(() => schema.parse(12345)).toThrow("Invalid IP address")
|
|
395
|
+
expect(() => schema.parse(true)).toThrow("Invalid IP address")
|
|
396
|
+
})
|
|
397
|
+
|
|
398
|
+
it("should reject 'null' and 'undefined' string values when required", () => {
|
|
399
|
+
const schema = ip(true)
|
|
400
|
+
|
|
401
|
+
expect(() => schema.parse("null")).toThrow("Required")
|
|
402
|
+
expect(() => schema.parse("undefined")).toThrow("Required")
|
|
403
|
+
})
|
|
404
|
+
|
|
405
|
+
it("should handle double :: in IPv6 as invalid", () => {
|
|
406
|
+
expect(validateIPv6("2001:db8::85a3::7334")).toBe(false)
|
|
407
|
+
expect(validateIPv6("::1::2")).toBe(false)
|
|
408
|
+
})
|
|
409
|
+
|
|
410
|
+
it("should handle too many groups in IPv6 as invalid", () => {
|
|
411
|
+
expect(validateIPv6("2001:0db8:85a3:0000:0000:8a2e:0370:7334:extra")).toBe(false)
|
|
412
|
+
})
|
|
413
|
+
|
|
414
|
+
it("should handle groups exceeding 4 hex characters as invalid", () => {
|
|
415
|
+
expect(validateIPv6("12345::1")).toBe(false)
|
|
416
|
+
expect(validateIPv6("2001:0db8:85a3:00000:0000:8a2e:0370:7334")).toBe(false)
|
|
417
|
+
})
|
|
418
|
+
})
|
|
419
|
+
})
|