diff options
-rw-r--r-- | packages/connect/src/http_client.ts | 80 | ||||
-rw-r--r-- | packages/connect/src/utils/orderbook_channel_message_parser.ts (renamed from packages/connect/src/utils/orderbook_channel_message_parsers.ts) | 18 | ||||
-rw-r--r-- | packages/connect/src/utils/relayer_response_json_parsers.ts | 42 | ||||
-rw-r--r-- | packages/connect/src/utils/type_converters.ts | 22 | ||||
-rw-r--r-- | packages/connect/src/ws_orderbook_channel.ts | 4 | ||||
-rw-r--r-- | packages/connect/test/orderbook_channel_message_parsers_test.ts | 26 |
6 files changed, 125 insertions, 67 deletions
diff --git a/packages/connect/src/http_client.ts b/packages/connect/src/http_client.ts index dcaf5d9a9..15c613923 100644 --- a/packages/connect/src/http_client.ts +++ b/packages/connect/src/http_client.ts @@ -18,7 +18,7 @@ import { TokenPairsItem, TokenPairsRequest, } from './types'; -import { typeConverters } from './utils/type_converters'; +import { relayerResponseJsonParsers } from './utils/relayer_response_json_parsers'; /** * This class includes all the functionality related to interacting with a set of HTTP endpoints @@ -48,17 +48,13 @@ export class HttpClient implements Client { const requestOpts = { params: request, }; - const tokenPairs = await this._requestAsync('/token_pairs', HttpRequestType.Get, requestOpts); - assert.doesConformToSchema('tokenPairs', tokenPairs, schemas.relayerApiTokenPairsResponseSchema); - _.each(tokenPairs, (tokenPair: object) => { - typeConverters.convertStringsFieldsToBigNumbers(tokenPair, [ - 'tokenA.minAmount', - 'tokenA.maxAmount', - 'tokenB.minAmount', - 'tokenB.maxAmount', - ]); - }); - return tokenPairs; + const result = await this._requestAsync( + '/token_pairs', + HttpRequestType.Get, + relayerResponseJsonParsers.parseTokenPairsJson, + requestOpts, + ); + return result; } /** * Retrieve orders from the API @@ -72,10 +68,13 @@ export class HttpClient implements Client { const requestOpts = { params: request, }; - const orders = await this._requestAsync(`/orders`, HttpRequestType.Get, requestOpts); - assert.doesConformToSchema('orders', orders, schemas.signedOrdersSchema); - _.each(orders, (order: object) => typeConverters.convertOrderStringFieldsToBigNumber(order)); - return orders; + const result = await this._requestAsync( + `/orders`, + HttpRequestType.Get, + relayerResponseJsonParsers.parseOrdersJson, + requestOpts, + ); + return result; } /** * Retrieve a specific order from the API @@ -84,10 +83,12 @@ export class HttpClient implements Client { */ public async getOrderAsync(orderHash: string): Promise<SignedOrder> { assert.doesConformToSchema('orderHash', orderHash, schemas.orderHashSchema); - const order = await this._requestAsync(`/order/${orderHash}`, HttpRequestType.Get); - assert.doesConformToSchema('order', order, schemas.signedOrderSchema); - typeConverters.convertOrderStringFieldsToBigNumber(order); - return order; + const result = await this._requestAsync( + `/order/${orderHash}`, + HttpRequestType.Get, + relayerResponseJsonParsers.parseOrderJson, + ); + return result; } /** * Retrieve an orderbook from the API @@ -99,10 +100,13 @@ export class HttpClient implements Client { const requestOpts = { params: request, }; - const orderBook = await this._requestAsync('/orderbook', HttpRequestType.Get, requestOpts); - assert.doesConformToSchema('orderBook', orderBook, schemas.relayerApiOrderBookResponseSchema); - typeConverters.convertOrderbookStringFieldsToBigNumber(orderBook); - return orderBook; + const result = await this._requestAsync( + '/orderbook', + HttpRequestType.Get, + relayerResponseJsonParsers.parseOrderbookResponseJson, + requestOpts, + ); + return result; } /** * Retrieve fee information from the API @@ -114,10 +118,13 @@ export class HttpClient implements Client { const requestOpts = { payload: request, }; - const fees = await this._requestAsync('/fees', HttpRequestType.Post, requestOpts); - assert.doesConformToSchema('fees', fees, schemas.relayerApiFeesResponseSchema); - typeConverters.convertStringsFieldsToBigNumbers(fees, ['makerFee', 'takerFee']); - return fees; + const result = await this._requestAsync( + '/fees', + HttpRequestType.Post, + relayerResponseJsonParsers.parseFeesResponseJson, + requestOpts, + ); + return result; } /** * Submit a signed order to the API @@ -128,13 +135,16 @@ export class HttpClient implements Client { const requestOpts = { payload: signedOrder, }; - await this._requestAsync('/order', HttpRequestType.Post, requestOpts); + await this._requestAsync( + '/order', + HttpRequestType.Post, + _.noop, + requestOpts, + ); } - private async _requestAsync( - path: string, - requestType: HttpRequestType, - requestOptions?: HttpRequestOptions, - ): Promise<any> { + private async _requestAsync<T>(path: string, requestType: HttpRequestType, + jsonParser: (json: any) => T, + requestOptions?: HttpRequestOptions): Promise<T> { const params = _.get(requestOptions, 'params'); const payload = _.get(requestOptions, 'payload'); let query = ''; @@ -156,6 +166,6 @@ export class HttpClient implements Client { throw Error(response.statusText); } const json = await response.json(); - return json; + return jsonParser(json); } } diff --git a/packages/connect/src/utils/orderbook_channel_message_parsers.ts b/packages/connect/src/utils/orderbook_channel_message_parser.ts index a4f22dc4b..9a9ca8901 100644 --- a/packages/connect/src/utils/orderbook_channel_message_parsers.ts +++ b/packages/connect/src/utils/orderbook_channel_message_parser.ts @@ -4,10 +4,10 @@ import * as _ from 'lodash'; import { OrderbookChannelMessage, OrderbookChannelMessageTypes } from '../types'; -import { typeConverters } from './type_converters'; +import { relayerResponseJsonParsers } from './relayer_response_json_parsers'; -export const orderbookChannelMessageParsers = { - parser(utf8Data: string): OrderbookChannelMessage { +export const orderbookChannelMessageParser = { + parse(utf8Data: string): OrderbookChannelMessage { const messageObj = JSON.parse(utf8Data); const type: string = _.get(messageObj, 'type'); assert.assert(!_.isUndefined(type), `Message is missing a type parameter: ${utf8Data}`); @@ -15,15 +15,15 @@ export const orderbookChannelMessageParsers = { switch (type) { case OrderbookChannelMessageTypes.Snapshot: { assert.doesConformToSchema('message', messageObj, schemas.relayerApiOrderbookChannelSnapshotSchema); - const orderbook = messageObj.payload; - typeConverters.convertOrderbookStringFieldsToBigNumber(orderbook); - return messageObj; + const orderbookJson = messageObj.payload; + const orderbook = relayerResponseJsonParsers.parseOrderbookResponseJson(orderbookJson); + return _.assign(messageObj, { payload: orderbook }); } case OrderbookChannelMessageTypes.Update: { assert.doesConformToSchema('message', messageObj, schemas.relayerApiOrderbookChannelUpdateSchema); - const order = messageObj.payload; - typeConverters.convertOrderStringFieldsToBigNumber(order); - return messageObj; + const orderJson = messageObj.payload; + const order = relayerResponseJsonParsers.parseOrderJson(orderJson); + return _.assign(messageObj, { payload: order }); } default: { return { diff --git a/packages/connect/src/utils/relayer_response_json_parsers.ts b/packages/connect/src/utils/relayer_response_json_parsers.ts new file mode 100644 index 000000000..02d88f26d --- /dev/null +++ b/packages/connect/src/utils/relayer_response_json_parsers.ts @@ -0,0 +1,42 @@ +import {assert} from '@0xproject/assert'; +import {schemas} from '@0xproject/json-schemas'; +import * as _ from 'lodash'; + +import { + FeesResponse, + OrderbookResponse, + SignedOrder, + TokenPairsItem, +} from '../types'; + +import {typeConverters} from './type_converters'; + +export const relayerResponseJsonParsers = { + parseTokenPairsJson(json: any): TokenPairsItem[] { + assert.doesConformToSchema('tokenPairs', json, schemas.relayerApiTokenPairsResponseSchema); + return json.map((tokenPair: any) => { + return typeConverters.convertStringsFieldsToBigNumbers(tokenPair, [ + 'tokenA.minAmount', + 'tokenA.maxAmount', + 'tokenB.minAmount', + 'tokenB.maxAmount', + ]); + }); + }, + parseOrdersJson(json: any): SignedOrder[] { + assert.doesConformToSchema('orders', json, schemas.signedOrdersSchema); + return json.map((order: object) => typeConverters.convertOrderStringFieldsToBigNumber(order)); + }, + parseOrderJson(json: any): SignedOrder { + assert.doesConformToSchema('order', json, schemas.signedOrderSchema); + return typeConverters.convertOrderStringFieldsToBigNumber(json); + }, + parseOrderbookResponseJson(json: any): OrderbookResponse { + assert.doesConformToSchema('orderBook', json, schemas.relayerApiOrderBookResponseSchema); + return typeConverters.convertOrderbookStringFieldsToBigNumber(json); + }, + parseFeesResponseJson(json: any): FeesResponse { + assert.doesConformToSchema('fees', json, schemas.relayerApiFeesResponseSchema); + return typeConverters.convertStringsFieldsToBigNumbers(json, ['makerFee', 'takerFee']); + }, +}; diff --git a/packages/connect/src/utils/type_converters.ts b/packages/connect/src/utils/type_converters.ts index ccbc40677..ad27e0b97 100644 --- a/packages/connect/src/utils/type_converters.ts +++ b/packages/connect/src/utils/type_converters.ts @@ -1,15 +1,17 @@ import { BigNumber } from 'bignumber.js'; import * as _ from 'lodash'; -// TODO: convert all of these to non-mutating, pure functions export const typeConverters = { - convertOrderbookStringFieldsToBigNumber(orderbook: object): void { - _.each(orderbook, (orders: object[]) => { - _.each(orders, (order: object) => this.convertOrderStringFieldsToBigNumber(order)); - }); + convertOrderbookStringFieldsToBigNumber(orderbook: any): any { + const bids = _.get(orderbook, 'bids', []); + const asks = _.get(orderbook, 'asks', []); + return { + bids: bids.map((order: any) => this.convertOrderStringFieldsToBigNumber(order)), + asks: asks.map((order: any) => this.convertOrderStringFieldsToBigNumber(order)), + }; }, - convertOrderStringFieldsToBigNumber(order: object): void { - this.convertStringsFieldsToBigNumbers(order, [ + convertOrderStringFieldsToBigNumber(order: any): any { + return this.convertStringsFieldsToBigNumbers(order, [ 'makerTokenAmount', 'takerTokenAmount', 'makerFee', @@ -18,9 +20,11 @@ export const typeConverters = { 'salt', ]); }, - convertStringsFieldsToBigNumbers(obj: object, fields: string[]): void { + convertStringsFieldsToBigNumbers(obj: any, fields: string[]): any { + const result = _.assign({}, obj); _.each(fields, field => { - _.update(obj, field, (value: string) => new BigNumber(value)); + _.update(result, field, (value: string) => new BigNumber(value)); }); + return result; }, }; diff --git a/packages/connect/src/ws_orderbook_channel.ts b/packages/connect/src/ws_orderbook_channel.ts index 399f97319..822a022f4 100644 --- a/packages/connect/src/ws_orderbook_channel.ts +++ b/packages/connect/src/ws_orderbook_channel.ts @@ -11,7 +11,7 @@ import { WebsocketClientEventType, WebsocketConnectionEventType, } from './types'; -import { orderbookChannelMessageParsers } from './utils/orderbook_channel_message_parsers'; +import { orderbookChannelMessageParser } from './utils/orderbook_channel_message_parser'; /** * This class includes all the functionality related to interacting with a websocket endpoint @@ -104,7 +104,7 @@ export class WebSocketOrderbookChannel implements OrderbookChannel { if (!_.isUndefined(message.utf8Data)) { try { const utf8Data = message.utf8Data; - const parserResult = orderbookChannelMessageParsers.parser(utf8Data); + const parserResult = orderbookChannelMessageParser.parse(utf8Data); if (parserResult.requestId === requestId) { switch (parserResult.type) { case OrderbookChannelMessageTypes.Snapshot: { diff --git a/packages/connect/test/orderbook_channel_message_parsers_test.ts b/packages/connect/test/orderbook_channel_message_parsers_test.ts index e6cc05fed..cd7cdff12 100644 --- a/packages/connect/test/orderbook_channel_message_parsers_test.ts +++ b/packages/connect/test/orderbook_channel_message_parsers_test.ts @@ -2,7 +2,7 @@ import * as chai from 'chai'; import * as dirtyChai from 'dirty-chai'; import 'mocha'; -import { orderbookChannelMessageParsers } from '../src/utils/orderbook_channel_message_parsers'; +import { orderbookChannelMessageParser } from '../src/utils/orderbook_channel_message_parser'; import { orderResponse } from './fixtures/standard_relayer_api/order/0xabc67323774bdbd24d94f977fa9ac94a50f016026fd13f42990861238897721f'; import { orderbookResponse } from './fixtures/standard_relayer_api/orderbook'; @@ -20,20 +20,20 @@ chai.config.includeStack = true; chai.use(dirtyChai); const expect = chai.expect; -describe('orderbookChannelMessageParsers', () => { +describe('orderbookChannelMessageParser', () => { describe('#parser', () => { it('parses snapshot messages', () => { - const snapshotMessage = orderbookChannelMessageParsers.parser(snapshotOrderbookChannelMessage); + const snapshotMessage = orderbookChannelMessageParser.parse(snapshotOrderbookChannelMessage); expect(snapshotMessage.type).to.be.equal('snapshot'); expect(snapshotMessage.payload).to.be.deep.equal(orderbookResponse); }); it('parses update messages', () => { - const updateMessage = orderbookChannelMessageParsers.parser(updateOrderbookChannelMessage); + const updateMessage = orderbookChannelMessageParser.parse(updateOrderbookChannelMessage); expect(updateMessage.type).to.be.equal('update'); expect(updateMessage.payload).to.be.deep.equal(orderResponse); }); it('returns unknown message for messages with unsupported types', () => { - const unknownMessage = orderbookChannelMessageParsers.parser(unknownOrderbookChannelMessage); + const unknownMessage = orderbookChannelMessageParser.parse(unknownOrderbookChannelMessage); expect(unknownMessage.type).to.be.equal('unknown'); expect(unknownMessage.payload).to.be.undefined(); }); @@ -43,7 +43,7 @@ describe('orderbookChannelMessageParsers', () => { "requestId": 1, "payload": {} }`; - const badCall = () => orderbookChannelMessageParsers.parser(typelessMessage); + const badCall = () => orderbookChannelMessageParser.parse(typelessMessage); expect(badCall).throws(`Message is missing a type parameter: ${typelessMessage}`); }); it('throws when type is not a string', () => { @@ -53,22 +53,24 @@ describe('orderbookChannelMessageParsers', () => { "requestId": 1, "payload": {} }`; - const badCall = () => orderbookChannelMessageParsers.parser(messageWithBadType); + const badCall = () => orderbookChannelMessageParser.parse(messageWithBadType); expect(badCall).throws('Expected type to be of type string, encountered: 1'); }); it('throws when snapshot message has malformed payload', () => { - const badCall = () => orderbookChannelMessageParsers.parser(malformedSnapshotOrderbookChannelMessage); - const errMsg = - 'Validation errors: instance.payload requires property "bids", instance.payload requires property "asks"'; + const badCall = () => + orderbookChannelMessageParser.parse(malformedSnapshotOrderbookChannelMessage); + // tslint:disable-next-line:max-line-length + const errMsg = 'Validation errors: instance.payload requires property "bids", instance.payload requires property "asks"'; expect(badCall).throws(errMsg); }); it('throws when update message has malformed payload', () => { - const badCall = () => orderbookChannelMessageParsers.parser(malformedUpdateOrderbookChannelMessage); + const badCall = () => + orderbookChannelMessageParser.parse(malformedUpdateOrderbookChannelMessage); expect(badCall).throws(/^Expected message to conform to schema/); }); it('throws when input message is not valid JSON', () => { const nonJsonString = 'h93b{sdfs9fsd f'; - const badCall = () => orderbookChannelMessageParsers.parser(nonJsonString); + const badCall = () => orderbookChannelMessageParser.parse(nonJsonString); expect(badCall).throws('Unexpected token h in JSON at position 0'); }); }); |