# botched

Better error handling with JSON:API friendly error objects - inspired by [restify-errors][restify-errors-url], [verror][verror-url] and [boom][boom-url].

[![npm][npm-image]][npm-url]
[![CircleCI][circleci-image]][circleci-url]
[![Codecov branch][codecov-image]][codecov-url]
[![David][david-image]][david-url]
[![Known Vulnerabilities][snyk-image]][snyk-url]
[![renovate-app badge][renovate-image]][renovate-url]
[![Conventional Commits][conventional-commits-image]][conventional-commits-url]
[![license][license-image]][license-url]

## Documentation

- [Installation](#installation)
- [Usage](#usage)
- [Extend](#extend)
- [API](#api)
  - [BotchedError](#api-botched-error)
  - [createError](#api-create-error)
  - [createSerializer](#api-serialize)
  - [botch](#api-botch)
  - [wrap](#api-wrap)
  - [isBotched](#api-is-botched)
- [License](#license)

<a id="installation"></a>

## Installation

Install [botched][repo-url] in your project

```bash
$ npm i --save botched
```

<a id="usage"></a>

## Usage

Import the relevant error you need and use as you would using vanilla Errors.
Using the built in error serializer you can effortlessly log errors in a useful format without losing precision.

```js
import express from 'express';
import { createHttpError, createSerializer, Unauthorized } from 'botched';

// Create app
const app = express();
const serialize = createSerializer();

// Middleware that throws Unauthorized if `req.user` is not set
app.use((req, res, next) => {
  if (!req.user) {
    // Alternative 1: Create a new error instance of the type you need by
    let unauthorizedError = new Unauthorized('This endpoint requires authentication');

    // Alternative 2: It is also possible to dynamically create the error you need based on status code
    unauthorizedError = createHttpError(401, 'This endpoint requires authentication');

    return next(unauthorizedError);
  }
  return next();
});

// Error handling middleware to display beautiful errors and log the error details
app.use((err, req, res, next) => {
  // Serialize the error to a loggable format
  const serializedError = serialize(err);

  // Log it
  console.log(serializedError);

  // Respond with a http friendly error message while hiding sensitive details
  res.status(err.statusCode).json(err.toJSON());
});

// Listen
app.listen(3000);
```

<a id="extend"></a>

## Extend

It's very easy to extend with your own errors and it is highly recommended to do so.
The only requirement is that you extend `BotchedError`.

```js
import { BotchedError } from 'botched';

// Exports
export default class InvalidVersionError extends BotchedError {
  // Override default values
  static statusCode = 400;
  static title = 'Invalid Version Error';
}
```

<a id="api"></a>

## API

All Error constructors are variadic and accept the following signatures, all of which
are identical to the [VError and WError][verror-url] signatures.

<a id="api-botched-error"></a>

### BotchedError

The base Error class with the core functionality.

#### `new BotchedError(sprintf_args...)`

#### `new BotchedError(priorErr [, sprintf_args...])`

#### `new BotchedError(options [, sprinf_args...])`

##### Parameters:

- `options` {object}
  - `cause` {Error} - A referenced error
  - `id` {string} - A unique identifier for this instance of the error. Useful for correlating logs. Autogenerated by default.
  - `code` {string} - An identifier for this type of error. Useful for grouping errors.
  - `statusCode` {number} - The http status code to respond with if this error is used in a http response
  - `headers` {object} - Any additional headers to set if this error is used in a http response
  - `title` {string} - The title of the error, e.g. `Internal Server Error`.
  - `source` {object} - Pointer to the cause of the error. Useful for validation with JSON Schema.
    - `pointer` {string} - A JSON pointer to the error in a JSON Schema
  - `links` {object} - A link to where this error is described in more detail - e.g some documentation.
    - `about` {object|string} - An uri to the detailed documentation or an object containing the uri.
      - `href` {string} - The uri to the detailed documentation
      - `meta` {object} - Any additional data about the link
  - `meta` {object} - Any additional **non-sensitive** data or information related to this error that will be visible in `err.toJSON()`
  - `info` {object} - Any additional data or information related to this error

##### `botchedError.toJSON()`

Returns a JSON:API compliant error object with sensitive information stripped out.
In other words, `botchedError.info` is omitted.

<a id="api-create-error"></a>

### createError

Create [BotchedError](#api-botched-error) from status code.
Useful if you are dynamically creating errors.

#### `createError(statusCode, sprintf_args...)`

#### `createError(statusCode, priorErr [, sprintf_args...])`

#### `createError(statusCode, options [, sprinf_args...])`

<a id="api-serialize"></a>

### createSerializer

Serialize error objects to logging friendly objects.

#### `createSerializer([options])`

Create an error serializer function.
Provide the Error instance and receive a perfectly formatted object back when using the returned serializer function.
Note that this **will** include sensitive information as the purpose is to create logging friendly objects - not objects to be sent in API responses.
Use [BotchedError](#api-botched-error) for that.

##### Parameters:

- `options` {object}
  - `fullStack` {boolean} - Should top level Error have the full stack trace (including nested errors?) Default: true
  - `maxDepth` {number} - How deep should the serialization go? Default: 10

<a id="api-botch"></a>

### botch

#### `botch(error)`

Create a botched error from a source error or return the error directly if it is already a botched error.

**Note:** this will inherit information from the source error.
Only use this if you are certain that the error does not contain sensitive information.
See [wrap](#api-wrap) for a safer alternative.

If the error is not already a botched error, then a botched error will be created and inherit all botched properties.
The cause will be set to the source error.

<a id="api-wrap"></a>

### wrap

#### `wrap(error)`

Wrap an error to a botched error or return the error directly if it is already a botched error.
This is equivalent to [botch](#api-botch), but does not inherit any properties except the status code.
Use this if you cannot guarantee that the error does not include sensitive information.

If the error is not already a botched error, then a botched error will be created and only the status code will be inherited.
The cause will be set to the source error.

<a id="api-is-botched"></a>

### isBotched

#### `isBotched(error)`

Convenience method to check if the error is a [BotchedError](#api-botched-error).
Returns true if the error is a [BotchedError](#api-botched-error).

<a id="license"></a>

## TODO

1. Consider whether [isBotched](#api-is-botched) should fail if the [BotchedError.version](#api-botched-error) is not compatible

## License

[MIT](LICENSE.md)

[repo-url]: https://github.com/ersims/botched
[npm-url]: https://npmjs.org/package/botched
[npm-image]: https://img.shields.io/npm/v/botched.svg
[circleci-url]: https://circleci.com/gh/ersims/botched/tree/master
[circleci-image]: https://img.shields.io/circleci/project/github/ersims/botched/master.svg
[codecov-url]: https://codecov.io/gh/ersims/botched/tree/master
[codecov-image]: https://img.shields.io/codecov/c/github/ersims/botched/master.svg
[david-url]: https://david-dm.org/ersims/botched/master
[david-image]: https://img.shields.io/david/ersims/botched.svg
[snyk-url]: https://snyk.io/test/github/ersims/botched/master
[snyk-image]: https://snyk.io/test/github/ersims/botched/master/badge.svg
[renovate-url]: https://renovateapp.com/
[renovate-image]: https://img.shields.io/badge/renovate-app-blue.svg
[conventional-commits-image]: https://img.shields.io/badge/Conventional%20Commits-1.0.0-yellow.svg
[conventional-commits-url]: https://conventionalcommits.org/
[license-url]: https://github.com/ersims/botched/blob/master/LICENSE.md
[license-image]: https://img.shields.io/github/license/ersims/botched.svg
[restify-errors-url]: https://github.com/restify/errors
[boom-url]: https://github.com/hapijs/boom
[verror-url]: https://github.com/joyent/node-verror
