Add page options to relevant HttpClient methods

This commit is contained in:
Brandon Millman
2018-02-13 11:34:54 -08:00
parent 07d00cc515
commit 713c922e35
13 changed files with 147 additions and 70 deletions

View File

@@ -1,5 +1,9 @@
# CHANGELOG
## v0.6.0 - _TBD, 2018_
* Add pagination options to HttpClient methods (#393)
## v0.5.7 - _February 9, 2018_
* Fix publishing issue where .npmignore was not properly excluding undesired content (#389)

View File

@@ -13,14 +13,20 @@ import {
HttpRequestType,
OrderbookRequest,
OrderbookResponse,
OrdersRequest,
OrdersRequestOpts,
PagedRequestOpts,
SignedOrder,
TokenPairsItem,
TokenPairsRequest,
TokenPairsRequestOpts,
} from './types';
import { relayerResponseJsonParsers } from './utils/relayer_response_json_parsers';
const TRAILING_SLASHES_REGEX = /\/+$/;
const DEFAULT_PAGED_REQUEST_OPTS: PagedRequestOpts = {
page: 0,
per_page: 100,
};
/**
* This class includes all the functionality related to interacting with a set of HTTP endpoints
* that implement the standard relayer API v0
@@ -38,34 +44,37 @@ export class HttpClient implements Client {
}
/**
* Retrieve token pair info from the API
* @param request A TokenPairsRequest instance describing specific token information
* to retrieve
* @param requestOpts An optional (TokenPairsRequestOpts & PagedRequestOpts) instance describing token information
* to retrieve with page information, defaults to { page: 0, per_page: 100 }
* @return The resulting TokenPairsItems that match the request
*/
public async getTokenPairsAsync(request?: TokenPairsRequest): Promise<TokenPairsItem[]> {
if (!_.isUndefined(request)) {
assert.doesConformToSchema('request', request, clientSchemas.relayerTokenPairsRequestSchema);
public async getTokenPairsAsync(requestOpts?: TokenPairsRequestOpts & PagedRequestOpts): Promise<TokenPairsItem[]> {
if (!_.isUndefined(requestOpts)) {
assert.doesConformToSchema('requestOpts', requestOpts, clientSchemas.tokenPairsRequestOptsSchema);
assert.doesConformToSchema('requestOpts', requestOpts, clientSchemas.pagedRequestOptsSchema);
}
const requestOpts = {
params: request,
const httpRequestOpts = {
params: _.defaults({}, requestOpts, DEFAULT_PAGED_REQUEST_OPTS),
};
const responseJson = await this._requestAsync('/token_pairs', HttpRequestType.Get, requestOpts);
const responseJson = await this._requestAsync('/token_pairs', HttpRequestType.Get, httpRequestOpts);
const tokenPairs = relayerResponseJsonParsers.parseTokenPairsJson(responseJson);
return tokenPairs;
}
/**
* Retrieve orders from the API
* @param request An OrdersRequest instance describing specific orders to retrieve
* @param requestOpts An optional (OrdersRequestOpts & PagedRequestOpts) instance describing specific orders to retrieve
* with page information, defaults to { page: 0, per_page: 100 }
* @return The resulting SignedOrders that match the request
*/
public async getOrdersAsync(request?: OrdersRequest): Promise<SignedOrder[]> {
if (!_.isUndefined(request)) {
assert.doesConformToSchema('request', request, clientSchemas.relayerOrdersRequestSchema);
public async getOrdersAsync(requestOpts?: OrdersRequestOpts & PagedRequestOpts): Promise<SignedOrder[]> {
if (!_.isUndefined(requestOpts)) {
assert.doesConformToSchema('requestOpts', requestOpts, clientSchemas.ordersRequestOptsSchema);
assert.doesConformToSchema('requestOpts', requestOpts, clientSchemas.pagedRequestOptsSchema);
}
const requestOpts = {
params: request,
const httpRequestOpts = {
params: _.defaults({}, requestOpts, DEFAULT_PAGED_REQUEST_OPTS),
};
const responseJson = await this._requestAsync(`/orders`, HttpRequestType.Get, requestOpts);
const responseJson = await this._requestAsync(`/orders`, HttpRequestType.Get, httpRequestOpts);
const orders = relayerResponseJsonParsers.parseOrdersJson(responseJson);
return orders;
}
@@ -82,15 +91,22 @@ export class HttpClient implements Client {
}
/**
* Retrieve an orderbook from the API
* @param request An OrderbookRequest instance describing the specific orderbook to retrieve
* @param request An OrderbookRequest instance describing the specific orderbook to retrieve
* @param requestOpts An optional PagedRequestOpts instance describing page information, defaults to { page: 0, per_page: 100 }
* @return The resulting OrderbookResponse that matches the request
*/
public async getOrderbookAsync(request: OrderbookRequest): Promise<OrderbookResponse> {
assert.doesConformToSchema('request', request, clientSchemas.relayerOrderBookRequestSchema);
const requestOpts = {
params: request,
public async getOrderbookAsync(
request: OrderbookRequest,
requestOpts?: PagedRequestOpts,
): Promise<OrderbookResponse> {
assert.doesConformToSchema('request', request, clientSchemas.orderBookRequestSchema);
if (!_.isUndefined(requestOpts)) {
assert.doesConformToSchema('requestOpts', requestOpts, clientSchemas.pagedRequestOptsSchema);
}
const httpRequestOpts = {
params: _.defaults({}, request, requestOpts, DEFAULT_PAGED_REQUEST_OPTS),
};
const responseJson = await this._requestAsync('/orderbook', HttpRequestType.Get, requestOpts);
const responseJson = await this._requestAsync('/orderbook', HttpRequestType.Get, httpRequestOpts);
const orderbook = relayerResponseJsonParsers.parseOrderbookResponseJson(responseJson);
return orderbook;
}
@@ -100,11 +116,11 @@ export class HttpClient implements Client {
* @return The resulting FeesResponse that matches the request
*/
public async getFeesAsync(request: FeesRequest): Promise<FeesResponse> {
assert.doesConformToSchema('request', request, schemas.relayerApiFeesPayloadSchema);
const requestOpts = {
assert.doesConformToSchema('request', request, clientSchemas.feesRequestSchema);
const httpRequestOpts = {
payload: request,
};
const responseJson = await this._requestAsync('/fees', HttpRequestType.Post, requestOpts);
const responseJson = await this._requestAsync('/fees', HttpRequestType.Post, httpRequestOpts);
const fees = relayerResponseJsonParsers.parseFeesResponseJson(responseJson);
return fees;
}

View File

@@ -11,9 +11,10 @@ export {
OrderbookChannelSubscriptionOpts,
OrderbookRequest,
OrderbookResponse,
OrdersRequest,
OrdersRequestOpts,
PagedRequestOpts,
SignedOrder,
TokenPairsItem,
TokenPairsRequest,
TokenPairsRequestOpts,
TokenTradeInfo,
} from './types';

View File

@@ -0,0 +1,26 @@
export const feesRequestSchema = {
id: '/FeesRequest',
type: 'object',
properties: {
exchangeContractAddress: { $ref: '/Address' },
maker: { $ref: '/Address' },
taker: { $ref: '/Address' },
makerTokenAddress: { $ref: '/Address' },
takerTokenAddress: { $ref: '/Address' },
makerTokenAmount: { $ref: '/Number' },
takerTokenAmount: { $ref: '/Number' },
expirationUnixTimestampSec: { $ref: '/Number' },
salt: { $ref: '/Number' },
},
required: [
'exchangeContractAddress',
'maker',
'taker',
'makerTokenAddress',
'takerTokenAddress',
'makerTokenAmount',
'takerTokenAmount',
'expirationUnixTimestampSec',
'salt',
],
};

View File

@@ -1,8 +1,9 @@
export const relayerOrderBookRequestSchema = {
id: '/RelayerOrderBookRequest',
export const orderBookRequestSchema = {
id: '/OrderBookRequest',
type: 'object',
properties: {
baseTokenAddress: { $ref: '/Address' },
quoteTokenAddress: { $ref: '/Address' },
},
required: ['baseTokenAddress', 'quoteTokenAddress'],
};

View File

@@ -1,5 +1,5 @@
export const relayerOrdersRequestSchema = {
id: '/RelayerOrdersRequest',
export const ordersRequestOptsSchema = {
id: '/OrdersRequestOpts',
type: 'object',
properties: {
exchangeContractAddress: { $ref: '/Address' },

View File

@@ -0,0 +1,8 @@
export const pagedRequestOptsSchema = {
id: '/PagedRequestOpts',
type: 'object',
properties: {
page: { type: 'number' },
per_page: { type: 'number' },
},
};

View File

@@ -1,8 +0,0 @@
export const relayerOrderBookRequestSchema = {
id: '/RelayerOrderBookRequest',
type: 'object',
properties: {
baseTokenAddress: { $ref: '/Address' },
quoteTokenAddress: { $ref: '/Address' },
},
};

View File

@@ -1,9 +1,13 @@
import { relayerOrderBookRequestSchema } from './relayer_orderbook_request_schema';
import { relayerOrdersRequestSchema } from './relayer_orders_request_schema';
import { relayerTokenPairsRequestSchema } from './relayer_token_pairs_request_schema';
import { feesRequestSchema } from './fees_request_schema';
import { orderBookRequestSchema } from './orderbook_request_schema';
import { ordersRequestOptsSchema } from './orders_request_opts_schema';
import { pagedRequestOptsSchema } from './paged_request_opts_schema';
import { tokenPairsRequestOptsSchema } from './token_pairs_request_opts_schema';
export const schemas = {
relayerOrderBookRequestSchema,
relayerOrdersRequestSchema,
relayerTokenPairsRequestSchema,
feesRequestSchema,
orderBookRequestSchema,
ordersRequestOptsSchema,
pagedRequestOptsSchema,
tokenPairsRequestOptsSchema,
};

View File

@@ -1,5 +1,5 @@
export const relayerTokenPairsRequestSchema = {
id: '/RelayerTokenPairsRequest',
export const tokenPairsRequestOptsSchema = {
id: '/TokenPairsRequestOpts',
type: 'object',
properties: {
tokenA: { $ref: '/Address' },

View File

@@ -30,10 +30,10 @@ export interface ECSignature {
}
export interface Client {
getTokenPairsAsync: (request?: TokenPairsRequest) => Promise<TokenPairsItem[]>;
getOrdersAsync: (request?: OrdersRequest) => Promise<SignedOrder[]>;
getTokenPairsAsync: (requestOpts?: TokenPairsRequestOpts & PagedRequestOpts) => Promise<TokenPairsItem[]>;
getOrdersAsync: (requestOpts?: OrdersRequestOpts & PagedRequestOpts) => Promise<SignedOrder[]>;
getOrderAsync: (orderHash: string) => Promise<SignedOrder>;
getOrderbookAsync: (request: OrderbookRequest) => Promise<OrderbookResponse>;
getOrderbookAsync: (request: OrderbookRequest, requestOpts?: PagedRequestOpts) => Promise<OrderbookResponse>;
getFeesAsync: (request: FeesRequest) => Promise<FeesResponse>;
submitOrderAsync: (signedOrder: SignedOrder) => Promise<void>;
}
@@ -111,7 +111,7 @@ export enum WebsocketClientEventType {
ConnectFailed = 'connectFailed',
}
export interface TokenPairsRequest {
export interface TokenPairsRequestOpts {
tokenA?: string;
tokenB?: string;
}
@@ -128,7 +128,7 @@ export interface TokenTradeInfo {
precision: number;
}
export interface OrdersRequest {
export interface OrdersRequestOpts {
exchangeContractAddress?: string;
tokenAddress?: string;
makerTokenAddress?: string;
@@ -167,6 +167,11 @@ export interface FeesResponse {
takerFee: BigNumber;
}
export interface PagedRequestOpts {
page?: number;
per_page?: number;
}
export interface HttpRequestOptions {
params?: object;
payload?: object;

View File

@@ -40,19 +40,22 @@ describe('HttpClient', () => {
});
describe('#getTokenPairsAsync', () => {
const url = `${relayUrl}/token_pairs`;
it('gets token pairs', async () => {
fetchMock.get(url, tokenPairsResponseJSON);
it('gets token pairs with default options when none are provided', async () => {
const urlWithQuery = `${url}?page=0&per_page=100`;
fetchMock.get(urlWithQuery, tokenPairsResponseJSON);
const tokenPairs = await relayerClient.getTokenPairsAsync();
expect(tokenPairs).to.be.deep.equal(tokenPairsResponse);
});
it('gets specific token pairs for request', async () => {
it('gets token pairs with specified request options', async () => {
const tokenAddress = '0x323b5d4c32345ced77393b3530b1eed0f346429d';
const tokenPairsRequest = {
const tokenPairsRequestOpts = {
tokenA: tokenAddress,
page: 3,
per_page: 50,
};
const urlWithQuery = `${url}?tokenA=${tokenAddress}`;
const urlWithQuery = `${url}?page=3&per_page=50&tokenA=${tokenAddress}`;
fetchMock.get(urlWithQuery, tokenPairsResponseJSON);
const tokenPairs = await relayerClient.getTokenPairsAsync(tokenPairsRequest);
const tokenPairs = await relayerClient.getTokenPairsAsync(tokenPairsRequestOpts);
expect(tokenPairs).to.be.deep.equal(tokenPairsResponse);
});
it('throws an error for invalid JSON response', async () => {
@@ -62,17 +65,20 @@ describe('HttpClient', () => {
});
describe('#getOrdersAsync', () => {
const url = `${relayUrl}/orders`;
it('gets orders', async () => {
fetchMock.get(url, ordersResponseJSON);
it('gets orders with default options when none are provided', async () => {
const urlWithQuery = `${url}?page=0&per_page=100`;
fetchMock.get(urlWithQuery, ordersResponseJSON);
const orders = await relayerClient.getOrdersAsync();
expect(orders).to.be.deep.equal(ordersResponse);
});
it('gets specific orders for request', async () => {
it('gets orders with specified request options', async () => {
const tokenAddress = '0x323b5d4c32345ced77393b3530b1eed0f346429d';
const ordersRequest = {
tokenAddress,
page: 3,
per_page: 50,
};
const urlWithQuery = `${url}?tokenAddress=${tokenAddress}`;
const urlWithQuery = `${url}?page=3&per_page=50&tokenAddress=${tokenAddress}`;
fetchMock.get(urlWithQuery, ordersResponseJSON);
const orders = await relayerClient.getOrdersAsync(ordersRequest);
expect(orders).to.be.deep.equal(ordersResponse);
@@ -100,14 +106,27 @@ describe('HttpClient', () => {
baseTokenAddress: '0x323b5d4c32345ced77393b3530b1eed0f346429d',
quoteTokenAddress: '0xa2b31dacf30a9c50ca473337c01d8a201ae33e32',
};
const url = `${relayUrl}/orderbook?baseTokenAddress=${request.baseTokenAddress}&quoteTokenAddress=${
request.quoteTokenAddress
}`;
it('gets order book', async () => {
fetchMock.get(url, orderbookJSON);
const url = `${relayUrl}/orderbook`;
it('gets orderbook with default page options when none are provided', async () => {
const urlWithQuery = `${url}?baseTokenAddress=${
request.baseTokenAddress
}&page=0&per_page=100&quoteTokenAddress=${request.quoteTokenAddress}`;
fetchMock.get(urlWithQuery, orderbookJSON);
const orderbook = await relayerClient.getOrderbookAsync(request);
expect(orderbook).to.be.deep.equal(orderbookResponse);
});
it('gets orderbook with specified page options', async () => {
const urlWithQuery = `${url}?baseTokenAddress=${
request.baseTokenAddress
}&page=3&per_page=50&quoteTokenAddress=${request.quoteTokenAddress}`;
fetchMock.get(urlWithQuery, orderbookJSON);
const pagedRequestOptions = {
page: 3,
per_page: 50,
};
const orderbook = await relayerClient.getOrderbookAsync(request, pagedRequestOptions);
expect(orderbook).to.be.deep.equal(orderbookResponse);
});
it('throws an error for invalid JSON response', async () => {
fetchMock.get(url, { test: 'dummy' });
expect(relayerClient.getOrderbookAsync(request)).to.be.rejected();

View File

@@ -51,9 +51,10 @@ const docsInfoConfig: DocsInfoConfig = {
'OrderbookChannelSubscriptionOpts',
'OrderbookRequest',
'OrderbookResponse',
'OrdersRequest',
'OrdersRequestOpts',
'PagedRequestOpts',
'TokenPairsItem',
'TokenPairsRequest',
'TokenPairsRequestOpts',
'TokenTradeInfo',
'Order',
'SignedOrder',