diff --git a/client/testfixture/scripts/index.ts b/client/testfixture/scripts/index.ts index 89866a7e..05edbaf5 100644 --- a/client/testfixture/scripts/index.ts +++ b/client/testfixture/scripts/index.ts @@ -1,4 +1,4 @@ -import { addRoute, parsePath, query, htmlHandler, jsonHandler, stringHandler } from "../trailbase.js"; +import { addRoute, parsePath, query, htmlHandler, jsonHandler, stringHandler, HttpError } from "../trailbase.js"; import type { JsonRequestType, ParsedPath, StringRequestType } from "../trailbase.d.ts"; addRoute("GET", "/test", stringHandler(async (req: StringRequestType) => { @@ -43,3 +43,7 @@ addRoute("GET", "/json", jsonHandler((_req: JsonRequestType) => { } }; })); + +addRoute("GET", "/error", jsonHandler((_req: JsonRequestType) => { + throw new HttpError(418, "I'm a teapot"); +})); diff --git a/client/testfixture/trailbase.d.ts b/client/testfixture/trailbase.d.ts index 096ee6c5..5dcad5a3 100644 --- a/client/testfixture/trailbase.d.ts +++ b/client/testfixture/trailbase.d.ts @@ -77,6 +77,13 @@ export declare enum StatusCodes { INSUFFICIENT_STORAGE = 507, NETWORK_AUTHENTICATION_REQUIRED = 511 } +export declare class HttpError extends Error { + readonly statusCode: number; + readonly headers: [string, string][] | undefined; + constructor(statusCode: number, message?: string, headers?: [string, string][]); + toString(): string; + toResponse(): ResponseType; +} export type StringRequestType = { uri: string; params: PathParamsType; diff --git a/client/testfixture/trailbase.js b/client/testfixture/trailbase.js index e5be1b79..56c6e8bb 100644 --- a/client/testfixture/trailbase.js +++ b/client/testfixture/trailbase.js @@ -346,6 +346,26 @@ export var StatusCodes; /// gain network access. StatusCodes[StatusCodes["NETWORK_AUTHENTICATION_REQUIRED"] = 511] = "NETWORK_AUTHENTICATION_REQUIRED"; })(StatusCodes || (StatusCodes = {})); +export class HttpError extends Error { + statusCode; + headers; + constructor(statusCode, message, headers) { + super(message); + this.statusCode = statusCode; + this.headers = headers; + } + toString() { + return `HttpError(${this.statusCode}, ${this.message})`; + } + toResponse() { + const m = this.message; + return { + headers: this.headers, + status: this.statusCode, + body: m !== "" ? encodeFallback(m) : undefined, + }; + } +} export function stringHandler(f) { return async (req) => { try { @@ -373,6 +393,9 @@ export function stringHandler(f) { }; } catch (err) { + if (err instanceof HttpError) { + return err.toResponse(); + } return { status: StatusCodes.INTERNAL_SERVER_ERROR, body: encodeFallback(`Uncaught error: ${err}`), @@ -408,6 +431,9 @@ export function htmlHandler(f) { }; } catch (err) { + if (err instanceof HttpError) { + return err.toResponse(); + } return { status: StatusCodes.INTERNAL_SERVER_ERROR, body: encodeFallback(`Uncaught error: ${err}`), @@ -444,6 +470,9 @@ export function jsonHandler(f) { }; } catch (err) { + if (err instanceof HttpError) { + return err.toResponse(); + } return { headers: [["content-type", "application/json"]], status: StatusCodes.INTERNAL_SERVER_ERROR, diff --git a/trailbase-core/js/src/index.ts b/trailbase-core/js/src/index.ts index 89de1e1f..bbddd370 100644 --- a/trailbase-core/js/src/index.ts +++ b/trailbase-core/js/src/index.ts @@ -365,6 +365,34 @@ export enum StatusCodes { NETWORK_AUTHENTICATION_REQUIRED = 511, } +export class HttpError extends Error { + readonly statusCode: number; + readonly headers: [string, string][] | undefined; + + constructor( + statusCode: number, + message?: string, + headers?: [string, string][], + ) { + super(message); + this.statusCode = statusCode; + this.headers = headers; + } + + public override toString(): string { + return `HttpError(${this.statusCode}, ${this.message})`; + } + + toResponse(): ResponseType { + const m = this.message; + return { + headers: this.headers, + status: this.statusCode, + body: m !== "" ? encodeFallback(m) : undefined, + }; + } +} + export type StringRequestType = { uri: string; params: PathParamsType; @@ -408,6 +436,9 @@ export function stringHandler( body: respBody ? encodeFallback(respBody) : undefined, }; } catch (err) { + if (err instanceof HttpError) { + return err.toResponse(); + } return { status: StatusCodes.INTERNAL_SERVER_ERROR, body: encodeFallback(`Uncaught error: ${err}`), @@ -454,6 +485,9 @@ export function htmlHandler( body: respBody ? encodeFallback(respBody) : undefined, }; } catch (err) { + if (err instanceof HttpError) { + return err.toResponse(); + } return { status: StatusCodes.INTERNAL_SERVER_ERROR, body: encodeFallback(`Uncaught error: ${err}`), @@ -507,6 +541,9 @@ export function jsonHandler( body: encodeFallback(JSON.stringify(resp)), }; } catch (err) { + if (err instanceof HttpError) { + return err.toResponse(); + } return { headers: [["content-type", "application/json"]], status: StatusCodes.INTERNAL_SERVER_ERROR,