@hy_ong/zod-kit 0.0.4 → 0.0.6
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/.claude/settings.local.json +28 -0
- package/LICENSE +21 -0
- package/README.md +465 -97
- package/debug.js +21 -0
- package/debug.ts +16 -0
- package/dist/index.cjs +3127 -146
- package/dist/index.d.cts +3021 -25
- package/dist/index.d.ts +3021 -25
- package/dist/index.js +3081 -144
- package/eslint.config.mts +8 -0
- package/package.json +10 -9
- package/src/config.ts +1 -1
- package/src/i18n/locales/en.json +161 -25
- package/src/i18n/locales/zh-TW.json +165 -26
- package/src/index.ts +17 -7
- package/src/validators/common/boolean.ts +191 -0
- package/src/validators/common/date.ts +299 -0
- package/src/validators/common/datetime.ts +673 -0
- package/src/validators/common/email.ts +313 -0
- package/src/validators/common/file.ts +384 -0
- package/src/validators/common/id.ts +471 -0
- package/src/validators/common/number.ts +319 -0
- package/src/validators/common/password.ts +386 -0
- package/src/validators/common/text.ts +271 -0
- package/src/validators/common/time.ts +600 -0
- package/src/validators/common/url.ts +347 -0
- package/src/validators/taiwan/business-id.ts +262 -0
- package/src/validators/taiwan/fax.ts +327 -0
- package/src/validators/taiwan/mobile.ts +242 -0
- package/src/validators/taiwan/national-id.ts +425 -0
- package/src/validators/taiwan/postal-code.ts +1049 -0
- package/src/validators/taiwan/tel.ts +330 -0
- package/tests/common/boolean.test.ts +340 -92
- package/tests/common/date.test.ts +458 -0
- package/tests/common/datetime.test.ts +693 -0
- package/tests/common/email.test.ts +232 -60
- package/tests/common/file.test.ts +479 -0
- package/tests/common/id.test.ts +535 -0
- package/tests/common/number.test.ts +230 -60
- package/tests/common/password.test.ts +271 -44
- package/tests/common/text.test.ts +210 -13
- package/tests/common/time.test.ts +528 -0
- package/tests/common/url.test.ts +492 -67
- package/tests/taiwan/business-id.test.ts +240 -0
- package/tests/taiwan/fax.test.ts +463 -0
- package/tests/taiwan/mobile.test.ts +373 -0
- package/tests/taiwan/national-id.test.ts +435 -0
- package/tests/taiwan/postal-code.test.ts +705 -0
- package/tests/taiwan/tel.test.ts +467 -0
- package/eslint.config.mjs +0 -10
- package/src/common/boolean.ts +0 -36
- package/src/common/date.ts +0 -43
- package/src/common/email.ts +0 -44
- package/src/common/integer.ts +0 -46
- package/src/common/number.ts +0 -37
- package/src/common/password.ts +0 -33
- package/src/common/text.ts +0 -34
- package/src/common/url.ts +0 -37
- package/tests/common/integer.test.ts +0 -90
|
@@ -0,0 +1,535 @@
|
|
|
1
|
+
import { describe, expect, it, beforeEach } from "vitest"
|
|
2
|
+
import { Locale, setLocale, id, detectIdType, validateIdType, ID_PATTERNS } from "../../src"
|
|
3
|
+
|
|
4
|
+
const locales = [
|
|
5
|
+
{
|
|
6
|
+
locale: "en",
|
|
7
|
+
messages: {
|
|
8
|
+
required: "Required",
|
|
9
|
+
invalid: "Invalid ID format",
|
|
10
|
+
minLength: "Must be at least 5 characters",
|
|
11
|
+
maxLength: "Must be at most 10 characters",
|
|
12
|
+
numeric: "Must be a numeric ID",
|
|
13
|
+
uuid: "Must be a valid UUID",
|
|
14
|
+
objectId: "Must be a valid MongoDB ObjectId",
|
|
15
|
+
nanoid: "Must be a valid Nano ID",
|
|
16
|
+
snowflake: "Must be a valid Snowflake ID",
|
|
17
|
+
cuid: "Must be a valid CUID",
|
|
18
|
+
ulid: "Must be a valid ULID",
|
|
19
|
+
shortid: "Must be a valid Short ID",
|
|
20
|
+
customFormat: "Invalid ID format",
|
|
21
|
+
includes: "Must include test",
|
|
22
|
+
excludes: "Must not contain bad",
|
|
23
|
+
startsWith: "Must start with id",
|
|
24
|
+
endsWith: "Must end with _end",
|
|
25
|
+
},
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
locale: "zh-TW",
|
|
29
|
+
messages: {
|
|
30
|
+
required: "必填",
|
|
31
|
+
invalid: "無效的 ID 格式",
|
|
32
|
+
minLength: "長度至少 5 字元",
|
|
33
|
+
maxLength: "長度最多 10 字元",
|
|
34
|
+
numeric: "必須為數字 ID",
|
|
35
|
+
uuid: "必須為有效的 UUID",
|
|
36
|
+
objectId: "必須為有效的 MongoDB ObjectId",
|
|
37
|
+
nanoid: "必須為有效的 Nano ID",
|
|
38
|
+
snowflake: "必須為有效的 Snowflake ID",
|
|
39
|
+
cuid: "必須為有效的 CUID",
|
|
40
|
+
ulid: "必須為有效的 ULID",
|
|
41
|
+
shortid: "必須為有效的 Short ID",
|
|
42
|
+
customFormat: "無效的 ID 格式",
|
|
43
|
+
includes: "必須包含「test」",
|
|
44
|
+
excludes: "不得包含「bad」",
|
|
45
|
+
startsWith: "必須以「id」開頭",
|
|
46
|
+
endsWith: "必須以「_end」結尾",
|
|
47
|
+
},
|
|
48
|
+
},
|
|
49
|
+
] as const
|
|
50
|
+
|
|
51
|
+
// Valid test IDs for different formats
|
|
52
|
+
const validIds = {
|
|
53
|
+
numeric: ["1", "123", "999999", "0"],
|
|
54
|
+
uuid: [
|
|
55
|
+
"550e8400-e29b-41d4-a716-446655440000",
|
|
56
|
+
"6ba7b810-9dad-11d1-80b4-00c04fd430c8",
|
|
57
|
+
"f47ac10b-58cc-4372-a567-0e02b2c3d479"
|
|
58
|
+
],
|
|
59
|
+
objectId: [
|
|
60
|
+
"507f1f77bcf86cd799439011",
|
|
61
|
+
"507f191e810c19729de860ea",
|
|
62
|
+
"5a9427648b0beebeb69579cc"
|
|
63
|
+
],
|
|
64
|
+
nanoid: [
|
|
65
|
+
"V1StGXR8_Z5jdHi6B-myT",
|
|
66
|
+
"3IBBoOd_b1YSlnKdvQ8fK",
|
|
67
|
+
"9_xnJ2QZt8vKl3_Kj5f7N"
|
|
68
|
+
],
|
|
69
|
+
snowflake: [
|
|
70
|
+
"1234567890123456789",
|
|
71
|
+
"9876543210987654321",
|
|
72
|
+
"5555555555555555555"
|
|
73
|
+
],
|
|
74
|
+
cuid: [
|
|
75
|
+
"cjld2cjxh0000qzrmn831i7rn",
|
|
76
|
+
"ckfr9f3rm0000jo082qvo1234",
|
|
77
|
+
"cl9ebqhxs000109kygp36rup2"
|
|
78
|
+
],
|
|
79
|
+
ulid: [
|
|
80
|
+
"01BX5ZZKBKACTAV9WEVGEMMVRY",
|
|
81
|
+
"01F4A9WRNHXQM6WR0P9T2XKZG4",
|
|
82
|
+
"01GFQJ8KWT7M9QZRV4S3N2P8X5"
|
|
83
|
+
],
|
|
84
|
+
shortid: [
|
|
85
|
+
"S1x8U-213",
|
|
86
|
+
"B1Ox_-hV2",
|
|
87
|
+
"rkH-OgHfr",
|
|
88
|
+
"H1f_-OxBf",
|
|
89
|
+
"_rJfOgSGr"
|
|
90
|
+
]
|
|
91
|
+
} as const
|
|
92
|
+
|
|
93
|
+
// Invalid test IDs for different formats
|
|
94
|
+
const invalidIds = {
|
|
95
|
+
numeric: ["abc", "12a", ""],
|
|
96
|
+
uuid: [
|
|
97
|
+
"550e8400-e29b-41d4-a716-44665544000", // too short
|
|
98
|
+
"550e8400-e29b-41d4-a716-44665544000g", // too long
|
|
99
|
+
"550e8400-e29b-61d4-a716-446655440000", // wrong version (6 instead of 1-5)
|
|
100
|
+
"550e8400-e29b-41d4-c716-446655440000", // wrong variant (c instead of 8,9,a,b)
|
|
101
|
+
"not-a-uuid-at-all"
|
|
102
|
+
],
|
|
103
|
+
objectId: [
|
|
104
|
+
"507f1f77bcf86cd79943901", // too short
|
|
105
|
+
"507f1f77bcf86cd799439011g", // invalid character
|
|
106
|
+
"507f1f77bcf86cd799439011aa" // too long
|
|
107
|
+
],
|
|
108
|
+
nanoid: [
|
|
109
|
+
"V1StGXR8_Z5jdHi6B-myT_", // too long
|
|
110
|
+
"V1StGXR8_Z5jdHi6B-my", // too short
|
|
111
|
+
"V1StGXR8_Z5jdHi6B-my&" // invalid character
|
|
112
|
+
],
|
|
113
|
+
snowflake: [
|
|
114
|
+
"123456789012345678", // too short
|
|
115
|
+
"12345678901234567890", // too long
|
|
116
|
+
"123456789012345678a" // invalid character
|
|
117
|
+
],
|
|
118
|
+
cuid: [
|
|
119
|
+
"jld2cjxh0000qzrmn831i7rn", // missing 'c' prefix
|
|
120
|
+
"cjld2cjxh0000qzrmn831i7rn_", // too long
|
|
121
|
+
"cjld2cjxh0000qzrmn831i7r" // too short
|
|
122
|
+
],
|
|
123
|
+
ulid: [
|
|
124
|
+
"01BX5ZZKBKACTAV9WEVGEMMVR", // too short
|
|
125
|
+
"01BX5ZZKBKACTAV9WEVGEMMVRYX", // too long
|
|
126
|
+
"01BX5ZZKBKACTAV9WEVGEMMVO" // invalid character 'O'
|
|
127
|
+
],
|
|
128
|
+
shortid: [
|
|
129
|
+
"S1x8U-", // too short
|
|
130
|
+
"S1x8U-213-very-long", // too long
|
|
131
|
+
"S1x8U-213+" // invalid character
|
|
132
|
+
]
|
|
133
|
+
} as const
|
|
134
|
+
|
|
135
|
+
describe.each(locales)("id() locale: $locale", ({ locale, messages }) => {
|
|
136
|
+
beforeEach(() => setLocale(locale as Locale))
|
|
137
|
+
|
|
138
|
+
describe("basic functionality", () => {
|
|
139
|
+
it("should pass with valid ID", () => {
|
|
140
|
+
const schema = id()
|
|
141
|
+
expect(schema.parse("123")).toBe("123")
|
|
142
|
+
expect(schema.parse("550e8400-e29b-41d4-a716-446655440000")).toBe("550e8400-e29b-41d4-a716-446655440000")
|
|
143
|
+
})
|
|
144
|
+
|
|
145
|
+
it("should fail with empty string when required", () => {
|
|
146
|
+
const schema = id()
|
|
147
|
+
expect(() => schema.parse("")).toThrow(messages.required)
|
|
148
|
+
expect(() => schema.parse(null)).toThrow(messages.required)
|
|
149
|
+
expect(() => schema.parse(undefined)).toThrow(messages.required)
|
|
150
|
+
})
|
|
151
|
+
|
|
152
|
+
it("should pass with null when not required", () => {
|
|
153
|
+
const schema = id({ required: false })
|
|
154
|
+
expect(schema.parse("")).toBe(null)
|
|
155
|
+
expect(schema.parse(null)).toBe(null)
|
|
156
|
+
expect(schema.parse(undefined)).toBe(null)
|
|
157
|
+
})
|
|
158
|
+
|
|
159
|
+
it("should handle default values", () => {
|
|
160
|
+
const schema = id({ required: false, defaultValue: "123" })
|
|
161
|
+
expect(schema.parse("")).toBe("123")
|
|
162
|
+
expect(schema.parse(null)).toBe("123")
|
|
163
|
+
expect(schema.parse(undefined)).toBe("123")
|
|
164
|
+
expect(schema.parse("456")).toBe("456")
|
|
165
|
+
})
|
|
166
|
+
|
|
167
|
+
it("should apply transform function", () => {
|
|
168
|
+
const schema = id({ type: "numeric", transform: (val) => val.toUpperCase() })
|
|
169
|
+
expect(schema.parse("123")).toBe("123")
|
|
170
|
+
})
|
|
171
|
+
})
|
|
172
|
+
|
|
173
|
+
describe("length validation", () => {
|
|
174
|
+
it("should fail with string shorter than minLength", () => {
|
|
175
|
+
const schema = id({ minLength: 5 })
|
|
176
|
+
expect(() => schema.parse("123")).toThrow(messages.minLength)
|
|
177
|
+
})
|
|
178
|
+
|
|
179
|
+
it("should fail with string longer than maxLength", () => {
|
|
180
|
+
const schema = id({ maxLength: 10 })
|
|
181
|
+
expect(() => schema.parse("12345678901")).toThrow(messages.maxLength)
|
|
182
|
+
})
|
|
183
|
+
|
|
184
|
+
it("should pass with valid length", () => {
|
|
185
|
+
const schema = id({ minLength: 3, maxLength: 10 })
|
|
186
|
+
expect(schema.parse("12345")).toBe("12345")
|
|
187
|
+
})
|
|
188
|
+
})
|
|
189
|
+
|
|
190
|
+
describe("ID type validation", () => {
|
|
191
|
+
describe("numeric IDs", () => {
|
|
192
|
+
it("should accept valid numeric IDs", () => {
|
|
193
|
+
const schema = id({ type: "numeric" })
|
|
194
|
+
validIds.numeric.forEach(validId => {
|
|
195
|
+
expect(schema.parse(validId)).toBe(validId)
|
|
196
|
+
})
|
|
197
|
+
})
|
|
198
|
+
|
|
199
|
+
it("should reject invalid numeric IDs", () => {
|
|
200
|
+
const schema = id({ type: "numeric" })
|
|
201
|
+
invalidIds.numeric.forEach(invalidId => {
|
|
202
|
+
expect(() => schema.parse(invalidId)).toThrow()
|
|
203
|
+
})
|
|
204
|
+
})
|
|
205
|
+
})
|
|
206
|
+
|
|
207
|
+
describe("UUID validation", () => {
|
|
208
|
+
it("should accept valid UUIDs", () => {
|
|
209
|
+
const schema = id({ type: "uuid" })
|
|
210
|
+
validIds.uuid.forEach(validUuid => {
|
|
211
|
+
expect(schema.parse(validUuid)).toBe(validUuid)
|
|
212
|
+
})
|
|
213
|
+
})
|
|
214
|
+
|
|
215
|
+
it("should reject invalid UUIDs", () => {
|
|
216
|
+
const schema = id({ type: "uuid" })
|
|
217
|
+
invalidIds.uuid.forEach(invalidUuid => {
|
|
218
|
+
expect(() => schema.parse(invalidUuid)).toThrow(messages.uuid)
|
|
219
|
+
})
|
|
220
|
+
})
|
|
221
|
+
})
|
|
222
|
+
|
|
223
|
+
describe("ObjectId validation", () => {
|
|
224
|
+
it("should accept valid ObjectIds", () => {
|
|
225
|
+
const schema = id({ type: "objectId" })
|
|
226
|
+
validIds.objectId.forEach(validObjectId => {
|
|
227
|
+
expect(schema.parse(validObjectId)).toBe(validObjectId)
|
|
228
|
+
})
|
|
229
|
+
})
|
|
230
|
+
|
|
231
|
+
it("should reject invalid ObjectIds", () => {
|
|
232
|
+
const schema = id({ type: "objectId" })
|
|
233
|
+
invalidIds.objectId.forEach(invalidObjectId => {
|
|
234
|
+
expect(() => schema.parse(invalidObjectId)).toThrow(messages.objectId)
|
|
235
|
+
})
|
|
236
|
+
})
|
|
237
|
+
})
|
|
238
|
+
|
|
239
|
+
describe("Nanoid validation", () => {
|
|
240
|
+
it("should accept valid Nanoids", () => {
|
|
241
|
+
const schema = id({ type: "nanoid" })
|
|
242
|
+
validIds.nanoid.forEach(validNanoid => {
|
|
243
|
+
expect(schema.parse(validNanoid)).toBe(validNanoid)
|
|
244
|
+
})
|
|
245
|
+
})
|
|
246
|
+
|
|
247
|
+
it("should reject invalid Nanoids", () => {
|
|
248
|
+
const schema = id({ type: "nanoid" })
|
|
249
|
+
invalidIds.nanoid.forEach(invalidNanoid => {
|
|
250
|
+
expect(() => schema.parse(invalidNanoid)).toThrow(messages.nanoid)
|
|
251
|
+
})
|
|
252
|
+
})
|
|
253
|
+
})
|
|
254
|
+
|
|
255
|
+
describe("Snowflake validation", () => {
|
|
256
|
+
it("should accept valid Snowflake IDs", () => {
|
|
257
|
+
const schema = id({ type: "snowflake" })
|
|
258
|
+
validIds.snowflake.forEach(validSnowflake => {
|
|
259
|
+
expect(schema.parse(validSnowflake)).toBe(validSnowflake)
|
|
260
|
+
})
|
|
261
|
+
})
|
|
262
|
+
|
|
263
|
+
it("should reject invalid Snowflake IDs", () => {
|
|
264
|
+
const schema = id({ type: "snowflake" })
|
|
265
|
+
invalidIds.snowflake.forEach(invalidSnowflake => {
|
|
266
|
+
expect(() => schema.parse(invalidSnowflake)).toThrow(messages.snowflake)
|
|
267
|
+
})
|
|
268
|
+
})
|
|
269
|
+
})
|
|
270
|
+
|
|
271
|
+
describe("CUID validation", () => {
|
|
272
|
+
it("should accept valid CUIDs", () => {
|
|
273
|
+
const schema = id({ type: "cuid" })
|
|
274
|
+
validIds.cuid.forEach(validCuid => {
|
|
275
|
+
expect(schema.parse(validCuid)).toBe(validCuid)
|
|
276
|
+
})
|
|
277
|
+
})
|
|
278
|
+
|
|
279
|
+
it("should reject invalid CUIDs", () => {
|
|
280
|
+
const schema = id({ type: "cuid" })
|
|
281
|
+
invalidIds.cuid.forEach(invalidCuid => {
|
|
282
|
+
expect(() => schema.parse(invalidCuid)).toThrow(messages.cuid)
|
|
283
|
+
})
|
|
284
|
+
})
|
|
285
|
+
})
|
|
286
|
+
|
|
287
|
+
describe("ULID validation", () => {
|
|
288
|
+
it("should accept valid ULIDs", () => {
|
|
289
|
+
const schema = id({ type: "ulid" })
|
|
290
|
+
validIds.ulid.forEach(validUlid => {
|
|
291
|
+
expect(schema.parse(validUlid)).toBe(validUlid)
|
|
292
|
+
})
|
|
293
|
+
})
|
|
294
|
+
|
|
295
|
+
it("should reject invalid ULIDs", () => {
|
|
296
|
+
const schema = id({ type: "ulid" })
|
|
297
|
+
invalidIds.ulid.forEach(invalidUlid => {
|
|
298
|
+
expect(() => schema.parse(invalidUlid)).toThrow(messages.ulid)
|
|
299
|
+
})
|
|
300
|
+
})
|
|
301
|
+
})
|
|
302
|
+
|
|
303
|
+
describe("ShortId validation", () => {
|
|
304
|
+
it("should accept valid ShortIds", () => {
|
|
305
|
+
const schema = id({ type: "shortid" })
|
|
306
|
+
validIds.shortid.forEach(validShortid => {
|
|
307
|
+
expect(schema.parse(validShortid)).toBe(validShortid)
|
|
308
|
+
})
|
|
309
|
+
})
|
|
310
|
+
|
|
311
|
+
it("should reject invalid ShortIds", () => {
|
|
312
|
+
const schema = id({ type: "shortid" })
|
|
313
|
+
invalidIds.shortid.forEach(invalidShortid => {
|
|
314
|
+
expect(() => schema.parse(invalidShortid)).toThrow(messages.shortid)
|
|
315
|
+
})
|
|
316
|
+
})
|
|
317
|
+
})
|
|
318
|
+
|
|
319
|
+
describe("auto detection", () => {
|
|
320
|
+
it("should accept any valid ID format when type is auto", () => {
|
|
321
|
+
const schema = id({ type: "auto" })
|
|
322
|
+
|
|
323
|
+
Object.values(validIds).flat().forEach(validId => {
|
|
324
|
+
expect(schema.parse(validId)).toBe(validId)
|
|
325
|
+
})
|
|
326
|
+
})
|
|
327
|
+
|
|
328
|
+
it("should reject invalid ID formats when type is auto", () => {
|
|
329
|
+
const schema = id({ type: "auto" })
|
|
330
|
+
|
|
331
|
+
expect(() => schema.parse("bad-id")).toThrow(messages.invalid)
|
|
332
|
+
expect(() => schema.parse("abc")).toThrow(messages.invalid)
|
|
333
|
+
expect(() => schema.parse("!@#$%")).toThrow(messages.invalid)
|
|
334
|
+
})
|
|
335
|
+
})
|
|
336
|
+
|
|
337
|
+
describe("allowedTypes", () => {
|
|
338
|
+
it("should accept IDs that match any allowed type", () => {
|
|
339
|
+
const schema = id({ allowedTypes: ["numeric", "uuid"] })
|
|
340
|
+
|
|
341
|
+
expect(schema.parse("123")).toBe("123")
|
|
342
|
+
expect(schema.parse("550e8400-e29b-41d4-a716-446655440000")).toBe("550e8400-e29b-41d4-a716-446655440000")
|
|
343
|
+
})
|
|
344
|
+
|
|
345
|
+
it("should reject IDs that don't match allowed types", () => {
|
|
346
|
+
const schema = id({ allowedTypes: ["numeric", "uuid"] })
|
|
347
|
+
|
|
348
|
+
expect(() => schema.parse("507f1f77bcf86cd799439011")).toThrow() // ObjectId
|
|
349
|
+
expect(() => schema.parse("V1StGXR8_Z5jdHi6B-myT")).toThrow() // Nanoid
|
|
350
|
+
})
|
|
351
|
+
})
|
|
352
|
+
})
|
|
353
|
+
|
|
354
|
+
describe("content validation", () => {
|
|
355
|
+
it("should enforce includes requirement", () => {
|
|
356
|
+
const schema = id({ includes: "test" })
|
|
357
|
+
expect(() => schema.parse("123")).toThrow(messages.includes)
|
|
358
|
+
expect(schema.parse("test123")).toBe("test123")
|
|
359
|
+
})
|
|
360
|
+
|
|
361
|
+
it("should enforce excludes requirement with string", () => {
|
|
362
|
+
const schema = id({ excludes: "bad" })
|
|
363
|
+
expect(() => schema.parse("bad123")).toThrow(messages.excludes)
|
|
364
|
+
expect(schema.parse("good123")).toBe("good123")
|
|
365
|
+
})
|
|
366
|
+
|
|
367
|
+
it("should enforce excludes requirement with array", () => {
|
|
368
|
+
const schema = id({ excludes: ["bad", "evil"] })
|
|
369
|
+
expect(() => schema.parse("bad123")).toThrow()
|
|
370
|
+
expect(() => schema.parse("evil123")).toThrow()
|
|
371
|
+
expect(schema.parse("good123")).toBe("good123")
|
|
372
|
+
})
|
|
373
|
+
|
|
374
|
+
it("should enforce startsWith requirement", () => {
|
|
375
|
+
const schema = id({ startsWith: "id" })
|
|
376
|
+
expect(() => schema.parse("123")).toThrow(messages.startsWith)
|
|
377
|
+
expect(schema.parse("id123")).toBe("id123")
|
|
378
|
+
})
|
|
379
|
+
|
|
380
|
+
it("should enforce endsWith requirement", () => {
|
|
381
|
+
const schema = id({ endsWith: "_end" })
|
|
382
|
+
expect(() => schema.parse("123")).toThrow(messages.endsWith)
|
|
383
|
+
expect(schema.parse("123_end")).toBe("123_end")
|
|
384
|
+
})
|
|
385
|
+
|
|
386
|
+
it("should enforce custom regex requirement", () => {
|
|
387
|
+
const schema = id({ customRegex: /^[A-Z]{2}\d{4}$/ })
|
|
388
|
+
expect(() => schema.parse("ab1234")).toThrow(messages.customFormat)
|
|
389
|
+
expect(() => schema.parse("AB123")).toThrow(messages.customFormat)
|
|
390
|
+
expect(schema.parse("AB1234")).toBe("AB1234")
|
|
391
|
+
})
|
|
392
|
+
})
|
|
393
|
+
|
|
394
|
+
describe("case sensitivity", () => {
|
|
395
|
+
it("should be case sensitive by default", () => {
|
|
396
|
+
const schema = id({ startsWith: "ID" })
|
|
397
|
+
expect(() => schema.parse("id123")).toThrow()
|
|
398
|
+
expect(schema.parse("ID123")).toBe("ID123")
|
|
399
|
+
})
|
|
400
|
+
|
|
401
|
+
it("should be case insensitive when configured", () => {
|
|
402
|
+
const schema = id({ startsWith: "id", caseSensitive: false })
|
|
403
|
+
expect(schema.parse("id123")).toBe("id123")
|
|
404
|
+
expect(schema.parse("ID123")).toBe("id123") // transformed to lowercase
|
|
405
|
+
})
|
|
406
|
+
|
|
407
|
+
it("should preserve case for UUID and ObjectId even when case insensitive", () => {
|
|
408
|
+
const schema = id({ type: "uuid", caseSensitive: false })
|
|
409
|
+
const uuid = "550E8400-E29B-41D4-A716-446655440000"
|
|
410
|
+
expect(schema.parse(uuid)).toBe(uuid) // case preserved for UUID
|
|
411
|
+
})
|
|
412
|
+
})
|
|
413
|
+
|
|
414
|
+
describe("custom i18n messages", () => {
|
|
415
|
+
it("should use custom i18n messages when provided", () => {
|
|
416
|
+
const customMessages = {
|
|
417
|
+
en: {
|
|
418
|
+
required: "Custom required message",
|
|
419
|
+
invalid: "Custom invalid message",
|
|
420
|
+
numeric: "Custom numeric message",
|
|
421
|
+
},
|
|
422
|
+
"zh-TW": {
|
|
423
|
+
required: "自訂必填訊息",
|
|
424
|
+
invalid: "自訂無效訊息",
|
|
425
|
+
numeric: "自訂數字訊息",
|
|
426
|
+
},
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
const schema = id({
|
|
430
|
+
type: "numeric",
|
|
431
|
+
i18n: customMessages,
|
|
432
|
+
})
|
|
433
|
+
|
|
434
|
+
if (locale === "en") {
|
|
435
|
+
expect(() => schema.parse("")).toThrow("Custom required message")
|
|
436
|
+
expect(() => schema.parse("abc")).toThrow("Custom numeric message")
|
|
437
|
+
} else {
|
|
438
|
+
expect(() => schema.parse("")).toThrow("自訂必填訊息")
|
|
439
|
+
expect(() => schema.parse("abc")).toThrow("自訂數字訊息")
|
|
440
|
+
}
|
|
441
|
+
})
|
|
442
|
+
|
|
443
|
+
it("should fallback to default i18n when custom message not provided", () => {
|
|
444
|
+
const customMessages = {
|
|
445
|
+
en: { required: "Custom required message" },
|
|
446
|
+
"zh-TW": { required: "自訂必填訊息" },
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
const schema = id({
|
|
450
|
+
type: "numeric",
|
|
451
|
+
i18n: customMessages,
|
|
452
|
+
})
|
|
453
|
+
|
|
454
|
+
if (locale === "en") {
|
|
455
|
+
expect(() => schema.parse("")).toThrow("Custom required message")
|
|
456
|
+
expect(() => schema.parse("abc")).toThrow(messages.numeric)
|
|
457
|
+
} else {
|
|
458
|
+
expect(() => schema.parse("")).toThrow("自訂必填訊息")
|
|
459
|
+
expect(() => schema.parse("abc")).toThrow(messages.numeric)
|
|
460
|
+
}
|
|
461
|
+
})
|
|
462
|
+
})
|
|
463
|
+
|
|
464
|
+
describe("complex scenarios", () => {
|
|
465
|
+
it("should handle all features combined", () => {
|
|
466
|
+
const schema = id({
|
|
467
|
+
type: "uuid",
|
|
468
|
+
minLength: 30,
|
|
469
|
+
maxLength: 50,
|
|
470
|
+
includes: "-",
|
|
471
|
+
excludes: ["test", "bad"],
|
|
472
|
+
startsWith: "550e",
|
|
473
|
+
caseSensitive: false,
|
|
474
|
+
})
|
|
475
|
+
|
|
476
|
+
// Should fail for various reasons
|
|
477
|
+
expect(() => schema.parse("123")).toThrow() // not UUID
|
|
478
|
+
expect(() => schema.parse("550e8400-e29b-41d4-test-446655440000")).toThrow() // contains "test"
|
|
479
|
+
expect(() => schema.parse("abc-123")).toThrow() // doesn't start with "550e"
|
|
480
|
+
|
|
481
|
+
// Should pass (UUID preserves case even with caseSensitive: false)
|
|
482
|
+
expect(schema.parse("550E8400-E29B-41D4-A716-446655440000")).toBe("550E8400-E29B-41D4-A716-446655440000")
|
|
483
|
+
})
|
|
484
|
+
|
|
485
|
+
it("should work with optional ID and all features", () => {
|
|
486
|
+
const schema = id({
|
|
487
|
+
required: false,
|
|
488
|
+
type: "numeric",
|
|
489
|
+
minLength: 3,
|
|
490
|
+
maxLength: 10,
|
|
491
|
+
})
|
|
492
|
+
|
|
493
|
+
expect(schema.parse(null)).toBe(null)
|
|
494
|
+
expect(schema.parse("")).toBe(null)
|
|
495
|
+
expect(() => schema.parse("ab")).toThrow() // not numeric
|
|
496
|
+
expect(() => schema.parse("12")).toThrow() // too short
|
|
497
|
+
expect(schema.parse("12345")).toBe("12345")
|
|
498
|
+
})
|
|
499
|
+
|
|
500
|
+
it("should handle multiple allowed types with constraints", () => {
|
|
501
|
+
const schema = id({
|
|
502
|
+
allowedTypes: ["uuid", "objectId"],
|
|
503
|
+
includes: "4",
|
|
504
|
+
minLength: 20,
|
|
505
|
+
})
|
|
506
|
+
|
|
507
|
+
expect(schema.parse("550e8400-e29b-41d4-a716-446655440000")).toBe("550e8400-e29b-41d4-a716-446655440000")
|
|
508
|
+
expect(schema.parse("507f1f77bcf86cd799439041")).toBe("507f1f77bcf86cd799439041")
|
|
509
|
+
expect(() => schema.parse("123456")).toThrow() // numeric not allowed
|
|
510
|
+
expect(() => schema.parse("507f1f77bcf86cd799339011")).toThrow() // no "4"
|
|
511
|
+
})
|
|
512
|
+
})
|
|
513
|
+
|
|
514
|
+
describe("utility functions", () => {
|
|
515
|
+
it("should expose detectIdType function", () => {
|
|
516
|
+
expect(detectIdType("123")).toBe("numeric")
|
|
517
|
+
expect(detectIdType("550e8400-e29b-41d4-a716-446655440000")).toBe("uuid")
|
|
518
|
+
expect(detectIdType("507f1f77bcf86cd799439011")).toBe("objectId")
|
|
519
|
+
expect(detectIdType("not-id")).toBe(null)
|
|
520
|
+
})
|
|
521
|
+
|
|
522
|
+
it("should expose validateIdType function", () => {
|
|
523
|
+
expect(validateIdType("123", "numeric")).toBe(true)
|
|
524
|
+
expect(validateIdType("abc", "numeric")).toBe(false)
|
|
525
|
+
expect(validateIdType("550e8400-e29b-41d4-a716-446655440000", "uuid")).toBe(true)
|
|
526
|
+
expect(validateIdType("invalid-uuid", "uuid")).toBe(false)
|
|
527
|
+
})
|
|
528
|
+
|
|
529
|
+
it("should expose ID_PATTERNS constant", () => {
|
|
530
|
+
expect(ID_PATTERNS.numeric.test("123")).toBe(true)
|
|
531
|
+
expect(ID_PATTERNS.uuid.test("550e8400-e29b-41d4-a716-446655440000")).toBe(true)
|
|
532
|
+
expect(ID_PATTERNS.objectId.test("507f1f77bcf86cd799439011")).toBe(true)
|
|
533
|
+
})
|
|
534
|
+
})
|
|
535
|
+
})
|