Skip to content

feat: Add ensureError utility for converting unknown values to error#275

Open
abretonc7s wants to merge 6 commits intomainfrom
feat/add-ensure-error-utility
Open

feat: Add ensureError utility for converting unknown values to error#275
abretonc7s wants to merge 6 commits intomainfrom
feat/add-ensure-error-utility

Conversation

@abretonc7s
Copy link

@abretonc7s abretonc7s commented Feb 4, 2026

Summary

This utility already exists in metamask-mobile and is duplicated across different teams/codebases. It makes sense to have it published in @metamask/utils as a shared utility.

Currently, when catching errors in JavaScript/TypeScript, the caught value can be anything (unknown). The existing error utilities provide:

  • Type guards: isErrorWithCode, isErrorWithMessage, isErrorWithStack
  • Message extraction: getErrorMessage(error) → returns a string
  • Error wrapping: wrapError(error, message) → creates new Error with cause link

However, there is no utility to guarantee an Error instance from an unknown caught value without wrapping it in a new Error.

This PR adds ensureError(error: unknown): Error which:

  • Returns the original Error instance unchanged if already an Error
  • Converts non-Error values to Error("Unknown error") with the original value preserved in cause

For adding context, developers can compose with wrapError: wrapError(ensureError(err), 'context')

Why this is needed

In mobile, we have many locations doing error instanceof Error ? error : new Error(error). This utility standardizes that pattern. External libraries and third-party SDKs sometimes throw non-Error values (undefined, strings, objects), and we need to convert them to proper Error instances for Sentry to process correctly.

Real example: METAMASK-MOBILE-5DKA - 10,675 occurrences affecting 429 users where HyperLiquid SDK throws undefined.

Key differences from wrapError:

Utility Purpose Output
getErrorMessage() Extract message safely string
wrapError() Add context, create error chain Error (new, with cause)
ensureError() Guarantee Error instance Error (original or converted)
  • ensureError returns the original Error if already an Error (no wrapping)
  • wrapError always creates a new Error with a cause chain

Implementation notes

  • Uses direct property assignment for cause to preserve the original thrown value for debugging
  • Simple type assertion avoids the need for polyfills or TypeScript ignores

Test plan

  • Added comprehensive tests for all scenarios (Error instances, fs.promises errors, strings, objects, null, undefined)
  • Tests verify Error instances are returned unchanged (preserving stack traces)
  • Tests verify original value is preserved in cause
  • All existing tests pass
  • 100% code coverage maintained
  • Linting passes
  • Build succeeds

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@abretonc7s abretonc7s enabled auto-merge (squash) February 4, 2026 05:15
@abretonc7s abretonc7s changed the title feat: Add ensureError utility for converting unknown values to errorError feat: Add ensureError utility for converting unknown values to error Feb 4, 2026
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adds a new ensureError utility function to the @metamask/utils package that guarantees an Error instance from unknown values without wrapping existing errors. This addresses a common need across MetaMask codebases where caught errors need to be converted to Error instances while preserving original errors unchanged.

Changes:

  • Adds ensureError(error: unknown, context?: string): Error function that returns existing Error instances unchanged or converts other values to Error instances
  • Provides comprehensive test coverage for all code paths including Error instances, fs.promises errors, primitives (strings, numbers), objects, and null/undefined
  • Updates export snapshots in both index and node entry points

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated no comments.

File Description
src/errors.ts Implements the new ensureError function with proper type guards and message handling
src/errors.test.ts Adds comprehensive test suite covering all scenarios including Error preservation, type conversions, and context handling
src/index.test.ts Updates export snapshot to include ensureError in alphabetical order
src/node.test.ts Updates export snapshot to include ensureError in alphabetical order

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@Gudahtt
Copy link
Member

Gudahtt commented Feb 4, 2026

I had a couple of questions, but good idea to make this a shared utility. I've wanted this for a long time. The type checking/coercion in every single catch block is such a pain.

- Use `{ cause: error }` to preserve original value instead of string conversion
- Standardize message to "Unknown error" for all non-Error values
- Remove unnecessary number-to-error test case
- Update tests to verify cause preservation

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Use ErrorWithCause polyfill when native Error cause is not supported
- Follows same pattern as existing wrapError function
- Add istanbul ignore for runtime-dependent branch

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Copy link

@cursor cursor bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Gudahtt
Gudahtt previously requested changes Feb 5, 2026
Copy link
Member

@Gudahtt Gudahtt left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Requesting changes due to disallowed @ts-ignore directives

@Gudahtt Gudahtt dismissed their stale review February 5, 2026 16:02

Requested changes (ts-ignore removal) have been made

@abretonc7s abretonc7s requested a review from Gudahtt February 6, 2026 07:34
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants