@hy_ong/zod-kit 0.1.2 → 0.1.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/package.json +1 -1
- package/src/validators/common/date.ts +26 -16
- package/src/validators/common/datetime.ts +46 -28
- package/src/validators/common/email.ts +57 -15
- package/src/validators/common/id.ts +40 -26
- package/src/validators/common/number.ts +80 -43
- package/src/validators/common/password.ts +30 -18
- package/src/validators/common/text.ts +48 -14
- package/src/validators/common/time.ts +34 -22
- package/src/validators/common/url.ts +40 -23
- package/src/validators/taiwan/business-id.ts +19 -19
- package/src/validators/taiwan/fax.ts +29 -28
- package/src/validators/taiwan/mobile.ts +29 -28
- package/src/validators/taiwan/national-id.ts +27 -27
- package/src/validators/taiwan/postal-code.ts +51 -45
- package/src/validators/taiwan/tel.ts +29 -28
- package/tests/taiwan/business-id.test.ts +23 -23
- package/tests/taiwan/fax.test.ts +34 -34
- package/tests/taiwan/mobile.test.ts +33 -33
- package/tests/taiwan/national-id.test.ts +32 -32
- package/tests/taiwan/postal-code.test.ts +62 -62
- package/tests/taiwan/tel.test.ts +34 -34
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hy_ong/zod-kit",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.3",
|
|
4
4
|
"description": "A comprehensive TypeScript library providing pre-built Zod validation schemas with full internationalization support for common data types and Taiwan-specific formats",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"zod",
|
|
@@ -238,38 +238,44 @@ export function date<IsRequired extends boolean = false>(required?: IsRequired,
|
|
|
238
238
|
|
|
239
239
|
const baseSchema = isRequired ? z.preprocess(preprocessFn, z.string()) : z.preprocess(preprocessFn, z.string().nullable())
|
|
240
240
|
|
|
241
|
-
const schema = baseSchema.
|
|
242
|
-
if (val === null) return
|
|
241
|
+
const schema = baseSchema.superRefine((val, ctx) => {
|
|
242
|
+
if (val === null) return
|
|
243
243
|
|
|
244
244
|
// Required check
|
|
245
245
|
if (isRequired && (val === "" || val === "null" || val === "undefined")) {
|
|
246
|
-
|
|
246
|
+
ctx.addIssue({ code: "custom", message: getMessage("required") })
|
|
247
|
+
return
|
|
247
248
|
}
|
|
248
249
|
|
|
249
250
|
// Format validation
|
|
250
251
|
if (val !== null && !dayjs(val, format, true).isValid()) {
|
|
251
|
-
|
|
252
|
+
ctx.addIssue({ code: "custom", message: getMessage("format", { format }) })
|
|
253
|
+
return
|
|
252
254
|
}
|
|
253
255
|
|
|
254
256
|
const dateObj = dayjs(val, format)
|
|
255
257
|
|
|
256
258
|
// Range checks
|
|
257
259
|
if (val !== null && min !== undefined && !dateObj.isSameOrAfter(dayjs(min, format))) {
|
|
258
|
-
|
|
260
|
+
ctx.addIssue({ code: "custom", message: getMessage("min", { min }) })
|
|
261
|
+
return
|
|
259
262
|
}
|
|
260
263
|
if (val !== null && max !== undefined && !dateObj.isSameOrBefore(dayjs(max, format))) {
|
|
261
|
-
|
|
264
|
+
ctx.addIssue({ code: "custom", message: getMessage("max", { max }) })
|
|
265
|
+
return
|
|
262
266
|
}
|
|
263
267
|
|
|
264
268
|
// String content checks
|
|
265
269
|
if (val !== null && includes !== undefined && !val.includes(includes)) {
|
|
266
|
-
|
|
270
|
+
ctx.addIssue({ code: "custom", message: getMessage("includes", { includes }) })
|
|
271
|
+
return
|
|
267
272
|
}
|
|
268
273
|
if (val !== null && excludes !== undefined) {
|
|
269
274
|
const excludeList = Array.isArray(excludes) ? excludes : [excludes]
|
|
270
275
|
for (const exclude of excludeList) {
|
|
271
276
|
if (val.includes(exclude)) {
|
|
272
|
-
|
|
277
|
+
ctx.addIssue({ code: "custom", message: getMessage("excludes", { excludes: exclude }) })
|
|
278
|
+
return
|
|
273
279
|
}
|
|
274
280
|
}
|
|
275
281
|
}
|
|
@@ -279,27 +285,31 @@ export function date<IsRequired extends boolean = false>(required?: IsRequired,
|
|
|
279
285
|
const targetDate = dateObj.startOf('day')
|
|
280
286
|
|
|
281
287
|
if (val !== null && mustBePast && !targetDate.isBefore(today)) {
|
|
282
|
-
|
|
288
|
+
ctx.addIssue({ code: "custom", message: getMessage("past") })
|
|
289
|
+
return
|
|
283
290
|
}
|
|
284
291
|
if (val !== null && mustBeFuture && !targetDate.isAfter(today)) {
|
|
285
|
-
|
|
292
|
+
ctx.addIssue({ code: "custom", message: getMessage("future") })
|
|
293
|
+
return
|
|
286
294
|
}
|
|
287
295
|
if (val !== null && mustBeToday && !targetDate.isSame(today)) {
|
|
288
|
-
|
|
296
|
+
ctx.addIssue({ code: "custom", message: getMessage("today") })
|
|
297
|
+
return
|
|
289
298
|
}
|
|
290
299
|
if (val !== null && mustNotBeToday && targetDate.isSame(today)) {
|
|
291
|
-
|
|
300
|
+
ctx.addIssue({ code: "custom", message: getMessage("notToday") })
|
|
301
|
+
return
|
|
292
302
|
}
|
|
293
303
|
|
|
294
304
|
// Weekday/weekend validations
|
|
295
305
|
if (val !== null && weekdaysOnly && (dateObj.day() === 0 || dateObj.day() === 6)) {
|
|
296
|
-
|
|
306
|
+
ctx.addIssue({ code: "custom", message: getMessage("weekday") })
|
|
307
|
+
return
|
|
297
308
|
}
|
|
298
309
|
if (val !== null && weekendsOnly && dateObj.day() !== 0 && dateObj.day() !== 6) {
|
|
299
|
-
|
|
310
|
+
ctx.addIssue({ code: "custom", message: getMessage("weekend") })
|
|
311
|
+
return
|
|
300
312
|
}
|
|
301
|
-
|
|
302
|
-
return true
|
|
303
313
|
})
|
|
304
314
|
|
|
305
315
|
return schema as unknown as DateSchema<IsRequired>
|
|
@@ -515,25 +515,27 @@ export function datetime<IsRequired extends boolean = false>(required?: IsRequir
|
|
|
515
515
|
|
|
516
516
|
const baseSchema = isRequired ? z.preprocess(preprocessFn, z.string()) : z.preprocess(preprocessFn, z.string().nullable())
|
|
517
517
|
|
|
518
|
-
const schema = baseSchema.
|
|
519
|
-
if (val === null) return
|
|
518
|
+
const schema = baseSchema.superRefine((val, ctx) => {
|
|
519
|
+
if (val === null) return
|
|
520
520
|
|
|
521
521
|
// Required check
|
|
522
522
|
if (isRequired && (val === "" || val === "null" || val === "undefined")) {
|
|
523
|
-
|
|
523
|
+
ctx.addIssue({ code: "custom", message: getMessage("required") })
|
|
524
|
+
return
|
|
524
525
|
}
|
|
525
526
|
|
|
526
|
-
if (val === null) return
|
|
527
|
-
if (!isRequired && val === "") return
|
|
527
|
+
if (val === null) return
|
|
528
|
+
if (!isRequired && val === "") return
|
|
528
529
|
|
|
529
530
|
// Whitelist check
|
|
530
531
|
if (whitelist && whitelist.length > 0) {
|
|
531
532
|
if (whitelist.includes(val)) {
|
|
532
|
-
return
|
|
533
|
+
return
|
|
533
534
|
}
|
|
534
535
|
// If whitelistOnly is true, reject values not in whitelist
|
|
535
536
|
if (whitelistOnly) {
|
|
536
|
-
|
|
537
|
+
ctx.addIssue({ code: "custom", message: getMessage("notInWhitelist") })
|
|
538
|
+
return
|
|
537
539
|
}
|
|
538
540
|
// Otherwise, continue with normal validation
|
|
539
541
|
}
|
|
@@ -541,32 +543,36 @@ export function datetime<IsRequired extends boolean = false>(required?: IsRequir
|
|
|
541
543
|
// Custom regex validation (overrides format validation)
|
|
542
544
|
if (regex) {
|
|
543
545
|
if (!regex.test(val)) {
|
|
544
|
-
|
|
546
|
+
ctx.addIssue({ code: "custom", message: getMessage("customRegex") })
|
|
547
|
+
return
|
|
545
548
|
}
|
|
546
549
|
} else {
|
|
547
550
|
// DateTime format validation (only if no regex is provided)
|
|
548
551
|
if (!validateDateTimeFormat(val, format)) {
|
|
549
|
-
|
|
552
|
+
ctx.addIssue({ code: "custom", message: getMessage("format", { format }) })
|
|
553
|
+
return
|
|
550
554
|
}
|
|
551
555
|
}
|
|
552
556
|
|
|
553
557
|
// String content checks
|
|
554
558
|
if (includes && !val.includes(includes)) {
|
|
555
|
-
|
|
559
|
+
ctx.addIssue({ code: "custom", message: getMessage("includes", { includes }) })
|
|
560
|
+
return
|
|
556
561
|
}
|
|
557
562
|
|
|
558
563
|
if (excludes) {
|
|
559
564
|
const excludeList = Array.isArray(excludes) ? excludes : [excludes]
|
|
560
565
|
for (const exclude of excludeList) {
|
|
561
566
|
if (val.includes(exclude)) {
|
|
562
|
-
|
|
567
|
+
ctx.addIssue({ code: "custom", message: getMessage("excludes", { excludes: exclude }) })
|
|
568
|
+
return
|
|
563
569
|
}
|
|
564
570
|
}
|
|
565
571
|
}
|
|
566
572
|
|
|
567
573
|
// Skip datetime parsing and range validation if using custom regex
|
|
568
574
|
if (regex) {
|
|
569
|
-
return
|
|
575
|
+
return
|
|
570
576
|
}
|
|
571
577
|
|
|
572
578
|
// Parse datetime for validation
|
|
@@ -575,32 +581,38 @@ export function datetime<IsRequired extends boolean = false>(required?: IsRequir
|
|
|
575
581
|
// Check if it's a format issue or parsing issue
|
|
576
582
|
const pattern = DATETIME_PATTERNS[format]
|
|
577
583
|
if (!pattern.test(val.trim())) {
|
|
578
|
-
|
|
584
|
+
ctx.addIssue({ code: "custom", message: getMessage("format", { format }) })
|
|
585
|
+
return
|
|
579
586
|
} else {
|
|
580
|
-
|
|
587
|
+
ctx.addIssue({ code: "custom", message: getMessage("invalid") })
|
|
588
|
+
return
|
|
581
589
|
}
|
|
582
590
|
}
|
|
583
591
|
|
|
584
592
|
// Hour validation
|
|
585
593
|
const hour = parsed.hour()
|
|
586
594
|
if (minHour !== undefined && hour < minHour) {
|
|
587
|
-
|
|
595
|
+
ctx.addIssue({ code: "custom", message: getMessage("hour", { minHour, maxHour: maxHour ?? 23 }) })
|
|
596
|
+
return
|
|
588
597
|
}
|
|
589
598
|
if (maxHour !== undefined && hour > maxHour) {
|
|
590
|
-
|
|
599
|
+
ctx.addIssue({ code: "custom", message: getMessage("hour", { minHour: minHour ?? 0, maxHour }) })
|
|
600
|
+
return
|
|
591
601
|
}
|
|
592
602
|
|
|
593
603
|
// Allowed hours check
|
|
594
604
|
if (allowedHours && allowedHours.length > 0) {
|
|
595
605
|
if (!allowedHours.includes(hour)) {
|
|
596
|
-
|
|
606
|
+
ctx.addIssue({ code: "custom", message: getMessage("hour", { allowedHours: allowedHours.join(", ") }) })
|
|
607
|
+
return
|
|
597
608
|
}
|
|
598
609
|
}
|
|
599
610
|
|
|
600
611
|
// Minute step validation
|
|
601
612
|
const minute = parsed.minute()
|
|
602
613
|
if (minuteStep !== undefined && minute % minuteStep !== 0) {
|
|
603
|
-
|
|
614
|
+
ctx.addIssue({ code: "custom", message: getMessage("minute", { minuteStep }) })
|
|
615
|
+
return
|
|
604
616
|
}
|
|
605
617
|
|
|
606
618
|
// DateTime range validation (min/max)
|
|
@@ -608,7 +620,8 @@ export function datetime<IsRequired extends boolean = false>(required?: IsRequir
|
|
|
608
620
|
const minParsed = typeof min === "string" ? parseDateTimeValue(min, format, timezone) : dayjs(min)
|
|
609
621
|
if (minParsed && parsed.isBefore(minParsed)) {
|
|
610
622
|
const minFormatted = typeof min === "string" ? min : minParsed.format(format)
|
|
611
|
-
|
|
623
|
+
ctx.addIssue({ code: "custom", message: getMessage("min", { min: minFormatted }) })
|
|
624
|
+
return
|
|
612
625
|
}
|
|
613
626
|
}
|
|
614
627
|
|
|
@@ -616,7 +629,8 @@ export function datetime<IsRequired extends boolean = false>(required?: IsRequir
|
|
|
616
629
|
const maxParsed = typeof max === "string" ? parseDateTimeValue(max, format, timezone) : dayjs(max)
|
|
617
630
|
if (maxParsed && parsed.isAfter(maxParsed)) {
|
|
618
631
|
const maxFormatted = typeof max === "string" ? max : maxParsed.format(format)
|
|
619
|
-
|
|
632
|
+
ctx.addIssue({ code: "custom", message: getMessage("max", { max: maxFormatted }) })
|
|
633
|
+
return
|
|
620
634
|
}
|
|
621
635
|
}
|
|
622
636
|
|
|
@@ -624,33 +638,37 @@ export function datetime<IsRequired extends boolean = false>(required?: IsRequir
|
|
|
624
638
|
const now = timezone ? dayjs().tz(timezone) : dayjs()
|
|
625
639
|
|
|
626
640
|
if (mustBePast && !parsed.isBefore(now)) {
|
|
627
|
-
|
|
641
|
+
ctx.addIssue({ code: "custom", message: getMessage("past") })
|
|
642
|
+
return
|
|
628
643
|
}
|
|
629
644
|
|
|
630
645
|
if (mustBeFuture && !parsed.isAfter(now)) {
|
|
631
|
-
|
|
646
|
+
ctx.addIssue({ code: "custom", message: getMessage("future") })
|
|
647
|
+
return
|
|
632
648
|
}
|
|
633
649
|
|
|
634
650
|
if (mustBeToday && !parsed.isSame(now, "day")) {
|
|
635
|
-
|
|
651
|
+
ctx.addIssue({ code: "custom", message: getMessage("today") })
|
|
652
|
+
return
|
|
636
653
|
}
|
|
637
654
|
|
|
638
655
|
if (mustNotBeToday && parsed.isSame(now, "day")) {
|
|
639
|
-
|
|
656
|
+
ctx.addIssue({ code: "custom", message: getMessage("notToday") })
|
|
657
|
+
return
|
|
640
658
|
}
|
|
641
659
|
|
|
642
660
|
// Weekday validations
|
|
643
661
|
const dayOfWeek = parsed.day() // 0 = Sunday, 1 = Monday, ..., 6 = Saturday
|
|
644
662
|
|
|
645
663
|
if (weekdaysOnly && (dayOfWeek === 0 || dayOfWeek === 6)) {
|
|
646
|
-
|
|
664
|
+
ctx.addIssue({ code: "custom", message: getMessage("weekday") })
|
|
665
|
+
return
|
|
647
666
|
}
|
|
648
667
|
|
|
649
668
|
if (weekendsOnly && dayOfWeek !== 0 && dayOfWeek !== 6) {
|
|
650
|
-
|
|
669
|
+
ctx.addIssue({ code: "custom", message: getMessage("weekend") })
|
|
670
|
+
return
|
|
651
671
|
}
|
|
652
|
-
|
|
653
|
-
return true
|
|
654
672
|
})
|
|
655
673
|
|
|
656
674
|
return schema as unknown as DateTimeSchema<IsRequired>
|
|
@@ -209,37 +209,61 @@ export function email<IsRequired extends boolean = false>(required?: IsRequired,
|
|
|
209
209
|
z.union([z.string().email(), z.null()])
|
|
210
210
|
)
|
|
211
211
|
|
|
212
|
-
const schema = baseSchema.
|
|
212
|
+
const schema = baseSchema.superRefine((val, ctx) => {
|
|
213
213
|
// Required check first
|
|
214
214
|
if (isRequired && val === null) {
|
|
215
|
-
|
|
215
|
+
ctx.addIssue({
|
|
216
|
+
code: "custom",
|
|
217
|
+
message: getMessage("required")
|
|
218
|
+
})
|
|
219
|
+
return
|
|
216
220
|
}
|
|
217
221
|
|
|
218
|
-
if (val === null) return
|
|
222
|
+
if (val === null) return
|
|
219
223
|
|
|
220
224
|
// Invalid email check
|
|
221
225
|
if (typeof val !== "string") {
|
|
222
|
-
|
|
226
|
+
ctx.addIssue({
|
|
227
|
+
code: "custom",
|
|
228
|
+
message: getMessage("invalid")
|
|
229
|
+
})
|
|
230
|
+
return
|
|
223
231
|
}
|
|
224
232
|
|
|
225
233
|
// Length checks
|
|
226
234
|
if (minLength !== undefined && val.length < minLength) {
|
|
227
|
-
|
|
235
|
+
ctx.addIssue({
|
|
236
|
+
code: "custom",
|
|
237
|
+
message: getMessage("minLength", { minLength })
|
|
238
|
+
})
|
|
239
|
+
return
|
|
228
240
|
}
|
|
229
241
|
if (maxLength !== undefined && val.length > maxLength) {
|
|
230
|
-
|
|
242
|
+
ctx.addIssue({
|
|
243
|
+
code: "custom",
|
|
244
|
+
message: getMessage("maxLength", { maxLength })
|
|
245
|
+
})
|
|
246
|
+
return
|
|
231
247
|
}
|
|
232
248
|
|
|
233
249
|
// Content checks
|
|
234
250
|
if (includes !== undefined && !val.includes(includes)) {
|
|
235
|
-
|
|
251
|
+
ctx.addIssue({
|
|
252
|
+
code: "custom",
|
|
253
|
+
message: getMessage("includes", { includes })
|
|
254
|
+
})
|
|
255
|
+
return
|
|
236
256
|
}
|
|
237
257
|
|
|
238
258
|
if (excludes !== undefined) {
|
|
239
259
|
const excludeList = Array.isArray(excludes) ? excludes : [excludes]
|
|
240
260
|
for (const exclude of excludeList) {
|
|
241
261
|
if (val.includes(exclude)) {
|
|
242
|
-
|
|
262
|
+
ctx.addIssue({
|
|
263
|
+
code: "custom",
|
|
264
|
+
message: getMessage("includes", { includes: exclude })
|
|
265
|
+
})
|
|
266
|
+
return
|
|
243
267
|
}
|
|
244
268
|
}
|
|
245
269
|
}
|
|
@@ -247,7 +271,11 @@ export function email<IsRequired extends boolean = false>(required?: IsRequired,
|
|
|
247
271
|
// Extract domain from email
|
|
248
272
|
const emailDomain = val.split("@")[1]?.toLowerCase()
|
|
249
273
|
if (!emailDomain) {
|
|
250
|
-
|
|
274
|
+
ctx.addIssue({
|
|
275
|
+
code: "custom",
|
|
276
|
+
message: getMessage("invalid")
|
|
277
|
+
})
|
|
278
|
+
return
|
|
251
279
|
}
|
|
252
280
|
|
|
253
281
|
// Business email check (should come before domain validation)
|
|
@@ -260,7 +288,11 @@ export function email<IsRequired extends boolean = false>(required?: IsRequired,
|
|
|
260
288
|
})
|
|
261
289
|
|
|
262
290
|
if (isFreeProvider) {
|
|
263
|
-
|
|
291
|
+
ctx.addIssue({
|
|
292
|
+
code: "custom",
|
|
293
|
+
message: getMessage("businessOnly")
|
|
294
|
+
})
|
|
295
|
+
return
|
|
264
296
|
}
|
|
265
297
|
}
|
|
266
298
|
|
|
@@ -275,7 +307,11 @@ export function email<IsRequired extends boolean = false>(required?: IsRequired,
|
|
|
275
307
|
})
|
|
276
308
|
|
|
277
309
|
if (isBlacklisted) {
|
|
278
|
-
|
|
310
|
+
ctx.addIssue({
|
|
311
|
+
code: "custom",
|
|
312
|
+
message: getMessage("domainBlacklist", { domain: emailDomain })
|
|
313
|
+
})
|
|
314
|
+
return
|
|
279
315
|
}
|
|
280
316
|
}
|
|
281
317
|
|
|
@@ -291,7 +327,11 @@ export function email<IsRequired extends boolean = false>(required?: IsRequired,
|
|
|
291
327
|
})
|
|
292
328
|
|
|
293
329
|
if (!isAllowed) {
|
|
294
|
-
|
|
330
|
+
ctx.addIssue({
|
|
331
|
+
code: "custom",
|
|
332
|
+
message: getMessage("domain", { domain: Array.isArray(domain) ? domain.join(", ") : domain })
|
|
333
|
+
})
|
|
334
|
+
return
|
|
295
335
|
}
|
|
296
336
|
}
|
|
297
337
|
|
|
@@ -305,11 +345,13 @@ export function email<IsRequired extends boolean = false>(required?: IsRequired,
|
|
|
305
345
|
})
|
|
306
346
|
|
|
307
347
|
if (isDisposable) {
|
|
308
|
-
|
|
348
|
+
ctx.addIssue({
|
|
349
|
+
code: "custom",
|
|
350
|
+
message: getMessage("noDisposable")
|
|
351
|
+
})
|
|
352
|
+
return
|
|
309
353
|
}
|
|
310
354
|
}
|
|
311
|
-
|
|
312
|
-
return true
|
|
313
355
|
})
|
|
314
356
|
|
|
315
357
|
return schema as unknown as EmailSchema<IsRequired>
|
|
@@ -369,34 +369,37 @@ export function id<IsRequired extends boolean = false, Type extends IdType | und
|
|
|
369
369
|
// Create base schema based on type
|
|
370
370
|
if (isNumericType) {
|
|
371
371
|
// Use z.any() to avoid Zod's built-in type checking, then validate manually
|
|
372
|
-
const numericSchema = z.preprocess(preprocessNumericFn, z.any()).
|
|
372
|
+
const numericSchema = z.preprocess(preprocessNumericFn, z.any()).superRefine((val, ctx) => {
|
|
373
373
|
// Allow null for optional fields
|
|
374
|
-
if (!isRequired && val === null) return
|
|
374
|
+
if (!isRequired && val === null) return
|
|
375
375
|
|
|
376
376
|
// Required check for undefined/null/empty (empty string when required)
|
|
377
377
|
if (val === undefined || (isRequired && val === null)) {
|
|
378
|
-
|
|
378
|
+
ctx.addIssue({ code: "custom", message: getMessage("required") })
|
|
379
|
+
return
|
|
379
380
|
}
|
|
380
381
|
|
|
381
382
|
// Numeric validation - check if it's an actual number (not NaN)
|
|
382
383
|
if (typeof val !== "number" || isNaN(val)) {
|
|
383
|
-
|
|
384
|
+
ctx.addIssue({ code: "custom", message: getMessage("numeric") })
|
|
385
|
+
return
|
|
384
386
|
}
|
|
385
387
|
|
|
386
388
|
// Length checks on string representation
|
|
387
389
|
const strVal = String(val)
|
|
388
390
|
if (!ID_PATTERNS.numeric.test(strVal)) {
|
|
389
|
-
|
|
391
|
+
ctx.addIssue({ code: "custom", message: getMessage("numeric") })
|
|
392
|
+
return
|
|
390
393
|
}
|
|
391
394
|
|
|
392
395
|
if (minLength !== undefined && strVal.length < minLength) {
|
|
393
|
-
|
|
396
|
+
ctx.addIssue({ code: "custom", message: getMessage("minLength", { minLength }) })
|
|
397
|
+
return
|
|
394
398
|
}
|
|
395
399
|
if (maxLength !== undefined && strVal.length > maxLength) {
|
|
396
|
-
|
|
400
|
+
ctx.addIssue({ code: "custom", message: getMessage("maxLength", { maxLength }) })
|
|
401
|
+
return
|
|
397
402
|
}
|
|
398
|
-
|
|
399
|
-
return true
|
|
400
403
|
})
|
|
401
404
|
|
|
402
405
|
return numericSchema as unknown as IdSchema<IsRequired, Type>
|
|
@@ -406,12 +409,13 @@ export function id<IsRequired extends boolean = false, Type extends IdType | und
|
|
|
406
409
|
const baseSchema = isRequired ? z.preprocess(preprocessStringFn, z.string()) : z.preprocess(preprocessStringFn, z.string().nullable())
|
|
407
410
|
|
|
408
411
|
const schema = baseSchema
|
|
409
|
-
.
|
|
410
|
-
if (val === null) return
|
|
412
|
+
.superRefine((val, ctx) => {
|
|
413
|
+
if (val === null) return
|
|
411
414
|
|
|
412
415
|
// Required check
|
|
413
416
|
if (isRequired && (val === "" || val === "null" || val === "undefined")) {
|
|
414
|
-
|
|
417
|
+
ctx.addIssue({ code: "custom", message: getMessage("required") })
|
|
418
|
+
return
|
|
415
419
|
}
|
|
416
420
|
|
|
417
421
|
// Create comparison value for case-insensitive checks
|
|
@@ -419,10 +423,12 @@ export function id<IsRequired extends boolean = false, Type extends IdType | und
|
|
|
419
423
|
|
|
420
424
|
// Length checks
|
|
421
425
|
if (val !== null && minLength !== undefined && val.length < minLength) {
|
|
422
|
-
|
|
426
|
+
ctx.addIssue({ code: "custom", message: getMessage("minLength", { minLength }) })
|
|
427
|
+
return
|
|
423
428
|
}
|
|
424
429
|
if (val !== null && maxLength !== undefined && val.length > maxLength) {
|
|
425
|
-
|
|
430
|
+
ctx.addIssue({ code: "custom", message: getMessage("maxLength", { maxLength }) })
|
|
431
|
+
return
|
|
426
432
|
}
|
|
427
433
|
|
|
428
434
|
// Check if we have content-based validations that override format checking
|
|
@@ -431,7 +437,8 @@ export function id<IsRequired extends boolean = false, Type extends IdType | und
|
|
|
431
437
|
// Custom regex validation (overrides ID format validation)
|
|
432
438
|
if (val !== null && customRegex !== undefined) {
|
|
433
439
|
if (!customRegex.test(val)) {
|
|
434
|
-
|
|
440
|
+
ctx.addIssue({ code: "custom", message: getMessage("customFormat") })
|
|
441
|
+
return
|
|
435
442
|
}
|
|
436
443
|
} else if (val !== null && !hasContentValidations) {
|
|
437
444
|
// ID type validation (only if no custom regex or content validations)
|
|
@@ -442,19 +449,22 @@ export function id<IsRequired extends boolean = false, Type extends IdType | und
|
|
|
442
449
|
isValidId = allowedTypes.some((allowedType: IdType) => validateIdType(val, allowedType))
|
|
443
450
|
if (!isValidId) {
|
|
444
451
|
const typeNames = allowedTypes.join(", ")
|
|
445
|
-
|
|
452
|
+
ctx.addIssue({ code: "custom", message: getMessage("invalid") + ` (allowed types: ${typeNames})` })
|
|
453
|
+
return
|
|
446
454
|
}
|
|
447
455
|
} else if (type && type !== "auto") {
|
|
448
456
|
// Validate specific type
|
|
449
457
|
isValidId = validateIdType(val, type)
|
|
450
458
|
if (!isValidId) {
|
|
451
|
-
|
|
459
|
+
ctx.addIssue({ code: "custom", message: getMessage(type as keyof IdMessages) || getMessage("invalid") })
|
|
460
|
+
return
|
|
452
461
|
}
|
|
453
462
|
} else {
|
|
454
463
|
// Auto-detection - must match at least one known pattern
|
|
455
464
|
isValidId = detectIdType(val) !== null
|
|
456
465
|
if (!isValidId) {
|
|
457
|
-
|
|
466
|
+
ctx.addIssue({ code: "custom", message: getMessage("invalid") })
|
|
467
|
+
return
|
|
458
468
|
}
|
|
459
469
|
}
|
|
460
470
|
} else if (val !== null && hasContentValidations && type && type !== "auto" && !customRegex) {
|
|
@@ -463,11 +473,13 @@ export function id<IsRequired extends boolean = false, Type extends IdType | und
|
|
|
463
473
|
const isValidType = allowedTypes.some((allowedType: IdType) => validateIdType(val, allowedType))
|
|
464
474
|
if (!isValidType) {
|
|
465
475
|
const typeNames = allowedTypes.join(", ")
|
|
466
|
-
|
|
476
|
+
ctx.addIssue({ code: "custom", message: getMessage("invalid") + ` (allowed types: ${typeNames})` })
|
|
477
|
+
return
|
|
467
478
|
}
|
|
468
479
|
} else {
|
|
469
480
|
if (!validateIdType(val, type)) {
|
|
470
|
-
|
|
481
|
+
ctx.addIssue({ code: "custom", message: getMessage(type as keyof IdMessages) || getMessage("invalid") })
|
|
482
|
+
return
|
|
471
483
|
}
|
|
472
484
|
}
|
|
473
485
|
}
|
|
@@ -478,25 +490,27 @@ export function id<IsRequired extends boolean = false, Type extends IdType | und
|
|
|
478
490
|
const searchIncludes = !caseSensitive && includes ? includes.toLowerCase() : includes
|
|
479
491
|
|
|
480
492
|
if (val !== null && startsWith !== undefined && !comparisonVal.startsWith(searchStartsWith!)) {
|
|
481
|
-
|
|
493
|
+
ctx.addIssue({ code: "custom", message: getMessage("startsWith", { startsWith }) })
|
|
494
|
+
return
|
|
482
495
|
}
|
|
483
496
|
if (val !== null && endsWith !== undefined && !comparisonVal.endsWith(searchEndsWith!)) {
|
|
484
|
-
|
|
497
|
+
ctx.addIssue({ code: "custom", message: getMessage("endsWith", { endsWith }) })
|
|
498
|
+
return
|
|
485
499
|
}
|
|
486
500
|
if (val !== null && includes !== undefined && !comparisonVal.includes(searchIncludes!)) {
|
|
487
|
-
|
|
501
|
+
ctx.addIssue({ code: "custom", message: getMessage("includes", { includes }) })
|
|
502
|
+
return
|
|
488
503
|
}
|
|
489
504
|
if (val !== null && excludes !== undefined) {
|
|
490
505
|
const excludeList = Array.isArray(excludes) ? excludes : [excludes]
|
|
491
506
|
for (const exclude of excludeList) {
|
|
492
507
|
const searchExclude = !caseSensitive ? exclude.toLowerCase() : exclude
|
|
493
508
|
if (comparisonVal.includes(searchExclude)) {
|
|
494
|
-
|
|
509
|
+
ctx.addIssue({ code: "custom", message: getMessage("excludes", { excludes: exclude }) })
|
|
510
|
+
return
|
|
495
511
|
}
|
|
496
512
|
}
|
|
497
513
|
}
|
|
498
|
-
|
|
499
|
-
return true
|
|
500
514
|
})
|
|
501
515
|
.transform((val) => {
|
|
502
516
|
if (val === null) return val
|