diff --git a/packages/jellyfish-api-core/__tests__/container_adapter_client.ts b/packages/jellyfish-api-core/__tests__/container_adapter_client.ts index 4915c89b03..13019e3560 100644 --- a/packages/jellyfish-api-core/__tests__/container_adapter_client.ts +++ b/packages/jellyfish-api-core/__tests__/container_adapter_client.ts @@ -1,5 +1,6 @@ -import { JellyfishJSON, ApiClient, Precision, RpcApiError } from '../src' +import { ApiClient, RpcApiError } from '../src' import { DeFiDContainer } from '@defichain/testcontainers' +import { JellyfishJSON, Precision, PrecisionPath } from '@defichain/jellyfish-json' /** * Jellyfish client adapter for container @@ -16,7 +17,7 @@ export class ContainerAdapterClient extends ApiClient { /** * Wrap the call from client to testcontainers. */ - async call (method: string, params: any[], precision: Precision): Promise { + async call (method: string, params: any[], precision: Precision | PrecisionPath): Promise { const body = JellyfishJSON.stringify({ jsonrpc: '1.0', id: Math.floor(Math.random() * 100000000000000), @@ -25,9 +26,9 @@ export class ContainerAdapterClient extends ApiClient { }) const text = await this.container.post(body) - const response = JellyfishJSON.parse(text, precision) - - const { result, error } = response + const { result, error } = JellyfishJSON.parse(text, { + result: precision + }) if (error !== undefined && error !== null) { throw new RpcApiError(error) diff --git a/packages/jellyfish-api-core/src/category/blockchain.ts b/packages/jellyfish-api-core/src/category/blockchain.ts index ada36ea4a8..225c79f0ae 100644 --- a/packages/jellyfish-api-core/src/category/blockchain.ts +++ b/packages/jellyfish-api-core/src/category/blockchain.ts @@ -79,7 +79,11 @@ export class Blockchain { * @return Promise */ async getTxOut (txId: string, index: number, includeMempool = true): Promise { - return await this.client.call('gettxout', [txId, index, includeMempool], { value: 'bignumber' }) + return await this.client.call('gettxout', [ + txId, index, includeMempool + ], { + value: 'bignumber' + }) } } diff --git a/packages/jellyfish-api-jsonrpc/src/index.ts b/packages/jellyfish-api-jsonrpc/src/index.ts index 369f15724c..b3d727bbcf 100644 --- a/packages/jellyfish-api-jsonrpc/src/index.ts +++ b/packages/jellyfish-api-jsonrpc/src/index.ts @@ -8,6 +8,7 @@ import { import fetch from 'cross-fetch' import { Response } from 'cross-fetch/lib.fetch' import AbortController from 'abort-controller' +import { PrecisionPath } from '@defichain/jellyfish-json' /** * ClientOptions for JsonRpc @@ -58,7 +59,7 @@ export class JsonRpcClient extends ApiClient { /** * Implements JSON-RPC 1.0 specification for ApiClient */ - async call (method: string, params: any[], precision: Precision): Promise { + async call (method: string, params: any[], precision: Precision | PrecisionPath): Promise { const body = JsonRpcClient.stringify(method, params) const response = await this.fetchTimeout(body) const text = await response.text() @@ -83,8 +84,10 @@ export class JsonRpcClient extends ApiClient { }) } - private static parse (text: string, precision: Precision): any { - const { result, error } = JellyfishJSON.parse(text, precision) + private static parse (text: string, precision: Precision | PrecisionPath): any { + const { result, error } = JellyfishJSON.parse(text, { + result: precision + }) if (error !== undefined && error !== null) { throw new RpcApiError(error) diff --git a/packages/jellyfish-json/__tests__/remap.test.ts b/packages/jellyfish-json/__tests__/remap.test.ts index c956d21529..ef4ddd6f97 100644 --- a/packages/jellyfish-json/__tests__/remap.test.ts +++ b/packages/jellyfish-json/__tests__/remap.test.ts @@ -1,4 +1,4 @@ -import { parse } from 'lossless-json' +import { LosslessNumber, parse } from 'lossless-json' import { PrecisionPath, remap } from '../src/remap' import { BigNumber } from '../src' @@ -145,8 +145,28 @@ describe('remap individually', () => { }) }) +it('should remap lossless | number | bignumber', () => { + const parsed = parseAndRemap(`{ + "a": 1, + "b": 2, + "c": 3 + }`, { + a: 'bignumber', + b: 'lossless', + c: 'number' + }) + + expect(parsed.a instanceof BigNumber).toBe(true) + expect(parsed.a.toString()).toBe('1') + + expect(parsed.b instanceof LosslessNumber).toBe(true) + expect(parsed.b.toString()).toBe('2') + + expect(parsed.c).toBe(3) +}) + describe('remap invalid mapping should succeed', () => { - it('should ignore invalid remapping', () => { + it('should ignore invalid mapping', () => { const parsed = parseAndRemap(`{ "ignored": 123.4, "num": 1000 @@ -158,4 +178,40 @@ describe('remap invalid mapping should succeed', () => { expect(parsed.ignored).toBe(123.4) expect(parsed.num).toBe(1000) }) + + it('should ignore invalid null object', () => { + remap({ + invalid: null + }, { + invalid: 'bignumber' + }) + }) + + it('should ignore invalid undefined object', () => { + remap({ + invalid: undefined + }, { + invalid: 'bignumber' + }) + }) + + it('should ignore invalid null mapping', () => { + parseAndRemap(`{ + "invalid": 123.4, + "num": 1000 + }`, { + // @ts-expect-error + invalid: undefined + }) + }) + + it('should ignore invalid undefined mapping', () => { + parseAndRemap(`{ + "invalid": 123.4, + "num": 1000 + }`, { + // @ts-expect-error + invalid: undefined + }) + }) }) diff --git a/packages/jellyfish-json/src/remap.ts b/packages/jellyfish-json/src/remap.ts index 12cb540740..d14ed9c6f7 100644 --- a/packages/jellyfish-json/src/remap.ts +++ b/packages/jellyfish-json/src/remap.ts @@ -1,5 +1,6 @@ import BigNumber from 'bignumber.js' import { LosslessNumber } from 'lossless-json' +import { Precision } from './index' /** * Path based precision mapping @@ -26,7 +27,7 @@ import { LosslessNumber } from 'lossless-json' * } */ export interface PrecisionPath { - [path: string]: 'bignumber' | PrecisionPath + [path: string]: Precision | PrecisionPath } /** @@ -41,40 +42,52 @@ export function remap (losslessObj: any, precision: PrecisionPath): any { * @param {any} losslessObj to deeply remap * @param {'bignumber' | PrecisionPath} precision path mapping */ -function deepRemap (losslessObj: any, precision: 'bignumber' | PrecisionPath): any { +function deepRemap (losslessObj: any, precision: Precision | PrecisionPath): any { + if (losslessObj === null || losslessObj === undefined) { + return losslessObj + } + if (typeof precision !== 'object') { - return reviveObjectAs(losslessObj, precision) + return reviveAs(losslessObj, precision) } + if (Array.isArray(losslessObj)) { return losslessObj.map(obj => deepRemap(obj, precision)) } + if (losslessObj instanceof LosslessNumber) { + return reviveLosslessAs(losslessObj) + } + for (const [key, value] of Object.entries(losslessObj)) { losslessObj[key] = deepRemap(value, precision[key]) } - return reviveObjectAs(losslessObj) + return losslessObj } /** - * Array will deeply remapped, object keys will be iterated on. + * Array will deeply remapped, object keys will be iterated on as keys. * * @param {any} losslessObj to revive - * @param {'bignumber'} precision to use, specific 'bignumber' for BigNumber else always default to number + * @param precision to use, specific 'bignumber' for BigNumber or values always ignored and default to number */ -function reviveObjectAs (losslessObj: any, precision?: 'bignumber' | string): any { - if (Array.isArray(losslessObj)) { - return losslessObj.map((v: any) => reviveObjectAs(v, precision)) +function reviveAs (losslessObj: any, precision?: Precision): any { + if (losslessObj === null || losslessObj === undefined) { + return losslessObj } if (losslessObj instanceof LosslessNumber) { return reviveLosslessAs(losslessObj, precision) } + if (Array.isArray(losslessObj)) { + return losslessObj.map((v: any) => reviveAs(v, precision)) + } + if (typeof losslessObj === 'object') { for (const [key, value] of Object.entries(losslessObj)) { - losslessObj[key] = reviveObjectAs(value, precision) + losslessObj[key] = reviveAs(value, precision) } - return losslessObj } return losslessObj @@ -82,9 +95,13 @@ function reviveObjectAs (losslessObj: any, precision?: 'bignumber' | string): an /** * @param {LosslessNumber} losslessNum to revive as bignumber or number if precision != bignumber - * @param {'bignumber'} precision to use, specific 'bignumber' for BigNumber else always default to number + * @param {Precision} precision to use, specific 'bignumber' for BigNumber else always default to number */ -function reviveLosslessAs (losslessNum: LosslessNumber, precision?: 'bignumber' | string): BigNumber | number { +function reviveLosslessAs (losslessNum: LosslessNumber, precision?: Precision): BigNumber | LosslessNumber | number { + if (precision === 'lossless') { + return losslessNum + } + if (precision === 'bignumber') { return new BigNumber(losslessNum.toString()) }