errors: add toError utility and migrate assertError usages
Add a `toError` utility function to `@backstage/errors` that converts unknown values to `ErrorLike` objects. If the value is already error-like it is returned as-is. Strings are used directly as the error message, and other values are stringified with a fallback to JSON.stringify to avoid unhelpful `[object Object]` messages. Non-error causes passed to `CustomErrorBase` are now converted and stored using `toError` rather than discarded. Existing `assertError` call sites across the codebase are migrated to `toError`. Signed-off-by: Patrik Oldsberg <poldsberg@gmail.com> Made-with: Cursor
This commit is contained in:
@@ -155,4 +155,7 @@ export class ServiceUnavailableError extends CustomErrorBase {}
|
||||
|
||||
// @public
|
||||
export function stringifyError(error: unknown): string;
|
||||
|
||||
// @public
|
||||
export function toError(value: unknown): ErrorLike;
|
||||
```
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
*/
|
||||
|
||||
import { stringifyError } from '../serialization/error';
|
||||
import { isError } from './assertion';
|
||||
import { toError } from './assertion';
|
||||
|
||||
/**
|
||||
* A base class that custom Error classes can inherit from.
|
||||
@@ -63,6 +63,6 @@ export class CustomErrorBase extends Error {
|
||||
}
|
||||
}
|
||||
|
||||
this.cause = isError(cause) ? cause : undefined;
|
||||
this.cause = cause !== undefined ? toError(cause) : undefined;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { assertError, isError } from './assertion';
|
||||
import { assertError, isError, toError } from './assertion';
|
||||
import { NotFoundError } from './common';
|
||||
import { CustomErrorBase } from './CustomErrorBase';
|
||||
|
||||
@@ -77,3 +77,85 @@ describe('isError', () => {
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
describe('toError', () => {
|
||||
it.each(areErrors)(
|
||||
'should pass through error-like values as-is %#',
|
||||
error => {
|
||||
expect(toError(error)).toBe(error);
|
||||
},
|
||||
);
|
||||
|
||||
it('should preserve the original error instance', () => {
|
||||
const original = new NotFoundError('not found');
|
||||
expect(toError(original)).toBe(original);
|
||||
});
|
||||
|
||||
it('should use strings directly as the error message', () => {
|
||||
const result = toError('something went wrong');
|
||||
expect(result).toBeInstanceOf(Error);
|
||||
expect(result.message).toBe('something went wrong');
|
||||
});
|
||||
|
||||
it('should handle empty strings', () => {
|
||||
const result = toError('');
|
||||
expect(result).toBeInstanceOf(Error);
|
||||
expect(result.message).toBe('');
|
||||
});
|
||||
|
||||
it('should wrap undefined', () => {
|
||||
const result = toError(undefined);
|
||||
expect(result).toBeInstanceOf(Error);
|
||||
expect(result.message).toBe("unknown error 'undefined'");
|
||||
});
|
||||
|
||||
it('should wrap null', () => {
|
||||
const result = toError(null);
|
||||
expect(result).toBeInstanceOf(Error);
|
||||
expect(result.message).toBe("unknown error 'null'");
|
||||
});
|
||||
|
||||
it('should wrap numbers', () => {
|
||||
expect(toError(0).message).toBe("unknown error '0'");
|
||||
expect(toError(42).message).toBe("unknown error '42'");
|
||||
});
|
||||
|
||||
it('should wrap booleans', () => {
|
||||
expect(toError(false).message).toBe("unknown error 'false'");
|
||||
expect(toError(true).message).toBe("unknown error 'true'");
|
||||
});
|
||||
|
||||
it('should wrap plain objects using JSON when toString is unhelpful', () => {
|
||||
expect(toError({ name: 'e' }).message).toBe(`unknown error '{"name":"e"}'`);
|
||||
expect(toError({ message: '' }).message).toBe(
|
||||
`unknown error '{"message":""}'`,
|
||||
);
|
||||
expect(toError({ code: 404, detail: 'missing' }).message).toBe(
|
||||
`unknown error '{"code":404,"detail":"missing"}'`,
|
||||
);
|
||||
});
|
||||
|
||||
it('should fall back to [object Object] for empty plain objects', () => {
|
||||
expect(toError({}).message).toBe("unknown error '[object Object]'");
|
||||
});
|
||||
|
||||
it('should wrap arrays', () => {
|
||||
expect(toError([]).message).toBe("unknown error ''");
|
||||
expect(toError([1, 2]).message).toBe("unknown error '1,2'");
|
||||
});
|
||||
|
||||
it('should handle objects with a custom toString', () => {
|
||||
const obj = { toString: () => 'custom string' };
|
||||
expect(toError(obj).message).toBe("unknown error 'custom string'");
|
||||
});
|
||||
|
||||
it('should handle symbols', () => {
|
||||
const result = toError(Symbol('test'));
|
||||
expect(result).toBeInstanceOf(Error);
|
||||
expect(result.message).toBe("unknown error 'Symbol(test)'");
|
||||
});
|
||||
|
||||
it('should handle BigInt', () => {
|
||||
expect(toError(BigInt(42)).message).toBe("unknown error '42'");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -71,3 +71,30 @@ export function assertError(value: unknown): asserts value is ErrorLike {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts an unknown value to an {@link ErrorLike} object.
|
||||
*
|
||||
* If the value is already an {@link ErrorLike} object, it is returned as-is. Otherwise, a new
|
||||
* `Error` is created with the value stringified as the message.
|
||||
*
|
||||
* @public
|
||||
* @param value - an unknown value
|
||||
* @returns an {@link ErrorLike} object
|
||||
*/
|
||||
export function toError(value: unknown): ErrorLike {
|
||||
if (isError(value)) {
|
||||
return value;
|
||||
}
|
||||
if (typeof value === 'string') {
|
||||
return new Error(value) as ErrorLike;
|
||||
}
|
||||
const str = String(value);
|
||||
if (str === '[object Object]') {
|
||||
const json = JSON.stringify(value);
|
||||
if (json !== '{}') {
|
||||
return new Error(`unknown error '${json}'`) as ErrorLike;
|
||||
}
|
||||
}
|
||||
return new Error(`unknown error '${str}'`) as ErrorLike;
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
export { assertError, isError } from './assertion';
|
||||
export { assertError, isError, toError } from './assertion';
|
||||
export type { ErrorLike } from './assertion';
|
||||
export {
|
||||
AuthenticationError,
|
||||
|
||||
Reference in New Issue
Block a user