Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow passing a cause to predefined error functions #83

Merged
merged 6 commits into from
Apr 17, 2023
Merged
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
14 changes: 9 additions & 5 deletions jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,11 @@ module.exports = {
collectCoverage: true,

// An array of glob patterns indicating a set of files for which coverage information should be collected
collectCoverageFrom: ['./src/**/*.ts', '!./src/__fixtures__/**/*'],
collectCoverageFrom: [
'./src/**/*.ts',
'!./src/__fixtures__/**/*',
'!./src/index.ts',
],

// The directory where Jest should output its coverage files
coverageDirectory: 'coverage',
Expand All @@ -41,10 +45,10 @@ module.exports = {
// An object that configures minimum threshold enforcement for coverage results
coverageThreshold: {
global: {
branches: 94.5,
functions: 85.36,
lines: 96.88,
statements: 96.88,
branches: 94.25,
functions: 94.11,
lines: 97.02,
statements: 97.02,
},
},

Expand Down
24 changes: 20 additions & 4 deletions src/classes.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
import safeStringify from 'fast-safe-stringify';
import { Json, JsonRpcError as SerializedJsonRpcError } from '@metamask/utils';
import {
isPlainObject,
Json,
JsonRpcError as SerializedJsonRpcError,
} from '@metamask/utils';
import { DataWithOptionalCause, serializeCause } from './utils';

export { SerializedJsonRpcError };

Expand All @@ -9,7 +14,7 @@ export { SerializedJsonRpcError };
*
* Permits any integer error code.
*/
export class JsonRpcError<T extends Json> extends Error {
export class JsonRpcError<T extends DataWithOptionalCause> extends Error {
public code: number;

public data?: T;
Expand Down Expand Up @@ -40,13 +45,22 @@ export class JsonRpcError<T extends Json> extends Error {
code: this.code,
message: this.message,
};

if (this.data !== undefined) {
serialized.data = this.data;
// `this.data` is not guaranteed to be a plain object, but this simplifies
// the type guard below. We can safely cast it because we know it's a
// JSON-serializable value.
serialized.data = this.data as { [key: string]: Json };

if (isPlainObject(this.data)) {
serialized.data.cause = serializeCause(this.data.cause);
}
}

if (this.stack) {
serialized.stack = this.stack;
}

return serialized;
}

Expand All @@ -65,7 +79,9 @@ export class JsonRpcError<T extends Json> extends Error {
* Error subclass implementing Ethereum Provider errors per EIP-1193.
* Permits integer error codes in the [ 1000 <= 4999 ] range.
*/
export class EthereumProviderError<T extends Json> extends JsonRpcError<T> {
export class EthereumProviderError<
T extends DataWithOptionalCause,
> extends JsonRpcError<T> {
/**
* Create an Ethereum Provider JSON-RPC error.
*
Expand Down
59 changes: 52 additions & 7 deletions src/errors.test.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { assert, isPlainObject } from '@metamask/utils';
import { getMessageFromCode, JSON_RPC_SERVER_ERROR_MESSAGE } from './utils';
import {
dummyData,
Expand Down Expand Up @@ -45,7 +46,7 @@ describe('custom provider error options', () => {
});
});

describe('ethError.rpc.server', () => {
describe('rpcErrors.server', () => {
it('throws on invalid input', () => {
expect(() => {
// @ts-expect-error Invalid input
Expand All @@ -63,6 +64,16 @@ describe('ethError.rpc.server', () => {
rpcErrors.server({ code: 1 });
}).toThrow('"code" must be an integer such that: -32099 <= code <= -32005');
});

it('returns appropriate value', () => {
const error = rpcErrors.server({
code: SERVER_ERROR_CODE,
data: Object.assign({}, dummyData),
});

expect(error.code <= -32000 && error.code >= -32099).toBe(true);
expect(error.message).toBe(JSON_RPC_SERVER_ERROR_MESSAGE);
});
});

describe('rpcErrors', () => {
Expand All @@ -85,14 +96,26 @@ describe('rpcErrors', () => {
},
);

it('server returns appropriate value', () => {
const error = rpcErrors.server({
code: SERVER_ERROR_CODE,
data: Object.assign({}, dummyData),
it('serializes a cause', () => {
const error = rpcErrors.invalidInput({
data: {
foo: 'bar',
cause: new Error('foo'),
},
});

expect(error.code <= -32000 && error.code >= -32099).toBe(true);
expect(error.message).toBe(JSON_RPC_SERVER_ERROR_MESSAGE);
const serializedError = error.serialize();
assert(serializedError.data);
assert(isPlainObject(serializedError.data));

expect(serializedError.data.cause).not.toBeInstanceOf(Error);
expect(serializedError.data).toStrictEqual({
foo: 'bar',
cause: {
message: 'foo',
stack: expect.stringContaining('Error: foo'),
},
});
});
});

Expand Down Expand Up @@ -123,4 +146,26 @@ describe('providerErrors', () => {
expect(error.code).toBe(CUSTOM_ERROR_CODE);
expect(error.message).toBe(CUSTOM_ERROR_MESSAGE);
});

it('serializes a cause', () => {
const error = providerErrors.unauthorized({
data: {
foo: 'bar',
cause: new Error('foo'),
},
});

const serializedError = error.serialize();
assert(serializedError.data);
assert(isPlainObject(serializedError.data));

expect(serializedError.data.cause).not.toBeInstanceOf(Error);
expect(serializedError.data).toStrictEqual({
foo: 'bar',
cause: {
message: 'foo',
stack: expect.stringContaining('Error: foo'),
},
});
});
});
Loading