You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

111 lines
2.8 KiB

import { NextResponse } from "next/server";
export type ApiErrorCode =
| "AUTH_REQUIRED"
| "VALIDATION_ERROR"
| "CONFIG_ERROR"
| "PROVIDER_ERROR"
| "RATE_LIMITED"
| "INTERNAL_ERROR";
export function isAuthRequiredMessage(message: string): boolean {
const normalized = message.trim().toLowerCase();
if (!normalized) return false;
return (
normalized.includes("please login") ||
normalized.includes("please log in") ||
normalized.includes("login first") ||
normalized.includes("log in first") ||
normalized.includes("not logged in") ||
normalized.includes("session expired") ||
normalized.includes("token expired") ||
message.includes("请先登录") ||
message.includes("請先登入") ||
message.includes("未登录") ||
message.includes("未登入") ||
message.includes("登录已过期") ||
message.includes("登入已過期")
);
}
export class ApiError extends Error {
readonly status: number;
readonly code: ApiErrorCode;
constructor(status: number, code: ApiErrorCode, message: string) {
super(message);
this.name = "ApiError";
this.status = status;
this.code = code;
}
static unauthorized(message = "请先登录"): ApiError {
return new ApiError(401, "AUTH_REQUIRED", message);
}
static badRequest(message: string): ApiError {
return new ApiError(400, "VALIDATION_ERROR", message);
}
static config(message: string, status = 401): ApiError {
return new ApiError(status, "CONFIG_ERROR", message);
}
static provider(message: string, status = 500): ApiError {
if (isAuthRequiredMessage(message)) {
return ApiError.unauthorized(message);
}
return new ApiError(status, "PROVIDER_ERROR", message);
}
}
export interface ApiErrorPayload {
success: false;
requestId?: string;
code: ApiErrorCode;
error: string;
}
export function apiErrorResponse(
error: unknown,
requestId?: string,
fallbackMessage = "Request failed"
): NextResponse<ApiErrorPayload> {
if (error instanceof ApiError) {
return NextResponse.json<ApiErrorPayload>(
{
success: false,
requestId,
code: error.code,
error: error.message,
},
{ status: error.status }
);
}
const message = error instanceof Error ? error.message : fallbackMessage;
if (isAuthRequiredMessage(message)) {
return NextResponse.json<ApiErrorPayload>(
{
success: false,
requestId,
code: "AUTH_REQUIRED",
error: message,
},
{ status: 401 }
);
}
const isRateLimited = message.includes("429");
return NextResponse.json<ApiErrorPayload>(
{
success: false,
requestId,
code: isRateLimited ? "RATE_LIMITED" : "INTERNAL_ERROR",
error: isRateLimited ? "Rate limit reached. Please wait and try again." : message,
},
{ status: isRateLimited ? 429 : 500 }
);
}