diff --git a/src/errors.test.ts b/src/errors.test.ts index 12d07c54..b2c01d0c 100644 --- a/src/errors.test.ts +++ b/src/errors.test.ts @@ -1,6 +1,7 @@ import fs from 'fs'; import { + ensureError, getErrorMessage, isErrorWithCode, isErrorWithMessage, @@ -320,3 +321,68 @@ describe('getErrorMessage', () => { expect(getErrorMessage(undefined)).toBe(''); }); }); + +describe('ensureError', () => { + it('returns Error instance unchanged', () => { + const originalError = new Error('original message'); + const result = ensureError(originalError); + + expect(result).toBe(originalError); + expect(result.message).toBe('original message'); + }); + + it('returns fs.promises-style error unchanged', async () => { + let originalError; + try { + await fs.promises.readFile('/tmp/nonexistent', 'utf8'); + } catch (error: unknown) { + originalError = error; + } + + const result = ensureError(originalError); + + expect(result).toBe(originalError); + }); + + it('converts string to Error and preserves original as cause', () => { + const result = ensureError('something went wrong'); + + expect(result).toBeInstanceOf(Error); + expect(result.message).toBe('Unknown error'); + expect(result.cause).toBe('something went wrong'); + }); + + it('converts object to Error and preserves original as cause', () => { + const originalObject = { some: 'object' }; + const result = ensureError(originalObject); + + expect(result).toBeInstanceOf(Error); + expect(result.message).toBe('Unknown error'); + expect(result.cause).toBe(originalObject); + }); + + it('handles null with descriptive message', () => { + const result = ensureError(null); + + expect(result).toBeInstanceOf(Error); + expect(result.message).toBe('Unknown error'); + expect(result.cause).toBeNull(); + }); + + it('handles undefined with descriptive message', () => { + const result = ensureError(undefined); + + expect(result).toBeInstanceOf(Error); + expect(result.message).toBe('Unknown error'); + expect(result.cause).toBeUndefined(); + }); + + it('preserves stack trace for Error instances', () => { + const originalError = new Error('original message'); + const originalStack = originalError.stack; + + const result = ensureError(originalError); + + expect(result.stack).toBe(originalStack); + }); +}); diff --git a/src/errors.ts b/src/errors.ts index 72e6dc22..2bb8d890 100644 --- a/src/errors.ts +++ b/src/errors.ts @@ -119,3 +119,22 @@ export function wrapError( return new Error(String(originalError)); } + +/** + * Ensures we have a proper Error object. + * If the input is already an Error, returns it unchanged. + * Otherwise, converts to an Error with an appropriate message and preserves + * the original value as the cause. + * + * @param error - The caught error (could be Error, string, or unknown). + * @returns A proper Error instance. + */ +export function ensureError(error: unknown): Error { + if (isError(error)) { + return error; + } + + const newError: Error & { cause?: unknown } = new Error('Unknown error'); + newError.cause = error; + return newError; +} diff --git a/src/index.test.ts b/src/index.test.ts index 9fd22c9d..6204787f 100644 --- a/src/index.test.ts +++ b/src/index.test.ts @@ -90,6 +90,7 @@ describe('index', () => { "createNumber", "createProjectLogger", "definePattern", + "ensureError", "exactOptional", "fromWei", "getChecksumAddress", diff --git a/src/node.test.ts b/src/node.test.ts index 1532d9c3..c3b927fd 100644 --- a/src/node.test.ts +++ b/src/node.test.ts @@ -93,6 +93,7 @@ describe('node', () => { "definePattern", "directoryExists", "ensureDirectoryStructureExists", + "ensureError", "exactOptional", "fileExists", "forceRemove",