Created
June 28, 2025 19:05
-
-
Save kensleDev/c4ee39a4b13db4076d6a14846d4facb5 to your computer and use it in GitHub Desktop.
Curried zod validators
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // validations.ts | |
| import { z, ZodString, ZodNumber, ZodType, ZodTypeAny } from "zod"; | |
| // --- Types --- | |
| /** A validator that takes a label and returns a function that takes and returns a Zod schema */ export type LabeledRule<T extends ZodTypeAny> = (label: string) => (schema: T) => T; | |
| // --- Utility --- | |
| /** Compose multiple labeled rules into one schema builder, injecting the label into each */ export function compose<T extends ZodTypeAny>( label: string, ...rules: LabeledRule<T>[] ): (base: T) => T { return (base: T) => rules.reduce((schema, rule) => rule(label)(schema), base); } | |
| // --- String Validators --- | |
| export const minLength = (min: number, message?: string): LabeledRule<ZodString> => (label) => (schema) => schema.refine((val) => val.length >= min, { message: message || ${label} must be at least ${min} characters, }); | |
| export const maxLength = (max: number, message?: string): LabeledRule<ZodString> => (label) => (schema) => schema.refine((val) => val.length <= max, { message: message || ${label} must be at most ${max} characters, }); | |
| export const regex = (pattern: RegExp, message?: string): LabeledRule<ZodString> => (label) => (schema) => schema.refine((val) => pattern.test(val), { message: message || ${label} is invalid, }); | |
| export const noSpaces = (message?: string): LabeledRule<ZodString> => (label) => (schema) => schema.refine((val) => !val.includes(" "), { message: message || ${label} must not contain spaces, }); | |
| export const email = (message?: string): LabeledRule<ZodString> => (label) => (schema) => schema.email({ message: message || ${label} must be a valid email address }); | |
| export const url = (message?: string): LabeledRule<ZodString> => (label) => (schema) => schema.url({ message: message || ${label} must be a valid URL }); | |
| // --- Number Validators --- | |
| export const minValue = (min: number, message?: string): LabeledRule<ZodNumber> => (label) => (schema) => schema.refine((val) => val >= min, { message: message || ${label} must be at least ${min}, }); | |
| export const maxValue = (max: number, message?: string): LabeledRule<ZodNumber> => (label) => (schema) => schema.refine((val) => val <= max, { message: message || ${label} must be at most ${max}, }); | |
| export const positive = (message?: string): LabeledRule<ZodNumber> => (label) => (schema) => schema.positive({ message: message || ${label} must be a positive number }); | |
| export const nonNegative = (message?: string): LabeledRule<ZodNumber> => (label) => (schema) => schema.nonnegative({ message: message || ${label} must be zero or greater }); | |
| export const integer = (message?: string): LabeledRule<ZodNumber> => (label) => (schema) => schema.int({ message: message || ${label} must be an integer }); | |
| // --- Common Aliases --- | |
| export const requiredString = (label: string) => compose(label, minLength(1))(z.string()); | |
| export const requiredNumber = (label: string) => compose(label, nonNegative())(z.number()); | |
| // --- Generic Required --- | |
| export const required = (message?: string): LabeledRule<ZodTypeAny> => (label) => (schema) => schema.refine((val) => val !== undefined && val !== null && val !== "", { message: message || ${label} is required, }); | |
| // --- Example Auth Schemas --- | |
| export const loginSchema = z.object({ email: compose("Email", required(), email())(z.string()), password: compose("Password", required(), minLength(8))(z.string()), }); | |
| export const registerSchema = z.object({ email: compose("Email", required(), email())(z.string()), password: compose("Password", required(), minLength(8))(z.string()), confirmPassword: compose("Confirm Password", required(), minLength(8))(z.string()), }).refine((data) => data.password === data.confirmPassword, { path: ["confirmPassword"], message: "Passwords must match", }); | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment