63 lines
2.0 KiB
TypeScript
63 lines
2.0 KiB
TypeScript
import { NextResponse } from "next/server";
|
|
|
|
export type ParseResult<T> =
|
|
| { 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<number | undefined> {
|
|
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<number | undefined> {
|
|
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<number> {
|
|
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 });
|
|
}
|