Skip to content

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

Merged
abretonc7s merged 6 commits into
mainfrom
feat/add-ensure-error-utility
Feb 9, 2026
Merged

feat: Add ensureError utility for converting unknown values to error#275
abretonc7s merged 6 commits into
mainfrom
feat/add-ensure-error-utility

Conversation

@abretonc7s

@abretonc7sabretonc7s commented Feb 4, 2026

Copy link
Copy Markdown
Contributor

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:

UtilityPurposeOutput
getErrorMessage()Extract message safelystring
wrapError()Add context, create error chainError (new, with cause)
ensureError()Guarantee Error instanceError (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
@abretonc7sabretonc7s changed the title feat: Add ensureError utility for converting unknown values to errorErrorfeat: Add ensureError utility for converting unknown values to errorFeb 4, 2026
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

CopilotAI left a comment

Copy link
Copy Markdown

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.

FileDescription
src/errors.tsImplements the new ensureError function with proper type guards and message handling
src/errors.test.tsAdds comprehensive test suite covering all scenarios including Error preservation, type conversions, and context handling
src/index.test.tsUpdates export snapshot to include ensureError in alphabetical order
src/node.test.tsUpdates export snapshot to include ensureError in alphabetical order

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

Comment threadsrc/errors.test.ts Outdated
Comment threadsrc/errors.ts Outdated
Comment threadsrc/errors.ts Outdated
@Gudahtt

Copy link
Copy Markdown
Member

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>
Comment threadsrc/errors.ts Outdated
- 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>

@cursorcursorBot left a comment

Copy link
Copy Markdown

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.

Comment threadsrc/errors.ts Outdated
Comment threadsrc/errors.ts Outdated
Comment threadsrc/errors.ts Outdated
Gudahtt
Gudahtt previously requested changes Feb 5, 2026

@GudahttGudahtt left a comment

Copy link
Copy Markdown
Member

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

Comment threadsrc/errors.ts Outdated
@Gudahtt
Gudahtt dismissed their stale reviewFebruary 5, 2026 16:02

Requested changes (ts-ignore removal) have been made

@GudahttGudahtt left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

LGTM!

@abretonc7s
abretonc7s merged commit 9c53368 into mainFeb 9, 2026
22 checks passed
@abretonc7s
abretonc7s deleted the feat/add-ensure-error-utility branch February 9, 2026 12:48
Sign up for freeto 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.

4 participants

@abretonc7s@Gudahtt@mcmire