Skip to content

Commit

Permalink
SolanaError now supports the cause property (#2235)
Browse files Browse the repository at this point in the history
# Summary

The runtime has built-in support for ‘sub exceptions.’ You can read about it here: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error/cause

> It is used when catching and re-throwing an error with a more-specific or useful error message in order to still have access to the original error.

This PR brings `cause` to `SolanaError` so that we can use it to rethrow errors under one umbrella error. Developers will have access to the inner error via `SolanaError#cause`.

The first error that will make use of this is the websocket close error, in the next PR.

# Test Plan

```shell
pnpm turbo test:unit:node test:unit:browser
```
  • Loading branch information
steveluscher committed Mar 4, 2024
1 parent 8023370 commit 803b2d8
Show file tree
Hide file tree
Showing 4 changed files with 94 additions and 17 deletions.
79 changes: 66 additions & 13 deletions packages/errors/src/__tests__/error-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,22 +4,75 @@ import { getErrorMessage } from '../message-formatter';
jest.mock('../message-formatter');

describe('SolanaError', () => {
let error123: SolanaError;
beforeEach(() => {
error123 = new SolanaError(
// @ts-expect-error Mock error codes don't conform to `SolanaErrorCode`
123,
{ foo: 'bar' },
);
describe('given an error with context', () => {
let errorWithContext: SolanaError;
beforeEach(() => {
errorWithContext = new SolanaError(
// @ts-expect-error Mock error codes don't conform to `SolanaErrorCode`
123,
{ foo: 'bar' },
);
});
it('exposes its error code', () => {
expect(errorWithContext.context).toHaveProperty('__code', 123);
});
it('exposes its context', () => {
expect(errorWithContext.context).toHaveProperty('foo', 'bar');
});
it('exposes no cause', () => {
expect(errorWithContext.cause).toBeUndefined();
});
it('calls the message formatter with the code and context', () => {
expect(getErrorMessage).toHaveBeenCalledWith(123, { foo: 'bar' });
});
});
it('exposes its error code', () => {
expect(error123.context).toHaveProperty('__code', 123);
describe('given an error with no context', () => {
beforeEach(() => {
new SolanaError(
// @ts-expect-error Mock error codes don't conform to `SolanaErrorCode`
123,
undefined,
);
});
it('calls the message formatter with undefined context', () => {
expect(getErrorMessage).toHaveBeenCalledWith(123, undefined);
});
});
it('exposes its context', () => {
expect(error123.context).toHaveProperty('foo', 'bar');
describe('given an error with a cause', () => {
let errorWithCause: SolanaError;
let cause: unknown;
beforeEach(() => {
cause = {};
errorWithCause = new SolanaError(
// @ts-expect-error Mock error codes don't conform to `SolanaErrorCode`
123,
{ cause },
);
});
it('exposes its cause', () => {
expect(errorWithCause.cause).toBe(cause);
});
});
it('calls the message formatter with the code and context', () => {
expect(getErrorMessage).toHaveBeenCalledWith(123, { foo: 'bar' });
describe.each(['cause'])('given an error with only the `%s` property from `ErrorOptions` present', propName => {
let errorOptionValue: unknown;
let errorWithOption: SolanaError;
beforeEach(() => {
errorOptionValue = Symbol();
errorWithOption = new SolanaError(
// @ts-expect-error Mock error codes don't conform to `SolanaErrorCode`
123,
{ [propName]: errorOptionValue },
);
});
it('omits the error option from its context', () => {
expect(errorWithOption.context).not.toHaveProperty(propName);
});
it('calls the message formatter with the error option omitted', () => {
expect(getErrorMessage).toHaveBeenCalledWith(
123,
expect.not.objectContaining({ [propName]: errorOptionValue }),
);
});
});
it('sets its message to the output of the message formatter', async () => {
expect.assertions(1);
Expand Down
11 changes: 11 additions & 0 deletions packages/errors/src/__typetests__/error-typetest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,3 +42,14 @@ if (isSolanaError(e, SOLANA_ERROR__TRANSACTION_MISSING_SIGNATURES)) {
// @ts-expect-error Context belongs to another error code
e.context satisfies SolanaErrorContext[typeof SOLANA_ERROR__TRANSACTION_SIGNATURE_NOT_COMPUTABLE];
}

// `SolanaErrorContext` must not contain any keys reserved by `ErrorOptions` (eg. `cause`)
null as unknown as SolanaErrorContext satisfies {
[Code in keyof SolanaErrorContext]: SolanaErrorContext[Code] extends undefined
? undefined
: {
[PP in keyof SolanaErrorContext[Code]]: PP extends keyof ErrorOptions
? never
: SolanaErrorContext[Code][PP];
};
};
20 changes: 16 additions & 4 deletions packages/errors/src/error.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,24 @@ type SolanaErrorCodedContext = Readonly<{
export class SolanaError<TErrorCode extends SolanaErrorCode = SolanaErrorCode> extends Error {
readonly context: SolanaErrorCodedContext[TErrorCode];
constructor(
...[code, context]: SolanaErrorContext[TErrorCode] extends undefined
? [code: TErrorCode]
: [code: TErrorCode, context: SolanaErrorContext[TErrorCode]]
...[code, contextAndErrorOptions]: SolanaErrorContext[TErrorCode] extends undefined
? [code: TErrorCode, errorOptions?: ErrorOptions | undefined]
: [code: TErrorCode, contextAndErrorOptions: SolanaErrorContext[TErrorCode] & (ErrorOptions | undefined)]
) {
let context: SolanaErrorContext[TErrorCode] | undefined;
let errorOptions: ErrorOptions | undefined;
if (contextAndErrorOptions) {
// If the `ErrorOptions` type ever changes, update this code.
const { cause, ...contextRest } = contextAndErrorOptions;
if (cause) {
errorOptions = { cause };
}
if (Object.keys(contextRest).length > 0) {
context = contextRest as SolanaErrorContext[TErrorCode];
}
}
const message = getErrorMessage(code, context);
super(message);
super(message, errorOptions);
this.context = {
__code: code,
...context,
Expand Down
1 change: 1 addition & 0 deletions packages/errors/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
"lib": [
"DOM",
"ES2015",
"ES2022.Error"
],
"resolveJsonModule": true
},
Expand Down

0 comments on commit 803b2d8

Please sign in to comment.