errors: added ForwardedError, unknown cause, and unknown fields in ErrorLike
Signed-off-by: Patrik Oldsberg <poldsberg@gmail.com>
This commit is contained in:
@@ -3,3 +3,5 @@
|
||||
---
|
||||
|
||||
Two new helpers have been added that make it easier to migrate to considering thrown errors to be of the type `unknown` in TypeScript. The helpers are `assertError` and `isError`, and can be called to make sure that an unknown value conforms to the shape of an `ErrorLike` object. The `assertError` function is a type-guard that throws in the case of a mismatch, while `isError` returns false.
|
||||
|
||||
A new error constructor has also been added, `ForwardedError`, which can be used to add context to a forwarded error. It requires both a message and a cause, and inherits the `name` property from the `cause`.
|
||||
|
||||
@@ -14,17 +14,28 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { isError } from './assertion';
|
||||
|
||||
/** @public */
|
||||
export class CustomErrorBase extends Error {
|
||||
readonly cause?: Error;
|
||||
|
||||
constructor(message?: string, cause?: Error) {
|
||||
constructor(message?: string, cause?: Error | unknown) {
|
||||
let assignedCause: Error | undefined = undefined;
|
||||
|
||||
let fullMessage = message;
|
||||
if (cause) {
|
||||
if (fullMessage) {
|
||||
fullMessage += `; caused by ${cause}`;
|
||||
if (cause !== undefined) {
|
||||
let causeStr;
|
||||
if (isError(cause)) {
|
||||
assignedCause = cause;
|
||||
causeStr = String(cause);
|
||||
} else {
|
||||
fullMessage = `caused by ${cause}`;
|
||||
causeStr = `unknown error '${cause}'`;
|
||||
}
|
||||
if (fullMessage) {
|
||||
fullMessage += `; caused by ${causeStr}`;
|
||||
} else {
|
||||
fullMessage = `caused by ${causeStr}`;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,6 +44,6 @@ export class CustomErrorBase extends Error {
|
||||
Error.captureStackTrace?.(this, this.constructor);
|
||||
|
||||
this.name = this.constructor.name;
|
||||
this.cause = cause;
|
||||
this.cause = assignedCause;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ export type ErrorLike = {
|
||||
name: string;
|
||||
message: string;
|
||||
stack?: string;
|
||||
[unknownKeys: string]: unknown;
|
||||
};
|
||||
|
||||
export function isError(val: unknown): val is ErrorLike {
|
||||
|
||||
@@ -18,7 +18,8 @@ import * as errors from './common';
|
||||
|
||||
describe('common', () => {
|
||||
it('extends Error properly', () => {
|
||||
for (const [name, E] of Object.entries(errors)) {
|
||||
const { ForwardedError: _, ...optionalCauseErrors } = { ...errors };
|
||||
for (const [name, E] of Object.entries(optionalCauseErrors)) {
|
||||
const error = new E('abcdef');
|
||||
expect(error.name).toBe(name);
|
||||
expect(error.message).toBe('abcdef');
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { isError } from './assertion';
|
||||
import { CustomErrorBase } from './CustomErrorBase';
|
||||
|
||||
/*
|
||||
@@ -72,3 +73,19 @@ export class ConflictError extends CustomErrorBase {}
|
||||
* @public
|
||||
*/
|
||||
export class NotModifiedError extends CustomErrorBase {}
|
||||
|
||||
/**
|
||||
* An error that forwards an underlying cause with additional context in the message.
|
||||
*
|
||||
* The `name` property of the error will be inherited from the `cause` if
|
||||
* possible, and will otherwise be set to `'Error'`.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export class ForwardedError extends CustomErrorBase {
|
||||
constructor(message: string, cause: Error | unknown) {
|
||||
super(message, cause);
|
||||
|
||||
this.name = isError(cause) ? this.constructor.name : 'Error';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@ export type { ErrorLike } from './assertion';
|
||||
export {
|
||||
AuthenticationError,
|
||||
ConflictError,
|
||||
ForwardedError,
|
||||
InputError,
|
||||
NotAllowedError,
|
||||
NotFoundError,
|
||||
|
||||
Reference in New Issue
Block a user