Merge pull request #311 from 0xProject/fix/httpClientJsonParsing

Merge connect json parsing refactor into development
This commit is contained in:
Brandon Millman
2018-01-09 14:38:33 -08:00
committed by GitHub
6 changed files with 84 additions and 54 deletions

View File

@@ -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,16 +48,8 @@ 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',
]);
});
const responseJson = await this._requestAsync('/token_pairs', HttpRequestType.Get, requestOpts);
const tokenPairs = relayerResponseJsonParsers.parseTokenPairsJson(responseJson);
return tokenPairs;
}
/**
@@ -72,9 +64,8 @@ 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));
const responseJson = await this._requestAsync(`/orders`, HttpRequestType.Get, requestOpts);
const orders = relayerResponseJsonParsers.parseOrdersJson(responseJson);
return orders;
}
/**
@@ -84,9 +75,8 @@ 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);
const responseJson = await this._requestAsync(`/order/${orderHash}`, HttpRequestType.Get);
const order = relayerResponseJsonParsers.parseOrderJson(responseJson);
return order;
}
/**
@@ -99,10 +89,9 @@ 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 responseJson = await this._requestAsync('/orderbook', HttpRequestType.Get, requestOpts);
const orderbook = relayerResponseJsonParsers.parseOrderbookResponseJson(responseJson);
return orderbook;
}
/**
* Retrieve fee information from the API
@@ -114,9 +103,8 @@ 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']);
const responseJson = await this._requestAsync('/fees', HttpRequestType.Post, requestOpts);
const fees = relayerResponseJsonParsers.parseFeesResponseJson(responseJson);
return fees;
}
/**

View File

@@ -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 {

View File

@@ -0,0 +1,37 @@
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']);
},
};

View File

@@ -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;
},
};

View File

@@ -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: {

View File

@@ -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,23 @@ 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 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');
});
});