diff --git a/packages/utils/src/index.ts b/packages/utils/src/index.ts index c45ef9c21..4e6cc7a2e 100644 --- a/packages/utils/src/index.ts +++ b/packages/utils/src/index.ts @@ -1,5 +1,6 @@ export { exists } from '@code-pushup/models'; export { Diff, comparePairs, matchArrayItemsByKey } from './lib/diff'; +export { stringifyError } from './lib/errors'; export { ProcessConfig, ProcessError, diff --git a/packages/utils/src/lib/errors.ts b/packages/utils/src/lib/errors.ts new file mode 100644 index 000000000..9c92cb38f --- /dev/null +++ b/packages/utils/src/lib/errors.ts @@ -0,0 +1,13 @@ +export function stringifyError(error: unknown): string { + // TODO: special handling for ZodError instances + if (error instanceof Error) { + if (error.name === 'Error' || error.message.startsWith(error.name)) { + return error.message; + } + return `${error.name}: ${error.message}`; + } + if (typeof error === 'string') { + return error; + } + return JSON.stringify(error); +} diff --git a/packages/utils/src/lib/errors.unit.test.ts b/packages/utils/src/lib/errors.unit.test.ts new file mode 100644 index 000000000..f6251e8bf --- /dev/null +++ b/packages/utils/src/lib/errors.unit.test.ts @@ -0,0 +1,25 @@ +import { stringifyError } from './errors'; + +describe('stringifyError', () => { + it('should use only message from plain Error instance', () => { + expect(stringifyError(new Error('something went wrong'))).toBe( + 'something went wrong', + ); + }); + + it('should use class name and message from Error extensions', () => { + expect(stringifyError(new TypeError('invalid value'))).toBe( + 'TypeError: invalid value', + ); + }); + + it('should keep strings "as is"', () => { + expect(stringifyError('something went wrong')).toBe('something went wrong'); + }); + + it('should format objects as JSON', () => { + expect(stringifyError({ status: 400, statusText: 'Bad Request' })).toBe( + '{"status":400,"statusText":"Bad Request"}', + ); + }); +});