diff --git a/docs/README.md b/docs/README.md index 171065ce7..999eaae86 100644 --- a/docs/README.md +++ b/docs/README.md @@ -4,6 +4,7 @@ Utility methods for this package are split into groups - [bn](bn.md) Utility methods to convert to and from `BN` objects - [buffer](buffer.md) Utility methods to convert to and from `Buffer` objects +- [ext](ext.md) Extensions to basic classes - [hex](hex.md) Internal utilities to create and test for hex values - [is](is.md) Type checking utilities - [jsonrpc](jsonrpc.md) Convenience functions for values from @polkadot/jsonrpc diff --git a/docs/ext.md b/docs/ext.md new file mode 100644 index 000000000..c904b674c --- /dev/null +++ b/docs/ext.md @@ -0,0 +1,22 @@ +# ext + +Extensions to basic classes + +- [ExtError](#exterror) Extension to the basic JS Error. + +## ExtError + +Extension to the basic JS Error. + +```js +ExtError (message: string = '', code: number = UNKNOWN_ERROR, value: any) +``` + + +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. + +```js +const { ExtError } = require('@polkadot/util'); + +throw new ExtError('some message', ExtError.CODES.METHOD_NOT_FOUND); // => error.code = -32601 +``` \ No newline at end of file diff --git a/docs/is.md b/docs/is.md index 3b0c248c9..9255ba19b 100644 --- a/docs/is.md +++ b/docs/is.md @@ -4,6 +4,7 @@ Type checking utilities - [isBN](#isbn) Tests for a `BN` object instance. - [isBuffer](#isbuffer) Tests for a `Buffer` object instance. +- [isError](#iserror) Tests for a `Error` object instance. - [isFunction](#isfunction) Tests for a `function`. - [isHex](#ishex) Tests for a hex string. - [isInstanceOf](#isinstanceof) Tests for a instance of a class. @@ -46,6 +47,23 @@ import { isBuffer } from '@polkadot/util'; console.log('isBuffer', isBuffer(Buffer.from([]))); // => true ``` +## isError + +Tests for a `Error` object instance. + +```js +isError (value: any): boolean +``` + + +Checks to see if the input object is an instance of `Error`. + +```js +import { isError } from '@polkadot/util'; + +console.log('isError', isError(new Error('message'))); // => true +``` + ## isFunction Tests for a `function`. diff --git a/src/ext/error.js b/src/ext/error.js new file mode 100644 index 000000000..766790be8 --- /dev/null +++ b/src/ext/error.js @@ -0,0 +1,56 @@ +// ISC, Copyright 2017 Jaco Greeff +// @flow + +import type { ExtErrorInterface } from '../types'; + +const isFunction = require('../is/function'); + +const UNKNOWN = -99999; + +/** + @name ExtError + @signature ExtError (message: string = '', code: number = UNKNOWN_ERROR, value: any) + @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 + const { ExtError } = require('@polkadot/util'); + + throw new ExtError('some message', ExtError.CODES.METHOD_NOT_FOUND); // => error.code = -32601 +*/ +module.exports = class ExtError extends Error implements ExtErrorInterface { + code: number; + data: any; + message: string; + name: string; + stack: string; + + constructor (message: string = '', code: number = UNKNOWN, data: any) { + super(); + + const extend = (name: string, value: any): void => { + Object.defineProperty(this, name, { + configurable: true, + enumerable: false, + value + }); + }; + + extend('message', String(message)); + extend('name', this.constructor.name); + extend('data', data); + extend('code', code); + + if (isFunction(Error.captureStackTrace)) { + Error.captureStackTrace(this, this.constructor); + } else { + extend('stack', (new Error(message)).stack); + } + } + + static CODES = { + UNKNOWN, + INVALID_JSONRPC: -99998, + METHOD_NOT_FOUND: -32601 // Rust client + }; +}; diff --git a/src/ext/error.spec.js b/src/ext/error.spec.js new file mode 100644 index 000000000..c74a8dfa1 --- /dev/null +++ b/src/ext/error.spec.js @@ -0,0 +1,97 @@ +// ISC, Copyright 2017 Jaco Greeff + +const ExtError = require('./error'); +const isError = require('../is/error'); + +describe('Error', () => { + describe('constructor', () => { + it('constructs an Error that is still an Error', () => { + expect( + isError( + new ExtError() + ) + ).toEqual(true); + }); + }); + + describe('static', () => { + it('exposes the .CODES as a static', () => { + expect( + Object.keys(ExtError.CODES) + ).not.toEqual(0); + }); + }); + + describe('constructor properties', () => { + it('sets the .message property', () => { + expect( + new ExtError('test message').message + ).toEqual('test message'); + }); + + it("sets the .message to '' when not set", () => { + expect( + new ExtError().message + ).toEqual(''); + }); + + it('sets the .code property', () => { + expect( + new ExtError('test message', 1234).code + ).toEqual(1234); + }); + + it('sets the .code to UKNOWN when not set', () => { + expect( + new ExtError('test message').code + ).toEqual(ExtError.CODES.UNKNOWN); + }); + + it('sets the .data property', () => { + const data = { some: { value: 'here' } }; + + expect( + new ExtError('test message', 1234, data).data + ).toEqual(data); + }); + }); + + describe('stack traces', () => { + let captureStackTrace; + + beforeEach(() => { + captureStackTrace = Error.captureStackTrace; + + Error.captureStackTrace = function (error) { + Object.defineProperty(error, 'stack', { + configurable: true, + get: function getStack () { + const value = 'some stack returned'; + + Object.defineProperty(this, 'stack', { value }); + + return value; + } + }); + }; + }); + + afterEach(() => { + Error.captureStackTrace = captureStackTrace; + }); + + it('captures via captureStackTrace when available', () => { + expect( + new ExtError().stack + ).toEqual('some stack returned'); + }); + + it('captures via stack when captureStackTrace not available', () => { + Error.captureStackTrace = null; + + expect( + new ExtError().stack.length + ).not.toEqual(0); + }); + }); +}); diff --git a/src/ext/index.js b/src/ext/index.js new file mode 100644 index 000000000..4abf51ffb --- /dev/null +++ b/src/ext/index.js @@ -0,0 +1,11 @@ +// ISC, Copyright 2017 Jaco Greeff +// @flow + +const ExtError = require('./error'); + +/** + @summary Extensions to basic classes +*/ +module.exports = { + ExtError +}; diff --git a/src/index.js b/src/index.js index 5461e5d4b..1111c1efc 100644 --- a/src/index.js +++ b/src/index.js @@ -3,6 +3,7 @@ const bn = require('./bn'); const buffer = require('./buffer'); +const ext = require('./ext'); const hex = require('./hex'); const is = require('./is'); const jsonrpc = require('./jsonrpc'); @@ -13,5 +14,5 @@ const logger = require('./logger'); @summary Utility methods for this package are split into groups */ module.exports = Object.assign( - {}, bn, buffer, hex, is, jsonrpc, keccak, logger + {}, bn, buffer, ext, hex, is, jsonrpc, keccak, logger ); diff --git a/src/is/error.js b/src/is/error.js new file mode 100644 index 000000000..40c638c5f --- /dev/null +++ b/src/is/error.js @@ -0,0 +1,19 @@ +// ISC, Copyright 2017 Jaco Greeff +// @flow + +const isInstanceOf = require('./instanceOf'); + +/** + @name isError + @signature isError (value: any): boolean + @summary Tests for a `Error` object instance. + @description + Checks to see if the input object is an instance of `Error`. + @example + import { isError } from '@polkadot/util'; + + console.log('isError', isError(new Error('message'))); // => true +*/ +module.exports = function isError (value: any): boolean { + return isInstanceOf(value, Error); +}; diff --git a/src/is/index.js b/src/is/index.js index 55bfcd232..d26502865 100644 --- a/src/is/index.js +++ b/src/is/index.js @@ -2,6 +2,8 @@ // @flow const isBN = require('./bn'); +const isBuffer = require('./buffer'); +const isError = require('./error'); const isFunction = require('./function'); const isHex = require('./hex'); const isInstanceOf = require('./instanceOf'); @@ -14,6 +16,8 @@ const isUndefined = require('./undefined'); */ module.exports = { isBN, + isBuffer, + isError, isFunction, isHex, isInstanceOf, diff --git a/src/is/index.spec.js b/src/is/index.spec.js index f86308da3..f4417d24d 100644 --- a/src/is/index.spec.js +++ b/src/is/index.spec.js @@ -4,7 +4,7 @@ const BN = require('bn.js'); -const { isBN, isFunction, isHex, isInstanceOf, isNumber, isString, isUndefined } = require('./index'); +const { isBN, isBuffer, isError, isFunction, isHex, isInstanceOf, isNumber, isString, isUndefined } = require('./index'); describe('is', () => { describe('isBN', () => { @@ -21,6 +21,34 @@ describe('is', () => { }); }); + describe('isBuffer', () => { + it('returns true when a Buffer value', () => { + expect( + isBuffer(Buffer.from([])) + ).toEqual(true); + }); + + it('returns false on non-Buffer values', () => { + expect( + isBuffer(0) + ).toEqual(false); + }); + }); + + describe('isError', () => { + it('returns true when an Error value', () => { + expect( + isError(new Error('testing')) + ).toEqual(true); + }); + + it('returns false on non-Error values', () => { + expect( + isError(0) + ).toEqual(false); + }); + }); + describe('isFunction', () => { it('returns true on valid functions', () => { expect( diff --git a/src/jsonrpc/index.spec.js b/src/jsonrpc/index.spec.js index c09cd8695..01fca97c0 100644 --- a/src/jsonrpc/index.spec.js +++ b/src/jsonrpc/index.spec.js @@ -10,11 +10,23 @@ describe('jsonrpc', () => { 'test_method', [ { name: 'b', type: 'A' }, - { name: 'a', type: 'B' } + { name: 'a', type: 'B' }, + { type: 'C' }, + { type: 'D' } ], { type: 'A' } ) - ).toEqual('test_method (b: A, a: B): A'); + ).toEqual('test_method (b: A, a: B, C, D): A'); + }); + + it('empty inputs format correctly', () => { + expect( + jsonrpcSignature( + 'test_method', + [], + { type: 'A' } + ) + ).toEqual('test_method (): A'); }); }); }); diff --git a/src/jsonrpc/signature.js b/src/jsonrpc/signature.js index 1ae6620b3..eabbcd861 100644 --- a/src/jsonrpc/signature.js +++ b/src/jsonrpc/signature.js @@ -3,6 +3,12 @@ import type { InterfaceInputType, InterfaceOutputType } from '@polkadot/jsonrpc/types'; +function formatParam (param: InterfaceInputType | InterfaceOutputType): string { + return param.name + ? `${((param.name: any): string)}: ${param.type}` + : param.type; +} + /** @name jsonrpcSignature @signature jsonrpcSignature (name: string, _inputs: Array, _output: InterfaceOutputType): string @@ -17,9 +23,8 @@ import type { InterfaceInputType, InterfaceOutputType } from '@polkadot/jsonrpc/ )); // => test_method (destination: Address): Address */ module.exports = function jsonrpcSignature (name: string, _inputs: Array, _output: InterfaceOutputType): string { - const inputParams: string = _inputs - .map(({ name, type }) => `${name}: ${type}`) - .join(', '); + const inputs: string = _inputs.map(formatParam).join(', '); + const output: string = formatParam(_output); - return `${name} (${inputParams}): ${_output.type}`; + return `${name} (${inputs}): ${output}`; }; diff --git a/src/types.js b/src/types.js new file mode 100644 index 000000000..c66d976dc --- /dev/null +++ b/src/types.js @@ -0,0 +1,9 @@ +// ISC, Copyright 2017 Jaco Greeff +// @flow + +export interface ExtErrorInterface { + code: number; + data: any; + message: string; + stack: string; +}