75 lines
2.0 KiB
TypeScript
75 lines
2.0 KiB
TypeScript
import { z } from "zod/v4";
|
|
|
|
/**
|
|
* Helper to handle query string arrays that can be either:
|
|
* - Single value: ?expand=invoices → "invoices"
|
|
* - Comma-separated: ?expand=invoices,rewards → ["invoices", "rewards"] (requires queryMiddleware)
|
|
* - Multiple values: ?expand=invoices&expand=products → ["invoices", "products"]
|
|
*
|
|
* Normalizes all to array format for consistent Zod validation
|
|
*
|
|
* Note: Comma-separated parsing requires the queryMiddleware to be applied first.
|
|
* The middleware uses the `qs` library to parse query strings with comma support.
|
|
*
|
|
* @example
|
|
* ```ts
|
|
* const schema = z.object({
|
|
* expand: queryStringArray(z.enum(CusExpand)).optional(),
|
|
* });
|
|
* ```
|
|
*/
|
|
export function queryStringArray<T extends z.ZodTypeAny>(schema: T) {
|
|
return z.preprocess((val) => {
|
|
// Convert single string to array for consistent handling
|
|
if (typeof val === "string") {
|
|
return [val];
|
|
}
|
|
return val;
|
|
}, z.array(schema));
|
|
}
|
|
|
|
/**
|
|
* Helper to handle query string integers that come in as strings and need to be converted to numbers.
|
|
* Query parameters are always strings, so this helper parses them to integers for validation.
|
|
*
|
|
* @example
|
|
* ```ts
|
|
* const schema = z.object({
|
|
* limit: queryInteger({ min: 1, max: 100 }).default(10),
|
|
* offset: queryInteger({ min: 0 }).default(0),
|
|
* });
|
|
* ```
|
|
*/
|
|
export function queryInteger(options?: {
|
|
min?: number;
|
|
max?: number;
|
|
error?: string;
|
|
}) {
|
|
let schema = z.number().int({ message: options?.error });
|
|
|
|
if (options?.min !== undefined) {
|
|
schema = schema.min(options.min, {
|
|
message: options?.error || `must be at least ${options.min}`,
|
|
});
|
|
}
|
|
|
|
if (options?.max !== undefined) {
|
|
schema = schema.max(options.max, {
|
|
message: options?.error || `must be at most ${options.max}`,
|
|
});
|
|
}
|
|
|
|
return z.preprocess((val) => {
|
|
// If already a number, return as-is
|
|
if (typeof val === "number") {
|
|
return val;
|
|
}
|
|
// Parse string to integer
|
|
if (typeof val === "string") {
|
|
const parsed = Number.parseInt(val, 10);
|
|
return Number.isNaN(parsed) ? val : parsed;
|
|
}
|
|
return val;
|
|
}, schema);
|
|
}
|