Skip to content

Commit

Permalink
Implement SnapError class
Browse files Browse the repository at this point in the history
  • Loading branch information
Mrtenz committed Oct 10, 2023
1 parent 4fca3bd commit c60af82
Show file tree
Hide file tree
Showing 5 changed files with 449 additions and 3 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -1781,6 +1781,47 @@ describe('BaseSnapExecutor', () => {
});
});

it('handles `SnapError`s', async () => {
const CODE = `
module.exports.onRpcRequest = () => {
throw new SnapError('foo');
};
`;

const executor = new TestSnapExecutor();
await executor.executeSnap(1, MOCK_SNAP_ID, CODE, ['SnapError']);

expect(await executor.readCommand()).toStrictEqual({
jsonrpc: '2.0',
id: 1,
result: 'OK',
});

await executor.writeCommand({
jsonrpc: '2.0',
id: 2,
method: 'snapRpc',
params: [
MOCK_SNAP_ID,
HandlerType.OnRpcRequest,
MOCK_ORIGIN,
{ jsonrpc: '2.0', method: 'foo', params: {} },
],
});

expect(await executor.readCommand()).toStrictEqual({
id: 2,
jsonrpc: '2.0',
error: {
code: -32603,
message: 'foo',
data: {
originalError: {},
},
},
});
});

describe('executeSnap', () => {
[
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,10 @@ const fallbackError = {
message: 'Execution Environment Error',
};

const unhandledError = ethErrors.rpc.internal({
message: 'Unhandled Snap Error',
});

export type InvokeSnapArgs = Omit<SnapExportsParameters[0], 'chainId'>;

export type InvokeSnap = (
Expand Down Expand Up @@ -179,7 +183,7 @@ export class BaseSnapExecutor {
private errorHandler(error: unknown, data: Record<string, Json>) {
const constructedError = constructError(error);
const serializedError = serializeError(constructedError, {
fallbackError,
fallbackError: unhandledError,
shouldIncludeStack: false,
});

Expand Down Expand Up @@ -358,6 +362,7 @@ export class BaseSnapExecutor {
module: snapModule,
exports: snapModule.exports,
});

// All of those are JavaScript runtime specific and self referential,
// but we add them for compatibility sake with external libraries.
//
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import type { SnapId } from '@metamask/snaps-utils';
import { SnapError } from '@metamask/snaps-utils';

import { rootRealmGlobal } from '../globalObject';
import consoleEndowment from './console';
Expand Down Expand Up @@ -42,6 +43,7 @@ const commonEndowments: CommonEndowmentSpecification[] = [
{ endowment: Int8Array, name: 'Int8Array' },
{ endowment: Int16Array, name: 'Int16Array' },
{ endowment: Int32Array, name: 'Int32Array' },
{ endowment: SnapError, name: 'SnapError' },
{ endowment: Uint8Array, name: 'Uint8Array' },
{ endowment: Uint8ClampedArray, name: 'Uint8ClampedArray' },
{ endowment: Uint16Array, name: 'Uint16Array' },
Expand Down
242 changes: 241 additions & 1 deletion packages/snaps-utils/src/errors.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { ethErrors } from 'eth-rpc-errors';

import { getErrorMessage } from './errors';
import { BaseSnapError, getErrorMessage, SnapError } from './errors';

describe('getErrorMessage', () => {
it('returns the error message if the error is an object with a message property', () => {
Expand All @@ -18,3 +18,243 @@ describe('getErrorMessage', () => {
expect(getErrorMessage({ foo: 'bar' })).toBe('[object Object]');
});
});

describe('SnapError', () => {
it('creates an error from a message', () => {
const error = new SnapError('foo');

expect(error).toBeInstanceOf(Error);
expect(error).toBeInstanceOf(BaseSnapError);
expect(error).toBeInstanceOf(SnapError);
expect(error.message).toBe('foo');
expect(error.code).toBe(-32603);
expect(error.data).toStrictEqual({});
expect(error.stack).toBeDefined();
expect(error.toJSON()).toStrictEqual({
code: -32603,
message: 'foo',
data: {
stack: error.stack,
},
});
});

it('creates an error from a message and code', () => {
const error = new SnapError({
message: 'foo',
code: -32000,
});

expect(error).toBeInstanceOf(Error);
expect(error).toBeInstanceOf(BaseSnapError);
expect(error).toBeInstanceOf(SnapError);
expect(error.message).toBe('foo');
expect(error.code).toBe(-32000);
expect(error.data).toStrictEqual({});
expect(error.stack).toBeDefined();
expect(error.toJSON()).toStrictEqual({
code: -32000,
message: 'foo',
data: {
stack: error.stack,
},
});
});

it('creates an error from a message and data', () => {
const error = new SnapError('foo', { foo: 'bar' });

expect(error).toBeInstanceOf(Error);
expect(error).toBeInstanceOf(BaseSnapError);
expect(error).toBeInstanceOf(SnapError);
expect(error.message).toBe('foo');
expect(error.code).toBe(-32603);
expect(error.data).toStrictEqual({ foo: 'bar' });
expect(error.stack).toBeDefined();
});

it('creates an error from a message, code, and data', () => {
const error = new SnapError(
{
message: 'foo',
code: -32000,
},
{ foo: 'bar' },
);

expect(error).toBeInstanceOf(Error);
expect(error).toBeInstanceOf(BaseSnapError);
expect(error).toBeInstanceOf(SnapError);
expect(error.message).toBe('foo');
expect(error.code).toBe(-32000);
expect(error.data).toStrictEqual({ foo: 'bar' });
expect(error.stack).toBeDefined();
expect(error.toJSON()).toStrictEqual({
code: -32000,
message: 'foo',
data: {
foo: 'bar',
stack: error.stack,
},
});
});

it('creates an error from an error', () => {
const error = new SnapError(new Error('foo'));

expect(error).toBeInstanceOf(Error);
expect(error).toBeInstanceOf(BaseSnapError);
expect(error).toBeInstanceOf(SnapError);
expect(error.message).toBe('foo');
expect(error.code).toBe(-32603);
expect(error.data).toStrictEqual({});
expect(error.stack).toBeDefined();
expect(error.toJSON()).toStrictEqual({
code: -32603,
message: 'foo',
data: {
stack: error.stack,
},
});
});

it('creates an error from an error and data', () => {
const error = new SnapError(new Error('foo'), { foo: 'bar' });

expect(error).toBeInstanceOf(Error);
expect(error).toBeInstanceOf(BaseSnapError);
expect(error).toBeInstanceOf(SnapError);
expect(error.message).toBe('foo');
expect(error.code).toBe(-32603);
expect(error.data).toStrictEqual({ foo: 'bar' });
expect(error.stack).toBeDefined();
expect(error.toJSON()).toStrictEqual({
code: -32603,
message: 'foo',
data: {
foo: 'bar',
stack: error.stack,
},
});
});

it('creates an error from a JsonRpcError', () => {
const error = new SnapError(ethErrors.rpc.invalidParams('foo'));

expect(error).toBeInstanceOf(Error);
expect(error).toBeInstanceOf(BaseSnapError);
expect(error).toBeInstanceOf(SnapError);
expect(error.message).toBe('foo');
expect(error.code).toBe(-32602);
expect(error.data).toStrictEqual({});
expect(error.stack).toBeDefined();
expect(error.toJSON()).toStrictEqual({
code: -32602,
message: 'foo',
data: {
stack: error.stack,
},
});
});

it('creates an error from a JsonRpcError and data', () => {
const error = new SnapError(ethErrors.rpc.invalidParams('foo'), {
foo: 'bar',
});

expect(error).toBeInstanceOf(Error);
expect(error).toBeInstanceOf(BaseSnapError);
expect(error).toBeInstanceOf(SnapError);
expect(error.message).toBe('foo');
expect(error.code).toBe(-32602);
expect(error.data).toStrictEqual({ foo: 'bar' });
expect(error.stack).toBeDefined();
expect(error.toJSON()).toStrictEqual({
code: -32602,
message: 'foo',
data: {
foo: 'bar',
stack: error.stack,
},
});
});

it('creates an error from a JsonRpcError with a code of 0', () => {
const error = new SnapError({
message: 'foo',
code: 0,
});

expect(error).toBeInstanceOf(Error);
expect(error).toBeInstanceOf(BaseSnapError);
expect(error).toBeInstanceOf(SnapError);
expect(error.message).toBe('foo');
expect(error.code).toBe(0);
expect(error.data).toStrictEqual({});
expect(error.stack).toBeDefined();
expect(error.toJSON()).toStrictEqual({
code: 0,
message: 'foo',
data: {
stack: error.stack,
},
});
});

it('creates an error from a JsonRpcError with a code of 0 and data', () => {
const error = new SnapError(
{
message: 'foo',
code: 0,
},
{ foo: 'bar' },
);

expect(error).toBeInstanceOf(Error);
expect(error).toBeInstanceOf(BaseSnapError);
expect(error).toBeInstanceOf(SnapError);
expect(error.message).toBe('foo');
expect(error.code).toBe(0);
expect(error.data).toStrictEqual({ foo: 'bar' });
expect(error.stack).toBeDefined();
expect(error.toJSON()).toStrictEqual({
code: 0,
message: 'foo',
data: {
foo: 'bar',
stack: error.stack,
},
});
});

it('creates an error from a JsonRpcError with a code of 0 and merges the data', () => {
const error = new SnapError(
{
message: 'foo',
code: 0,
data: {
foo: 'baz',
bar: 'qux',
},
},
{ foo: 'bar' },
);

expect(error).toBeInstanceOf(Error);
expect(error).toBeInstanceOf(BaseSnapError);
expect(error).toBeInstanceOf(SnapError);
expect(error.message).toBe('foo');
expect(error.code).toBe(0);
expect(error.data).toStrictEqual({ foo: 'bar', bar: 'qux' });
expect(error.stack).toBeDefined();
expect(error.toJSON()).toStrictEqual({
code: 0,
message: 'foo',
data: {
foo: 'bar',
bar: 'qux',
stack: error.stack,
},
});
});
});
Loading

0 comments on commit c60af82

Please sign in to comment.