From df74b466efd60eb3d2895a8e5adda45c8ef57a76 Mon Sep 17 00:00:00 2001 From: Kael Shipman Date: Fri, 19 Jan 2024 16:44:13 -0700 Subject: [PATCH] Complete rewrite --- .github/workflows/ci.yml | 6 +- .husky/pre-commit | 2 +- .npmignore | 8 - README.md | 215 +++++------ changelog.md | 12 + docs/assets/highlight.css | 7 + docs/assets/navigation.js | 2 +- docs/assets/search.js | 2 +- docs/classes/BadGateway.html | 21 -- docs/classes/BadRequest.html | 21 -- docs/classes/DuplicateResource.html | 21 -- docs/classes/Forbidden.html | 21 -- docs/classes/GatewayTimeout.html | 21 -- docs/classes/HttpError.html | 53 ++- docs/classes/InsufficientStorage.html | 21 -- docs/classes/InternalServerError.html | 21 -- docs/classes/LoopDetected.html | 21 -- docs/classes/MethodNotAllowed.html | 21 -- docs/classes/NotAcceptable.html | 21 -- docs/classes/NotFound.html | 21 -- docs/classes/NotImplemented.html | 21 -- docs/classes/ServiceUnavailable.html | 21 -- docs/classes/TooManyRequests.html | 21 -- docs/classes/Unauthorized.html | 21 -- docs/classes/UnsupportedMediaType.html | 21 -- docs/functions/isHttpError.html | 6 +- docs/index.html | 101 +++--- docs/interfaces/GenericParams.html | 1 - docs/interfaces/ObstructionInterface.html | 4 - docs/modules.html | 27 +- docs/types/ErrorMeta.html | 6 + docs/types/HttpErrorJSON.html | 3 + docs/types/HttpErrorStatuses-1.html | 1 + docs/types/HttpStatusCode.html | 1 - docs/types/HttpStatusCodes.html | 1 + docs/types/ObstructionInterface.html | 4 + docs/variables/HttpErrorStatuses.html | 1 + package.json | 25 +- src/httpErrors.ts | 281 +++++++++++++++ src/index.ts | 418 ---------------------- tests/HttpErrors.spec.ts | 257 +++++++------ 41 files changed, 682 insertions(+), 1098 deletions(-) delete mode 100644 .npmignore delete mode 100644 docs/classes/BadGateway.html delete mode 100644 docs/classes/BadRequest.html delete mode 100644 docs/classes/DuplicateResource.html delete mode 100644 docs/classes/Forbidden.html delete mode 100644 docs/classes/GatewayTimeout.html delete mode 100644 docs/classes/InsufficientStorage.html delete mode 100644 docs/classes/InternalServerError.html delete mode 100644 docs/classes/LoopDetected.html delete mode 100644 docs/classes/MethodNotAllowed.html delete mode 100644 docs/classes/NotAcceptable.html delete mode 100644 docs/classes/NotFound.html delete mode 100644 docs/classes/NotImplemented.html delete mode 100644 docs/classes/ServiceUnavailable.html delete mode 100644 docs/classes/TooManyRequests.html delete mode 100644 docs/classes/Unauthorized.html delete mode 100644 docs/classes/UnsupportedMediaType.html delete mode 100644 docs/interfaces/GenericParams.html delete mode 100644 docs/interfaces/ObstructionInterface.html create mode 100644 docs/types/ErrorMeta.html create mode 100644 docs/types/HttpErrorJSON.html create mode 100644 docs/types/HttpErrorStatuses-1.html delete mode 100644 docs/types/HttpStatusCode.html create mode 100644 docs/types/HttpStatusCodes.html create mode 100644 docs/types/ObstructionInterface.html create mode 100644 docs/variables/HttpErrorStatuses.html create mode 100644 src/httpErrors.ts delete mode 100644 src/index.ts diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b71b438..f4ad35a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -16,6 +16,10 @@ jobs: - uses: actions/checkout@v4 name: Checkout + - run: | + OUTPUT="$(jq --arg nodeVersion ${{ matrix.node-version }} '.engines.node = $nodeVersion' package.json)" + echo "$OUTPUT" > package.json + - uses: pnpm/action-setup@v2 with: version: 8 @@ -26,5 +30,5 @@ jobs: cache: pnpm cache-dependency-path: ./pnpm-lock.yaml - - run: pnpm i && pnpm t + - run: pnpm i && pnpm check name: Lint and Test diff --git a/.husky/pre-commit b/.husky/pre-commit index f9616e8..d6a11ef 100755 --- a/.husky/pre-commit +++ b/.husky/pre-commit @@ -10,5 +10,5 @@ if [ -z "$SKIP_LINTING" ]; then fi if [ -z "$SKIP_TESTS" ]; then - pnpm test:jest + pnpm test fi diff --git a/.npmignore b/.npmignore deleted file mode 100644 index 5c80332..0000000 --- a/.npmignore +++ /dev/null @@ -1,8 +0,0 @@ -.github -.eslint* -.prettier* -dist-tests/* -src/* -tests/* -tsconfig.* -.vimrc diff --git a/README.md b/README.md index f967198..2b2d3bc 100644 --- a/README.md +++ b/README.md @@ -1,171 +1,174 @@ Http Errors ================================================================================================================== -This is a small library that presents a set of pre-configured errors that correspond to some common HTTP status codes, such as 400, 401, 403, 404, 500, etc.... +This is a small library whose primary export is the `HttpError` class, a derivative of the native `Error` class. +`HttpError` can be instantiated and thrown like any error, except that it carries additional information that is +pertinent to HTTP interactions. -The idea is that you can throw these like normal errors in your code, but then use them to fine-tune your actual HTTP response in a global handler function. For example, using express: +For example: ```ts import * as express from "express"; -import * as Errors from "http-errors"; +import { HttpError, isHttpError } from "http-errors"; import { authenticate } from "./Authnz"; +import { doThings } from "./myLib"; +import { SimpleLoggerConsole } from "@wymp/simple-logger-console"; const app = express(); +const log = new SimpleLoggerConsole(); // Normal endpoint app.get("/my/endpoint", function(req, res, next) { try { if (!req.headers("Authorization")) { - throw new Errors.Unauthorized( + throw new HttpError( + 401, "You must pass a standard Authorization header to use this endpoint", - "MissingAuthHeader" + { subcode: "MissingAuthHeader" } ); } - // May throw Errors.Forbidden + // May throw additional HTTP errors, such as 400, 401 or 403 authenticate(req.headers("Authorization")); + doThings(req); + // If nothing threw, just return 200 with our data return res.status(200).send({ data: "Yay!" }); } catch (e) { + // Otherwise, pass the error to next next(e); } }); -// Global handler, handling errors for all endpoints -app.use(function(e: Error, req, res, next) { +// Global error handler +app.use(function(_e: Error, req, res, next) { // If it's not already an HttpError, convert it into an InternalServerError (500) - if (!Errors.isHttpError(e)) { - e = Errors.InternalServerError.fromError(e); - } + const e = HttpError.from(_e); + + // Log the error + log[e.logLevel](e.stack); - // This happens to be JSON:API structure, but you could use the data however you'd like + // Return the response with the correct status and structure return res.status(e.status).send({ - errors: [ - { - status: e.status, - code: e.code!, - title: e.title, - detail: e.message, - }, - ], + ok: false, + // Note that an HttpError is properly serialized by JSON.stringify, so we don't need to do anything extra here + error: e }); }); ``` + ## API -The best way to understand the API for these errors is to simply look at the -[definitions file](https://github.com/wymp/ts-http-errors/blob/current/src/index.ts), -which is fairly small. However, for ease of reference, below is an overview: ### isHttpError() -This is a simple function that offers typeguarding for errors. For any catch block, you -can simply pass in the error object you receive, and if it's not an HttpError, you can -convert it to one using the static `fromError()` method available on all errors in the library. +A typeguard allowing you to check if a given error is an `HttpError`. Probably better to just use `HttpError.from` +instead. -### HttpError -This is the (abstract) base class for all errors in this library. All errors have the following -properties, which are defined in this base class: - -- `readonly tag: "HttpError" = "HttpError"` -- Always `HttpError` so you can easily tell whether - or not you're dealing with an `HttpError`. -- `readonly name: string;` -- An error ID which is usually statically defined. For example, - a "Bad Request" might be defined with this property set to `BadRequest`, such that you can - always determine what type of error you're dealing with at runtime. -- `readonly status: HttpStatusCode;` -- any of the (finite) list of valid HTTP numeric status - codes. This is usually defined statically in the specific error definition, so you don't have - to set it yourself. -- `errno?: number;` -- Part of the `NodeJS.ErrnoException` interface. -- `code?: string;` -- A useful code indicating what specific error this is (e.g., `IncorrectEmail`, -- `readonly subcode?: string;` -- A secondary code to further specify the error (e.g., `IncorrectFormat`, - `MiddleNameRequired`, etc....) -- `path?: string;` -- The path through the data where the error occurred (e.g., - `data.attributes.legalName`) -- `syscall?: string;` -- Part of the `NodeJS.ErrnoException` interface. -- `stack?: string;` -- Part of the `NodeJS.ErrnoException` interface. -- `obstructions: Array>;` -- This error's - array of obstructions (see [Obstructions](#obstructions) below). - -`HttpError` itself is an abstract class. You'll actually be using descendent classes when -throwing errors (see [Basic Errors](#basic-errors) below). These descendent errors are -distinguishable at runtime (and in the context of -[discriminated unions](https://www.typescriptlang.org/docs/handbook/advanced-types.html#discriminated-unions)) -via the `name` attribute, which will usually be the same as the constructor, but is defined -statically in the descendant class definitions. - -The `code`, and `subcode` properties allow for further differentiation. `code` is a property -from the `NodeJS.ErrnoException` interface and often used by native errors. Therefore, it is -usually avoided in `HttpError`s. `subcode`, on the other hand, is settable via the optional -second constructor parameter on every `HttpError` and may be used to very specifically -describe the error. - -For example, you might define and use a new `InvalidEmail` Error like so: +### `HttpError.from` -``` -class InvalidEmailError extends BadRequest { - code = "InvalidEmail" -} +Takes any Error object and converts it to an `HttpError`. This is most often used in catch blocks to turn native Errors +into `HttpError`s like so: -// ... +```ts +try { + woops this is gonna throw +} catch (_err) { + const err = HttpError.from(_err); -throw new InvalidEmailError("Your email must be in standard format", "IncorrectFormat"); + // Now we know it's an HttpError and can access its fields + // ... +} ``` -In the above example, you can easily throw an error with a lot of data attached to it -by default, then add specificity ("IncorrectFormat") in context. -### `fromError` +### `HttpError.toJSON` and `(static) HttpError.fromJSON` -`HttpError` defines a static method, `fromError` which takes any Error object and converts -it to an `HttpError` of the given type. This is most often used to turn native Errors into -`InternalServerErrors` like so: +These methods allow you to easily serialize to JSON and de-serialize back into a native `HttpError`. To serialize, +simply pass the error to `JSON.stringify` and it will output important fields (although not `headers` or `stack`, to +avoid accidental leakage). To de-serialize, pass either the string or the already-deserialized object into the +`HttpError.fromJSON` static method. -```ts -try { - woops this is gonna throw -} catch (e) { - if (!isHttpError(e)) { - e = InternalServerError.fromError(e); - } +For example: - // Now we know it's an HttpError. Format it and return a response - // ... -} +```ts +const e = new HttpError(404, "Not Found", { obstructions: [{ code: "something", text: "Some explanation" }] }); +const json = JSON.stringify(e); +const inflated = HttpError.fromJSON(json); +// e.status === inflated.status +// e.name === inflated.name +// e.obstructions == inflated.obstructions +// etc... ``` + +### HttpError + +Throw this error instead of native errors in order to attach additional data pertinent to your application and to the +HTTP context. + +Important fields are: + +* `status` - The status code of the error. (You must pass this into the constructor, and it defaults to 500 when + converting regular errors) +* `name` - The standardized status text corresponding to the status code. (See also `HttpErrorStatuses`) +* `subcode` - You can use this field to offer a concise, immutable code indicating more specifically what the error + pertains to. In the example above, we used `MissingAuthHeader` as the subcode in our 401. This can help the client + understand what needs to change about the request in order for it to succeed. +* `logLevel` - This is an internal field that you can use in your system to determine whether or not to log this error. +* `obstructions` - An optional collection of more specific data about why the user cannot do what they want to do. See + below. +* `headers` - Some errors (such as 401) require certain headers to be returned. This allows you to do that. + + ### Obstructions -The concept of obstructions is specific to the `HttpError`s world. An obstruction is defined -as follows: +The concept of obstructions is specific to the `HttpError` world. An obstruction is defined as follows: ```ts -interface ObstructionInterface { - code: string; - text: string; - params?: ParamSet; -} +export type ObstructionInterface = Data extends undefined + ? { code: string; text: string } + : { code: string; text: string; data: Data }; ``` -These are meant to be light-weight and data-dense packets that allow you to communicate to -consumers about multiple issues that are preventing a given request from completing -successfully. +These are meant to be light-weight and data-dense packets that allow you to communicate to consumers about multiple +issues that are preventing a given request from completing successfully. -Imagine you're registering a new user. Using the `BadRequest` error with obstructions, you -can easily stop execution and send back a 400 with useful information from anywhere in your -code: +Imagine you're registering a new user. Using an `HttpError` with obstructions, you can easily stop execution and send +back a 400 with useful information from anywhere in your code. In the following example, there are certain things for +which we immediately throw and other things where we build up a list of obstructions and then throw. ```ts +// library: @my-org/types + +// First we'll define our obstructions. This allows front- and back-end code to work with type-safe errors +import { ObstructionInterface } from "@wymp/http-errors"; +export type MyObstructions = + | ObstructionInterface<"NoUserName"> + | ObstructionInterface<"NoEmail"> + | ObstructionInterface<"InvalidEmail", { email: string; pattern: string }> + | ObstructionInterface<"NoPassword"> + | ObstructionInterface<"NoPasswordConf"> + | ObstructionInterface<"PasswordConfMismatch">; +``` + +```ts +// app: @my-org/core + +import { MyObstructions } from "@my-org/types"; + const body = req.body; if (!body) { - throw new BadRequest("Missing request body. Did you send it?"); + throw new HttpError(400, "Missing request body. Did you send it?"); } if (!body.user) { - throw new BadRequest("Missing incoming user object. Did you send it?"); + throw new HttpError(400, "Missing incoming user object. Did you send it?"); } -const obstructions: Array> = []; +const obstructions: Array = []; +const ourEmailRegex = /.../; if (!body.user.name) { obstructions.push({ @@ -187,9 +190,9 @@ if (!body.user.email) { // Note that we can provide data so consumers can be more detailed about // how they display the errors - params: { + data: { email: body.user.email, - pattern: ourEmailRegex + pattern: ourEmailRegex.toString() } }); } @@ -217,11 +220,9 @@ if (!body.user.password) { } if (obstructions.length > 0) { - const e = new BadRequest("There were problems registering your user."); - e.obstructions = obstructions; - throw e; + throw new HttpError(400, "There were problems registering your user.", { obstructions }); } -// Now we know it's safe. Continue processing here.... +// Continue processing.... ``` diff --git a/changelog.md b/changelog.md index a268b7d..e107746 100644 --- a/changelog.md +++ b/changelog.md @@ -1,6 +1,17 @@ Changelog ================================================================ +## v4.0.0 + +**TOTAL REWRITE - MAJOR BREAKING CHANGES** + +* Removed all specific error subclasses (`BadRequest`, `InternalServerError`, etc.) +* Changed constructor interface of `HttpError` +* Change `ObstructionInterface` + +See readme for details. + + ## v3.0.0 * Switched to pnpm @@ -10,6 +21,7 @@ Changelog * Added github CI * Switched to publishing on npmjs.com + ## v1.4.0 * Added public `loglevel` property to `HttpError` to capture the level at which the error diff --git a/docs/assets/highlight.css b/docs/assets/highlight.css index 48ff7f1..f1f8821 100644 --- a/docs/assets/highlight.css +++ b/docs/assets/highlight.css @@ -19,6 +19,8 @@ --dark-hl-8: #B5CEA8; --light-hl-9: #267F99; --dark-hl-9: #4EC9B0; + --light-hl-10: #811F3F; + --dark-hl-10: #D16969; --light-code-background: #FFFFFF; --dark-code-background: #1E1E1E; } @@ -34,6 +36,7 @@ --hl-7: var(--light-hl-7); --hl-8: var(--light-hl-8); --hl-9: var(--light-hl-9); + --hl-10: var(--light-hl-10); --code-background: var(--light-code-background); } } @@ -48,6 +51,7 @@ --hl-7: var(--dark-hl-7); --hl-8: var(--dark-hl-8); --hl-9: var(--dark-hl-9); + --hl-10: var(--dark-hl-10); --code-background: var(--dark-code-background); } } @@ -62,6 +66,7 @@ --hl-7: var(--light-hl-7); --hl-8: var(--light-hl-8); --hl-9: var(--light-hl-9); + --hl-10: var(--light-hl-10); --code-background: var(--light-code-background); } @@ -76,6 +81,7 @@ --hl-7: var(--dark-hl-7); --hl-8: var(--dark-hl-8); --hl-9: var(--dark-hl-9); + --hl-10: var(--dark-hl-10); --code-background: var(--dark-code-background); } @@ -89,4 +95,5 @@ .hl-7 { color: var(--hl-7); } .hl-8 { color: var(--hl-8); } .hl-9 { color: var(--hl-9); } +.hl-10 { color: var(--hl-10); } pre, code { background: var(--code-background); } diff --git a/docs/assets/navigation.js b/docs/assets/navigation.js index 1c4ead7..65638b2 100644 --- a/docs/assets/navigation.js +++ b/docs/assets/navigation.js @@ -1 +1 @@ -window.navigationData = "data:application/octet-stream;base64,H4sIAAAAAAAAA4WUy05DIRCG34V1Y7Xx2p13TawaW1fGxRSmlshhEAa1Gt/d2FR7Ts9I198/HwMMPHwqxndWfXXBHIYMnNMxGVQdFYCnqq94FjB1m3RjypVTHfVsvVH93ubB3tZO76vz5zpHj9HqW4hQpaXKesY4AY2p20is6HZ2a6qbceKYNVvyl7/lolEKlsQ/OzqNkeLSph2ktNjsHDXrt3r7tfojMHf4kjFxW7BkJcO9h8xTivYDTdtRpyXLGcWxNQZ9W/GHSvXXxGeUvdDBLylVD5CnZK6JD52jN2kfq4k1vRxqjYFh7FBsaIlLnpMcnNXAeIeJctSCqxUp31TKIVBkNAM0FkazICilVMk6IhqAny0mJbWFK4GSaz7yHtwQ4yvGf+ZaCK25jcsqOKzQs3SzTb7mrZwD4xvMxLeyYCXDT8tW472HV7BOHo92pmRcLDqyFVIW3nCTl88+5cnEaoueh0wRnoTmhFDJeUUUTpBRiydfpyWLTcIvN8l+/kumbg03LbvbX4/fDHqciRoGAAA=" \ No newline at end of file +window.navigationData = "data:application/octet-stream;base64,H4sIAAAAAAAAA43QTwuCQBAF8O8yZ8u0/14jqKA8eIwOq664ZKvsjFGE3z0yqNU29Pze/ODN8QHEbwQebIiKtVK5CohRiRzBgoJRCh5cmRIszDjaP6VhSpcMLDgLGYM3diurD0j3woQNnCbnjpZzZ9o23+1VHptFLe7S/BBJlRGJXG4lcZWwiLdJU6fLrUftObE29gn67KzLu8A//P3bK+wtfZUoY4i60zQcd6HdCzQISSnrj6CtxU1lNqlOTxU652VdAgAA" \ No newline at end of file diff --git a/docs/assets/search.js b/docs/assets/search.js index bb7393c..5391c31 100644 --- a/docs/assets/search.js +++ b/docs/assets/search.js @@ -1 +1 @@ -window.searchData = "data:application/octet-stream;base64,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"; \ No newline at end of file +window.searchData = "data:application/octet-stream;base64,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"; \ No newline at end of file diff --git a/docs/classes/BadGateway.html b/docs/classes/BadGateway.html deleted file mode 100644 index 369c524..0000000 --- a/docs/classes/BadGateway.html +++ /dev/null @@ -1,21 +0,0 @@ -BadGateway | @wymp/http-errors

Class BadGateway<ObstructionParams>

Type Parameters

Hierarchy

Constructors

Properties

message: string
tag: "HttpError" = ...
errno?: number
code?: string
path?: string
syscall?: string
stack?: string
loglevel: SimpleLogLevel = "error"
subcode?: string
obstructions: ObstructionInterface<ObstructionParams>[] = []
headers: {
    [name: string]: string;
} = {}

Type declaration

  • [name: string]: string
name: string = "BadGateway"
status: 502 = 502

Methods

  • Converts any regular error into the given type of HttpError. For example, to convert an error -into a "BadRequest" error: const badreq = BadRequest.fromError(new Error("something"))

    -

    Type Parameters

    • T

    Parameters

    • this: (new (msg) => T)
        • new (msg): T
        • Parameters

          • msg: string

          Returns T

    • e: Error

    Returns T

Generated using TypeDoc

\ No newline at end of file diff --git a/docs/classes/BadRequest.html b/docs/classes/BadRequest.html deleted file mode 100644 index 6c1825b..0000000 --- a/docs/classes/BadRequest.html +++ /dev/null @@ -1,21 +0,0 @@ -BadRequest | @wymp/http-errors

Class BadRequest<ObstructionParams>

Type Parameters

Hierarchy

Constructors

Properties

message: string
tag: "HttpError" = ...
errno?: number
code?: string
path?: string
syscall?: string
stack?: string
subcode?: string
obstructions: ObstructionInterface<ObstructionParams>[] = []
headers: {
    [name: string]: string;
} = {}

Type declaration

  • [name: string]: string
name: string = "BadRequest"
status: 400 = 400
loglevel: SimpleLogLevel = "warning"

Methods

  • Converts any regular error into the given type of HttpError. For example, to convert an error -into a "BadRequest" error: const badreq = BadRequest.fromError(new Error("something"))

    -

    Type Parameters

    • T

    Parameters

    • this: (new (msg) => T)
        • new (msg): T
        • Parameters

          • msg: string

          Returns T

    • e: Error

    Returns T

Generated using TypeDoc

\ No newline at end of file diff --git a/docs/classes/DuplicateResource.html b/docs/classes/DuplicateResource.html deleted file mode 100644 index 849f594..0000000 --- a/docs/classes/DuplicateResource.html +++ /dev/null @@ -1,21 +0,0 @@ -DuplicateResource | @wymp/http-errors

Class DuplicateResource<ObstructionParams>

Type Parameters

Hierarchy

  • HttpError<ObstructionParams>
    • DuplicateResource

Constructors

Properties

message: string
tag: "HttpError" = ...
errno?: number
code?: string
path?: string
syscall?: string
stack?: string
subcode?: string
obstructions: ObstructionInterface<ObstructionParams>[] = []
headers: {
    [name: string]: string;
} = {}

Type declaration

  • [name: string]: string
name: string = "DuplicateResourceError"
status: 409 = 409
loglevel: SimpleLogLevel = "notice"

Methods

  • Converts any regular error into the given type of HttpError. For example, to convert an error -into a "BadRequest" error: const badreq = BadRequest.fromError(new Error("something"))

    -

    Type Parameters

    • T

    Parameters

    • this: (new (msg) => T)
        • new (msg): T
        • Parameters

          • msg: string

          Returns T

    • e: Error

    Returns T

Generated using TypeDoc

\ No newline at end of file diff --git a/docs/classes/Forbidden.html b/docs/classes/Forbidden.html deleted file mode 100644 index a50bff6..0000000 --- a/docs/classes/Forbidden.html +++ /dev/null @@ -1,21 +0,0 @@ -Forbidden | @wymp/http-errors

Class Forbidden<ObstructionParams>

Type Parameters

Hierarchy

Constructors

Properties

message: string
tag: "HttpError" = ...
errno?: number
code?: string
path?: string
syscall?: string
stack?: string
subcode?: string
obstructions: ObstructionInterface<ObstructionParams>[] = []
headers: {
    [name: string]: string;
} = {}

Type declaration

  • [name: string]: string
name: string = "Forbidden"
status: 403 = 403
loglevel: SimpleLogLevel = "notice"

Methods

  • Converts any regular error into the given type of HttpError. For example, to convert an error -into a "BadRequest" error: const badreq = BadRequest.fromError(new Error("something"))

    -

    Type Parameters

    • T

    Parameters

    • this: (new (msg) => T)
        • new (msg): T
        • Parameters

          • msg: string

          Returns T

    • e: Error

    Returns T

Generated using TypeDoc

\ No newline at end of file diff --git a/docs/classes/GatewayTimeout.html b/docs/classes/GatewayTimeout.html deleted file mode 100644 index 72e6e70..0000000 --- a/docs/classes/GatewayTimeout.html +++ /dev/null @@ -1,21 +0,0 @@ -GatewayTimeout | @wymp/http-errors

Class GatewayTimeout<ObstructionParams>

Type Parameters

Hierarchy

Constructors

Properties

message: string
tag: "HttpError" = ...
errno?: number
code?: string
path?: string
syscall?: string
stack?: string
loglevel: SimpleLogLevel = "error"
subcode?: string
obstructions: ObstructionInterface<ObstructionParams>[] = []
headers: {
    [name: string]: string;
} = {}

Type declaration

  • [name: string]: string
name: string = "GatewayTimeout"
status: 504 = 504

Methods

  • Converts any regular error into the given type of HttpError. For example, to convert an error -into a "BadRequest" error: const badreq = BadRequest.fromError(new Error("something"))

    -

    Type Parameters

    • T

    Parameters

    • this: (new (msg) => T)
        • new (msg): T
        • Parameters

          • msg: string

          Returns T

    • e: Error

    Returns T

Generated using TypeDoc

\ No newline at end of file diff --git a/docs/classes/HttpError.html b/docs/classes/HttpError.html index 86e86e8..e8e187e 100644 --- a/docs/classes/HttpError.html +++ b/docs/classes/HttpError.html @@ -1,21 +1,44 @@ -HttpError | @wymp/http-errors

Class HttpError<ObstructionParams>Abstract

Type Parameters

Hierarchy

Constructors

constructor +HttpError | @wymp/http-errors

Class HttpError<Status, Obstructions>

An HttpError is an error that can be thrown from within a route handler (or any other library code) to provide more +details to a system in the context of an HTTP request. It is a subclass of Error, so it can be thrown and caught +like any other error, but it also has some additional properties that can be used to provide more information about +the error.

+

Specifically, an HttpError contains all of the information necessary to send an HTTP error response to the client, +including the status code, status text, optional headers (for example, to set a WWW-Authenticate header for a 401 +response), a log-level (helping to indicate to the back-end system the log level at which this error should be +logged), an optional subcode to help the consumer programatically understand the error, a top-level message, and +an optional array of "obstructions" that can be used by the consumer to help the user understand what they need to +do to resolve the error.

+

Example

In a request handler

+
import { HttpError, HttpErrorStatuses } from "@wymp/http-error";
import { myLoginChecker } from "./myLoginLibrary";

// For the purposes of this example, myLoginChecker will return the type indicated here
type LoginCheck =
| { success: true; session: { token: string; refresh: string } }
| { success: false; obstructions: ObstructionInterface[] }

// Login handler - This might be hooked up to a POST /login route or something
export const loginHandler = async (req, res, next) => {
try {
const { username, password } = req.body;

const response: LoginCheck = await myLoginChecker(username, password);

if (!response.success) {
throw new HttpError(400, "Login failed", {
subcode: 'LOGIN_FAILED',
obstructions: response.obstructions,
});
}

res.json(response);
} catch (err) {
next(err);
}
} +
+

In the server's error handler

+
import { isHttpError } from "@wymp/http-errors";
import { SimpleLoggerInterface } from "@wymp/ts-simple-interfaces";

export errorHandler = (log: SimpleLoggerInterface) => (_err, req, res, next) => {
const err = isHttpError(_err) ? _err : HttpError.from(_err);

// Log the error
log[err.logLevel](err.stack);

// Format the error and send it off
res.status(err.status).json({
ok: false,
// Automatically serializes correctly because of our toJSON method
error: err,
});
} +
+

Type Parameters

Hierarchy

  • Error
    • HttpError

Constructors

  • Type Parameters

    Parameters

    • msg: string
    • Optional subcode: string
    • obstructions: ObstructionInterface<ObstructionParams>[] = []
    • headers: {
          [name: string]: string;
      } = {}
      • [name: string]: string

    Returns HttpError<ObstructionParams>

Properties

message: string
tag: "HttpError" = ...
name: string
status: number
errno?: number
code?: string
path?: string
syscall?: string
stack?: string
loglevel: SimpleLogLevel = "error"
subcode?: string
obstructions: ObstructionInterface<ObstructionParams>[] = []
headers: {
    [name: string]: string;
} = {}

Type declaration

  • [name: string]: string

Methods

  • Converts any regular error into the given type of HttpError. For example, to convert an error -into a "BadRequest" error: const badreq = BadRequest.fromError(new Error("something"))

    -

    Type Parameters

    • T

    Parameters

    • this: (new (msg) => T)
        • new (msg): T
        • Parameters

          • msg: string

          Returns T

    • e: Error

    Returns T

Generated using TypeDoc

\ No newline at end of file +status +

Methods

Constructors

  • Type Parameters

    • Status extends 400 | 401 | 402 | 403 | 404 | 405 | 406 | 407 | 408 | 409 | 410 | 411 | 412 | 413 | 414 | 415 | 416 | 417 | 418 | 420 | 422 | 423 | 424 | 425 | 426 | 428 | 429 | 431 | 444 | 449 | 450 | 451 | 499 | 500 | 501 | 502 | 503 | 504 | 505 | 506 | 507 | 508 | 509 | 510 | 511 | 598 | 599 = 400 | 401 | 402 | 403 | 404 | 405 | 406 | 407 | 408 | 409 | 410 | 411 | 412 | 413 | 414 | 415 | 416 | 417 | 418 | 420 | 422 | 423 | 424 | 425 | 426 | 428 | 429 | 431 | 444 | 449 | 450 | 451 | 499 | 500 | 501 | 502 | 503 | 504 | 505 | 506 | 507 | 508 | 509 | 510 | 511 | 598 | 599

    • Obstructions extends {
          code: string;
          text: string;
      } = {
          code: string;
          text: string;
      }

    Parameters

    • status: Status
    • msg: string
    • Optional meta: ErrorMeta<Obstructions>

    Returns HttpError<Status, Obstructions>

Properties

message: string
stack?: string
tag: "HttpError" = ...

A tag identifying this object as an HttpError. Any object having this tag should implement this interface

+
name: {
    400: "Bad Request";
    401: "Unauthorized";
    402: "Payment Required";
    403: "Forbidden";
    404: "Not Found";
    405: "Method Not Allowed";
    406: "Not Acceptable";
    407: "Proxy Authentication Required";
    408: "Request Timeout";
    409: "Conflict";
    410: "Gone";
    411: "Length Required";
    412: "Precondition Failed";
    413: "Payload Too Large";
    414: "URI Too Long";
    415: "Unsupported Media Type";
    416: "Range Not Satisfiable";
    417: "Expectation Failed";
    418: "I'm a teapot";
    420: "Enhance Your Calm";
    422: "Unprocessable Entity";
    423: "Locked";
    424: "Failed Dependency";
    425: "Reserved for WebDAV";
    426: "Upgrade Required";
    428: "Precondition Required";
    429: "Too Many Requests";
    431: "Request Header Fields Too Large";
    444: "No Response";
    449: "Retry With";
    450: "Blocked by Windows Parental Controls";
    451: "Unavailable For Legal Reasons";
    499: "Client Closed Request";
    500: "Internal Server Error";
    501: "Not Implemented";
    502: "Bad Gateway";
    503: "Service Unavailable";
    504: "Gateway Timeout";
    505: "HTTP Version Not Supported";
    506: "Variant Also Negotiates";
    507: "Insufficient Storage";
    508: "Loop Detected";
    509: "Bandwidth Limit Exceeded";
    510: "Not Extended";
    511: "Network Authentication Required";
    598: "Network read timeout error";
    599: "Network connect timeout error";
}[Status]

The name of the error, which will always be the official text associated with the status code

+
subcode?: string

An optional subcode that you can use to further understand why an error occurred.

+
logLevel: SimpleLogLevel

The level at which this error should be logged. This can be useful for keeping log noise down for unimportant +errors such as 404s while elevating more important errors, such as "database down".

+
obstructions: Obstructions[]

An optional list of obstructions that can help the user understand why they can't do what they're trying to do

+
headers: Record<string, string>

Headers to attach to the HTTP response. Useful for things like attaching the WWW-Authenticate header to 401 +responses.

+
status: Status

Methods

  • Create an HTTP error from a random error

    +

    NOTE: If the passed-in error is already an HttpError, it will be passed back without modification. Any status or +metadata passed in will not be applied in this case. Because of this, you can think of the status and meta +params as defaults.

    +

    Type Parameters

    • Status extends 400 | 401 | 402 | 403 | 404 | 405 | 406 | 407 | 408 | 409 | 410 | 411 | 412 | 413 | 414 | 415 | 416 | 417 | 418 | 420 | 422 | 423 | 424 | 425 | 426 | 428 | 429 | 431 | 444 | 449 | 450 | 451 | 499 | 500 | 501 | 502 | 503 | 504 | 505 | 506 | 507 | 508 | 509 | 510 | 511 | 598 | 599

    • Obstructions extends {
          code: string;
          text: string;
      } = {
          code: string;
          text: string;
      }

    Parameters

    • err: Error
    • status: Status
    • Optional meta: ErrorMeta<Obstructions>

    Returns HttpError<Status, {
        code: string;
        text: string;
    }>

  • Type Parameters

    • Obstructions extends {
          code: string;
          text: string;
      } = {
          code: string;
          text: string;
      }

    Parameters

    • err: Error
    • Optional meta: ErrorMeta<Obstructions>

    Returns HttpError<400 | 401 | 402 | 403 | 404 | 405 | 406 | 407 | 408 | 409 | 410 | 411 | 412 | 413 | 414 | 415 | 416 | 417 | 418 | 420 | 422 | 423 | 424 | 425 | 426 | 428 | 429 | 431 | 444 | 449 | 450 | 451 | 499 | 500 | 501 | 502 | 503 | 504 | 505 | 506 | 507 | 508 | 509 | 510 | 511 | 598 | 599, {
        code: string;
        text: string;
    }>

  • Serialize an HTTP error to JSON

    +

    Returns HttpErrorJSON<Status, Obstructions>

  • Create an HTTP error from a JSON representation

    +

    Type Parameters

    • Status extends 400 | 401 | 402 | 403 | 404 | 405 | 406 | 407 | 408 | 409 | 410 | 411 | 412 | 413 | 414 | 415 | 416 | 417 | 418 | 420 | 422 | 423 | 424 | 425 | 426 | 428 | 429 | 431 | 444 | 449 | 450 | 451 | 499 | 500 | 501 | 502 | 503 | 504 | 505 | 506 | 507 | 508 | 509 | 510 | 511 | 598 | 599 = 400 | 401 | 402 | 403 | 404 | 405 | 406 | 407 | 408 | 409 | 410 | 411 | 412 | 413 | 414 | 415 | 416 | 417 | 418 | 420 | 422 | 423 | 424 | 425 | 426 | 428 | 429 | 431 | 444 | 449 | 450 | 451 | 499 | 500 | 501 | 502 | 503 | 504 | 505 | 506 | 507 | 508 | 509 | 510 | 511 | 598 | 599

    • Obstructions extends {
          code: string;
          text: string;
      } = {
          code: string;
          text: string;
      }

    Parameters

    Returns HttpError<Status, Obstructions>

Generated using TypeDoc

\ No newline at end of file diff --git a/docs/classes/InsufficientStorage.html b/docs/classes/InsufficientStorage.html deleted file mode 100644 index 2e5fee0..0000000 --- a/docs/classes/InsufficientStorage.html +++ /dev/null @@ -1,21 +0,0 @@ -InsufficientStorage | @wymp/http-errors

Class InsufficientStorage<ObstructionParams>

Type Parameters

Hierarchy

  • HttpError<ObstructionParams>
    • InsufficientStorage

Constructors

Properties

message: string
tag: "HttpError" = ...
errno?: number
code?: string
path?: string
syscall?: string
stack?: string
loglevel: SimpleLogLevel = "error"
subcode?: string
obstructions: ObstructionInterface<ObstructionParams>[] = []
headers: {
    [name: string]: string;
} = {}

Type declaration

  • [name: string]: string
name: string = "InsufficientStorage"
status: 507 = 507

Methods

  • Converts any regular error into the given type of HttpError. For example, to convert an error -into a "BadRequest" error: const badreq = BadRequest.fromError(new Error("something"))

    -

    Type Parameters

    • T

    Parameters

    • this: (new (msg) => T)
        • new (msg): T
        • Parameters

          • msg: string

          Returns T

    • e: Error

    Returns T

Generated using TypeDoc

\ No newline at end of file diff --git a/docs/classes/InternalServerError.html b/docs/classes/InternalServerError.html deleted file mode 100644 index 8de99ea..0000000 --- a/docs/classes/InternalServerError.html +++ /dev/null @@ -1,21 +0,0 @@ -InternalServerError | @wymp/http-errors

Class InternalServerError<ObstructionParams>

Type Parameters

Hierarchy

  • HttpError<ObstructionParams>
    • InternalServerError

Constructors

Properties

message: string
tag: "HttpError" = ...
errno?: number
code?: string
path?: string
syscall?: string
stack?: string
loglevel: SimpleLogLevel = "error"
subcode?: string
obstructions: ObstructionInterface<ObstructionParams>[] = []
headers: {
    [name: string]: string;
} = {}

Type declaration

  • [name: string]: string
name: string = "InternalServerError"
status: 500 = 500

Methods

  • Converts any regular error into the given type of HttpError. For example, to convert an error -into a "BadRequest" error: const badreq = BadRequest.fromError(new Error("something"))

    -

    Type Parameters

    • T

    Parameters

    • this: (new (msg) => T)
        • new (msg): T
        • Parameters

          • msg: string

          Returns T

    • e: Error

    Returns T

Generated using TypeDoc

\ No newline at end of file diff --git a/docs/classes/LoopDetected.html b/docs/classes/LoopDetected.html deleted file mode 100644 index 5fccf43..0000000 --- a/docs/classes/LoopDetected.html +++ /dev/null @@ -1,21 +0,0 @@ -LoopDetected | @wymp/http-errors

Class LoopDetected<ObstructionParams>

Type Parameters

Hierarchy

Constructors

Properties

message: string
tag: "HttpError" = ...
errno?: number
code?: string
path?: string
syscall?: string
stack?: string
loglevel: SimpleLogLevel = "error"
subcode?: string
obstructions: ObstructionInterface<ObstructionParams>[] = []
headers: {
    [name: string]: string;
} = {}

Type declaration

  • [name: string]: string
name: string = "LoopDetected"
status: 508 = 508

Methods

  • Converts any regular error into the given type of HttpError. For example, to convert an error -into a "BadRequest" error: const badreq = BadRequest.fromError(new Error("something"))

    -

    Type Parameters

    • T

    Parameters

    • this: (new (msg) => T)
        • new (msg): T
        • Parameters

          • msg: string

          Returns T

    • e: Error

    Returns T

Generated using TypeDoc

\ No newline at end of file diff --git a/docs/classes/MethodNotAllowed.html b/docs/classes/MethodNotAllowed.html deleted file mode 100644 index f1324d0..0000000 --- a/docs/classes/MethodNotAllowed.html +++ /dev/null @@ -1,21 +0,0 @@ -MethodNotAllowed | @wymp/http-errors

Class MethodNotAllowed<ObstructionParams>

Type Parameters

Hierarchy

Constructors

Properties

message: string
tag: "HttpError" = ...
errno?: number
code?: string
path?: string
syscall?: string
stack?: string
subcode?: string
obstructions: ObstructionInterface<ObstructionParams>[] = []
headers: {
    [name: string]: string;
} = {}

Type declaration

  • [name: string]: string
name: string = "MethodNotAllowed"
status: 405 = 405
loglevel: SimpleLogLevel = "notice"

Methods

  • Converts any regular error into the given type of HttpError. For example, to convert an error -into a "BadRequest" error: const badreq = BadRequest.fromError(new Error("something"))

    -

    Type Parameters

    • T

    Parameters

    • this: (new (msg) => T)
        • new (msg): T
        • Parameters

          • msg: string

          Returns T

    • e: Error

    Returns T

Generated using TypeDoc

\ No newline at end of file diff --git a/docs/classes/NotAcceptable.html b/docs/classes/NotAcceptable.html deleted file mode 100644 index 8580998..0000000 --- a/docs/classes/NotAcceptable.html +++ /dev/null @@ -1,21 +0,0 @@ -NotAcceptable | @wymp/http-errors

Class NotAcceptable<ObstructionParams>

Type Parameters

Hierarchy

Constructors

Properties

message: string
tag: "HttpError" = ...
errno?: number
code?: string
path?: string
syscall?: string
stack?: string
subcode?: string
obstructions: ObstructionInterface<ObstructionParams>[] = []
headers: {
    [name: string]: string;
} = {}

Type declaration

  • [name: string]: string
name: string = "NotAcceptable"
status: 406 = 406
loglevel: SimpleLogLevel = "notice"

Methods

  • Converts any regular error into the given type of HttpError. For example, to convert an error -into a "BadRequest" error: const badreq = BadRequest.fromError(new Error("something"))

    -

    Type Parameters

    • T

    Parameters

    • this: (new (msg) => T)
        • new (msg): T
        • Parameters

          • msg: string

          Returns T

    • e: Error

    Returns T

Generated using TypeDoc

\ No newline at end of file diff --git a/docs/classes/NotFound.html b/docs/classes/NotFound.html deleted file mode 100644 index 871c03c..0000000 --- a/docs/classes/NotFound.html +++ /dev/null @@ -1,21 +0,0 @@ -NotFound | @wymp/http-errors

Class NotFound<ObstructionParams>

Type Parameters

Hierarchy

Constructors

Properties

message: string
tag: "HttpError" = ...
errno?: number
code?: string
path?: string
syscall?: string
stack?: string
subcode?: string
obstructions: ObstructionInterface<ObstructionParams>[] = []
headers: {
    [name: string]: string;
} = {}

Type declaration

  • [name: string]: string
name: string = "NotFound"
status: 404 = 404
loglevel: SimpleLogLevel = "notice"

Methods

  • Converts any regular error into the given type of HttpError. For example, to convert an error -into a "BadRequest" error: const badreq = BadRequest.fromError(new Error("something"))

    -

    Type Parameters

    • T

    Parameters

    • this: (new (msg) => T)
        • new (msg): T
        • Parameters

          • msg: string

          Returns T

    • e: Error

    Returns T

Generated using TypeDoc

\ No newline at end of file diff --git a/docs/classes/NotImplemented.html b/docs/classes/NotImplemented.html deleted file mode 100644 index dd17422..0000000 --- a/docs/classes/NotImplemented.html +++ /dev/null @@ -1,21 +0,0 @@ -NotImplemented | @wymp/http-errors

Class NotImplemented<ObstructionParams>

Type Parameters

Hierarchy

Constructors

Properties

message: string
tag: "HttpError" = ...
errno?: number
code?: string
path?: string
syscall?: string
stack?: string
loglevel: SimpleLogLevel = "error"
subcode?: string
obstructions: ObstructionInterface<ObstructionParams>[] = []
headers: {
    [name: string]: string;
} = {}

Type declaration

  • [name: string]: string
name: string = "NotImplemented"
status: 501 = 501

Methods

  • Converts any regular error into the given type of HttpError. For example, to convert an error -into a "BadRequest" error: const badreq = BadRequest.fromError(new Error("something"))

    -

    Type Parameters

    • T

    Parameters

    • this: (new (msg) => T)
        • new (msg): T
        • Parameters

          • msg: string

          Returns T

    • e: Error

    Returns T

Generated using TypeDoc

\ No newline at end of file diff --git a/docs/classes/ServiceUnavailable.html b/docs/classes/ServiceUnavailable.html deleted file mode 100644 index 468b352..0000000 --- a/docs/classes/ServiceUnavailable.html +++ /dev/null @@ -1,21 +0,0 @@ -ServiceUnavailable | @wymp/http-errors

Class ServiceUnavailable<ObstructionParams>

Type Parameters

Hierarchy

  • HttpError<ObstructionParams>
    • ServiceUnavailable

Constructors

Properties

message: string
tag: "HttpError" = ...
errno?: number
code?: string
path?: string
syscall?: string
stack?: string
loglevel: SimpleLogLevel = "error"
subcode?: string
obstructions: ObstructionInterface<ObstructionParams>[] = []
headers: {
    [name: string]: string;
} = {}

Type declaration

  • [name: string]: string
name: string = "ServiceUnavailable"
status: 503 = 503

Methods

  • Converts any regular error into the given type of HttpError. For example, to convert an error -into a "BadRequest" error: const badreq = BadRequest.fromError(new Error("something"))

    -

    Type Parameters

    • T

    Parameters

    • this: (new (msg) => T)
        • new (msg): T
        • Parameters

          • msg: string

          Returns T

    • e: Error

    Returns T

Generated using TypeDoc

\ No newline at end of file diff --git a/docs/classes/TooManyRequests.html b/docs/classes/TooManyRequests.html deleted file mode 100644 index 7a87643..0000000 --- a/docs/classes/TooManyRequests.html +++ /dev/null @@ -1,21 +0,0 @@ -TooManyRequests | @wymp/http-errors

Class TooManyRequests<ObstructionParams>

Type Parameters

Hierarchy

Constructors

Properties

message: string
tag: "HttpError" = ...
errno?: number
code?: string
path?: string
syscall?: string
stack?: string
subcode?: string
obstructions: ObstructionInterface<ObstructionParams>[] = []
headers: {
    [name: string]: string;
} = {}

Type declaration

  • [name: string]: string
name: string = "TooManyRequests"
status: 429 = 429
loglevel: SimpleLogLevel = "warning"

Methods

  • Converts any regular error into the given type of HttpError. For example, to convert an error -into a "BadRequest" error: const badreq = BadRequest.fromError(new Error("something"))

    -

    Type Parameters

    • T

    Parameters

    • this: (new (msg) => T)
        • new (msg): T
        • Parameters

          • msg: string

          Returns T

    • e: Error

    Returns T

Generated using TypeDoc

\ No newline at end of file diff --git a/docs/classes/Unauthorized.html b/docs/classes/Unauthorized.html deleted file mode 100644 index 895607e..0000000 --- a/docs/classes/Unauthorized.html +++ /dev/null @@ -1,21 +0,0 @@ -Unauthorized | @wymp/http-errors

Class Unauthorized<ObstructionParams>

Type Parameters

Hierarchy

Constructors

Properties

message: string
tag: "HttpError" = ...
errno?: number
code?: string
path?: string
syscall?: string
stack?: string
subcode?: string
obstructions: ObstructionInterface<ObstructionParams>[] = []
headers: {
    [name: string]: string;
} = {}

Type declaration

  • [name: string]: string
name: string = "Unauthorized"
status: 401 = 401
loglevel: SimpleLogLevel = "notice"

Methods

  • Converts any regular error into the given type of HttpError. For example, to convert an error -into a "BadRequest" error: const badreq = BadRequest.fromError(new Error("something"))

    -

    Type Parameters

    • T

    Parameters

    • this: (new (msg) => T)
        • new (msg): T
        • Parameters

          • msg: string

          Returns T

    • e: Error

    Returns T

Generated using TypeDoc

\ No newline at end of file diff --git a/docs/classes/UnsupportedMediaType.html b/docs/classes/UnsupportedMediaType.html deleted file mode 100644 index 333a2a1..0000000 --- a/docs/classes/UnsupportedMediaType.html +++ /dev/null @@ -1,21 +0,0 @@ -UnsupportedMediaType | @wymp/http-errors

Class UnsupportedMediaType<ObstructionParams>

Type Parameters

Hierarchy

  • HttpError<ObstructionParams>
    • UnsupportedMediaType

Constructors

Properties

message: string
tag: "HttpError" = ...
errno?: number
code?: string
path?: string
syscall?: string
stack?: string
subcode?: string
obstructions: ObstructionInterface<ObstructionParams>[] = []
headers: {
    [name: string]: string;
} = {}

Type declaration

  • [name: string]: string
name: string = "UnsupportedMediaType"
status: 415 = 415
loglevel: SimpleLogLevel = "info"

Methods

  • Converts any regular error into the given type of HttpError. For example, to convert an error -into a "BadRequest" error: const badreq = BadRequest.fromError(new Error("something"))

    -

    Type Parameters

    • T

    Parameters

    • this: (new (msg) => T)
        • new (msg): T
        • Parameters

          • msg: string

          Returns T

    • e: Error

    Returns T

Generated using TypeDoc

\ No newline at end of file diff --git a/docs/functions/isHttpError.html b/docs/functions/isHttpError.html index b506e5a..3e54ce8 100644 --- a/docs/functions/isHttpError.html +++ b/docs/functions/isHttpError.html @@ -1 +1,5 @@ -isHttpError | @wymp/http-errors

Generated using TypeDoc

\ No newline at end of file +isHttpError | @wymp/http-errors

Function isHttpError

  • A type-guard to help you determine whether an error is an HttpError. This is useful for error handlers that might +receive errors from other libraries that might not be HttpErrors, but that you want to treat as HttpErrors.

    +

    Parameters

    • e: any

    Returns e is HttpError<400 | 401 | 402 | 403 | 404 | 405 | 406 | 407 | 408 | 409 | 410 | 411 | 412 | 413 | 414 | 415 | 416 | 417 | 418 | 420 | 422 | 423 | 424 | 425 | 426 | 428 | 429 | 431 | 444 | 449 | 450 | 451 | 499 | 500 | 501 | 502 | 503 | 504 | 505 | 506 | 507 | 508 | 509 | 510 | 511 | 598 | 599, {
        code: string;
        text: string;
    }>

    Example

    export errorHandler = (_err: Error, req: Request, res: Response, next: NextFunction) => {
    const err = isHttpError(_err) ? _err : HttpError.from(_err);
    // ...
    } +
    +

Generated using TypeDoc

\ No newline at end of file diff --git a/docs/index.html b/docs/index.html index 0bc34eb..842e6ce 100644 --- a/docs/index.html +++ b/docs/index.html @@ -1,66 +1,47 @@ -@wymp/http-errors

@wymp/http-errors

Http Errors

This is a small library that presents a set of pre-configured errors that correspond to some common HTTP status codes, such as 400, 401, 403, 404, 500, etc....

-

The idea is that you can throw these like normal errors in your code, but then use them to fine-tune your actual HTTP response in a global handler function. For example, using express:

-
import * as express from "express";
import * as Errors from "http-errors";
import { authenticate } from "./Authnz";

const app = express();

// Normal endpoint
app.get("/my/endpoint", function(req, res, next) {
try {
if (!req.headers("Authorization")) {
throw new Errors.Unauthorized(
"You must pass a standard Authorization header to use this endpoint",
"MissingAuthHeader"
);
}

// May throw Errors.Forbidden
authenticate(req.headers("Authorization"));

return res.status(200).send({ data: "Yay!" });
} catch (e) {
next(e);
}
});

// Global handler, handling errors for all endpoints
app.use(function(e: Error, req, res, next) {
// If it's not already an HttpError, convert it into an InternalServerError (500)
if (!Errors.isHttpError(e)) {
e = Errors.InternalServerError.fromError(e);
}

// This happens to be JSON:API structure, but you could use the data however you'd like
return res.status(e.status).send({
errors: [
{
status: e.status,
code: e.code!,
title: e.title,
detail: e.message,
},
],
});
}); +@wymp/http-errors

@wymp/http-errors

Http Errors

This is a small library whose primary export is the HttpError class, a derivative of the native Error class. +HttpError can be instantiated and thrown like any error, except that it carries additional information that is +pertinent to HTTP interactions.

+

For example:

+
import * as express from "express";
import { HttpError, isHttpError } from "http-errors";
import { authenticate } from "./Authnz";
import { doThings } from "./myLib";
import { SimpleLoggerConsole } from "@wymp/simple-logger-console";

const app = express();
const log = new SimpleLoggerConsole();

// Normal endpoint
app.get("/my/endpoint", function(req, res, next) {
try {
if (!req.headers("Authorization")) {
throw new HttpError(
401,
"You must pass a standard Authorization header to use this endpoint",
{ subcode: "MissingAuthHeader" }
);
}

// May throw additional HTTP errors, such as 400, 401 or 403
authenticate(req.headers("Authorization"));
doThings(req);

// If nothing threw, just return 200 with our data
return res.status(200).send({ data: "Yay!" });
} catch (e) {
// Otherwise, pass the error to next
next(e);
}
});

// Global error handler
app.use(function(_e: Error, req, res, next) {
// If it's not already an HttpError, convert it into an InternalServerError (500)
const e = HttpError.from(_e);

// Log the error
log[e.logLevel](e.stack);

// Return the response with the correct status and structure
return res.status(e.status).send({
ok: false,
// Note that an HttpError is properly serialized by JSON.stringify, so we don't need to do anything extra here
error: e
});
});
-

API

The best way to understand the API for these errors is to simply look at the -definitions file, -which is fairly small. However, for ease of reference, below is an overview:

-

isHttpError()

This is a simple function that offers typeguarding for errors. For any catch block, you -can simply pass in the error object you receive, and if it's not an HttpError, you can -convert it to one using the static fromError() method available on all errors in the library.

-

HttpError

This is the (abstract) base class for all errors in this library. All errors have the following -properties, which are defined in this base class:

+

API

isHttpError()

A typeguard allowing you to check if a given error is an HttpError. Probably better to just use HttpError.from +instead.

+

HttpError.from

Takes any Error object and converts it to an HttpError. This is most often used in catch blocks to turn native Errors +into HttpErrors like so:

+
try {
woops this is gonna throw
} catch (_err) {
const err = HttpError.from(_err);

// Now we know it's an HttpError and can access its fields
// ...
} +
+

HttpError.toJSON and (static) HttpError.fromJSON

These methods allow you to easily serialize to JSON and de-serialize back into a native HttpError. To serialize, +simply pass the error to JSON.stringify and it will output important fields (although not headers or stack, to +avoid accidental leakage). To de-serialize, pass either the string or the already-deserialized object into the +HttpError.fromJSON static method.

+

For example:

+
const e = new HttpError(404, "Not Found", { obstructions: [{ code: "something", text: "Some explanation" }] });
const json = JSON.stringify(e);
const inflated = HttpError.fromJSON(json);
// e.status === inflated.status
// e.name === inflated.name
// e.obstructions == inflated.obstructions
// etc... +
+

HttpError

Throw this error instead of native errors in order to attach additional data pertinent to your application and to the +HTTP context.

+

Important fields are:

    -
  • readonly tag: "HttpError" = "HttpError" -- Always HttpError so you can easily tell whether -or not you're dealing with an HttpError.
  • -
  • readonly name: string; -- An error ID which is usually statically defined. For example, -a "Bad Request" might be defined with this property set to BadRequest, such that you can -always determine what type of error you're dealing with at runtime.
  • -
  • readonly status: HttpStatusCode; -- any of the (finite) list of valid HTTP numeric status -codes. This is usually defined statically in the specific error definition, so you don't have -to set it yourself.
  • -
  • errno?: number; -- Part of the NodeJS.ErrnoException interface.
  • -
  • code?: string; -- A useful code indicating what specific error this is (e.g., IncorrectEmail,
  • -
  • readonly subcode?: string; -- A secondary code to further specify the error (e.g., IncorrectFormat, -MiddleNameRequired, etc....)
  • -
  • path?: string; -- The path through the data where the error occurred (e.g., -data.attributes.legalName)
  • -
  • syscall?: string; -- Part of the NodeJS.ErrnoException interface.
  • -
  • stack?: string; -- Part of the NodeJS.ErrnoException interface.
  • -
  • obstructions: Array<ObstructionInterface<{[param: string]: any}>>; -- This error's -array of obstructions (see Obstructions below).
  • +
  • status - The status code of the error. (You must pass this into the constructor, and it defaults to 500 when +converting regular errors)
  • +
  • name - The standardized status text corresponding to the status code. (See also HttpErrorStatuses)
  • +
  • subcode - You can use this field to offer a concise, immutable code indicating more specifically what the error +pertains to. In the example above, we used MissingAuthHeader as the subcode in our 401. This can help the client +understand what needs to change about the request in order for it to succeed.
  • +
  • logLevel - This is an internal field that you can use in your system to determine whether or not to log this error.
  • +
  • obstructions - An optional collection of more specific data about why the user cannot do what they want to do. See +below.
  • +
  • headers - Some errors (such as 401) require certain headers to be returned. This allows you to do that.
-

HttpError itself is an abstract class. You'll actually be using descendent classes when -throwing errors (see Basic Errors below). These descendent errors are -distinguishable at runtime (and in the context of -discriminated unions) -via the name attribute, which will usually be the same as the constructor, but is defined -statically in the descendant class definitions.

-

The code, and subcode properties allow for further differentiation. code is a property -from the NodeJS.ErrnoException interface and often used by native errors. Therefore, it is -usually avoided in HttpErrors. subcode, on the other hand, is settable via the optional -second constructor parameter on every HttpError and may be used to very specifically -describe the error.

-

For example, you might define and use a new InvalidEmail Error like so:

-
class InvalidEmailError extends BadRequest {
code = "InvalidEmail"
}

// ...

throw new InvalidEmailError("Your email must be in standard format", "IncorrectFormat"); -
-

In the above example, you can easily throw an error with a lot of data attached to it -by default, then add specificity ("IncorrectFormat") in context.

-

fromError

HttpError defines a static method, fromError which takes any Error object and converts -it to an HttpError of the given type. This is most often used to turn native Errors into -InternalServerErrors like so:

-
try {
woops this is gonna throw
} catch (e) {
if (!isHttpError(e)) {
e = InternalServerError.fromError(e);
}

// Now we know it's an HttpError. Format it and return a response
// ...
} +

Obstructions

The concept of obstructions is specific to the HttpError world. An obstruction is defined as follows:

+
export type ObstructionInterface<Data = undefined> = Data extends undefined
? { code: string; text: string }
: { code: string; text: string; data: Data };
-

Obstructions

The concept of obstructions is specific to the HttpErrors world. An obstruction is defined -as follows:

-
interface ObstructionInterface<ParamSet extends {[param: string]: unknown}> {
code: string;
text: string;
params?: ParamSet;
} +

These are meant to be light-weight and data-dense packets that allow you to communicate to consumers about multiple +issues that are preventing a given request from completing successfully.

+

Imagine you're registering a new user. Using an HttpError with obstructions, you can easily stop execution and send +back a 400 with useful information from anywhere in your code. In the following example, there are certain things for +which we immediately throw and other things where we build up a list of obstructions and then throw.

+
// library: @my-org/types

// First we'll define our obstructions. This allows front- and back-end code to work with type-safe errors
import { ObstructionInterface } from "@wymp/http-errors";
export type MyObstructions =
| ObstructionInterface<"NoUserName">
| ObstructionInterface<"NoEmail">
| ObstructionInterface<"InvalidEmail", { email: string; pattern: string }>
| ObstructionInterface<"NoPassword">
| ObstructionInterface<"NoPasswordConf">
| ObstructionInterface<"PasswordConfMismatch">;
-

These are meant to be light-weight and data-dense packets that allow you to communicate to -consumers about multiple issues that are preventing a given request from completing -successfully.

-

Imagine you're registering a new user. Using the BadRequest error with obstructions, you -can easily stop execution and send back a 400 with useful information from anywhere in your -code:

-
const body = req.body;
if (!body) {
throw new BadRequest("Missing request body. Did you send it?");
}
if (!body.user) {
throw new BadRequest("Missing incoming user object. Did you send it?");
}

const obstructions: Array<ObstructionInterface<GenericParams>> = [];

if (!body.user.name) {
obstructions.push({
code: "NoUserName",
text: "You must send a user name to register."
});
}

if (!body.user.email) {
obstructions.push({
code: "NoEmail",
text: "You must send an email for the user you're registering."
});
} else {
if (!validateEmail(body.user.email, ourEmailRegex)) {
obstructions.push({
code: "InvalidEmail",
text: "Your email doesn't appear to be in valid format.",

// Note that we can provide data so consumers can be more detailed about
// how they display the errors
params: {
email: body.user.email,
pattern: ourEmailRegex
}
});
}
}

if (!body.user.password) {
obstructions.push({
code: "NoPassword",
text: "You haven't provided a password."
});
} else {
if (!body.user.passwordConfirmation) {
obstructions.push({
code: "NoPasswordConf",
text: "You haven't provided a password confirmation."
});
} else {
if (body.user.password !== body.user.passwordConfirmation) {
obstructions.push({
code: "PasswordConfMismatch",
text: "Your password confirmation doesn't match the password you entered."
});
}
}
}

if (obstructions.length > 0) {
const e = new BadRequest("There were problems registering your user.");
e.obstructions = obstructions;
throw e;
}

// Now we know it's safe. Continue processing here....
+
// app: @my-org/core

import { MyObstructions } from "@my-org/types";

const body = req.body;
if (!body) {
throw new HttpError(400, "Missing request body. Did you send it?");
}
if (!body.user) {
throw new HttpError(400, "Missing incoming user object. Did you send it?");
}

const obstructions: Array<MyObstructions> = [];
const ourEmailRegex = /.../;

if (!body.user.name) {
obstructions.push({
code: "NoUserName",
text: "You must send a user name to register."
});
}

if (!body.user.email) {
obstructions.push({
code: "NoEmail",
text: "You must send an email for the user you're registering."
});
} else {
if (!validateEmail(body.user.email, ourEmailRegex)) {
obstructions.push({
code: "InvalidEmail",
text: "Your email doesn't appear to be in valid format.",

// Note that we can provide data so consumers can be more detailed about
// how they display the errors
data: {
email: body.user.email,
pattern: ourEmailRegex.toString()
}
});
}
}

if (!body.user.password) {
obstructions.push({
code: "NoPassword",
text: "You haven't provided a password."
});
} else {
if (!body.user.passwordConfirmation) {
obstructions.push({
code: "NoPasswordConf",
text: "You haven't provided a password confirmation."
});
} else {
if (body.user.password !== body.user.passwordConfirmation) {
obstructions.push({
code: "PasswordConfMismatch",
text: "Your password confirmation doesn't match the password you entered."
});
}
}
}

if (obstructions.length > 0) {
throw new HttpError(400, "There were problems registering your user.", { obstructions });
}

// Continue processing....
-

Generated using TypeDoc

\ No newline at end of file +

Generated using TypeDoc

\ No newline at end of file diff --git a/docs/interfaces/GenericParams.html b/docs/interfaces/GenericParams.html deleted file mode 100644 index f605a96..0000000 --- a/docs/interfaces/GenericParams.html +++ /dev/null @@ -1 +0,0 @@ -GenericParams | @wymp/http-errors

Generated using TypeDoc

\ No newline at end of file diff --git a/docs/interfaces/ObstructionInterface.html b/docs/interfaces/ObstructionInterface.html deleted file mode 100644 index 41976d5..0000000 --- a/docs/interfaces/ObstructionInterface.html +++ /dev/null @@ -1,4 +0,0 @@ -ObstructionInterface | @wymp/http-errors

Interface ObstructionInterface<ParamSet>

interface ObstructionInterface {
    code: string;
    text: string;
    params?: ParamSet;
}

Type Parameters

Properties

Properties

code: string
text: string
params?: ParamSet

Generated using TypeDoc

\ No newline at end of file diff --git a/docs/modules.html b/docs/modules.html index 59779c6..51219dc 100644 --- a/docs/modules.html +++ b/docs/modules.html @@ -1,22 +1,9 @@ @wymp/http-errors

Generated using TypeDoc

\ No newline at end of file +

Generated using TypeDoc

\ No newline at end of file diff --git a/docs/types/ErrorMeta.html b/docs/types/ErrorMeta.html new file mode 100644 index 0000000..3ff497b --- /dev/null +++ b/docs/types/ErrorMeta.html @@ -0,0 +1,6 @@ +ErrorMeta | @wymp/http-errors

Type alias ErrorMeta<Obstructions>

ErrorMeta<Obstructions>: {
    subcode?: string;
    logLevel?: SimpleLogLevel;
    obstructions?: Obstructions[];
    headers?: Record<string, string>;
}

Metadata for an HTTP error.

+

Type Parameters

Type declaration

Generated using TypeDoc

\ No newline at end of file diff --git a/docs/types/HttpErrorJSON.html b/docs/types/HttpErrorJSON.html new file mode 100644 index 0000000..7dcb006 --- /dev/null +++ b/docs/types/HttpErrorJSON.html @@ -0,0 +1,3 @@ +HttpErrorJSON | @wymp/http-errors

Type alias HttpErrorJSON<Status, Obstructions>

HttpErrorJSON<Status, Obstructions>: {
    tag: "HttpError";
    name: HttpErrorStatuses[Status];
    status: Status;
    subcode?: string;
    logLevel: string;
    obstructions?: Obstructions[];
    message: string;
}

The JSON representation of an HttpError

+

Note that we're intentionally omitting stack and headers here to avoid accidental leakage of sensitive information.

+

Type Parameters

Type declaration

  • tag: "HttpError"
  • name: HttpErrorStatuses[Status]
  • status: Status
  • Optional subcode?: string
  • logLevel: string
  • Optional obstructions?: Obstructions[]
  • message: string

Generated using TypeDoc

\ No newline at end of file diff --git a/docs/types/HttpErrorStatuses-1.html b/docs/types/HttpErrorStatuses-1.html new file mode 100644 index 0000000..24841c1 --- /dev/null +++ b/docs/types/HttpErrorStatuses-1.html @@ -0,0 +1 @@ +HttpErrorStatuses | @wymp/http-errors

Type alias HttpErrorStatuses

HttpErrorStatuses: typeof HttpErrorStatuses[keyof typeof HttpErrorStatuses]

Generated using TypeDoc

\ No newline at end of file diff --git a/docs/types/HttpStatusCode.html b/docs/types/HttpStatusCode.html deleted file mode 100644 index b31442a..0000000 --- a/docs/types/HttpStatusCode.html +++ /dev/null @@ -1 +0,0 @@ -HttpStatusCode | @wymp/http-errors

Type alias HttpStatusCode

HttpStatusCode: 400 | 401 | 402 | 403 | 404 | 405 | 406 | 407 | 408 | 409 | 410 | 411 | 412 | 413 | 414 | 415 | 416 | 417 | 418 | 420 | 422 | 423 | 424 | 425 | 426 | 428 | 429 | 431 | 444 | 449 | 450 | 451 | 499 | 500 | 501 | 502 | 503 | 504 | 505 | 506 | 507 | 508 | 509 | 510 | 511 | 598 | 599

Generated using TypeDoc

\ No newline at end of file diff --git a/docs/types/HttpStatusCodes.html b/docs/types/HttpStatusCodes.html new file mode 100644 index 0000000..689293b --- /dev/null +++ b/docs/types/HttpStatusCodes.html @@ -0,0 +1 @@ +HttpStatusCodes | @wymp/http-errors

Type alias HttpStatusCodes

HttpStatusCodes: keyof typeof HttpErrorStatuses

Generated using TypeDoc

\ No newline at end of file diff --git a/docs/types/ObstructionInterface.html b/docs/types/ObstructionInterface.html new file mode 100644 index 0000000..d342df2 --- /dev/null +++ b/docs/types/ObstructionInterface.html @@ -0,0 +1,4 @@ +ObstructionInterface | @wymp/http-errors

Type alias ObstructionInterface<Code, Data>

ObstructionInterface<Code, Data>: Data extends undefined
    ? {
        code: Code;
        text: string;
    }
    : {
        code: Code;
        text: string;
        data: Data;
    }

An obstruction - a lightweight object providing a short, machine-readable code along with longer, human-readable text +and optional machine-readable data that can be used to help the user understand more about what's keeping them from +being able to do the thing they want to do.

+

Type Parameters

  • Code extends string = string

  • Data = undefined

Generated using TypeDoc

\ No newline at end of file diff --git a/docs/variables/HttpErrorStatuses.html b/docs/variables/HttpErrorStatuses.html new file mode 100644 index 0000000..ee9f8d7 --- /dev/null +++ b/docs/variables/HttpErrorStatuses.html @@ -0,0 +1 @@ +HttpErrorStatuses | @wymp/http-errors

Variable HttpErrorStatusesConst

HttpErrorStatuses: {
    400: "Bad Request";
    401: "Unauthorized";
    402: "Payment Required";
    403: "Forbidden";
    404: "Not Found";
    405: "Method Not Allowed";
    406: "Not Acceptable";
    407: "Proxy Authentication Required";
    408: "Request Timeout";
    409: "Conflict";
    410: "Gone";
    411: "Length Required";
    412: "Precondition Failed";
    413: "Payload Too Large";
    414: "URI Too Long";
    415: "Unsupported Media Type";
    416: "Range Not Satisfiable";
    417: "Expectation Failed";
    418: "I'm a teapot";
    420: "Enhance Your Calm";
    422: "Unprocessable Entity";
    423: "Locked";
    424: "Failed Dependency";
    425: "Reserved for WebDAV";
    426: "Upgrade Required";
    428: "Precondition Required";
    429: "Too Many Requests";
    431: "Request Header Fields Too Large";
    444: "No Response";
    449: "Retry With";
    450: "Blocked by Windows Parental Controls";
    451: "Unavailable For Legal Reasons";
    499: "Client Closed Request";
    500: "Internal Server Error";
    501: "Not Implemented";
    502: "Bad Gateway";
    503: "Service Unavailable";
    504: "Gateway Timeout";
    505: "HTTP Version Not Supported";
    506: "Variant Also Negotiates";
    507: "Insufficient Storage";
    508: "Loop Detected";
    509: "Bandwidth Limit Exceeded";
    510: "Not Extended";
    511: "Network Authentication Required";
    598: "Network read timeout error";
    599: "Network connect timeout error";
} = ...

Type declaration

  • Readonly 400: "Bad Request"
  • Readonly 401: "Unauthorized"
  • Readonly 402: "Payment Required"
  • Readonly 403: "Forbidden"
  • Readonly 404: "Not Found"
  • Readonly 405: "Method Not Allowed"
  • Readonly 406: "Not Acceptable"
  • Readonly 407: "Proxy Authentication Required"
  • Readonly 408: "Request Timeout"
  • Readonly 409: "Conflict"
  • Readonly 410: "Gone"
  • Readonly 411: "Length Required"
  • Readonly 412: "Precondition Failed"
  • Readonly 413: "Payload Too Large"
  • Readonly 414: "URI Too Long"
  • Readonly 415: "Unsupported Media Type"
  • Readonly 416: "Range Not Satisfiable"
  • Readonly 417: "Expectation Failed"
  • Readonly 418: "I'm a teapot"
  • Readonly 420: "Enhance Your Calm"
  • Readonly 422: "Unprocessable Entity"
  • Readonly 423: "Locked"
  • Readonly 424: "Failed Dependency"
  • Readonly 425: "Reserved for WebDAV"
  • Readonly 426: "Upgrade Required"
  • Readonly 428: "Precondition Required"
  • Readonly 429: "Too Many Requests"
  • Readonly 431: "Request Header Fields Too Large"
  • Readonly 444: "No Response"
  • Readonly 449: "Retry With"
  • Readonly 450: "Blocked by Windows Parental Controls"
  • Readonly 451: "Unavailable For Legal Reasons"
  • Readonly 499: "Client Closed Request"
  • Readonly 500: "Internal Server Error"
  • Readonly 501: "Not Implemented"
  • Readonly 502: "Bad Gateway"
  • Readonly 503: "Service Unavailable"
  • Readonly 504: "Gateway Timeout"
  • Readonly 505: "HTTP Version Not Supported"
  • Readonly 506: "Variant Also Negotiates"
  • Readonly 507: "Insufficient Storage"
  • Readonly 508: "Loop Detected"
  • Readonly 509: "Bandwidth Limit Exceeded"
  • Readonly 510: "Not Extended"
  • Readonly 511: "Network Authentication Required"
  • Readonly 598: "Network read timeout error"
  • Readonly 599: "Network connect timeout error"

Generated using TypeDoc

\ No newline at end of file diff --git a/package.json b/package.json index fafb276..d504150 100644 --- a/package.json +++ b/package.json @@ -1,18 +1,29 @@ { "name": "@wymp/http-errors", - "version": "3.0.0", + "version": "4.0.0", "description": "A set of extensions to the standard Error class that add features that make it easy to convert thrown errors into detailed HTTP responses.", "repository": { "type": "git", "url": "https://github.com/wymp/ts-http-errors.git" }, - "main": "dist/index.js", - "types": "dist/index.d.ts", + "main": "dist/httpErrors.js", + "types": "dist/httpErrors.d.ts", + "engines": { + "node": ">=14.0.0", + "pnpm": "^8" + }, + "files": [ + "dist", + "src", + "changelog.md", + "README.md" + ], "scripts": { "build": "tsc", + "check": "pnpm typecheck && pnpm prettier && pnpm lint && pnpm test", "clean": "rm -Rf dist || true; rm -Rf docs || true", - "docs:gen": "npx typedoc src/index.ts --out ./docs --sort visibility --sort source-order", - "docs:serve": "pnpx http-server ./docs", + "docs:gen": "npx typedoc src/httpErrors.ts --out ./docs --sort visibility --sort source-order", + "docs:view": "pnpx http-server -o -c 60 ./docs", "format": "pnpm prettier:fix && pnpm lint:fix", "lint": "eslint src tests", "lint:fix": "pnpm lint --fix", @@ -21,8 +32,7 @@ "prepublishOnly": "pnpm clean && pnpm build && npm run docs:gen", "prettier": "prettier src tests --check", "prettier:fix": "pnpm prettier --write", - "test": "pnpm typecheck && pnpm prettier && pnpm lint && pnpm test:jest", - "test:jest": "jest --verbose", + "test": "jest", "typecheck": "tsc --noEmit" }, "author": "Wymp", @@ -47,6 +57,7 @@ "trailingComma": "es5" }, "jest": { + "verbose": true, "roots": [ "/tests" ], diff --git a/src/httpErrors.ts b/src/httpErrors.ts new file mode 100644 index 0000000..cedde5a --- /dev/null +++ b/src/httpErrors.ts @@ -0,0 +1,281 @@ +export const HttpErrorStatuses = { + 400: "Bad Request", + 401: "Unauthorized", + 402: "Payment Required", + 403: "Forbidden", + 404: "Not Found", + 405: "Method Not Allowed", + 406: "Not Acceptable", + 407: "Proxy Authentication Required", + 408: "Request Timeout", + 409: "Conflict", + 410: "Gone", + 411: "Length Required", + 412: "Precondition Failed", + 413: "Payload Too Large", + 414: "URI Too Long", + 415: "Unsupported Media Type", + 416: "Range Not Satisfiable", + 417: "Expectation Failed", + 418: "I'm a teapot", + 420: "Enhance Your Calm", + 422: "Unprocessable Entity", + 423: "Locked", + 424: "Failed Dependency", + 425: "Reserved for WebDAV", + 426: "Upgrade Required", + 428: "Precondition Required", + 429: "Too Many Requests", + 431: "Request Header Fields Too Large", + 444: "No Response", + 449: "Retry With", + 450: "Blocked by Windows Parental Controls", + 451: "Unavailable For Legal Reasons", + 499: "Client Closed Request", + 500: "Internal Server Error", + 501: "Not Implemented", + 502: "Bad Gateway", + 503: "Service Unavailable", + 504: "Gateway Timeout", + 505: "HTTP Version Not Supported", + 506: "Variant Also Negotiates", + 507: "Insufficient Storage", + 508: "Loop Detected", + 509: "Bandwidth Limit Exceeded", + 510: "Not Extended", + 511: "Network Authentication Required", + 598: "Network read timeout error", + 599: "Network connect timeout error", +} as const; +export type HttpErrorStatuses = (typeof HttpErrorStatuses)[keyof typeof HttpErrorStatuses]; + +export type HttpStatusCodes = keyof typeof HttpErrorStatuses; + +type SimpleLogLevel = "debug" | "info" | "notice" | "warning" | "error" | "alert" | "critical" | "emergency"; + +/** + * An obstruction - a lightweight object providing a short, machine-readable code along with longer, human-readable text + * and optional machine-readable data that can be used to help the user understand more about what's keeping them from + * being able to do the thing they want to do. + */ +export type ObstructionInterface = Data extends undefined + ? { code: Code; text: string } + : { code: Code; text: string; data: Data }; + +/** Metadata for an HTTP error. */ +export type ErrorMeta = { + /** @see {@link HttpError["subcode"]} */ + subcode?: string; + /** @see {@link HttpError["logLevel"]} */ + logLevel?: SimpleLogLevel; + /** @see {@link HttpError["obstructions"]} */ + obstructions?: Obstructions[]; + /** @see {@link HttpError["headers"]} */ + headers?: Record; +}; + +/** + * The JSON representation of an HttpError + * + * Note that we're intentionally omitting stack and headers here to avoid accidental leakage of sensitive information. + */ +export type HttpErrorJSON< + Status extends HttpStatusCodes = HttpStatusCodes, + Obstructions extends ObstructionInterface = ObstructionInterface, +> = { + tag: "HttpError"; + name: HttpErrorStatuses[Status]; + status: Status; + subcode?: string; + logLevel: string; + obstructions?: Obstructions[]; + message: string; +}; + +/** + * An HttpError is an error that can be thrown from within a route handler (or any other library code) to provide more + * details to a system in the context of an HTTP request. It is a subclass of Error, so it can be thrown and caught + * like any other error, but it also has some additional properties that can be used to provide more information about + * the error. + * + * Specifically, an HttpError contains all of the information necessary to send an HTTP error response to the client, + * including the status code, status text, optional headers (for example, to set a `WWW-Authenticate` header for a 401 + * response), a log-level (helping to indicate to the back-end system the log level at which this error should be + * logged), an optional subcode to help the consumer programatically understand the error, a top-level message, and + * an optional array of "obstructions" that can be used by the consumer to help the user understand what they need to + * do to resolve the error. + * + * @example + * + * **In a request handler** + * + * ```ts + * import { HttpError, HttpErrorStatuses } from "@wymp/http-error"; + * import { myLoginChecker } from "./myLoginLibrary"; + * + * // For the purposes of this example, myLoginChecker will return the type indicated here + * type LoginCheck = + * | { success: true; session: { token: string; refresh: string } } + * | { success: false; obstructions: ObstructionInterface[] } + * + * // Login handler - This might be hooked up to a POST /login route or something + * export const loginHandler = async (req, res, next) => { + * try { + * const { username, password } = req.body; + * + * const response: LoginCheck = await myLoginChecker(username, password); + * + * if (!response.success) { + * throw new HttpError(400, "Login failed", { + * subcode: 'LOGIN_FAILED', + * obstructions: response.obstructions, + * }); + * } + * + * res.json(response); + * } catch (err) { + * next(err); + * } + * } + * ``` + * + * **In the server's error handler** + * + * ```ts + * import { isHttpError } from "@wymp/http-errors"; + * import { SimpleLoggerInterface } from "@wymp/ts-simple-interfaces"; + * + * export errorHandler = (log: SimpleLoggerInterface) => (_err, req, res, next) => { + * const err = isHttpError(_err) ? _err : HttpError.from(_err); + * + * // Log the error + * log[err.logLevel](err.stack); + * + * // Format the error and send it off + * res.status(err.status).json({ + * ok: false, + * // Automatically serializes correctly because of our toJSON method + * error: err, + * }); + * } + * ``` + */ +export class HttpError< + Status extends HttpStatusCodes = HttpStatusCodes, + Obstructions extends ObstructionInterface = ObstructionInterface, +> extends Error { + /** A tag identifying this object as an HttpError. Any object having this tag should implement this interface */ + public readonly tag = "HttpError" as const; + /** The name of the error, which will always be the official text associated with the status code */ + public readonly name: (typeof HttpErrorStatuses)[Status]; + /** An optional subcode that you can use to further understand why an error occurred. */ + public readonly subcode?: string; + /** + * The level at which this error should be logged. This can be useful for keeping log noise down for unimportant + * errors such as 404s while elevating more important errors, such as "database down". + */ + public logLevel: SimpleLogLevel; + /** An optional list of obstructions that can help the user understand why they can't do what they're trying to do */ + public obstructions: Obstructions[]; + /** + * Headers to attach to the HTTP response. Useful for things like attaching the `WWW-Authenticate` header to 401 + * responses. + */ + public readonly headers: Record; + + public constructor( + public readonly status: Status, + msg: string, + meta?: ErrorMeta + ) { + super(msg); + this.name = HttpErrorStatuses[status]; + this.subcode = meta?.subcode; + this.logLevel = meta?.logLevel ?? "error"; + this.obstructions = meta?.obstructions ?? []; + this.headers = meta?.headers ?? {}; + } + + /** + * Create an HTTP error from a random error + * + * NOTE: If the passed-in error is already an HttpError, it will be passed back without modification. Any status or + * metadata passed in will not be applied in this case. Because of this, you can think of the `status` and `meta` + * params as defaults. + */ + public static from( + err: Error, + status: Status, + meta?: ErrorMeta + ): HttpError; + public static from( + err: Error, + meta?: ErrorMeta + ): HttpError; + public static from< + Status extends HttpStatusCodes = 500, + Obstructions extends ObstructionInterface = ObstructionInterface, + >( + err: Error, + statusOrMeta?: Status | ErrorMeta, + _meta?: ErrorMeta + ): HttpError { + if (isHttpError(err)) return err; + const status = typeof statusOrMeta === "number" ? statusOrMeta : 500; + const meta = typeof statusOrMeta === "object" ? statusOrMeta : _meta; + const e = new HttpError(status, err.message, meta); + e.stack = err.stack; + return e; + } + + /** Serialize an HTTP error to JSON */ + public toJSON(): HttpErrorJSON { + return { + tag: this.tag, + name: this.name, + status: this.status, + subcode: this.subcode, + logLevel: this.logLevel, + obstructions: this.obstructions, + message: this.message, + // NOTE: We're not including the stack or headers because that would create the possibility of accidentally + // leaking sensitive information to clients. Instead, if you want the stack or headers, you can explicitly include + // them in your output. + // stack: this.stack, + // headers: this.headers, + }; + } + + /** Create an HTTP error from a JSON representation */ + public static fromJSON< + Status extends HttpStatusCodes = HttpStatusCodes, + Obstructions extends ObstructionInterface = ObstructionInterface, + >(json: string | HttpErrorJSON): HttpError { + const obj = typeof json === "string" ? >JSON.parse(json) : json; + const meta: ErrorMeta = { + subcode: obj.subcode, + logLevel: obj.logLevel as SimpleLogLevel, + obstructions: obj.obstructions, + }; + return new HttpError(obj.status, obj.message, meta); + } +} + +/** + * A type-guard to help you determine whether an error is an HttpError. This is useful for error handlers that might + * receive errors from other libraries that might not be HttpErrors, but that you want to treat as HttpErrors. + * + * @example + * + * ```ts + * export errorHandler = (_err: Error, req: Request, res: Response, next: NextFunction) => { + * const err = isHttpError(_err) ? _err : HttpError.from(_err); + * // ... + * } + * ``` + */ + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export const isHttpError = function (e: any): e is HttpError { + return typeof e === "object" && Object.prototype.hasOwnProperty.call(e, "tag") && e.tag === "HttpError"; +}; diff --git a/src/index.ts b/src/index.ts deleted file mode 100644 index 4cd4c18..0000000 --- a/src/index.ts +++ /dev/null @@ -1,418 +0,0 @@ -export type HttpStatusCode = - | 400 - | 401 - | 402 - | 403 - | 404 - | 405 - | 406 - | 407 - | 408 - | 409 - | 410 - | 411 - | 412 - | 413 - | 414 - | 415 - | 416 - | 417 - | 418 - | 420 - | 422 - | 423 - | 424 - | 425 - | 426 - | 428 - | 429 - | 431 - | 444 - | 449 - | 450 - | 451 - | 499 - | 500 - | 501 - | 502 - | 503 - | 504 - | 505 - | 506 - | 507 - | 508 - | 509 - | 510 - | 511 - | 598 - | 599; - -export interface GenericParams { - [param: string]: unknown; -} - -export interface ObstructionInterface { - code: string; - text: string; - params?: ParamSet; -} - -type SimpleLogLevel = "debug" | "info" | "notice" | "warning" | "error" | "alert" | "critical" | "emergency"; - -export abstract class HttpError extends Error { - public readonly tag = "HttpError" as const; - public abstract readonly name: string; - public abstract readonly status: number; - public errno?: number; - public code?: string; - public path?: string; - public syscall?: string; - public stack?: string; - public loglevel: SimpleLogLevel = "error"; - - public constructor( - msg: string, - public readonly subcode?: string, - public obstructions: Array> = [], - public headers: { [name: string]: string } = {} - ) { - super(msg); - } - - /** - * Converts any regular error into the given type of HttpError. For example, to convert an error - * into a "BadRequest" error: `const badreq = BadRequest.fromError(new Error("something"))` - */ - public static fromError(this: { new (msg: string): T }, e: Error): T { - return new this(e.message); - } - - /** - * Shortcut to getting one of the defined errors in this package. If an error for the given code - * does not exist, returns a generic HttpError with the given code. - */ - public static withStatus( - code: number, - msg: string, - subcode?: string, - obstructions?: Array>, - headers?: { [name: string]: string } - ) { - switch (code) { - case 400: - return new BadRequest(msg, subcode, obstructions, headers); - case 401: - return new Unauthorized(msg, subcode, obstructions, headers); - case 403: - return new Forbidden(msg, subcode, obstructions, headers); - case 404: - return new NotFound(msg, subcode, obstructions, headers); - case 405: - return new MethodNotAllowed(msg, subcode, obstructions, headers); - case 406: - return new NotAcceptable(msg, subcode, obstructions, headers); - case 409: - return new DuplicateResource(msg, subcode, obstructions, headers); - case 415: - return new UnsupportedMediaType(msg, subcode, obstructions, headers); - case 429: - return new TooManyRequests(msg, subcode, obstructions, headers); - case 500: - return new InternalServerError(msg, subcode, obstructions, headers); - case 501: - return new NotImplemented(msg, subcode, obstructions, headers); - case 502: - return new BadGateway(msg, subcode, obstructions, headers); - case 503: - return new ServiceUnavailable(msg, subcode, obstructions, headers); - case 504: - return new GatewayTimeout(msg, subcode, obstructions, headers); - case 507: - return new InsufficientStorage(msg, subcode, obstructions, headers); - case 508: - return new LoopDetected(msg, subcode, obstructions, headers); - default: - return new (class extends HttpError { - public readonly name: string = `Http${code}Error`; - public readonly status = code; - public constructor( - msg: string, - public readonly subcode?: string, - public obstructions: Array> = [], - public headers: { [name: string]: string } = {} - ) { - super(msg, subcode, obstructions, headers); - this.code = `HTTP_${code}_ERROR`; - } - })(msg, subcode, obstructions, headers); - } - } -} - -// 400 Errors - -export class BadRequest extends HttpError { - public readonly name: string = "BadRequest"; - public readonly status = 400; - public loglevel: SimpleLogLevel = "warning"; - public constructor( - msg: string, - subcode?: string, - obstructions?: Array>, - headers?: { [name: string]: string } - ) { - super(msg, subcode, obstructions, headers); - this.code = "HTTP_BAD_REQUEST"; - } -} - -export class Unauthorized< - ObstructionParams extends GenericParams = GenericParams, -> extends HttpError { - public readonly name: string = "Unauthorized"; - public readonly status = 401; - public loglevel: SimpleLogLevel = "notice"; - public constructor( - msg: string, - subcode?: string, - obstructions?: Array>, - headers?: { [name: string]: string } - ) { - super(msg, subcode, obstructions, headers); - this.code = "HTTP_UNAUTHORIZED"; - } -} - -export class Forbidden extends HttpError { - public readonly name: string = "Forbidden"; - public readonly status = 403; - public loglevel: SimpleLogLevel = "notice"; - public constructor( - msg: string, - subcode?: string, - obstructions?: Array>, - headers?: { [name: string]: string } - ) { - super(msg, subcode, obstructions, headers); - this.code = "HTTP_FORBIDDEN"; - } -} - -export class NotFound extends HttpError { - public readonly name: string = "NotFound"; - public readonly status = 404; - public loglevel: SimpleLogLevel = "notice"; - public constructor( - msg: string, - subcode?: string, - obstructions?: Array>, - headers?: { [name: string]: string } - ) { - super(msg, subcode, obstructions, headers); - this.code = "HTTP_NOT_FOUND"; - } -} - -export class MethodNotAllowed< - ObstructionParams extends GenericParams = GenericParams, -> extends HttpError { - public readonly name: string = "MethodNotAllowed"; - public readonly status = 405; - public loglevel: SimpleLogLevel = "notice"; - public constructor( - msg: string, - subcode?: string, - obstructions?: Array>, - headers?: { [name: string]: string } - ) { - super(msg, subcode, obstructions, headers); - this.code = "HTTP_METHOD_NOT_ALLOWED"; - } -} - -export class NotAcceptable< - ObstructionParams extends GenericParams = GenericParams, -> extends HttpError { - public readonly name: string = "NotAcceptable"; - public readonly status = 406; - public loglevel: SimpleLogLevel = "notice"; - public constructor( - msg: string, - subcode?: string, - obstructions?: Array>, - headers?: { [name: string]: string } - ) { - super(msg, subcode, obstructions, headers); - this.code = "HTTP_NOT_ACCEPTABLE"; - } -} - -export class DuplicateResource< - ObstructionParams extends GenericParams = GenericParams, -> extends HttpError { - public readonly name: string = "DuplicateResourceError"; - public readonly status = 409; - public loglevel: SimpleLogLevel = "notice"; - public constructor( - msg: string, - subcode?: string, - obstructions?: Array>, - headers?: { [name: string]: string } - ) { - super(msg, subcode, obstructions, headers); - this.code = "HTTP_DUPLICATE_RESOURCE"; - } -} - -export class UnsupportedMediaType< - ObstructionParams extends GenericParams = GenericParams, -> extends HttpError { - public readonly name: string = "UnsupportedMediaType"; - public readonly status = 415; - public loglevel: SimpleLogLevel = "info"; - public constructor( - msg: string, - subcode?: string, - obstructions?: Array>, - headers?: { [name: string]: string } - ) { - super(msg, subcode, obstructions, headers); - this.code = "HTTP_UNSUPPORTED_MEDIA_TYPE"; - } -} - -export class TooManyRequests< - ObstructionParams extends GenericParams = GenericParams, -> extends HttpError { - public readonly name: string = "TooManyRequests"; - public readonly status = 429; - public loglevel: SimpleLogLevel = "warning"; - public constructor( - msg: string, - subcode?: string, - obstructions?: Array>, - headers?: { [name: string]: string } - ) { - super(msg, subcode, obstructions, headers); - this.code = "HTTP_TOO_MANY_REQUESTS"; - } -} - -// 500 Errors - -export class InternalServerError< - ObstructionParams extends GenericParams = GenericParams, -> extends HttpError { - public readonly name: string = "InternalServerError"; - public readonly status = 500; - public constructor( - msg: string, - subcode?: string, - obstructions?: Array>, - headers?: { [name: string]: string } - ) { - super(msg, subcode, obstructions, headers); - this.code = "HTTP_INTERNAL_SERVER_ERROR"; - } -} - -export class NotImplemented< - ObstructionParams extends GenericParams = GenericParams, -> extends HttpError { - public readonly name: string = "NotImplemented"; - public readonly status = 501; - public constructor( - msg: string, - subcode?: string, - obstructions?: Array>, - headers?: { [name: string]: string } - ) { - super(msg, subcode, obstructions, headers); - this.code = "HTTP_NOT_IMPLEMENTED"; - } -} - -export class BadGateway extends HttpError { - public readonly name: string = "BadGateway"; - public readonly status = 502; - public constructor( - msg: string, - subcode?: string, - obstructions?: Array>, - headers?: { [name: string]: string } - ) { - super(msg, subcode, obstructions, headers); - this.code = "HTTP_BAD_GATEWAY"; - } -} - -export class ServiceUnavailable< - ObstructionParams extends GenericParams = GenericParams, -> extends HttpError { - public readonly name: string = "ServiceUnavailable"; - public readonly status = 503; - public constructor( - msg: string, - subcode?: string, - obstructions?: Array>, - headers?: { [name: string]: string } - ) { - super(msg, subcode, obstructions, headers); - this.code = "HTTP_SERVICE_UNAVAILABLE"; - } -} - -export class GatewayTimeout< - ObstructionParams extends GenericParams = GenericParams, -> extends HttpError { - public readonly name: string = "GatewayTimeout"; - public readonly status = 504; - public constructor( - msg: string, - subcode?: string, - obstructions?: Array>, - headers?: { [name: string]: string } - ) { - super(msg, subcode, obstructions, headers); - this.code = "HTTP_GATEWAY_TIMEOUT"; - } -} - -export class InsufficientStorage< - ObstructionParams extends GenericParams = GenericParams, -> extends HttpError { - public readonly name: string = "InsufficientStorage"; - public readonly status = 507; - public constructor( - msg: string, - subcode?: string, - obstructions?: Array>, - headers?: { [name: string]: string } - ) { - super(msg, subcode, obstructions, headers); - this.code = "HTTP_INSUFFICIENT_STORAGE"; - } -} - -export class LoopDetected< - ObstructionParams extends GenericParams = GenericParams, -> extends HttpError { - public readonly name: string = "LoopDetected"; - public readonly status = 508; - public constructor( - msg: string, - subcode?: string, - obstructions?: Array>, - headers?: { [name: string]: string } - ) { - super(msg, subcode, obstructions, headers); - this.code = "HTTP_LOOP_DETECTED"; - } -} - -// Convenience functions - -// eslint-disable-next-line @typescript-eslint/no-explicit-any -export const isHttpError = function (e: any): e is HttpError { - return typeof e === "object" && Object.prototype.hasOwnProperty.call(e, "tag") && e.tag === "HttpError"; -}; diff --git a/tests/HttpErrors.spec.ts b/tests/HttpErrors.spec.ts index 1ea26c7..8547b13 100644 --- a/tests/HttpErrors.spec.ts +++ b/tests/HttpErrors.spec.ts @@ -1,47 +1,44 @@ -import * as errors from "../src/index"; +import { HttpError, HttpErrorStatuses, ObstructionInterface, isHttpError } from "../src/httpErrors"; // This is a test typing for errors with specific obstructions -declare interface ObOne extends errors.ObstructionInterface<{ country: string }> { - code: "ObOne"; -} +declare interface ObOne extends ObstructionInterface<"ObOne", { country: string }> {} declare interface ObTwo - extends errors.ObstructionInterface<{ - cashRequired: number; - cashOnHand: number; - }> { - code: "ObTwo"; -} + extends ObstructionInterface< + "ObTwo", + { + cashRequired: number; + cashOnHand: number; + } + > {} declare interface ObThree - extends errors.ObstructionInterface<{ - ourName: string; - theirName: string; - }> { - code: "ObThree"; -} + extends ObstructionInterface< + "ObThree", + { + ourName: string; + theirName: string; + } + > {} declare type SpecificObstructions = ObOne | ObTwo | ObThree; -class MySpecificError extends errors.BadRequest { - public readonly name: string = "MySpecificError"; - public obstructions: Array = []; -} - -// Tests -describe("General", () => { - it("should have correct data", function () { + +describe("Http Errors", () => { + test("should have correct data", function () { const msg = "Something happened!"; - const e = new errors.InternalServerError(msg); - expect(e.name).toBe("InternalServerError"); + const e = new HttpError(500, msg); + expect(e.name).toBe(HttpErrorStatuses[500]); expect(e.message).toBe(msg); expect(e.status).toBe(500); - expect(e.code).toBe("HTTP_INTERNAL_SERVER_ERROR"); + expect(e.subcode).toBeUndefined(); + expect(e.logLevel).toBe("error"); + expect(e.obstructions).toHaveLength(0); }); - it("should accept a subcode that is publicly accessible", function () { - const e = new errors.InternalServerError("Test error!", "test"); + test("should accept a subcode that is publicly accessible", function () { + const e = new HttpError(500, "Test error!", { subcode: "test" }); expect(e.subcode).toBe("test"); }); - it("should allow us to attach and access obstructions", function () { - const e = new errors.InternalServerError("Test error!", "test"); + test("should allow us to attach and access obstructions", function () { + const e = new HttpError(500, "Test error!", { subcode: "test" }); expect(e.subcode).toBe("test"); e.obstructions.push({ @@ -49,8 +46,8 @@ describe("General", () => { text: "There's something wrong with this thing.", }); - expect(1).toBe(e.obstructions.length); - expect("SomeObstruction").toBe(e.obstructions[0].code); + expect(e.obstructions).toHaveLength(1); + expect(e.obstructions[0].code).toBe("SomeObstruction"); const o = [ { code: "SomeObstruction", text: "Someting's wrong." }, @@ -58,44 +55,25 @@ describe("General", () => { ]; e.obstructions = o; - expect(2).toBe(e.obstructions.length); - expect("SomethingElse").toBe(e.obstructions[1].code); + expect(e.obstructions).toHaveLength(2); + expect(e.obstructions[1].code).toBe("SomethingElse"); }); - it("should handle specific typing of complex errors elegantly", () => { - const e = new MySpecificError("Test error!", "test"); - - // Shouldn't be allowed to do this. Uncomment to test: - /* - e.obstructions = [{ - code: "NotherThing", - text: "Nother thing is wrong", - params: { - ourCountry: "US", - theirCountry: "IT", - } - }]; - e.obstructions = [{ - code: "ObOne", - text: "First thing is wrong", - params: { - nope: "not valid" - } - }]; - */ + test("should handle specific typing of complex errors elegantly", () => { + const e = new HttpError<400, SpecificObstructions>(400, "Test error!", { subcode: "test" }); // _Should_ be allowed to do this e.obstructions.push({ code: "ObOne", text: "First thing is wrong", - params: { + data: { country: "US", }, }); e.obstructions.push({ code: "ObTwo", text: "Second thing is wrong", - params: { + data: { cashRequired: 10, cashOnHand: 1, }, @@ -103,7 +81,7 @@ describe("General", () => { e.obstructions.push({ code: "ObThree", text: "Third thing is wrong", - params: { + data: { ourName: "us", theirName: "them", }, @@ -117,85 +95,132 @@ describe("General", () => { // Type guard should work for diff-union const ob = e.obstructions[0]; if (ob.code === "ObOne") { - expect(ob.params).toBeDefined(); - expect("US").toBe(ob.params!.country); + expect(ob.data).toBeDefined(); + expect("US").toBe(ob.data.country); } + + // Shouldn't be allowed to do this. Uncomment to test: + e.obstructions = [ + { + // @ts-expect-error "NothingThing" is not one of the valid obstruction codes + code: "NotherThing", + text: "Nother thing is wrong", + data: { + // @ts-expect-error these are not valid parameters for any of our obstruction + ourCountry: "US", + theirCountry: "IT", + }, + }, + ]; + e.obstructions = [ + { + code: "ObOne", + text: "First thing is wrong", + data: { + // @ts-expect-error this is not a valid parameter for "ObOne" + nope: "not valid", + }, + }, + ]; }); test(`should accept additional obstruction and header parameters on construct`, () => { const ob = { code: `test`, text: `fulltext` }; - const header = { "X-Test-Error": "true" }; - const e = new errors.BadRequest(`bad request`, `subcode`, [ob], header); + const headers = { "X-Test-Error": "true" }; + const e = new HttpError(400, `bad request`, { subcode: `subcode`, obstructions: [ob], headers }); expect(e.obstructions).toHaveLength(1); expect(e.obstructions[0]).toMatchObject(ob); - expect(e.headers).toMatchObject(header); + expect(e.headers).toMatchObject(headers); expect(e.message).toBe(`bad request`); expect(e.subcode).toBe(`subcode`); }); test(`should allow specifying stricter obstructions`, () => { - const ob = { code: `test`, text: `fulltext`, params: { one: 1, two: 2 } }; - const e = new errors.BadRequest<{ one: number; two: number } | { three: number; four: number }>( - `bad request`, - `subcode`, - [ob] - ); + const ob: ObOne = { code: `ObOne`, text: `fulltext`, data: { country: "US" } }; + const e = new HttpError<400, SpecificObstructions>(400, `bad request`, { subcode: "subcode", obstructions: [ob] }); expect(e.obstructions).toHaveLength(1); expect(e.obstructions[0]).toMatchObject(ob); }); -}); -describe("fromError static method", () => { - const msg = "Test error"; - const error = new Error(msg); - it("should instantiate whatever class it's used on", function () { - let e: errors.HttpError = errors.InternalServerError.fromError(error); - expect(e.name).toBe("InternalServerError"); - expect(e.message).toBe(msg); - expect(e.status).toBe(500); - expect(e.code).toBe("HTTP_INTERNAL_SERVER_ERROR"); + describe("from static method", () => { + const msg = "Test error"; + const error = new Error(msg); - e = errors.BadGateway.fromError(error); - expect(e.name).toBe("BadGateway"); - expect(e.message).toBe(msg); - expect(e.status).toBe(502); - expect(e.code).toBe("HTTP_BAD_GATEWAY"); + test("should instantiate 500 error from generic error if not otherwise specified", function () { + const e = HttpError.from(error); + expect(e.name).toBe(HttpErrorStatuses[500]); + expect(e.message).toBe(msg); + expect(e.status).toBe(500); + }); + + test("should instantiate 502 error from generic error if 502 specified", function () { + const e = HttpError.from(error, 502); + expect(e.name).toBe(HttpErrorStatuses[502]); + expect(e.message).toBe(msg); + expect(e.status).toBe(502); + }); + + test("should pass back a passed-in HttpError without modifications", () => { + const subcode = "test"; + const obstructions = [{ code: "test", text: "fulltext" }]; + const _e = new HttpError(404, "Test error", { subcode, obstructions }); + const e = HttpError.from(_e, 415, { subcode: "test2" }); + expect(e).toEqual(_e); + }); }); -}); -describe("withStatus static method", () => { - it("should instantiate different errors according to the code provided", () => { - const badreq = errors.HttpError.withStatus(400, "Bad request"); - expect(badreq.status).toBe(400); - expect(badreq.name).toBe("BadRequest"); - expect(badreq.code).toBe("HTTP_BAD_REQUEST"); - expect(badreq.message).toBe("Bad request"); - - const loop = errors.HttpError.withStatus(508, "Loop!"); - expect(loop.status).toBe(508); - expect(loop.name).toBe("LoopDetected"); - expect(loop.code).toBe("HTTP_LOOP_DETECTED"); - expect(loop.message).toBe("Loop!"); - - const custom = errors.HttpError.withStatus(555, "My custom error"); - expect(custom.status).toBe(555); - expect(custom.name).toBe("Http555Error"); - expect(custom.code).toBe("HTTP_555_ERROR"); - expect(custom.message).toBe("My custom error"); + describe("isHttpError", () => { + test("should typeguard correctly", function () { + [new HttpError(500, "Test error"), new Error("Test error")].forEach((error) => { + try { + throw error; + } catch (_e) { + const e = isHttpError(_e) ? _e : HttpError.from(_e); + expect(e.name).toBe(HttpErrorStatuses[500]); + expect(e.status).toBe(500); + expect(e.message).toBe("Test error"); + } + }); + }); }); -}); -describe("isHttpError", () => { - it("should typeguard correctly", function () { - [new errors.InternalServerError("Test error"), new Error("Test error")].forEach(function (error) { - try { - throw error; - } catch (_e) { - const e = errors.isHttpError(_e) ? _e : errors.InternalServerError.fromError(_e); - expect(e.name).toBe("InternalServerError"); - expect(e.status).toBe(500); - } + describe("JSON", () => { + const status = 404 as const; + const obstructions = [{ code: "test", text: "fulltext" }]; + const subcode = "subcode"; + const headers = { "X-Test-Error": "true" }; + const e = new HttpError(status, "Test error", { obstructions, subcode, headers }); + + test("should serialize using JSON.stringify", () => { + const json = JSON.stringify(e); + const parsed = JSON.parse(json); + expect(parsed).toMatchObject({ + tag: "HttpError", + status, + name: HttpErrorStatuses[status], + subcode, + logLevel: "error", + obstructions, + message: "Test error", + }); + expect(parsed.stack).toBeUndefined(); + expect(parsed.headers).toBeUndefined(); + }); + + test("should successfully inflate from JSON string", () => { + const json = JSON.stringify(e); + const err = HttpError.fromJSON(json); + expect(err).toBeInstanceOf(HttpError); + expect(err.status).toBe(status); + expect(err.name).toBe(HttpErrorStatuses[status]); + expect(err.subcode).toBe(subcode); + expect(err.message).toBe("Test error"); + expect(err.logLevel).toBe("error"); + expect(err.obstructions).toHaveLength(1); + expect(err.obstructions[0]).toMatchObject(obstructions[0]); + expect(JSON.stringify(err.headers)).toBe("{}"); + expect(err.stack).toBeDefined(); }); }); });