/** zod helpers for query-string parameters (strings in → typed values out). */ import { z } from "zod"; const empty = (v: unknown) => (v === "" || v === null ? undefined : v); export const numParam = z.preprocess((v) => { const x = empty(v); if (x === undefined) return undefined; const n = Number(x); return Number.isFinite(n) ? n : x; }, z.number().optional()); export const intParam = z.preprocess((v) => { const x = empty(v); if (x === undefined) return undefined; const n = Number(x); return Number.isFinite(n) ? Math.trunc(n) : x; }, z.number().int().optional()); export const boolParam = z.preprocess((v) => { const x = empty(v); if (x === undefined) return undefined; if (typeof x === "boolean") return x; const s = String(x).toLowerCase(); if (["1", "true", "yes", "y", "on"].includes(s)) return true; if (["0", "false", "no", "n", "off"].includes(s)) return false; return x; }, z.boolean().optional()); export const strParam = z.preprocess(empty, z.string().trim().max(300).optional()); export const csvParam = z.preprocess(empty, z.string().trim().max(1000).optional()); export const pageParam = z.preprocess((v) => { const x = empty(v); if (x === undefined) return 1; const n = Number(x); return Number.isFinite(n) && n >= 1 ? Math.trunc(n) : 1; }, z.number().int().min(1)); export const orderParam = z.preprocess(empty, z.enum(["asc", "desc"]).optional()); export const facilityFiltersSchema = z.object({ q: strParam, country: csvParam, region: strParam, metro: strParam, operator: strParam, status: csvParam, type: csvParam, min_mw: numParam, max_mw: numParam, opened_from: intParam, opened_to: intParam, planned_from: intParam, planned_to: intParam, cloud: strParam, hyperscale: boolParam, colocation: boolParam, ai: boolParam, renewable: boolParam, has_mw: boolParam, has_geo: boolParam, confidence: csvParam, sort: z.preprocess(empty, z.enum(["name", "mw", "updated", "opened", "completeness"]).optional()), order: orderParam, page: pageParam, per_page: intParam, }); export type FacilityFiltersInput = z.infer;