From 454290b53042b72a835200647f448175f72eaf3b Mon Sep 17 00:00:00 2001 From: Erik Marks Date: Mon, 11 Jan 2021 09:13:17 -0800 Subject: [PATCH 1/3] Make error getter argument object optional --- src/errors.ts | 44 ++++++++++++++------------- src/utils.ts | 68 ++++++++++++++++++++++-------------------- test/serializeError.js | 15 ++++------ 3 files changed, 64 insertions(+), 63 deletions(-) diff --git a/src/errors.ts b/src/errors.ts index ab13308..cb2c107 100644 --- a/src/errors.ts +++ b/src/errors.ts @@ -11,7 +11,7 @@ interface ServerErrorOptions extends EthereumErrorOptions { code: number; } -type CustomErrorOptions = ServerErrorOptions; +type CustomErrorArg = ServerErrorOptions; type EthErrorsArg = EthereumErrorOptions | string; @@ -21,35 +21,35 @@ export const ethErrors = { /** * Get a JSON RPC 2.0 Parse (-32700) error. */ - parse: (arg: EthErrorsArg) => getEthJsonRpcError( + parse: (arg?: EthErrorsArg) => getEthJsonRpcError( errorCodes.rpc.parse, arg, ), /** * Get a JSON RPC 2.0 Invalid Request (-32600) error. */ - invalidRequest: (arg: EthErrorsArg) => getEthJsonRpcError( + invalidRequest: (arg?: EthErrorsArg) => getEthJsonRpcError( errorCodes.rpc.invalidRequest, arg, ), /** * Get a JSON RPC 2.0 Invalid Params (-32602) error. */ - invalidParams: (arg: EthErrorsArg) => getEthJsonRpcError( + invalidParams: (arg?: EthErrorsArg) => getEthJsonRpcError( errorCodes.rpc.invalidParams, arg, ), /** * Get a JSON RPC 2.0 Method Not Found (-32601) error. */ - methodNotFound: (arg: EthErrorsArg) => getEthJsonRpcError( + methodNotFound: (arg?: EthErrorsArg) => getEthJsonRpcError( errorCodes.rpc.methodNotFound, arg, ), /** * Get a JSON RPC 2.0 Internal (-32603) error. */ - internal: (arg: EthErrorsArg) => getEthJsonRpcError( + internal: (arg?: EthErrorsArg) => getEthJsonRpcError( errorCodes.rpc.internal, arg, ), @@ -74,42 +74,42 @@ export const ethErrors = { /** * Get an Ethereum JSON RPC Invalid Input (-32000) error. */ - invalidInput: (arg: EthErrorsArg) => getEthJsonRpcError( + invalidInput: (arg?: EthErrorsArg) => getEthJsonRpcError( errorCodes.rpc.invalidInput, arg, ), /** * Get an Ethereum JSON RPC Resource Not Found (-32001) error. */ - resourceNotFound: (arg: EthErrorsArg) => getEthJsonRpcError( + resourceNotFound: (arg?: EthErrorsArg) => getEthJsonRpcError( errorCodes.rpc.resourceNotFound, arg, ), /** * Get an Ethereum JSON RPC Resource Unavailable (-32002) error. */ - resourceUnavailable: (arg: EthErrorsArg) => getEthJsonRpcError( + resourceUnavailable: (arg?: EthErrorsArg) => getEthJsonRpcError( errorCodes.rpc.resourceUnavailable, arg, ), /** * Get an Ethereum JSON RPC Transaction Rejected (-32003) error. */ - transactionRejected: (arg: EthErrorsArg) => getEthJsonRpcError( + transactionRejected: (arg?: EthErrorsArg) => getEthJsonRpcError( errorCodes.rpc.transactionRejected, arg, ), /** * Get an Ethereum JSON RPC Method Not Supported (-32004) error. */ - methodNotSupported: (arg: EthErrorsArg) => getEthJsonRpcError( + methodNotSupported: (arg?: EthErrorsArg) => getEthJsonRpcError( errorCodes.rpc.methodNotSupported, arg, ), /** * Get an Ethereum JSON RPC Limit Exceeded (-32005) error. */ - limitExceeded: (arg: EthErrorsArg) => getEthJsonRpcError( + limitExceeded: (arg?: EthErrorsArg) => getEthJsonRpcError( errorCodes.rpc.limitExceeded, arg, ), }, @@ -119,7 +119,7 @@ export const ethErrors = { /** * Get an Ethereum Provider User Rejected Request (4001) error. */ - userRejectedRequest: (arg: EthErrorsArg) => { + userRejectedRequest: (arg?: EthErrorsArg) => { return getEthProviderError( errorCodes.provider.userRejectedRequest, arg, ); @@ -128,7 +128,7 @@ export const ethErrors = { /** * Get an Ethereum Provider Unauthorized (4100) error. */ - unauthorized: (arg: EthErrorsArg) => { + unauthorized: (arg?: EthErrorsArg) => { return getEthProviderError( errorCodes.provider.unauthorized, arg, ); @@ -137,7 +137,7 @@ export const ethErrors = { /** * Get an Ethereum Provider Unsupported Method (4200) error. */ - unsupportedMethod: (arg: EthErrorsArg) => { + unsupportedMethod: (arg?: EthErrorsArg) => { return getEthProviderError( errorCodes.provider.unsupportedMethod, arg, ); @@ -146,7 +146,7 @@ export const ethErrors = { /** * Get an Ethereum Provider Not Connected (4900) error. */ - disconnected: (arg: EthErrorsArg) => { + disconnected: (arg?: EthErrorsArg) => { return getEthProviderError( errorCodes.provider.disconnected, arg, ); @@ -155,7 +155,7 @@ export const ethErrors = { /** * Get an Ethereum Provider Chain Not Connected (4901) error. */ - chainDisconnected: (arg: EthErrorsArg) => { + chainDisconnected: (arg?: EthErrorsArg) => { return getEthProviderError( errorCodes.provider.chainDisconnected, arg, ); @@ -164,11 +164,13 @@ export const ethErrors = { /** * Get a custom Ethereum Provider error. */ - custom: (opts: CustomErrorOptions) => { + custom: (opts: CustomErrorArg) => { if (!opts || typeof opts !== 'object' || Array.isArray(opts)) { throw new Error('Ethereum Provider custom errors must provide single object argument.'); } + const { code, message, data } = opts; + if (!message || typeof message !== 'string') { throw new Error( '"message" must be a nonempty string', @@ -181,7 +183,7 @@ export const ethErrors = { // Internal -function getEthJsonRpcError(code: number, arg: EthErrorsArg): EthereumRpcError { +function getEthJsonRpcError(code: number, arg?: EthErrorsArg): EthereumRpcError { const [message, data] = parseOpts(arg); return new EthereumRpcError( code, @@ -190,7 +192,7 @@ function getEthJsonRpcError(code: number, arg: EthErrorsArg): EthereumRpcE ); } -function getEthProviderError(code: number, arg: EthErrorsArg): EthereumProviderError { +function getEthProviderError(code: number, arg?: EthErrorsArg): EthereumProviderError { const [message, data] = parseOpts(arg); return new EthereumProviderError( code, @@ -199,7 +201,7 @@ function getEthProviderError(code: number, arg: EthErrorsArg): EthereumPro ); } -function parseOpts(arg: EthErrorsArg): [string?, T?] { +function parseOpts(arg?: EthErrorsArg): [string?, T?] { if (arg) { if (typeof arg === 'string') { return [arg]; diff --git a/src/utils.ts b/src/utils.ts index 2160209..e3e0015 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -1,3 +1,4 @@ +import safeStringify from 'fast-safe-stringify'; import { errorCodes, errorValues } from './error-constants'; import { EthereumRpcError, SerializedEthereumRpcError } from './classes'; @@ -86,44 +87,52 @@ export function serializeError( if ( error && typeof error === 'object' && - !Array.isArray(error) && - hasKey(error as Record, 'code') && - isValidCode((error as SerializedEthereumRpcError).code) + !Array.isArray(error) ) { const _error = error as Partial; - serialized.code = _error.code; - if (_error.message && typeof _error.message === 'string') { - serialized.message = _error.message; + if ( + hasKey(_error, 'code') && + isValidCode((_error as SerializedEthereumRpcError).code) + ) { + serialized.code = _error.code; + } else { + serialized.code = fallbackError.code; + } - if (hasKey(_error, 'data')) { - serialized.data = _error.data; - } + if ( + hasKey(_error, 'message') && + typeof (_error as SerializedEthereumRpcError).message === 'string' + ) { + serialized.message = _error.message; + } else if (serialized.code === fallbackError.code) { + serialized.message = fallbackError.message; } else { - serialized.message = getMessageFromCode( - (serialized as SerializedEthereumRpcError).code, - ); + serialized.message = getMessageFromCode(( + serialized as SerializedEthereumRpcError).code); + } + + if (hasKey(_error, 'data')) { + serialized.data = _error.data; + } - serialized.data = { originalError: assignOriginalError(error) }; + if ( + shouldIncludeStack && + hasKey(_error, 'stack') && + typeof (_error as SerializedEthereumRpcError).stack === 'string' + ) { + serialized.stack = _error.stack; } } else { serialized.code = fallbackError.code; + serialized.message = fallbackError.message; - const message = (error as any)?.message; - - serialized.message = ( - message && typeof message === 'string' - ? message - : fallbackError.message - ); - serialized.data = { originalError: assignOriginalError(error) }; + const originalError = safeStringify(error); + if (originalError) { + serialized.data = { originalError }; + } } - const stack = (error as any)?.stack; - - if (shouldIncludeStack && error && stack && typeof stack === 'string') { - serialized.stack = stack; - } return serialized as SerializedEthereumRpcError; } @@ -133,13 +142,6 @@ function isJsonRpcServerError(code: number): boolean { return code >= -32099 && code <= -32000; } -function assignOriginalError(error: unknown): unknown { - if (error && typeof error === 'object' && !Array.isArray(error)) { - return Object.assign({}, error); - } - return error; -} - function hasKey(obj: Record, key: string) { return Object.prototype.hasOwnProperty.call(obj, key); } diff --git a/test/serializeError.js b/test/serializeError.js index 1fee3fb..893b423 100644 --- a/test/serializeError.js +++ b/test/serializeError.js @@ -1,5 +1,6 @@ const test = require('tape'); const dequal = require('fast-deep-equal'); +const safeStringify = require('fast-safe-stringify'); const { ethErrors, serializeError, errorCodes } = require('../dist'); const { getMessageFromCode } = require('../dist/utils'); @@ -38,7 +39,7 @@ test('invalid error: non-object', (t) => { { code: rpcCodes.internal, message: getMessageFromCode(rpcCodes.internal), - data: { originalError: invalidError0 }, + data: { originalError: safeStringify(invalidError0) }, }, ), 'serialized error matches expected result', @@ -54,7 +55,7 @@ test('invalid error: null', (t) => { { code: rpcCodes.internal, message: getMessageFromCode(rpcCodes.internal), - data: { originalError: invalidError5 }, + data: { originalError: safeStringify(invalidError5) }, }, ), 'serialized error matches expected result', @@ -70,7 +71,6 @@ test('invalid error: undefined', (t) => { { code: rpcCodes.internal, message: getMessageFromCode(rpcCodes.internal), - data: { originalError: invalidError6 }, }, ), 'serialized error matches expected result', @@ -86,7 +86,7 @@ test('invalid error: array', (t) => { { code: rpcCodes.internal, message: getMessageFromCode(rpcCodes.internal), - data: { originalError: invalidError1 }, + data: { originalError: safeStringify(invalidError1) }, }, ), 'serialized error matches expected result', @@ -102,7 +102,6 @@ test('invalid error: invalid code', (t) => { { code: rpcCodes.internal, message: getMessageFromCode(rpcCodes.internal), - data: { originalError: Object.assign({}, invalidError2) }, }, ), 'serialized error matches expected result', @@ -118,7 +117,6 @@ test('invalid error: valid code, undefined message', (t) => { { code: 4001, message: getMessageFromCode(4001), - data: { originalError: Object.assign({}, invalidError3) }, }, ), 'serialized error matches expected result', @@ -134,7 +132,7 @@ test('invalid error: non-string message with data', (t) => { { code: 4001, message: getMessageFromCode(4001), - data: { originalError: Object.assign({}, invalidError4) }, + data: { ...dummyData }, }, ), 'serialized error matches expected result', @@ -150,7 +148,7 @@ test('invalid error: invalid code with string message', (t) => { { code: rpcCodes.internal, message: dummyMessage, - data: { originalError: Object.assign({}, invalidError7) }, + data: { ...dummyData }, }, ), 'serialized error matches expected result', @@ -169,7 +167,6 @@ test('invalid error: invalid code, no message, custom fallback', (t) => { { code: rpcCodes.methodNotFound, message: 'foo', - data: { originalError: Object.assign({}, invalidError2) }, }, ), 'serialized error matches expected result', From 5a0fcaca3728614a8d8b102ac1c048296e000826 Mon Sep 17 00:00:00 2001 From: Erik Marks Date: Mon, 11 Jan 2021 09:41:08 -0800 Subject: [PATCH 2/3] Add test cases for no argument --- test/errors.js | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/test/errors.js b/test/errors.js index ea8327e..31e8e6a 100644 --- a/test/errors.js +++ b/test/errors.js @@ -114,6 +114,11 @@ test('test exported object for correctness', (t) => { ); } }); + t.comment('Handles no argument.'); + validateError( + ethErrors.rpc.internal(), + 'internal', undefined, t, + ); t.comment('End: Ethereum RPC'); t.comment('Begin: Ethereum Provider'); @@ -137,6 +142,11 @@ test('test exported object for correctness', (t) => { ); } }); + t.comment('Handles no argument.'); + validateError( + ethErrors.provider.unauthorized(), + 'unauthorized', undefined, t, true, + ); t.comment('End: Ethereum Provider'); t.end(); }); From de6d466075879a6d3f0592f5ca0df496e6724850 Mon Sep 17 00:00:00 2001 From: Erik Marks Date: Mon, 11 Jan 2021 10:03:14 -0800 Subject: [PATCH 3/3] Revert unintended changes --- src/utils.ts | 68 ++++++++++++++++++++---------------------- test/serializeError.js | 15 ++++++---- 2 files changed, 42 insertions(+), 41 deletions(-) diff --git a/src/utils.ts b/src/utils.ts index e3e0015..2160209 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -1,4 +1,3 @@ -import safeStringify from 'fast-safe-stringify'; import { errorCodes, errorValues } from './error-constants'; import { EthereumRpcError, SerializedEthereumRpcError } from './classes'; @@ -87,52 +86,44 @@ export function serializeError( if ( error && typeof error === 'object' && - !Array.isArray(error) + !Array.isArray(error) && + hasKey(error as Record, 'code') && + isValidCode((error as SerializedEthereumRpcError).code) ) { const _error = error as Partial; + serialized.code = _error.code; - if ( - hasKey(_error, 'code') && - isValidCode((_error as SerializedEthereumRpcError).code) - ) { - serialized.code = _error.code; - } else { - serialized.code = fallbackError.code; - } - - if ( - hasKey(_error, 'message') && - typeof (_error as SerializedEthereumRpcError).message === 'string' - ) { + if (_error.message && typeof _error.message === 'string') { serialized.message = _error.message; - } else if (serialized.code === fallbackError.code) { - serialized.message = fallbackError.message; - } else { - serialized.message = getMessageFromCode(( - serialized as SerializedEthereumRpcError).code); - } - if (hasKey(_error, 'data')) { - serialized.data = _error.data; - } + if (hasKey(_error, 'data')) { + serialized.data = _error.data; + } + } else { + serialized.message = getMessageFromCode( + (serialized as SerializedEthereumRpcError).code, + ); - if ( - shouldIncludeStack && - hasKey(_error, 'stack') && - typeof (_error as SerializedEthereumRpcError).stack === 'string' - ) { - serialized.stack = _error.stack; + serialized.data = { originalError: assignOriginalError(error) }; } } else { serialized.code = fallbackError.code; - serialized.message = fallbackError.message; - const originalError = safeStringify(error); - if (originalError) { - serialized.data = { originalError }; - } + const message = (error as any)?.message; + + serialized.message = ( + message && typeof message === 'string' + ? message + : fallbackError.message + ); + serialized.data = { originalError: assignOriginalError(error) }; } + const stack = (error as any)?.stack; + + if (shouldIncludeStack && error && stack && typeof stack === 'string') { + serialized.stack = stack; + } return serialized as SerializedEthereumRpcError; } @@ -142,6 +133,13 @@ function isJsonRpcServerError(code: number): boolean { return code >= -32099 && code <= -32000; } +function assignOriginalError(error: unknown): unknown { + if (error && typeof error === 'object' && !Array.isArray(error)) { + return Object.assign({}, error); + } + return error; +} + function hasKey(obj: Record, key: string) { return Object.prototype.hasOwnProperty.call(obj, key); } diff --git a/test/serializeError.js b/test/serializeError.js index 893b423..1fee3fb 100644 --- a/test/serializeError.js +++ b/test/serializeError.js @@ -1,6 +1,5 @@ const test = require('tape'); const dequal = require('fast-deep-equal'); -const safeStringify = require('fast-safe-stringify'); const { ethErrors, serializeError, errorCodes } = require('../dist'); const { getMessageFromCode } = require('../dist/utils'); @@ -39,7 +38,7 @@ test('invalid error: non-object', (t) => { { code: rpcCodes.internal, message: getMessageFromCode(rpcCodes.internal), - data: { originalError: safeStringify(invalidError0) }, + data: { originalError: invalidError0 }, }, ), 'serialized error matches expected result', @@ -55,7 +54,7 @@ test('invalid error: null', (t) => { { code: rpcCodes.internal, message: getMessageFromCode(rpcCodes.internal), - data: { originalError: safeStringify(invalidError5) }, + data: { originalError: invalidError5 }, }, ), 'serialized error matches expected result', @@ -71,6 +70,7 @@ test('invalid error: undefined', (t) => { { code: rpcCodes.internal, message: getMessageFromCode(rpcCodes.internal), + data: { originalError: invalidError6 }, }, ), 'serialized error matches expected result', @@ -86,7 +86,7 @@ test('invalid error: array', (t) => { { code: rpcCodes.internal, message: getMessageFromCode(rpcCodes.internal), - data: { originalError: safeStringify(invalidError1) }, + data: { originalError: invalidError1 }, }, ), 'serialized error matches expected result', @@ -102,6 +102,7 @@ test('invalid error: invalid code', (t) => { { code: rpcCodes.internal, message: getMessageFromCode(rpcCodes.internal), + data: { originalError: Object.assign({}, invalidError2) }, }, ), 'serialized error matches expected result', @@ -117,6 +118,7 @@ test('invalid error: valid code, undefined message', (t) => { { code: 4001, message: getMessageFromCode(4001), + data: { originalError: Object.assign({}, invalidError3) }, }, ), 'serialized error matches expected result', @@ -132,7 +134,7 @@ test('invalid error: non-string message with data', (t) => { { code: 4001, message: getMessageFromCode(4001), - data: { ...dummyData }, + data: { originalError: Object.assign({}, invalidError4) }, }, ), 'serialized error matches expected result', @@ -148,7 +150,7 @@ test('invalid error: invalid code with string message', (t) => { { code: rpcCodes.internal, message: dummyMessage, - data: { ...dummyData }, + data: { originalError: Object.assign({}, invalidError7) }, }, ), 'serialized error matches expected result', @@ -167,6 +169,7 @@ test('invalid error: invalid code, no message, custom fallback', (t) => { { code: rpcCodes.methodNotFound, message: 'foo', + data: { originalError: Object.assign({}, invalidError2) }, }, ), 'serialized error matches expected result',