Add rpc error fields (#4637)
* [WIP] Add RpcError Currently located in rpc-core at request of maintainer. However unclear as to how it should be used, because the error needs to be thrown in rpc-provider, and depending on it causes a dependency loop. * Move `RpcError` to `rpc-provider` Also add tests
This commit is contained in:
@@ -0,0 +1,101 @@
|
|||||||
|
// Copyright 2017-2022 @polkadot/rpc-provider authors & contributors
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
import { isError } from '@polkadot/util/is/error';
|
||||||
|
|
||||||
|
import RpcError from './error';
|
||||||
|
|
||||||
|
describe('RpcError', (): void => {
|
||||||
|
describe('constructor', (): void => {
|
||||||
|
it('constructs an Error that is still an Error', (): void => {
|
||||||
|
expect(
|
||||||
|
isError(
|
||||||
|
new RpcError()
|
||||||
|
)
|
||||||
|
).toEqual(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('static', (): void => {
|
||||||
|
it('exposes the .CODES as a static', (): void => {
|
||||||
|
expect(
|
||||||
|
Object.keys(RpcError.CODES)
|
||||||
|
).not.toEqual(0);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('constructor properties', (): void => {
|
||||||
|
it('sets the .message property', (): void => {
|
||||||
|
expect(
|
||||||
|
new RpcError('test message').message
|
||||||
|
).toEqual('test message');
|
||||||
|
});
|
||||||
|
|
||||||
|
it("sets the .message to '' when not set", (): void => {
|
||||||
|
expect(
|
||||||
|
new RpcError().message
|
||||||
|
).toEqual('');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('sets the .code property', (): void => {
|
||||||
|
expect(
|
||||||
|
new RpcError('test message', 1234).code
|
||||||
|
).toEqual(1234);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('sets the .code to UKNOWN when not set', (): void => {
|
||||||
|
expect(
|
||||||
|
new RpcError('test message').code
|
||||||
|
).toEqual(RpcError.CODES.UNKNOWN);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('sets the .data property', (): void => {
|
||||||
|
const data = 'here';
|
||||||
|
|
||||||
|
expect(
|
||||||
|
new RpcError('test message', 1234, data).data
|
||||||
|
).toEqual(data);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('stack traces', (): void => {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/ban-types
|
||||||
|
let captureStackTrace: (targetObject: Record<string, any>, constructorOpt?: Function | undefined) => void;
|
||||||
|
|
||||||
|
beforeEach((): void => {
|
||||||
|
captureStackTrace = Error.captureStackTrace;
|
||||||
|
|
||||||
|
Error.captureStackTrace = function (error): void {
|
||||||
|
Object.defineProperty(error, 'stack', {
|
||||||
|
configurable: true,
|
||||||
|
get: function getStack (): string {
|
||||||
|
const value = 'some stack returned';
|
||||||
|
|
||||||
|
Object.defineProperty(this, 'stack', { value });
|
||||||
|
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach((): void => {
|
||||||
|
Error.captureStackTrace = captureStackTrace;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('captures via captureStackTrace when available', (): void => {
|
||||||
|
expect(
|
||||||
|
new RpcError().stack
|
||||||
|
).toEqual('some stack returned');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('captures via stack when captureStackTrace not available', (): void => {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||||
|
Error.captureStackTrace = null as any;
|
||||||
|
|
||||||
|
expect(
|
||||||
|
new RpcError().stack.length
|
||||||
|
).not.toEqual(0);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,64 @@
|
|||||||
|
// Copyright 2017-2022 @polkadot/rpc-provider authors & contributors
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
import { isFunction } from '@polkadot/util/is/function';
|
||||||
|
|
||||||
|
import { RpcErrorInterface } from '../types';
|
||||||
|
|
||||||
|
const UNKNOWN = -99999;
|
||||||
|
|
||||||
|
function extend (that: RpcError, name: string, value?: string | number): void {
|
||||||
|
Object.defineProperty(that, name, {
|
||||||
|
configurable: true,
|
||||||
|
enumerable: false,
|
||||||
|
value
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @name RpcError
|
||||||
|
* @summary Extension to the basic JS Error.
|
||||||
|
* @description
|
||||||
|
* The built-in JavaScript Error class is extended by adding a code to allow for Error categorization. In addition to the normal `stack`, `message`, the numeric `code` and `data` (any types) parameters are available on the object.
|
||||||
|
* @example
|
||||||
|
* <BR>
|
||||||
|
*
|
||||||
|
* ```javascript
|
||||||
|
* const { RpcError } from '@polkadot/util');
|
||||||
|
*
|
||||||
|
* throw new RpcError('some message', RpcError.CODES.METHOD_NOT_FOUND); // => error.code = -32601
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
export default class RpcError extends Error implements RpcErrorInterface {
|
||||||
|
public code!: number;
|
||||||
|
|
||||||
|
public data?: number | string;
|
||||||
|
|
||||||
|
public override message!: string;
|
||||||
|
|
||||||
|
public override name!: string;
|
||||||
|
|
||||||
|
public override stack!: string;
|
||||||
|
|
||||||
|
public constructor (message = '', code: number = UNKNOWN, data?: number | string) {
|
||||||
|
super();
|
||||||
|
|
||||||
|
extend(this, 'message', String(message));
|
||||||
|
extend(this, 'name', this.constructor.name);
|
||||||
|
extend(this, 'data', data);
|
||||||
|
extend(this, 'code', code);
|
||||||
|
|
||||||
|
if (isFunction(Error.captureStackTrace)) {
|
||||||
|
Error.captureStackTrace(this, this.constructor);
|
||||||
|
} else {
|
||||||
|
extend(this, 'stack', (new Error(message)).stack);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static CODES = {
|
||||||
|
ASSERT: -90009,
|
||||||
|
INVALID_JSONRPC: -99998,
|
||||||
|
METHOD_NOT_FOUND: -32601, // Rust client
|
||||||
|
UNKNOWN
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -5,6 +5,8 @@ import type { JsonRpcRequest, JsonRpcResponse, JsonRpcResponseBaseError } from '
|
|||||||
|
|
||||||
import { assert, isNumber, isString, isUndefined, stringify } from '@polkadot/util';
|
import { assert, isNumber, isString, isUndefined, stringify } from '@polkadot/util';
|
||||||
|
|
||||||
|
import RpcError from './error';
|
||||||
|
|
||||||
function formatErrorData (data?: string | number): string {
|
function formatErrorData (data?: string | number): string {
|
||||||
if (isUndefined(data)) {
|
if (isUndefined(data)) {
|
||||||
return '';
|
return '';
|
||||||
@@ -68,8 +70,9 @@ export class RpcCoder {
|
|||||||
private _checkError (error?: JsonRpcResponseBaseError): void {
|
private _checkError (error?: JsonRpcResponseBaseError): void {
|
||||||
if (error) {
|
if (error) {
|
||||||
const { code, data, message } = error;
|
const { code, data, message } = error;
|
||||||
|
const fmtMessage = `${code}: ${message}${formatErrorData(data)}`;
|
||||||
|
|
||||||
throw new Error(`${code}: ${message}${formatErrorData(data)}`);
|
throw new RpcError(fmtMessage, code, data);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,6 +17,13 @@ export interface JsonRpcResponseBaseError {
|
|||||||
message: string;
|
message: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface RpcErrorInterface {
|
||||||
|
code: number;
|
||||||
|
data?: string | number;
|
||||||
|
message: string;
|
||||||
|
stack: string;
|
||||||
|
}
|
||||||
|
|
||||||
interface JsonRpcResponseSingle {
|
interface JsonRpcResponseSingle {
|
||||||
error?: JsonRpcResponseBaseError;
|
error?: JsonRpcResponseBaseError;
|
||||||
result?: unknown;
|
result?: unknown;
|
||||||
|
|||||||
Reference in New Issue
Block a user