import { NextResponse } from "next/server"; export type ParseResult = | { ok: true; value: T } | { ok: false; field: string }; interface PositiveIntOptions { field: string; defaultValue: number; min: number; max?: number; } function parseInteger(raw: string | null, field: string): ParseResult { if (raw === null || raw === "") return { ok: true, value: undefined }; if (!/^-?\d+$/.test(raw)) return { ok: false, field }; const value = Number(raw); if (!Number.isSafeInteger(value)) return { ok: false, field }; return { ok: true, value }; } export function parseOptionalInt(raw: string | null, field: string): ParseResult { const parsed = parseInteger(raw, field); if (!parsed.ok || parsed.value === undefined) return parsed; if (parsed.value < 0) return { ok: false, field }; return parsed; } export function parsePositiveInt(raw: string | null, options: PositiveIntOptions): ParseResult { const parsed = parseInteger(raw, options.field); if (!parsed.ok) return parsed; let value = parsed.value ?? options.defaultValue; if (value < 0) return { ok: false, field: options.field }; if (value < options.min) value = options.min; if (options.max !== undefined && value > options.max) value = options.max; return { ok: true, value }; } export function parseTimestampRange( searchParams: URLSearchParams ): ParseResult<{ startTs?: number; endTs?: number }> { const start = parseOptionalInt(searchParams.get("start"), "start"); if (!start.ok) return start; const end = parseOptionalInt(searchParams.get("end"), "end"); if (!end.ok) return end; if (start.value !== undefined && end.value !== undefined && start.value > end.value) { return { ok: false, field: "range" }; } return { ok: true, value: { startTs: start.value, endTs: end.value } }; } export function jsonError(field?: string, status = 400) { const body = field ? { error: "Invalid query parameter", field } : { error: "Internal server error" }; return NextResponse.json(body, { status }); }