57 lines
1.6 KiB
TypeScript
57 lines
1.6 KiB
TypeScript
import { NextResponse } from "next/server";
|
|
import { ZodError, type z } from "zod";
|
|
|
|
/** Throwable API error with HTTP status code */
|
|
export class ApiError extends Error {
|
|
status: number;
|
|
constructor(message: string, status: number = 400) {
|
|
super(message);
|
|
this.name = "ApiError";
|
|
this.status = status;
|
|
}
|
|
}
|
|
|
|
/** Standard success response: { ok: true, ...data } */
|
|
export function apiOk(data?: Record<string, unknown>) {
|
|
return NextResponse.json({ ok: true, ...data });
|
|
}
|
|
|
|
/** Standard error response: { error: message } with given status */
|
|
export function apiError(message: string, status: number = 400) {
|
|
return NextResponse.json({ error: message }, { status });
|
|
}
|
|
|
|
/** Validation error from Zod: { error: fieldErrors } with 400 */
|
|
export function apiValidationError(zodError: z.ZodError) {
|
|
return NextResponse.json(
|
|
{ error: zodError.flatten().fieldErrors },
|
|
{ status: 400 },
|
|
);
|
|
}
|
|
|
|
/** Centralized error handler for API routes */
|
|
export function handleApiError(error: unknown): Response {
|
|
if (error instanceof ApiError) {
|
|
return apiError(error.message, error.status);
|
|
}
|
|
if (error instanceof ZodError) {
|
|
return apiValidationError(error);
|
|
}
|
|
// Prisma P2025 "Record not found"
|
|
if (
|
|
error instanceof Error &&
|
|
(error.constructor.name === "PrismaClientKnownRequestError" ||
|
|
error.name === "PrismaClientKnownRequestError") &&
|
|
(error as Error & { code?: string }).code === "P2025"
|
|
) {
|
|
return apiError("Not found", 404);
|
|
}
|
|
console.error(
|
|
"[API error]",
|
|
error instanceof Error
|
|
? { message: error.message, name: error.name }
|
|
: error,
|
|
);
|
|
return apiError("Internal server error", 500);
|
|
}
|