Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 14 additions & 2 deletions src/utils/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,19 @@ interface ErrorWithCode extends Error {
code: string
}

/**
* Narrow an `unknown` catch binding to an error carrying a `code`.
*
* `null` and `undefined` are legal `unknown` values — `throw null` and
* `Promise.reject()` with no reason both produce one — so they have to be ruled
* out before the property is read. Reading `.code` off a nullish value throws
* `TypeError: Cannot read properties of null`, which is the opposite of what a
* guard is for: the caller loses the error it was about to inspect or rethrow
* and gets one that says nothing about what actually failed.
*/
export const hasErrorCode = (error: unknown): error is ErrorWithCode => {
const untypedError = error as Partial<ErrorWithCode>
return !!untypedError.code
if (error === null || error === undefined) {
return false
}
return !!(error as Partial<ErrorWithCode>).code
}
18 changes: 18 additions & 0 deletions test/sdk/misc.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
} from "../../src/constants"
import {
decodeTokenIds,
hasErrorCode,
remapSharedStorefrontAddress,
} from "../../src/utils/utils"
import { BAYC_CONTRACT_ADDRESS } from "../utils/constants"
Expand Down Expand Up @@ -172,4 +173,21 @@ describe("SDK: misc", () => {
])
})
})

describe("hasErrorCode", () => {
it("should return true for an error carrying a code", () => {
expect(hasErrorCode({ code: "CALL_EXCEPTION" })).toBe(true)
expect(hasErrorCode(new Error("boom"))).toBe(false)
})

it("should return false for nullish input instead of throwing", () => {
// `null` and `undefined` are legal `unknown` values and both reach a
// `catch` binding (`throw null`, `Promise.reject()`). The guard read
// `.code` straight off the argument, so it threw a TypeError and hid the
// error its caller was about to rethrow.
expect(() => hasErrorCode(null)).not.toThrow()
expect(hasErrorCode(null)).toBe(false)
expect(hasErrorCode(undefined)).toBe(false)
})
})
})