72 lines
1.7 KiB
TypeScript
72 lines
1.7 KiB
TypeScript
/**
|
|||
|
|
* Typed API error with automatic HTTP response conversion.
|
||
|
|
* Mirrors `apps/interfaces/api/src/error.rs`.
|
||
|
|
*
|
||
|
|
* Each kind maps to a status code:
|
||
|
|
* - BadRequest → 400
|
||
|
|
* - Unauthorized → 401
|
||
|
|
* - NotFound → 404
|
||
|
|
* - Conflict → 409
|
||
|
|
* - TooManyRequests → 429
|
||
|
|
* - Internal → 500
|
||
|
|
* - ChatProxy → 502
|
||
|
|
*/
|
||
|
|
|
||
|
|
export type ApiErrorKind =
|
||
|
|
| "BadRequest"
|
||
|
|
| "Unauthorized"
|
||
|
|
| "NotFound"
|
||
|
|
| "Conflict"
|
||
|
|
| "TooManyRequests"
|
||
|
|
| "Internal"
|
||
|
|
| "ChatProxy";
|
||
|
|
|
||
|
|
export const HTTP_STATUS: Record<ApiErrorKind, number> = {
|
||
|
|
BadRequest: 400,
|
||
|
|
Unauthorized: 401,
|
||
|
|
NotFound: 404,
|
||
|
|
Conflict: 409,
|
||
|
|
TooManyRequests: 429,
|
||
|
|
Internal: 500,
|
||
|
|
ChatProxy: 502,
|
||
|
|
};
|
||
|
|
|
||
|
|
export class ApiError extends Error {
|
||
|
|
constructor(public kind: ApiErrorKind, message: string) {
|
||
|
|
super(message);
|
||
|
|
this.name = "ApiError";
|
||
|
|
}
|
||
|
|
|
||
|
|
get status(): number {
|
||
|
|
return HTTP_STATUS[this.kind];
|
||
|
|
}
|
||
|
|
|
||
|
|
static badRequest(msg: string): ApiError {
|
||
|
|
return new ApiError("BadRequest", msg);
|
||
|
|
}
|
||
|
|
static unauthorized(msg: string): ApiError {
|
||
|
|
return new ApiError("Unauthorized", msg);
|
||
|
|
}
|
||
|
|
static notFound(msg: string): ApiError {
|
||
|
|
return new ApiError("NotFound", msg);
|
||
|
|
}
|
||
|
|
static conflict(msg: string): ApiError {
|
||
|
|
return new ApiError("Conflict", msg);
|
||
|
|
}
|
||
|
|
static tooManyRequests(msg: string): ApiError {
|
||
|
|
return new ApiError("TooManyRequests", msg);
|
||
|
|
}
|
||
|
|
static internal(msg: string): ApiError {
|
||
|
|
return new ApiError("Internal", msg);
|
||
|
|
}
|
||
|
|
static chatProxy(msg: string): ApiError {
|
||
|
|
return new ApiError("ChatProxy", msg);
|
||
|
|
}
|
||
|
|
|
||
|
|
/** Whether an internal error's details should be hidden from the client. */
|
||
|
|
get exposeDetails(): boolean {
|
||
|
|
// Internal errors hide details; all others surface the message.
|
||
|
|
return this.kind !== "Internal";
|
||
|
|
}
|
||
|
|
}
|