Some checks failed
Deploy to Test Environment / deploy-to-test (push) Failing after 43s
153 lines
5.1 KiB
TypeScript
153 lines
5.1 KiB
TypeScript
// src/utils/zodUtils.ts
|
|
import { z } from 'zod';
|
|
|
|
/**
|
|
* A Zod schema for a required, non-empty string.
|
|
* @param message The error message to display if the string is empty or missing.
|
|
* @returns A Zod string schema.
|
|
*/
|
|
export const requiredString = (message: string) =>
|
|
z.preprocess(
|
|
// If the value is null or undefined, preprocess it to an empty string.
|
|
// This ensures that the subsequent `.min(1)` check will catch missing required fields.
|
|
(val) => val ?? '',
|
|
// Now, validate that the (potentially preprocessed) value is a string that, after trimming, has at least 1 character.
|
|
z.string().trim().min(1, message),
|
|
);
|
|
|
|
/**
|
|
* Creates a Zod schema for a numeric ID in request parameters.
|
|
* @param paramName The name of the parameter (e.g., 'id').
|
|
* @param message The error message for invalid input.
|
|
* @returns A Zod object schema for the params.
|
|
*/
|
|
export const numericIdParam = (
|
|
paramName: string,
|
|
message = `Invalid ID for parameter '${paramName}'. Must be a number.`,
|
|
) =>
|
|
z.object({
|
|
params: z.object({
|
|
[paramName]: z.coerce.number().int(message).positive(message),
|
|
}),
|
|
});
|
|
|
|
/**
|
|
* Creates a Zod schema for a UUID in request parameters.
|
|
* @param paramName The name of the parameter (e.g., 'id').
|
|
* @param message The error message for invalid input.
|
|
* @returns A Zod object schema for the params.
|
|
*/
|
|
export const uuidParamSchema = (paramName: string, message: string) =>
|
|
z.object({
|
|
params: z.object({
|
|
[paramName]: z.string().uuid(message),
|
|
}),
|
|
});
|
|
|
|
/**
|
|
* Creates a Zod schema for an optional, numeric query parameter that is coerced from a string.
|
|
* @param options Configuration for the validation like default value, min/max, and integer constraints.
|
|
* @returns A Zod schema for the number.
|
|
*/
|
|
export const optionalNumeric = (
|
|
options: {
|
|
default?: number;
|
|
min?: number;
|
|
max?: number;
|
|
integer?: boolean;
|
|
positive?: boolean;
|
|
nonnegative?: boolean;
|
|
} = {},
|
|
) => {
|
|
let numberSchema = z.coerce.number();
|
|
|
|
if (options.integer) numberSchema = numberSchema.int();
|
|
if (options.positive) numberSchema = numberSchema.positive();
|
|
else if (options.nonnegative) numberSchema = numberSchema.nonnegative();
|
|
|
|
if (options.min !== undefined) numberSchema = numberSchema.min(options.min);
|
|
if (options.max !== undefined) numberSchema = numberSchema.max(options.max);
|
|
|
|
// Make the number schema optional *before* preprocessing. This allows it to correctly handle
|
|
// the `undefined` value that our preprocessor generates from `null`.
|
|
const optionalNumberSchema = numberSchema.optional();
|
|
|
|
// This is crucial because z.coerce.number(null) results in 0, which bypasses
|
|
// the .optional() and .default() logic for null inputs. We want null to be
|
|
// treated as "not provided", just like undefined.
|
|
const schema = z.preprocess((val) => (val === null ? undefined : val), optionalNumberSchema);
|
|
|
|
if (options.default !== undefined) return schema.default(options.default);
|
|
|
|
return schema;
|
|
};
|
|
|
|
/**
|
|
* Creates a Zod schema for an optional date string in YYYY-MM-DD format.
|
|
* @param message Optional custom error message.
|
|
* @returns A Zod schema for the date string.
|
|
*/
|
|
export const optionalDate = (message?: string) => z.string().date(message).optional();
|
|
|
|
|
|
/**
|
|
* Creates a Zod schema for an optional boolean query parameter that is coerced from a string.
|
|
* Handles 'true', '1' as true and 'false', '0' as false.
|
|
* @param options Configuration for the validation like default value.
|
|
* @returns A Zod schema for the boolean.
|
|
*/
|
|
export const optionalBoolean = (
|
|
options: {
|
|
default?: boolean;
|
|
} = {},
|
|
) => {
|
|
const schema = z.preprocess((val) => {
|
|
if (val === 'true' || val === '1') return true;
|
|
if (val === 'false' || val === '0') return false;
|
|
if (val === '' || val === null) return undefined; // Treat empty string and null as not present
|
|
return val;
|
|
}, z.boolean().optional());
|
|
|
|
if (options.default !== undefined) {
|
|
return schema.default(options.default);
|
|
}
|
|
|
|
return schema;
|
|
};
|
|
|
|
/**
|
|
* Creates a Zod schema for an optional string.
|
|
* Treats null as undefined so it can be properly handled as optional.
|
|
* @returns A Zod schema for an optional string.
|
|
*/
|
|
export const optionalString = () =>
|
|
z.preprocess((val) => (val === null ? undefined : val), z.string().optional());
|
|
|
|
/**
|
|
* Creates a Zod schema for a required HTTP/HTTPS URL.
|
|
* Validates that the URL starts with http:// or https:// to match database constraints.
|
|
* @param message Optional custom error message.
|
|
* @returns A Zod schema for the URL string.
|
|
*/
|
|
export const httpUrl = (message = 'Must be a valid HTTP or HTTPS URL') =>
|
|
z
|
|
.string()
|
|
.url(message)
|
|
.regex(/^https?:\/\/.+/, message);
|
|
|
|
/**
|
|
* Creates a Zod schema for an optional HTTP/HTTPS URL.
|
|
* Validates that if provided, the URL starts with http:// or https://.
|
|
* @param message Optional custom error message.
|
|
* @returns A Zod schema for the optional URL string.
|
|
*/
|
|
export const optionalHttpUrl = (message = 'Must be a valid HTTP or HTTPS URL') =>
|
|
z.preprocess(
|
|
(val) => (val === null ? undefined : val),
|
|
z
|
|
.string()
|
|
.url(message)
|
|
.regex(/^https?:\/\/.+/, message)
|
|
.optional(),
|
|
);
|