Skip to content

Commit

Permalink
Rework to be a regular class instance
Browse files Browse the repository at this point in the history
  • Loading branch information
kanongil committed Dec 1, 2023
1 parent faf4cf6 commit 1813bef
Show file tree
Hide file tree
Showing 3 changed files with 132 additions and 171 deletions.
6 changes: 0 additions & 6 deletions API.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,6 @@ Rebuilds `error.output` using the other object properties where:

- `debug` - a Boolean that, when `true`, causes Internal Server Error messages to be left in tact. Defaults to `false`, meaning that Internal Server Error messages are redacted.

Note that `Boom` object will return `true` when used with `instanceof Boom`, but do not use the
`Boom` prototype (they are either plain `Error` or the error prototype passed in). This means
`Boom` objects should only be tested using `instanceof Boom` or `Boom.isBoom()` but not by looking
at the prototype or contructor information. This limitation is to avoid manipulating the prototype
chain which is very slow.

#### Helper Methods

##### `new Boom.Boom(message, [options])`
Expand Down
161 changes: 76 additions & 85 deletions lib/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -68,25 +68,32 @@ const internals = {

exports.Boom = class extends Error {

isBoom = true;
isServer;
data = null;
output;

constructor(messageOrError, options = {}) {

let cause;

if (messageOrError instanceof Error) {
return exports.boomify(Hoek.clone(messageOrError), options);
cause = messageOrError;
messageOrError = options.message;
options.message = null;
}
else if (typeof messageOrError !== 'string') {
messageOrError = options.message;
options.message = null;
}

const { statusCode = 500, data = null, ctor = exports.Boom } = options;
const error = new Error(messageOrError ? messageOrError : undefined); // Avoids settings null message
Error.captureStackTrace(error, ctor); // Filter the stack to our external API
error.data = data;
const boom = internals.initialize(error, statusCode);

Object.defineProperty(boom, 'typeof', { value: ctor });
const { statusCode = 500, data, decorate, message, ctor = exports.Boom } = options;

if (options.decorate) {
Object.assign(boom, options.decorate);
}
super(messageOrError ?? internals.codes.get(statusCode) ?? 'Unknown', { cause });
Error.captureStackTrace(this, ctor); // Filter the stack to our external API
this._apply(data, decorate, statusCode, message);

return boom;
Object.defineProperty(this, 'typeof', { value: ctor });
}

static [Symbol.hasInstance](instance) {
Expand All @@ -99,40 +106,79 @@ exports.Boom = class extends Error {

return this.prototype.isPrototypeOf(instance);
}
};

reformat(debug = false) {

exports.isBoom = function (err, statusCode) {
this.output.payload.statusCode = this.output.statusCode;
this.output.payload.error = internals.codes.get(this.output.statusCode) ?? 'Unknown';

return err instanceof Error && !!err.isBoom && (!statusCode || err.output.statusCode === statusCode);
};
if (this.output.statusCode === 500 && debug !== true) {
this.output.payload.message = 'An internal server error occurred'; // Hide actual error from user
}
else {
this.output.payload.message = this.message;
if (this.cause?.message) {
this.output.payload.message = (this.message === this.output.payload.error) ? this.cause.message : this.message + ': ' + this.cause.message;
}
}
}

_apply(data, decorate, statusCode, message) {

if (data !== undefined) {
this.data = data;
}

if (decorate) {
Object.assign(this, decorate);
}

exports.boomify = function (err, options) {
if (statusCode) {
const numberCode = parseInt(statusCode, 10);
Hoek.assert(!isNaN(numberCode) && numberCode >= 400, 'statusCode must be a number (400+):', statusCode);

Hoek.assert(err instanceof Error, 'Cannot wrap non-Error object');
this.isServer = numberCode >= 500;

options = options || {};
this.output = {
statusCode: numberCode,
payload: {},
headers: {}
};

if (options.data !== undefined) {
err.data = options.data;
}
if (message) {
this.message = `${message}: ${this.message}`;
}

if (options.decorate) {
Object.assign(err, options.decorate);
this.reformat();
}
}
};


exports.isBoom = function (err, statusCode) {

return err instanceof Error && !!err.isBoom && (!statusCode || err.output.statusCode === statusCode);
};


exports.boomify = function (err, options = {}) {

Hoek.assert(err instanceof Error, 'Cannot boomify non-Error object');

if (!err.isBoom) {
return internals.initialize(err, options.statusCode ?? 500, options.message);
return new exports.Boom(err, options);
}

if (options.override === false || // Defaults to true
!options.statusCode && !options.message) {
const { override, data, decorate, statusCode, message } = options;

return err;
if (override === false) { // Defaults to true
err._apply(data, decorate);
}
else {
err._apply(data, decorate, statusCode ?? err.output.statusCode, message);
}

return internals.initialize(err, options.statusCode ?? err.output.statusCode, options.message);
return err;
};


Expand Down Expand Up @@ -397,67 +443,12 @@ exports.badImplementation = function (message, data) {
return err;
};


internals.initialize = function (err, statusCode, message) {

const numberCode = parseInt(statusCode, 10);
Hoek.assert(!isNaN(numberCode) && numberCode >= 400, 'First argument must be a number (400+):', statusCode);

err.isBoom = true;
err.isServer = numberCode >= 500;

if (!err.hasOwnProperty('data')) {
err.data = null;
}

err.output = {
statusCode: numberCode,
payload: {},
headers: {}
};

Object.defineProperty(err, 'reformat', { value: internals.reformat, configurable: true });

if (!message &&
!err.message) {

err.reformat();
message = err.output.payload.error;
}

if (message) {
const props = Object.getOwnPropertyDescriptor(err, 'message') || Object.getOwnPropertyDescriptor(Object.getPrototypeOf(err), 'message');
Hoek.assert(!props || props.configurable && !props.get, 'The error is not compatible with boom');

err.message = message + (err.message ? ': ' + err.message : '');
err.output.payload.message = err.message;
}

err.reformat();
return err;
};


internals.reformat = function (debug = false) {

this.output.payload.statusCode = this.output.statusCode;
this.output.payload.error = internals.codes.get(this.output.statusCode) || 'Unknown';

if (this.output.statusCode === 500 && debug !== true) {
this.output.payload.message = 'An internal server error occurred'; // Hide actual error from user
}
else if (this.message) {
this.output.payload.message = this.message;
}
};


internals.serverError = function (messageOrError, data, statusCode, ctor) {

if (data instanceof Error &&
!data.isBoom) {

return exports.boomify(data, { statusCode, message: messageOrError });
return new exports.Boom(data, { statusCode, message: messageOrError, ctor });
}

return new exports.Boom(messageOrError, { statusCode, data, ctor });
Expand Down

0 comments on commit 1813bef

Please sign in to comment.