Files
community-rule/lib/server/responses.ts
T
2026-05-22 15:50:33 -06:00

93 lines
2.4 KiB
TypeScript

import { NextResponse } from "next/server";
/**
* Canonical API error contract for `app/api/**`.
*
* Response body shape: `{ error: { code, message }, details? }`.
*
* Codes are kept intentionally small. Add a new code only when an existing
* one cannot describe the failure; route handlers should not invent codes
* inline (use `errorJson(code, …)` here so the union stays the source of
* truth).
*/
export type ApiErrorCode =
| "db_unavailable"
| "unauthorized"
| "forbidden"
| "not_found"
| "validation_error"
| "invalid_json"
| "payload_too_large"
| "rate_limited"
| "server_misconfigured"
| "mail_failed"
| "internal_error"
| "conflict";
export interface ApiErrorBody {
error: { code: ApiErrorCode; message: string };
details?: unknown;
}
interface ErrorOpts {
details?: unknown;
headers?: HeadersInit;
}
export function errorJson(
code: ApiErrorCode,
message: string,
status: number,
opts: ErrorOpts = {},
): NextResponse {
const body: ApiErrorBody = { error: { code, message } };
if (opts.details !== undefined) {
body.details = opts.details;
}
return NextResponse.json(body, { status, headers: opts.headers });
}
export function dbUnavailable(): NextResponse {
return errorJson(
"db_unavailable",
"Database is not configured (CLOUDRON_POSTGRESQL_URL).",
503,
);
}
export function unauthorized(message = "Unauthorized"): NextResponse {
return errorJson("unauthorized", message, 401);
}
export function notFound(message = "Not found"): NextResponse {
return errorJson("not_found", message, 404);
}
export function forbidden(message = "Forbidden"): NextResponse {
return errorJson("forbidden", message, 403);
}
export function conflict(message = "Conflict"): NextResponse {
return errorJson("conflict", message, 409);
}
export function rateLimited(retryAfterMs: number): NextResponse {
const retryAfterSec = Math.max(1, Math.ceil(retryAfterMs / 1000));
return errorJson("rate_limited", "Too many requests", 429, {
details: { retryAfterMs },
headers: { "retry-after": String(retryAfterSec) },
});
}
export function serverMisconfigured(
message = "Server misconfiguration",
): NextResponse {
return errorJson("server_misconfigured", message, 500);
}
export function internalError(
message = "Internal server error",
): NextResponse {
return errorJson("internal_error", message, 500);
}