1.1.1 • Published 3 years ago

safe-error-class v1.1.1

Weekly downloads
-
License
BSD-4-Clause
Repository
gitlab
Last release
3 years ago

safe-error-class

Better errors that are extensible and don't leak sensitive data to end-users.

Quick Start

Use SafeError as an alternative to Error. You pass in an error id, an optional error message, and an optional dictionary of facets to attach to the error. See the FAQ at the bottom for helpful hints.

import { SafeError } from 'safe-error-class';

const err = new SafeError(`SOME_ERROR_ID`, `User password was incorrect`, { email: `silly.user@example.com` });

// All typical error properties are available as usual.
err.name; // --> SafeError
err.message; // --> SOME_ERROR_ID
err.stack; // --> SafeError: SOME_ERROR_ID...

// Plus some extra properties.
err.id; // --> SOME_ERROR_ID
err.sensitive; // --> User password was incorrect
err.facets.email; // --> silly.user@example.com

throw err;

Chaining Errors

SafeError can also chain (or wrap) other SafeError objects as well as plain JavaScript Error objects. When constructing a new SafeError just pass whatever error you caught into the sensitive parameter (instead of a string):

function parseJSON(input: string): Object {
	try {
		return JSON.parse(input);
	} catch (err: unknown) {
		const newErr = new SafeError(`PARSE_JSON`, err, { input });
		console.log(newErr.facets.$err.message); // SyntaxError: JSON.parse: unexpected character at line 1...
		throw newErr;
	}
}

Constructor Signature

new (id: string, sensitive?: string | SafeError | Error, facets?: IErrorFacets) => SafeError;

Recommended Pattern

The recommended way to use SafeError is not to throw them around like grenades, rather you should return them from your functions, and at the callsite check whether the return value was an instance of Error or not (do not check for SafeError, see below).

You should include an error ID that can be used to identify the error in logs and programatically, but this ID must not contain any sensitive information that can't be exposed in logs or to end-users.

However, it's safe to include sensitive information in the second and third parameters, as below:

function whatIsTheMeaningOfLife(answer: number): SafeError | void {
	if (answer != 42) return new SafeError(`BAD_ANSWER`, `Everybody knows the answer is 42`, { answer });
}

const something = whatIsTheMeaningOfLife(41);
if (something instanceof Error) {
	// Handle the error.
	console.error(`${err.id} - User was out by ${42 - err.facets.answer}!`);
}

// Otherwise continue the happy path!
// ...

We do not check for an instance of SafeError at the callsite, because as we know, Error grenades can be thrown around at will in JavaScript. By far the easiest thing to do is to catch these errors and return them:

function maybeExplode(): Error | void {
	try {
		throw new Error(`Explosion incoming...`); // Some third party code...
	} catch (err: unknown) {
		return err;
	}
}

But of course JavaScript isn't that clean. Anything and everything can be thrown, not just Error's. So you might be better off wrapping whatever gets caught into a new SafeError and be done with it:

function maybeExplode(): SafeError | void {
	try {
		throw `Explosion incoming...`; // Some evil third party code...
	} catch (err: unknown) {
		return new SafeError(`WHOOPS_IT_EXPLODED`, err);
	}
}

FAQ

What is safe-error-class?

The SafeError exported by this package can be used as a drop-in alternative for the built-in JavaScript Error. You pass in an error id, an optional error message, and an optional dictionary of facets to attach to the error. The error message and facets will not be exposed in logs or to end-users by mistake because they are not included in the typical Error's message property.

Can I chain errors?

Yes you can! When you catch an error you can pass it in as the second, 'sensitive', parameter when constructing a new SafeError:

function maybeExplode(): SafeError | void {
	try {
		throw new SafeError(`SOME_ERROR`, `Something bad this way comes`);
	} catch (err: unknown) {
		return new SafeError(`WHOOPS_IT_EXPLODED`, err);
	}
}

Can I wrap errors?

Yes, see: Can I chain errors?

1.1.1

3 years ago

1.1.0

3 years ago

1.0.0

3 years ago