import { STATUS_CODES } from 'node:http'; import OperationalError, { type OperationalErrorData } from './operational-error.ts'; interface HttpErrorData extends OperationalErrorData { statusCode?: number; } /** * Class representing an HTTP error. */ export default class HttpError extends OperationalError { override readonly name: string = 'HttpError'; readonly statusCode: number; readonly statusMessage: string; get status() { return this.statusCode; } constructor(message?: number | string | HttpErrorData); constructor(message: number | string, data?: HttpErrorData); constructor(message: number | string | HttpErrorData, data: HttpErrorData = {}) { if (typeof message === 'string') { data.message = message; } else if (typeof message === 'number') { data.statusCode = message; } else { data = message || data; } super(data); // Set the status code and message this.statusCode = typeof data.statusCode === 'number' ? HttpError.normalizeErrorStatusCode(data.statusCode) : (this.constructor as typeof HttpError).defaultStatusCode; this.statusMessage = HttpError.getMessageForStatusCode(this.statusCode); // Default the error code to one that includes the HTTP status if (this.code === OperationalError.defaultCode) { // @ts-expect-error yes we're writing to a readonly property but it's from within the // class so it's fine. The alternative would be to move everything to getters/setters // which is overkill just to get the types to stop complaining this.code = `HTTP_${this.statusCode}`; } // Default the error message to the status message if (this.message === OperationalError.defaultMessage) { this.message = this.statusMessage; } } /** * Reserved keys that should not appear in `HttpError.prototype.data`. */ protected static override reservedKeys = [ ...OperationalError.reservedKeys, 'statusCode', 'statusMessage' ]; protected static readonly defaultStatusCode: number = 500; /** * Normalize an HTTP status code. */ protected static normalizeErrorStatusCode(statusCode: number) { statusCode = Math.floor(statusCode); if (statusCode < 400 || statusCode > 599) { statusCode = 500; } return statusCode; } /** * Get the HTTP message for a given status code. */ private static getMessageForStatusCode(statusCode: number) { if (STATUS_CODES[statusCode]) { return STATUS_CODES[statusCode]; } if (statusCode >= 400 && statusCode <= 499) { return STATUS_CODES[400] as string; // Casting is OK because we know this exists } return STATUS_CODES[500] as string; // Casting is OK because we know this exists } }