import { Status, STATUS_TEXT } from "./deps.ts";
export interface HttpErrorOptions extends ErrorOptions { name?: string; message?: string; status?: number; expose?: boolean; [key: string]: unknown;}
export function optionsFromArgs< Init extends HttpErrorOptions = HttpErrorOptions,>( statusOrMessageOrOptions?: number | string | Init, messageOrOptions?: string | Init, options?: Init,): Init { let status: number | undefined = undefined; let message: string | undefined = undefined; let init: Init | undefined = options;
if (typeof statusOrMessageOrOptions === "number") { status = statusOrMessageOrOptions; if (typeof messageOrOptions === "string") { message = messageOrOptions; } else if (messageOrOptions) { init = messageOrOptions; message = init?.message; } } else if (typeof statusOrMessageOrOptions === "string") { message = statusOrMessageOrOptions; init = messageOrOptions as (Init | undefined); status = init?.status ?? status; } else if (typeof messageOrOptions === "string") { message = messageOrOptions; } else if (!init) { init = messageOrOptions ? messageOrOptions : statusOrMessageOrOptions; status = init?.status ?? status; message = init?.message; }
return { ...init, status, message } as Init;}
function errorNameForStatus(status: number): string { let name: string; if (STATUS_TEXT[status as Status]) { name = status === Status.Teapot ? "Teapot" : STATUS_TEXT[status as Status]!.replace(/\W/g, ""); if (status !== Status.InternalServerError) name += "Error"; } else { name = `Unknown${status < 500 ? "Client" : "Server"}Error`; } return name;}
export class HttpError< T extends Record<string, unknown> = Record<string, unknown>,> extends Error { status: number; expose: boolean; data: T;
constructor( status?: number, message?: string, options?: HttpErrorOptions & T, ); constructor(status?: number, options?: HttpErrorOptions & T); constructor(message?: string, options?: HttpErrorOptions & T); constructor(options?: HttpErrorOptions & T); constructor( statusOrMessageOrOptions?: number | string | (HttpErrorOptions & T), messageOrOptions?: string | (HttpErrorOptions & T), options?: HttpErrorOptions & T, ) { const init = optionsFromArgs( statusOrMessageOrOptions, messageOrOptions, options, ); const { message, name, expose, status: _status, cause, ...data } = init; const status = init.status ?? Status.InternalServerError;
if (status < 400 || status >= 600) { throw new RangeError("invalid error status"); }
const errorOptions = {} as ErrorOptions; if (typeof cause !== "undefined") errorOptions.cause = cause; super(message, errorOptions);
Object.defineProperty(this, "name", { configurable: true, enumerable: false, value: name ?? errorNameForStatus(status), writable: true, }); this.status = status; this.expose = expose ?? (status < 500); this.data = data as T; }
static from<T extends Record<string, unknown> = Record<string, unknown>>( error: HttpError<T> | Error | unknown, ): HttpError<T> { if (error instanceof HttpError) { return error; } else if (isHttpError(error)) { const { name, message, status, expose, cause, data } = error; const options = { ...(data), name, message, status, expose, cause, } as HttpErrorOptions & T; return new HttpError<T>(options); } else { return new HttpError(500, { cause: error }) as unknown as HttpError<T>; } }
static json<T extends Record<string, unknown> = Record<string, unknown>>( error: HttpError<T> | Error | unknown, ): HttpErrorOptions & T { const { name, message, status, expose, data } = isHttpError(error) ? error : HttpError.from(error); const json = { name, status, ...data, } as (HttpErrorOptions & T); if (expose && message) { json.message = message; } return json; }}
export function isHttpError(value: unknown): value is HttpError { return !!value && typeof value === "object" && (value instanceof HttpError || (value instanceof Error && typeof (value as HttpError).status === "number"));}