@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
|
@@ -98,6 +98,7 @@ export type NumberSchema<IsRequired extends boolean> = IsRequired extends true ?
|
|
|
98
98
|
* Creates a Zod schema for number validation with comprehensive constraints
|
|
99
99
|
*
|
|
100
100
|
* @template IsRequired - Whether the field is required (affects return type)
|
|
101
|
+
* @param required
|
|
101
102
|
* @param {NumberOptions<IsRequired>} [options] - Configuration options for number validation
|
|
102
103
|
* @returns {NumberSchema<IsRequired>} Zod schema for number validation
|
|
103
104
|
*
|
|
@@ -165,25 +166,10 @@ export type NumberSchema<IsRequired extends boolean> = IsRequired extends true ?
|
|
|
165
166
|
* @throws {z.ZodError} When validation fails with specific error messages
|
|
166
167
|
* @see {@link NumberOptions} for all available configuration options
|
|
167
168
|
*/
|
|
168
|
-
export function number<IsRequired extends boolean = false>(required?: IsRequired, options?: Omit<NumberOptions<IsRequired>,
|
|
169
|
-
const {
|
|
170
|
-
min,
|
|
171
|
-
max,
|
|
172
|
-
defaultValue,
|
|
173
|
-
type = "both",
|
|
174
|
-
positive,
|
|
175
|
-
negative,
|
|
176
|
-
nonNegative,
|
|
177
|
-
nonPositive,
|
|
178
|
-
multipleOf,
|
|
179
|
-
precision,
|
|
180
|
-
finite = true,
|
|
181
|
-
transform,
|
|
182
|
-
parseCommas = false,
|
|
183
|
-
i18n,
|
|
184
|
-
} = options ?? {}
|
|
169
|
+
export function number<IsRequired extends boolean = false>(required?: IsRequired, options?: Omit<NumberOptions<IsRequired>, "required">): NumberSchema<IsRequired> {
|
|
170
|
+
const { min, max, defaultValue, type = "both", positive, negative, nonNegative, nonPositive, multipleOf, precision, finite = true, transform, parseCommas = false, i18n } = options ?? {}
|
|
185
171
|
|
|
186
|
-
const isRequired = required ?? false as IsRequired
|
|
172
|
+
const isRequired = required ?? (false as IsRequired)
|
|
187
173
|
|
|
188
174
|
// Helper function to get custom message or fallback to default i18n
|
|
189
175
|
const getMessage = (key: keyof NumberMessages, params?: Record<string, any>) => {
|
|
@@ -243,79 +229,130 @@ export function number<IsRequired extends boolean = false>(required?: IsRequired
|
|
|
243
229
|
},
|
|
244
230
|
z.union([z.number(), z.null(), z.nan(), z.custom<number>((val) => val === Infinity || val === -Infinity)])
|
|
245
231
|
)
|
|
246
|
-
.
|
|
232
|
+
.superRefine((val, ctx) => {
|
|
247
233
|
// Required check first
|
|
248
234
|
if (isRequired && val === null) {
|
|
249
|
-
|
|
235
|
+
ctx.addIssue({
|
|
236
|
+
code: "custom",
|
|
237
|
+
message: getMessage("required"),
|
|
238
|
+
})
|
|
239
|
+
return
|
|
250
240
|
}
|
|
251
241
|
|
|
252
|
-
if (val === null) return
|
|
242
|
+
if (val === null) return
|
|
253
243
|
|
|
254
244
|
// Type validation for invalid inputs (NaN)
|
|
255
|
-
if (
|
|
245
|
+
if (isNaN(val)) {
|
|
256
246
|
if (type === "integer") {
|
|
257
|
-
|
|
247
|
+
ctx.addIssue({
|
|
248
|
+
code: "custom",
|
|
249
|
+
message: getMessage("integer"),
|
|
250
|
+
})
|
|
258
251
|
} else if (type === "float") {
|
|
259
|
-
|
|
252
|
+
ctx.addIssue({
|
|
253
|
+
code: "custom",
|
|
254
|
+
message: getMessage("float"),
|
|
255
|
+
})
|
|
260
256
|
} else {
|
|
261
|
-
|
|
257
|
+
ctx.addIssue({
|
|
258
|
+
code: "custom",
|
|
259
|
+
message: getMessage("invalid"),
|
|
260
|
+
})
|
|
262
261
|
}
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
// Invalid number check for non-numbers
|
|
266
|
-
if (typeof val !== "number") {
|
|
267
|
-
throw new z.ZodError([{ code: "custom", message: getMessage("invalid"), path: [] }])
|
|
262
|
+
return
|
|
268
263
|
}
|
|
269
264
|
|
|
270
265
|
// Finite check
|
|
271
266
|
if (finite && !Number.isFinite(val)) {
|
|
272
|
-
|
|
267
|
+
ctx.addIssue({
|
|
268
|
+
code: "custom",
|
|
269
|
+
message: getMessage("finite"),
|
|
270
|
+
})
|
|
271
|
+
return
|
|
273
272
|
}
|
|
274
273
|
|
|
275
274
|
// Type validation for valid numbers
|
|
276
275
|
if (type === "integer" && !Number.isInteger(val)) {
|
|
277
|
-
|
|
276
|
+
ctx.addIssue({
|
|
277
|
+
code: "custom",
|
|
278
|
+
message: getMessage("integer"),
|
|
279
|
+
})
|
|
280
|
+
return
|
|
278
281
|
}
|
|
279
282
|
if (type === "float" && Number.isInteger(val)) {
|
|
280
|
-
|
|
283
|
+
ctx.addIssue({
|
|
284
|
+
code: "custom",
|
|
285
|
+
message: getMessage("float"),
|
|
286
|
+
})
|
|
287
|
+
return
|
|
281
288
|
}
|
|
282
289
|
|
|
283
290
|
// Sign checks (more specific, should come first)
|
|
284
291
|
if (positive && val <= 0) {
|
|
285
|
-
|
|
292
|
+
ctx.addIssue({
|
|
293
|
+
code: "custom",
|
|
294
|
+
message: getMessage("positive"),
|
|
295
|
+
})
|
|
296
|
+
return
|
|
286
297
|
}
|
|
287
298
|
if (negative && val >= 0) {
|
|
288
|
-
|
|
299
|
+
ctx.addIssue({
|
|
300
|
+
code: "custom",
|
|
301
|
+
message: getMessage("negative"),
|
|
302
|
+
})
|
|
303
|
+
return
|
|
289
304
|
}
|
|
290
305
|
if (nonNegative && val < 0) {
|
|
291
|
-
|
|
306
|
+
ctx.addIssue({
|
|
307
|
+
code: "custom",
|
|
308
|
+
message: getMessage("nonNegative"),
|
|
309
|
+
})
|
|
310
|
+
return
|
|
292
311
|
}
|
|
293
312
|
if (nonPositive && val > 0) {
|
|
294
|
-
|
|
313
|
+
ctx.addIssue({
|
|
314
|
+
code: "custom",
|
|
315
|
+
message: getMessage("nonPositive"),
|
|
316
|
+
})
|
|
317
|
+
return
|
|
295
318
|
}
|
|
296
319
|
|
|
297
320
|
// Range checks
|
|
298
321
|
if (min !== undefined && val < min) {
|
|
299
|
-
|
|
322
|
+
ctx.addIssue({
|
|
323
|
+
code: "custom",
|
|
324
|
+
message: getMessage("min", { min }),
|
|
325
|
+
})
|
|
326
|
+
return
|
|
300
327
|
}
|
|
301
328
|
if (max !== undefined && val > max) {
|
|
302
|
-
|
|
329
|
+
ctx.addIssue({
|
|
330
|
+
code: "custom",
|
|
331
|
+
message: getMessage("max", { max }),
|
|
332
|
+
})
|
|
333
|
+
return
|
|
303
334
|
}
|
|
304
335
|
|
|
305
336
|
// Multiple of check
|
|
306
337
|
if (multipleOf !== undefined && val % multipleOf !== 0) {
|
|
307
|
-
|
|
338
|
+
ctx.addIssue({
|
|
339
|
+
code: "custom",
|
|
340
|
+
message: getMessage("multipleOf", { multipleOf }),
|
|
341
|
+
})
|
|
342
|
+
return
|
|
308
343
|
}
|
|
309
344
|
|
|
310
345
|
// Precision check
|
|
311
346
|
if (precision !== undefined) {
|
|
312
347
|
const decimalPlaces = (val.toString().split(".")[1] || "").length
|
|
313
348
|
if (decimalPlaces > precision) {
|
|
314
|
-
|
|
349
|
+
ctx.addIssue({
|
|
350
|
+
code: "custom",
|
|
351
|
+
message: getMessage("precision", { precision }),
|
|
352
|
+
})
|
|
353
|
+
return
|
|
315
354
|
}
|
|
316
355
|
}
|
|
317
|
-
|
|
318
|
-
return true
|
|
319
356
|
})
|
|
320
357
|
|
|
321
358
|
return schema as unknown as NumberSchema<IsRequired>
|
|
@@ -320,45 +320,55 @@ export function password<IsRequired extends boolean = false>(required?: IsRequir
|
|
|
320
320
|
|
|
321
321
|
const baseSchema = isRequired ? z.preprocess(preprocessFn, z.string()) : z.preprocess(preprocessFn, z.string().nullable())
|
|
322
322
|
|
|
323
|
-
const schema = baseSchema.
|
|
324
|
-
if (val === null) return
|
|
323
|
+
const schema = baseSchema.superRefine((val, ctx) => {
|
|
324
|
+
if (val === null) return
|
|
325
325
|
|
|
326
326
|
// Required check
|
|
327
327
|
if (isRequired && (val === "" || val === "null" || val === "undefined")) {
|
|
328
|
-
|
|
328
|
+
ctx.addIssue({ code: "custom", message: getMessage("required") })
|
|
329
|
+
return
|
|
329
330
|
}
|
|
330
331
|
|
|
331
332
|
// Length checks
|
|
332
333
|
if (val !== null && min !== undefined && val.length < min) {
|
|
333
|
-
|
|
334
|
+
ctx.addIssue({ code: "custom", message: getMessage("min", { min }) })
|
|
335
|
+
return
|
|
334
336
|
}
|
|
335
337
|
if (val !== null && max !== undefined && val.length > max) {
|
|
336
|
-
|
|
338
|
+
ctx.addIssue({ code: "custom", message: getMessage("max", { max }) })
|
|
339
|
+
return
|
|
337
340
|
}
|
|
338
341
|
|
|
339
342
|
// Character requirements
|
|
340
343
|
if (val !== null && uppercase && !/[A-Z]/.test(val)) {
|
|
341
|
-
|
|
344
|
+
ctx.addIssue({ code: "custom", message: getMessage("uppercase") })
|
|
345
|
+
return
|
|
342
346
|
}
|
|
343
347
|
if (val !== null && lowercase && !/[a-z]/.test(val)) {
|
|
344
|
-
|
|
348
|
+
ctx.addIssue({ code: "custom", message: getMessage("lowercase") })
|
|
349
|
+
return
|
|
345
350
|
}
|
|
346
351
|
if (val !== null && digits && !/[0-9]/.test(val)) {
|
|
347
|
-
|
|
352
|
+
ctx.addIssue({ code: "custom", message: getMessage("digits") })
|
|
353
|
+
return
|
|
348
354
|
}
|
|
349
355
|
if (val !== null && special && !/[!@#$%^&*()_+\-=[\]{};':"\\|,.<>/?]/.test(val)) {
|
|
350
|
-
|
|
356
|
+
ctx.addIssue({ code: "custom", message: getMessage("special") })
|
|
357
|
+
return
|
|
351
358
|
}
|
|
352
359
|
|
|
353
360
|
// Advanced security checks
|
|
354
361
|
if (val !== null && noRepeating && /(.)\1{2,}/.test(val)) {
|
|
355
|
-
|
|
362
|
+
ctx.addIssue({ code: "custom", message: getMessage("noRepeating") })
|
|
363
|
+
return
|
|
356
364
|
}
|
|
357
365
|
if (val !== null && noSequential && /(?:abc|bcd|cde|def|efg|fgh|ghi|hij|ijk|jkl|klm|lmn|mno|nop|opq|pqr|qrs|rst|stu|tuv|uvw|vwx|wxy|xyz|012|123|234|345|456|567|678|789)/i.test(val)) {
|
|
358
|
-
|
|
366
|
+
ctx.addIssue({ code: "custom", message: getMessage("noSequential") })
|
|
367
|
+
return
|
|
359
368
|
}
|
|
360
369
|
if (val !== null && noCommonWords && COMMON_PASSWORDS.some((common) => val.toLowerCase().includes(common.toLowerCase()))) {
|
|
361
|
-
|
|
370
|
+
ctx.addIssue({ code: "custom", message: getMessage("noCommonWords") })
|
|
371
|
+
return
|
|
362
372
|
}
|
|
363
373
|
if (val !== null && minStrength) {
|
|
364
374
|
const strength = calculatePasswordStrength(val)
|
|
@@ -366,27 +376,29 @@ export function password<IsRequired extends boolean = false>(required?: IsRequir
|
|
|
366
376
|
const currentLevel = strengthLevels.indexOf(strength)
|
|
367
377
|
const requiredLevel = strengthLevels.indexOf(minStrength)
|
|
368
378
|
if (currentLevel < requiredLevel) {
|
|
369
|
-
|
|
379
|
+
ctx.addIssue({ code: "custom", message: getMessage("minStrength", { minStrength }) })
|
|
380
|
+
return
|
|
370
381
|
}
|
|
371
382
|
}
|
|
372
383
|
|
|
373
384
|
// Content checks
|
|
374
385
|
if (val !== null && includes !== undefined && !val.includes(includes)) {
|
|
375
|
-
|
|
386
|
+
ctx.addIssue({ code: "custom", message: getMessage("includes", { includes }) })
|
|
387
|
+
return
|
|
376
388
|
}
|
|
377
389
|
if (val !== null && excludes !== undefined) {
|
|
378
390
|
const excludeList = Array.isArray(excludes) ? excludes : [excludes]
|
|
379
391
|
for (const exclude of excludeList) {
|
|
380
392
|
if (val.includes(exclude)) {
|
|
381
|
-
|
|
393
|
+
ctx.addIssue({ code: "custom", message: getMessage("excludes", { excludes: exclude }) })
|
|
394
|
+
return
|
|
382
395
|
}
|
|
383
396
|
}
|
|
384
397
|
}
|
|
385
398
|
if (val !== null && regex !== undefined && !regex.test(val)) {
|
|
386
|
-
|
|
399
|
+
ctx.addIssue({ code: "custom", message: getMessage("invalid", { regex }) })
|
|
400
|
+
return
|
|
387
401
|
}
|
|
388
|
-
|
|
389
|
-
return true
|
|
390
402
|
})
|
|
391
403
|
|
|
392
404
|
return schema as unknown as PasswordSchema<IsRequired>
|
|
@@ -223,52 +223,86 @@ export function text<IsRequired extends boolean = false>(required?: IsRequired,
|
|
|
223
223
|
|
|
224
224
|
const baseSchema = isRequired ? z.preprocess(preprocessFn, z.string()) : z.preprocess(preprocessFn, z.string().nullable())
|
|
225
225
|
|
|
226
|
-
// Single
|
|
227
|
-
const schema = baseSchema.
|
|
228
|
-
if (val === null) return
|
|
226
|
+
// Single superRefine with all validations for better performance
|
|
227
|
+
const schema = baseSchema.superRefine((val, ctx) => {
|
|
228
|
+
if (val === null) return
|
|
229
229
|
|
|
230
230
|
// Required check
|
|
231
231
|
if (isRequired && (val === "" || val === "null" || val === "undefined")) {
|
|
232
|
-
|
|
232
|
+
ctx.addIssue({
|
|
233
|
+
code: "custom",
|
|
234
|
+
message: getMessage("required")
|
|
235
|
+
})
|
|
236
|
+
return
|
|
233
237
|
}
|
|
234
238
|
|
|
235
239
|
// Not empty check (different from required - checks whitespace)
|
|
236
240
|
// For notEmpty, we need to check if the original string (before processing) was only whitespace
|
|
237
241
|
if (notEmpty && val !== null && val.trim() === "") {
|
|
238
|
-
|
|
242
|
+
ctx.addIssue({
|
|
243
|
+
code: "custom",
|
|
244
|
+
message: getMessage("notEmpty")
|
|
245
|
+
})
|
|
246
|
+
return
|
|
239
247
|
}
|
|
240
248
|
|
|
241
249
|
// Length checks
|
|
242
250
|
if (val !== null && minLength !== undefined && val.length < minLength) {
|
|
243
|
-
|
|
251
|
+
ctx.addIssue({
|
|
252
|
+
code: "custom",
|
|
253
|
+
message: getMessage("minLength", { minLength })
|
|
254
|
+
})
|
|
255
|
+
return
|
|
244
256
|
}
|
|
245
257
|
if (val !== null && maxLength !== undefined && val.length > maxLength) {
|
|
246
|
-
|
|
258
|
+
ctx.addIssue({
|
|
259
|
+
code: "custom",
|
|
260
|
+
message: getMessage("maxLength", { maxLength })
|
|
261
|
+
})
|
|
262
|
+
return
|
|
247
263
|
}
|
|
248
264
|
|
|
249
265
|
// String content checks
|
|
250
266
|
if (val !== null && startsWith !== undefined && !val.startsWith(startsWith)) {
|
|
251
|
-
|
|
267
|
+
ctx.addIssue({
|
|
268
|
+
code: "custom",
|
|
269
|
+
message: getMessage("startsWith", { startsWith })
|
|
270
|
+
})
|
|
271
|
+
return
|
|
252
272
|
}
|
|
253
273
|
if (val !== null && endsWith !== undefined && !val.endsWith(endsWith)) {
|
|
254
|
-
|
|
274
|
+
ctx.addIssue({
|
|
275
|
+
code: "custom",
|
|
276
|
+
message: getMessage("endsWith", { endsWith })
|
|
277
|
+
})
|
|
278
|
+
return
|
|
255
279
|
}
|
|
256
280
|
if (val !== null && includes !== undefined && !val.includes(includes)) {
|
|
257
|
-
|
|
281
|
+
ctx.addIssue({
|
|
282
|
+
code: "custom",
|
|
283
|
+
message: getMessage("includes", { includes })
|
|
284
|
+
})
|
|
285
|
+
return
|
|
258
286
|
}
|
|
259
287
|
if (val !== null && excludes !== undefined) {
|
|
260
288
|
const excludeList = Array.isArray(excludes) ? excludes : [excludes]
|
|
261
289
|
for (const exclude of excludeList) {
|
|
262
290
|
if (val.includes(exclude)) {
|
|
263
|
-
|
|
291
|
+
ctx.addIssue({
|
|
292
|
+
code: "custom",
|
|
293
|
+
message: getMessage("excludes", { excludes: exclude })
|
|
294
|
+
})
|
|
295
|
+
return
|
|
264
296
|
}
|
|
265
297
|
}
|
|
266
298
|
}
|
|
267
299
|
if (val !== null && regex !== undefined && !regex.test(val)) {
|
|
268
|
-
|
|
300
|
+
ctx.addIssue({
|
|
301
|
+
code: "custom",
|
|
302
|
+
message: getMessage("invalid", { regex })
|
|
303
|
+
})
|
|
304
|
+
return
|
|
269
305
|
}
|
|
270
|
-
|
|
271
|
-
return true
|
|
272
306
|
})
|
|
273
307
|
|
|
274
308
|
return schema as unknown as TextSchema<IsRequired>
|
|
@@ -465,25 +465,27 @@ export function time<IsRequired extends boolean = false>(required?: IsRequired,
|
|
|
465
465
|
|
|
466
466
|
const baseSchema = isRequired ? z.preprocess(preprocessFn, z.string()) : z.preprocess(preprocessFn, z.string().nullable())
|
|
467
467
|
|
|
468
|
-
const schema = baseSchema.
|
|
469
|
-
if (val === null) return
|
|
468
|
+
const schema = baseSchema.superRefine((val, ctx) => {
|
|
469
|
+
if (val === null) return
|
|
470
470
|
|
|
471
471
|
// Required check
|
|
472
472
|
if (isRequired && (val === "" || val === "null" || val === "undefined")) {
|
|
473
|
-
|
|
473
|
+
ctx.addIssue({ code: "custom", message: getMessage("required") })
|
|
474
|
+
return
|
|
474
475
|
}
|
|
475
476
|
|
|
476
|
-
if (val === null) return
|
|
477
|
-
if (!isRequired && val === "") return
|
|
477
|
+
if (val === null) return
|
|
478
|
+
if (!isRequired && val === "") return
|
|
478
479
|
|
|
479
480
|
// Whitelist check
|
|
480
481
|
if (whitelist && whitelist.length > 0) {
|
|
481
482
|
if (whitelist.includes(val)) {
|
|
482
|
-
return
|
|
483
|
+
return
|
|
483
484
|
}
|
|
484
485
|
// If whitelistOnly is true, reject values not in whitelist
|
|
485
486
|
if (whitelistOnly) {
|
|
486
|
-
|
|
487
|
+
ctx.addIssue({ code: "custom", message: getMessage("notInWhitelist") })
|
|
488
|
+
return
|
|
487
489
|
}
|
|
488
490
|
// Otherwise, continue with normal validation
|
|
489
491
|
}
|
|
@@ -491,60 +493,69 @@ export function time<IsRequired extends boolean = false>(required?: IsRequired,
|
|
|
491
493
|
// Custom regex validation (overrides format validation)
|
|
492
494
|
if (regex) {
|
|
493
495
|
if (!regex.test(val)) {
|
|
494
|
-
|
|
496
|
+
ctx.addIssue({ code: "custom", message: getMessage("customRegex") })
|
|
497
|
+
return
|
|
495
498
|
}
|
|
496
499
|
} else {
|
|
497
500
|
// Time format validation (only if no regex is provided)
|
|
498
501
|
if (!validateTimeFormat(val, format)) {
|
|
499
|
-
|
|
502
|
+
ctx.addIssue({ code: "custom", message: getMessage("format", { format }) })
|
|
503
|
+
return
|
|
500
504
|
}
|
|
501
505
|
}
|
|
502
506
|
|
|
503
507
|
// String content checks
|
|
504
508
|
if (includes && !val.includes(includes)) {
|
|
505
|
-
|
|
509
|
+
ctx.addIssue({ code: "custom", message: getMessage("includes", { includes }) })
|
|
510
|
+
return
|
|
506
511
|
}
|
|
507
512
|
|
|
508
513
|
if (excludes) {
|
|
509
514
|
const excludeList = Array.isArray(excludes) ? excludes : [excludes]
|
|
510
515
|
for (const exclude of excludeList) {
|
|
511
516
|
if (val.includes(exclude)) {
|
|
512
|
-
|
|
517
|
+
ctx.addIssue({ code: "custom", message: getMessage("excludes", { excludes: exclude }) })
|
|
518
|
+
return
|
|
513
519
|
}
|
|
514
520
|
}
|
|
515
521
|
}
|
|
516
522
|
|
|
517
523
|
// Skip time parsing and range validation if using custom regex
|
|
518
524
|
if (regex) {
|
|
519
|
-
return
|
|
525
|
+
return
|
|
520
526
|
}
|
|
521
527
|
|
|
522
528
|
// Parse time for range validation
|
|
523
529
|
const timeMinutes = parseTimeToMinutes(val, format)
|
|
524
530
|
if (timeMinutes === null) {
|
|
525
|
-
|
|
531
|
+
ctx.addIssue({ code: "custom", message: getMessage("invalid") })
|
|
532
|
+
return
|
|
526
533
|
}
|
|
527
534
|
|
|
528
535
|
// Hour validation
|
|
529
536
|
const hour = Math.floor(timeMinutes / 60)
|
|
530
537
|
if (minHour !== undefined && hour < minHour) {
|
|
531
|
-
|
|
538
|
+
ctx.addIssue({ code: "custom", message: getMessage("hour", { minHour, maxHour: maxHour ?? 23 }) })
|
|
539
|
+
return
|
|
532
540
|
}
|
|
533
541
|
if (maxHour !== undefined && hour > maxHour) {
|
|
534
|
-
|
|
542
|
+
ctx.addIssue({ code: "custom", message: getMessage("hour", { minHour: minHour ?? 0, maxHour }) })
|
|
543
|
+
return
|
|
535
544
|
}
|
|
536
545
|
|
|
537
546
|
// Allowed hours check
|
|
538
547
|
if (allowedHours && allowedHours.length > 0) {
|
|
539
548
|
if (!allowedHours.includes(hour)) {
|
|
540
|
-
|
|
549
|
+
ctx.addIssue({ code: "custom", message: getMessage("hour", { allowedHours: allowedHours.join(", ") }) })
|
|
550
|
+
return
|
|
541
551
|
}
|
|
542
552
|
}
|
|
543
553
|
|
|
544
554
|
// Minute step validation
|
|
545
555
|
const minute = timeMinutes % 60
|
|
546
556
|
if (minuteStep !== undefined && minute % minuteStep !== 0) {
|
|
547
|
-
|
|
557
|
+
ctx.addIssue({ code: "custom", message: getMessage("minute", { minuteStep }) })
|
|
558
|
+
return
|
|
548
559
|
}
|
|
549
560
|
|
|
550
561
|
// Second step validation (only for formats with seconds)
|
|
@@ -554,7 +565,8 @@ export function time<IsRequired extends boolean = false>(required?: IsRequired,
|
|
|
554
565
|
if (secondMatch) {
|
|
555
566
|
const seconds = parseInt(secondMatch[1], 10)
|
|
556
567
|
if (seconds % secondStep !== 0) {
|
|
557
|
-
|
|
568
|
+
ctx.addIssue({ code: "custom", message: getMessage("second", { secondStep }) })
|
|
569
|
+
return
|
|
558
570
|
}
|
|
559
571
|
}
|
|
560
572
|
}
|
|
@@ -563,18 +575,18 @@ export function time<IsRequired extends boolean = false>(required?: IsRequired,
|
|
|
563
575
|
if (min) {
|
|
564
576
|
const minMinutes = parseTimeToMinutes(min, format)
|
|
565
577
|
if (minMinutes !== null && timeMinutes < minMinutes) {
|
|
566
|
-
|
|
578
|
+
ctx.addIssue({ code: "custom", message: getMessage("min", { min }) })
|
|
579
|
+
return
|
|
567
580
|
}
|
|
568
581
|
}
|
|
569
582
|
|
|
570
583
|
if (max) {
|
|
571
584
|
const maxMinutes = parseTimeToMinutes(max, format)
|
|
572
585
|
if (maxMinutes !== null && timeMinutes > maxMinutes) {
|
|
573
|
-
|
|
586
|
+
ctx.addIssue({ code: "custom", message: getMessage("max", { max }) })
|
|
587
|
+
return
|
|
574
588
|
}
|
|
575
589
|
}
|
|
576
|
-
|
|
577
|
-
return true
|
|
578
590
|
})
|
|
579
591
|
|
|
580
592
|
return schema as unknown as TimeSchema<IsRequired>
|