Add json-schemas package to mono repo

This commit is contained in:
Brandon Millman 2017-11-13 19:32:01 -05:00
parent 08963f269b
commit 56b5619d24
41 changed files with 1557 additions and 20 deletions

View File

@ -88,7 +88,7 @@
},
"dependencies": {
"@0xproject/assert": "0.0.3",
"0x-json-schemas": "^0.6.1",
"@0xproject/json-schemas": "0.6.6",
"bignumber.js": "~4.1.0",
"compare-versions": "^3.0.1",
"es6-promisify": "^5.0.0",

View File

@ -1,6 +1,6 @@
import * as _ from 'lodash';
import BigNumber from 'bignumber.js';
import {SchemaValidator, schemas} from '0x-json-schemas';
import {SchemaValidator, schemas} from '@0xproject/json-schemas';
import {bigNumberConfigs} from './bignumber_config';
import * as ethUtil from 'ethereumjs-util';
import {Web3Wrapper} from './web3_wrapper';

View File

@ -1,7 +1,7 @@
import * as Web3 from 'web3';
import * as _ from 'lodash';
import promisify = require('es6-promisify');
import {SchemaValidator, schemas} from '0x-json-schemas';
import {SchemaValidator, schemas} from '@0xproject/json-schemas';
import {AbiType} from './types';
export class Contract implements Web3.ContractInstance {

View File

@ -1,7 +1,7 @@
import * as _ from 'lodash';
import * as Web3 from 'web3';
import BigNumber from 'bignumber.js';
import {schemas} from '0x-json-schemas';
import {schemas} from '@0xproject/json-schemas';
import {Web3Wrapper} from '../web3_wrapper';
import {
ECSignature,

View File

@ -1,6 +1,6 @@
import * as _ from 'lodash';
import BigNumber from 'bignumber.js';
import {schemas} from '0x-json-schemas';
import {schemas} from '@0xproject/json-schemas';
import {Web3Wrapper} from '../web3_wrapper';
import {assert} from '../utils/assert';
import {constants} from '../utils/constants';

View File

@ -1,5 +1,5 @@
import * as _ from 'lodash';
import {schemas} from '0x-json-schemas';
import {schemas} from '@0xproject/json-schemas';
import {ZeroEx} from '../0x';
import {EventWatcher} from './event_watcher';
import {assert} from '../utils/assert';

View File

@ -1,7 +1,7 @@
import * as _ from 'lodash';
import * as Web3 from 'web3';
import BigNumber from 'bignumber.js';
import {SchemaValidator, Schema} from '0x-json-schemas';
import {SchemaValidator, Schema} from '@0xproject/json-schemas';
import {assert as sharedAssert} from '@0xproject/assert';
import {Web3Wrapper} from '../web3_wrapper';
import {signatureUtils} from '../utils/signature_utils';

View File

@ -1,7 +1,7 @@
import * as _ from 'lodash';
import 'mocha';
import * as chai from 'chai';
import {SchemaValidator, schemas} from '0x-json-schemas';
import {SchemaValidator, schemas} from '@0xproject/json-schemas';
import {chaiSetup} from './utils/chai_setup';
import {web3Factory} from './utils/web3_factory';
import {ZeroEx, Token} from '../src';

View File

@ -38,7 +38,7 @@
"typescript": "^2.4.2"
},
"dependencies": {
"0x-json-schemas": "^0.6.5",
"@0xproject/json-schemas": "0.6.6",
"bignumber.js": "~4.1.0",
"ethereum-address": "^0.0.4",
"lodash": "^4.17.4",

View File

@ -2,7 +2,10 @@ import BigNumber from 'bignumber.js';
import * as ethereum_address from 'ethereum-address';
import * as _ from 'lodash';
import * as validUrl from 'valid-url';
import {SchemaValidator, Schema} from '0x-json-schemas';
import {
SchemaValidator,
Schema,
} from '@0xproject/json-schemas';
const HEX_REGEX = /^0x[0-9A-F]*$/i;

View File

@ -2,7 +2,7 @@ import 'mocha';
import * as dirtyChai from 'dirty-chai';
import * as chai from 'chai';
import {BigNumber} from 'bignumber.js';
import {schemas} from '0x-json-schemas';
import {schemas} from '@0xproject/json-schemas';
import {assert} from '../src/index';
chai.config.includeStack = true;

View File

@ -0,0 +1,15 @@
Contains 0x-related json schemas
## Usage:
```
import {SchemaValidator, ValidatorResult, schemas} from '@0xproject/json-schemas';
const {orderSchema} = schemas;
const validator = new SchemaValidator();
const order = {
...
};
const validatorResult: ValidatorResult = validator.validate(order, orderSchema); // Contains all errors
const isValid: boolean = validator.isValid(order, orderSchema); // Only returns boolean
```

View File

@ -0,0 +1,45 @@
{
"name": "@0xproject/json-schemas",
"version": "0.6.6",
"description": "0x-related json schemas",
"main": "lib/src/index.js",
"types": "lib/src/index.d.ts",
"scripts": {
"lint": "tslint src/*.ts test/*.ts",
"test": "run-s clean build run_mocha",
"test:circleci": "yarn test",
"run_mocha": "mocha lib/test/**/*_test.js",
"clean": "shx rm -rf _bundles lib test_temp",
"build": "tsc"
},
"repository": {
"type": "git",
"url": "https://github.com/0xProject/0x.js.git"
},
"author": "",
"license": "Apache-2.0",
"bugs": {
"url": "https://github.com/0xProject/0x.js/issues"
},
"homepage": "https://github.com/0xProject/0x.js/packages/json-schemas/README.md",
"dependencies": {
"jsonschema": "^1.2.0",
"lodash.values": "^4.3.0"
},
"devDependencies": {
"@types/lodash.foreach": "^4.5.3",
"@types/lodash.values": "^4.3.3",
"@types/mocha": "^2.2.42",
"bignumber.js": "^4.0.2",
"chai": "^4.1.1",
"chai-typescript-typings": "^0.0.1",
"dirty-chai": "^2.0.1",
"lodash.foreach": "^4.5.0",
"mocha": "^4.0.1",
"npm-run-all": "^4.1.1",
"shx": "^0.2.2",
"tslint": "~5.5.0",
"tslint-config-0xproject": "^0.0.2",
"typescript": "^2.4.2"
}
}

View File

@ -0,0 +1,11 @@
export const addressSchema = {
id: '/Address',
type: 'string',
pattern: '^0x[0-9a-f]{40}$',
};
export const numberSchema = {
id: '/Number',
type: 'string',
pattern: '^\\d+(\\.\\d+)?$',
};

View File

@ -0,0 +1,20 @@
export const ecSignatureParameterSchema = {
id: '/ECSignatureParameter',
type: 'string',
pattern: '^0[xX][0-9A-Fa-f]{64}$',
};
export const ecSignatureSchema = {
id: '/ECSignature',
properties: {
v: {
type: 'number',
minimum: 27,
maximum: 28,
},
r: {$ref: '/ECSignatureParameter'},
s: {$ref: '/ECSignatureParameter'},
},
required: ['v', 'r', 's'],
type: 'object',
};

View File

@ -0,0 +1,11 @@
export const indexFilterValuesSchema = {
id: '/IndexFilterValues',
additionalProperties: {
oneOf: [
{$ref: '/Number'},
{$ref: '/Address'},
{$ref: '/OrderHashSchema'},
],
},
type: 'object',
};

View File

@ -0,0 +1,12 @@
export const orderCancellationRequestsSchema = {
id: '/OrderCancellationRequests',
type: 'array',
items: {
properties: {
order: {$ref: '/Order'},
takerTokenCancelAmount: {$ref: '/Number'},
},
required: ['order', 'takerTokenCancelAmount'],
type: 'object',
},
};

View File

@ -0,0 +1,12 @@
export const orderFillOrKillRequestsSchema = {
id: '/OrderFillOrKillRequests',
type: 'array',
items: {
properties: {
signedOrder: {$ref: '/SignedOrder'},
fillTakerAmount: {$ref: '/Number'},
},
required: ['signedOrder', 'fillTakerAmount'],
type: 'object',
},
};

View File

@ -0,0 +1,12 @@
export const orderFillRequestsSchema = {
id: '/OrderFillRequests',
type: 'array',
items: {
properties: {
signedOrder: {$ref: '/SignedOrder'},
takerTokenFillAmount: {$ref: '/Number'},
},
required: ['signedOrder', 'takerTokenFillAmount'],
type: 'object',
},
};

View File

@ -0,0 +1,5 @@
export const orderHashSchema = {
id: '/OrderHashSchema',
type: 'string',
pattern: '^0x[0-9a-fA-F]{64}$',
};

View File

@ -0,0 +1,35 @@
export const orderSchema = {
id: '/Order',
properties: {
maker: {$ref: '/Address'},
taker: {$ref: '/Address'},
makerFee: {$ref: '/Number'},
takerFee: {$ref: '/Number'},
makerTokenAmount: {$ref: '/Number'},
takerTokenAmount: {$ref: '/Number'},
makerTokenAddress: {$ref: '/Address'},
takerTokenAddress: {$ref: '/Address'},
salt: {$ref: '/Number'},
feeRecipient: {$ref: '/Address'},
expirationUnixTimestampSec: {$ref: '/Number'},
exchangeContractAddress: {$ref: '/Address'},
},
required: [
'maker', 'taker', 'makerFee', 'takerFee', 'makerTokenAmount', 'takerTokenAmount',
'salt', 'feeRecipient', 'expirationUnixTimestampSec', 'exchangeContractAddress',
],
type: 'object',
};
export const signedOrderSchema = {
id: '/SignedOrder',
allOf: [
{ $ref: '/Order' },
{
properties: {
ecSignature: {$ref: '/ECSignature'},
},
required: ['ecSignature'],
},
],
};

View File

@ -0,0 +1,21 @@
export const relayerApiErrorResponseSchema = {
id: '/RelayerApiErrorResponse',
type: 'object',
properties: {
code: {type: 'number'},
reason: {type: 'string'},
validationErrors: {
type: 'array',
items: {
type: 'object',
properties: {
field: {type: 'string'},
code: {type: 'number'},
reason: {type: 'string'},
},
required: ['field', 'code', 'reason'],
},
},
},
required: ['code', 'reason'],
};

View File

@ -0,0 +1,19 @@
export const relayerApiFeesPayloadSchema = {
id: '/RelayerApiFeesPayload',
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',
'expirationUnixTimestampSec', 'salt',
],
};

View File

@ -0,0 +1,10 @@
export const relayerApiFeesResponseSchema = {
id: '/RelayerApiFeesResponse',
type: 'object',
properties: {
makerFee: {$ref: '/Number'},
takerFee: {$ref: '/Number'},
feeRecipient: {$ref: '/Address'},
},
required: ['makerFee', 'takerFee', 'feeRecipient'],
};

View File

@ -0,0 +1,22 @@
export const relayerApiOrderbookChannelSubscribeSchema = {
id: '/RelayerApiOrderbookChannelSubscribe',
type: 'object',
properties: {
type: {enum: ['subscribe']},
channel: {enum: ['orderbook']},
payload: {$ref: '/RelayerApiOrderbookChannelSubscribePayload'},
},
required: ['type', 'channel', 'payload'],
};
export const relayerApiOrderbookChannelSubscribePayload = {
id: '/RelayerApiOrderbookChannelSubscribePayload',
type: 'object',
properties: {
baseTokenAddress: {$ref: '/Address'},
quoteTokenAddress: {$ref: '/Address'},
snapshot: {type: 'boolean'},
limit: {type: 'number'},
},
required: ['baseTokenAddress', 'quoteTokenAddress'],
};

View File

@ -0,0 +1,21 @@
export const relayerApiOrderbookChannelSnapshotSchema = {
id: '/RelayerApiOrderbookChannelSnapshot',
type: 'object',
properties: {
type: {enum: ['snapshot']},
channel: {enum: ['orderbook']},
channelId: {type: 'number'},
payload: {$ref: '/RelayerApiOrderbookChannelSnapshotPayload'},
},
required: ['type', 'channel', 'channelId', 'payload'],
};
export const relayerApiOrderbookChannelSnapshotPayload = {
id: '/RelayerApiOrderbookChannelSnapshotPayload',
type: 'object',
properties: {
bids: {$ref: '/signedOrdersSchema'},
asks: {$ref: '/signedOrdersSchema'},
},
required: ['bids', 'asks'],
};

View File

@ -0,0 +1,11 @@
export const relayerApiOrderbookChannelUpdateSchema = {
id: '/RelayerApiOrderbookChannelUpdate',
type: 'object',
properties: {
type: {enum: ['update']},
channel: {enum: ['orderbook']},
channelId: {type: 'number'},
payload: {$ref: '/SignedOrder'},
},
required: ['type', 'channel', 'channelId', 'payload'],
};

View File

@ -0,0 +1,9 @@
export const relayerApiOrderBookResponseSchema = {
id: '/RelayerApiOrderBookResponse',
type: 'object',
properties: {
bids: {$ref: '/signedOrdersSchema'},
asks: {$ref: '/signedOrdersSchema'},
},
required: ['bids', 'asks'],
};

View File

@ -0,0 +1,24 @@
export const relayerApiTokenPairsResponseSchema = {
id: '/RelayerApiTokenPairsResponse',
type: 'array',
items: {
properties: {
tokenA: {$ref: '/RelayerApiTokenTradeInfo'},
tokenB: {$ref: '/RelayerApiTokenTradeInfo'},
},
required: ['tokenA', 'tokenB'],
type: 'object',
},
};
export const relayerApiTokenTradeInfoSchema = {
id: '/RelayerApiTokenTradeInfo',
type: 'object',
properties: {
address: {$ref: '/Address'},
minAmount: {$ref: '/Number'},
maxAmount: {$ref: '/Number'},
precision: {type: 'number'},
},
required: ['address'],
};

View File

@ -0,0 +1,5 @@
export const signedOrdersSchema = {
id: '/signedOrdersSchema',
type: 'array',
items: {$ref: '/SignedOrder'},
};

View File

@ -0,0 +1,20 @@
export const blockParamSchema = {
id: '/BlockParam',
oneOf: [
{
type: 'number',
},
{
enum: ['latest', 'earliest', 'pending'],
},
],
};
export const subscriptionOptsSchema = {
id: '/SubscriptionOpts',
properties: {
fromBlock: {$ref: '/BlockParam'},
toBlock: {$ref: '/BlockParam'},
},
type: 'object',
};

View File

@ -0,0 +1,11 @@
export const tokenSchema = {
id: '/Token',
properties: {
name: {type: 'string'},
symbol: {type: 'string'},
decimals: {type: 'number'},
address: {$ref: '/Address'},
},
required: ['name', 'symbol', 'decimals', 'address'],
type: 'object',
};

View File

@ -0,0 +1,42 @@
export const jsNumber = {
id: '/JsNumber',
type: 'number',
minimum: 0,
};
export const txDataSchema = {
id: '/TxData',
properties: {
from: {$ref: '/Address'},
to: {$ref: '/Address'},
value: {
oneOf: [
{$ref: '/Number'},
{$ref: '/JsNumber'},
],
},
gas: {
oneOf: [
{$ref: '/Number'},
{$ref: '/JsNumber'},
],
},
gasPrice: {
oneOf: [
{$ref: '/Number'},
{$ref: '/JsNumber'},
],
},
data: {
type: 'string',
pattern: '^0x[0-9a-f]*$',
},
nonce: {
type: 'number',
minimum: 0,
},
},
required: ['from'],
type: 'object',
additionalProperties: false,
};

View File

@ -0,0 +1,7 @@
declare module 'dirty-chai';
// es6-promisify declarations
declare function promisify(original: any, settings?: any): ((...arg: any[]) => Promise<any>);
declare module 'es6-promisify' {
export = promisify;
}

View File

@ -0,0 +1,4 @@
export {ValidatorResult, Schema} from 'jsonschema';
export {SchemaValidator} from './schema_validator';
export {schemas} from './schemas';

View File

@ -0,0 +1,28 @@
import values = require('lodash.values');
import {Validator, ValidatorResult, Schema} from 'jsonschema';
import {schemas} from './schemas';
export class SchemaValidator {
private validator: Validator;
constructor() {
this.validator = new Validator();
for (const schema of values(schemas)) {
this.validator.addSchema(schema, schema.id);
}
}
public addSchema(schema: Schema) {
this.validator.addSchema(schema, schema.id);
}
// In order to validate a complex JS object using jsonschema, we must replace any complex
// sub-types (e.g BigNumber) with a simpler string representation. Since BigNumber and other
// complex types implement the `toString` method, we can stringify the object and
// then parse it. The resultant object can then be checked using jsonschema.
public validate(instance: any, schema: Schema): ValidatorResult {
const jsonSchemaCompatibleObject = JSON.parse(JSON.stringify(instance));
return this.validator.validate(jsonSchemaCompatibleObject, schema);
}
public isValid(instance: any, schema: Schema): boolean {
const isValid = this.validate(instance, schema).errors.length === 0;
return isValid;
}
}

View File

@ -0,0 +1,99 @@
import {
numberSchema,
addressSchema,
} from '../schemas/basic_type_schemas';
import {
ecSignatureSchema,
ecSignatureParameterSchema,
} from '../schemas/ec_signature_schema';
import {
indexFilterValuesSchema,
} from '../schemas/index_filter_values_schema';
import {
orderCancellationRequestsSchema,
} from '../schemas/order_cancel_schema';
import {
orderFillOrKillRequestsSchema,
} from '../schemas/order_fill_or_kill_requests_schema';
import {
orderFillRequestsSchema,
} from '../schemas/order_fill_requests_schema';
import {
orderHashSchema,
} from '../schemas/order_hash_schema';
import {
orderSchema,
signedOrderSchema,
} from '../schemas/order_schemas';
import {
blockParamSchema,
subscriptionOptsSchema,
} from '../schemas/subscription_opts_schema';
import {
tokenSchema,
} from '../schemas/token_schema';
import {
signedOrdersSchema,
} from '../schemas/signed_orders_schema';
import {
relayerApiErrorResponseSchema,
} from '../schemas/relayer_api_error_response_schema';
import {
relayerApiFeesResponseSchema,
} from '../schemas/relayer_api_fees_response_schema';
import {
relayerApiFeesPayloadSchema,
} from '../schemas/relayer_api_fees_payload_schema';
import {
relayerApiOrderBookResponseSchema,
} from '../schemas/relayer_api_orderbook_response_schema';
import {
relayerApiTokenPairsResponseSchema,
relayerApiTokenTradeInfoSchema,
} from '../schemas/relayer_api_token_pairs_response_schema';
import {
jsNumber,
txDataSchema,
} from '../schemas/tx_data_schema';
import {
relayerApiOrderbookChannelSubscribeSchema,
relayerApiOrderbookChannelSubscribePayload,
} from '../schemas/relayer_api_orberbook_channel_subscribe_schema';
import {
relayerApiOrderbookChannelUpdateSchema,
} from '../schemas/relayer_api_orderbook_channel_update_response_schema';
import {
relayerApiOrderbookChannelSnapshotSchema,
relayerApiOrderbookChannelSnapshotPayload,
} from '../schemas/relayer_api_orderbook_channel_snapshot_schema';
export const schemas = {
numberSchema,
addressSchema,
ecSignatureSchema,
ecSignatureParameterSchema,
indexFilterValuesSchema,
orderCancellationRequestsSchema,
orderFillOrKillRequestsSchema,
orderFillRequestsSchema,
orderHashSchema,
orderSchema,
signedOrderSchema,
signedOrdersSchema,
blockParamSchema,
subscriptionOptsSchema,
tokenSchema,
jsNumber,
txDataSchema,
relayerApiErrorResponseSchema,
relayerApiFeesPayloadSchema,
relayerApiFeesResponseSchema,
relayerApiOrderBookResponseSchema,
relayerApiTokenPairsResponseSchema,
relayerApiTokenTradeInfoSchema,
relayerApiOrderbookChannelSubscribeSchema,
relayerApiOrderbookChannelSubscribePayload,
relayerApiOrderbookChannelUpdateSchema,
relayerApiOrderbookChannelSnapshotSchema,
relayerApiOrderbookChannelSnapshotPayload,
};

View File

@ -0,0 +1,972 @@
import 'mocha';
import forEach = require('lodash.foreach');
import * as dirtyChai from 'dirty-chai';
import * as chai from 'chai';
import BigNumber from 'bignumber.js';
import promisify = require('es6-promisify');
import {SchemaValidator, schemas} from '../src/index';
chai.config.includeStack = true;
chai.use(dirtyChai);
const expect = chai.expect;
const NULL_ADDRESS = '0x0000000000000000000000000000000000000000';
const {
numberSchema,
addressSchema,
ecSignatureSchema,
ecSignatureParameterSchema,
indexFilterValuesSchema,
orderCancellationRequestsSchema,
orderFillOrKillRequestsSchema,
orderFillRequestsSchema,
orderHashSchema,
orderSchema,
signedOrderSchema,
signedOrdersSchema,
blockParamSchema,
subscriptionOptsSchema,
tokenSchema,
jsNumber,
txDataSchema,
relayerApiErrorResponseSchema,
relayerApiOrderBookResponseSchema,
relayerApiTokenPairsResponseSchema,
relayerApiFeesPayloadSchema,
relayerApiFeesResponseSchema,
relayerApiOrderbookChannelSubscribeSchema,
relayerApiOrderbookChannelUpdateSchema,
relayerApiOrderbookChannelSnapshotSchema,
} = schemas;
describe('Schema', () => {
const validator = new SchemaValidator();
const validateAgainstSchema = (testCases: any[], schema: any, shouldFail = false) => {
forEach(testCases, (testCase: any) => {
const validationResult = validator.validate(testCase, schema);
const hasErrors = validationResult.errors.length !== 0;
if (shouldFail) {
if (!hasErrors) {
throw new Error(
`Expected testCase: ${JSON.stringify(testCase, null, '\t')} to fail and it didn't.`,
);
}
} else {
if (hasErrors) {
throw new Error(JSON.stringify(validationResult.errors, null, '\t'));
}
}
});
};
describe('#numberSchema', () => {
it('should validate valid numbers', () => {
const testCases = ['42', '0', '1.3', '0.2', '00.00'];
validateAgainstSchema(testCases, numberSchema);
});
it('should fail for invalid numbers', () => {
const testCases = ['.3', '1.', 'abacaba', 'и', '1..0'];
const shouldFail = true;
validateAgainstSchema(testCases, numberSchema, shouldFail);
});
});
describe('#addressSchema', () => {
it('should validate valid addresses', () => {
const testCases = ['0x8b0292b11a196601ed2ce54b665cafeca0347d42', NULL_ADDRESS];
validateAgainstSchema(testCases, addressSchema);
});
it('should fail for invalid addresses', () => {
const testCases = [
'0x',
'0',
'0x00',
'0xzzzzzzB11a196601eD2ce54B665CaFEca0347D42',
'0x8b0292B11a196601eD2ce54B665CaFEca0347D42',
];
const shouldFail = true;
validateAgainstSchema(testCases, addressSchema, shouldFail);
});
});
describe('#ecSignatureParameterSchema', () => {
it('should validate valid parameters', () => {
const testCases = [
'0x61a3ed31b43c8780e905a260a35faefcc527be7516aa11c0256729b5b351bc33',
'0X40349190569279751135161d22529dc25add4f6069af05be04cacbda2ace2254',
];
validateAgainstSchema(testCases, ecSignatureParameterSchema);
});
it('should fail for invalid parameters', () => {
const testCases = [
'0x61a3ed31b43c8780e905a260a35faefcc527be7516aa11c0256729b5b351bc3', // shorter
'0xzzzz9190569279751135161d22529dc25add4f6069af05be04cacbda2ace2254', // invalid characters
'40349190569279751135161d22529dc25add4f6069af05be04cacbda2ace2254', // no 0x
];
const shouldFail = true;
validateAgainstSchema(testCases, ecSignatureParameterSchema, shouldFail);
});
});
describe('#ecSignatureSchema', () => {
it('should validate valid signature', () => {
const signature = {
v: 27,
r: '0x61a3ed31b43c8780e905a260a35faefcc527be7516aa11c0256729b5b351bc33',
s: '0x40349190569279751135161d22529dc25add4f6069af05be04cacbda2ace2254',
};
const testCases = [
signature,
{
...signature,
v: 28,
},
];
validateAgainstSchema(testCases, ecSignatureSchema);
});
it('should fail for invalid signature', () => {
const v = 27;
const r = '0x61a3ed31b43c8780e905a260a35faefcc527be7516aa11c0256729b5b351bc33';
const s = '0x40349190569279751135161d22529dc25add4f6069af05be04cacbda2ace2254';
const testCases = [
{},
{v},
{r, s, v: 31},
];
const shouldFail = true;
validateAgainstSchema(testCases, ecSignatureSchema, shouldFail);
});
});
describe('#orderHashSchema', () => {
it('should validate valid order hash', () => {
const testCases = [
'0x61a3ed31B43c8780e905a260a35faefEc527be7516aa11c0256729b5b351bc33',
'0x40349190569279751135161d22529dc25add4f6069af05be04cacbda2ace2254',
];
validateAgainstSchema(testCases, orderHashSchema);
});
it('should fail for invalid order hash', () => {
const testCases = [
{},
'0x',
'0x8b0292B11a196601eD2ce54B665CaFEca0347D42',
'61a3ed31B43c8780e905a260a35faefEc527be7516aa11c0256729b5b351bc33',
];
const shouldFail = true;
validateAgainstSchema(testCases, orderHashSchema, shouldFail);
});
});
describe('#blockParamSchema', () => {
it('should validate valid block param', () => {
const testCases = [
42,
'latest',
'pending',
'earliest',
];
validateAgainstSchema(testCases, blockParamSchema);
});
it('should fail for invalid block param', () => {
const testCases = [
{},
'42',
'pemding',
];
const shouldFail = true;
validateAgainstSchema(testCases, blockParamSchema, shouldFail);
});
});
describe('#subscriptionOptsSchema', () => {
it('should validate valid subscription opts', () => {
const testCases = [
{fromBlock: 42, toBlock: 'latest'},
{fromBlock: 42},
{},
];
validateAgainstSchema(testCases, subscriptionOptsSchema);
});
it('should fail for invalid subscription opts', () => {
const testCases = [
{fromBlock: '42'},
];
const shouldFail = true;
validateAgainstSchema(testCases, subscriptionOptsSchema, shouldFail);
});
});
describe('#tokenSchema', () => {
const token = {
name: 'Zero Ex',
symbol: 'ZRX',
decimals: 100500,
address: '0x8b0292b11a196601ed2ce54b665cafeca0347d42',
url: 'https://0xproject.com',
};
it('should validate valid token', () => {
const testCases = [
token,
];
validateAgainstSchema(testCases, tokenSchema);
});
it('should fail for invalid token', () => {
const testCases = [
{
...token,
address: null,
},
{
...token,
decimals: undefined,
},
[],
4,
];
const shouldFail = true;
validateAgainstSchema(testCases, tokenSchema, shouldFail);
});
});
describe('order including schemas', () => {
const order = {
maker: NULL_ADDRESS,
taker: NULL_ADDRESS,
makerFee: '1',
takerFee: '2',
makerTokenAmount: '1',
takerTokenAmount: '2',
makerTokenAddress: NULL_ADDRESS,
takerTokenAddress: NULL_ADDRESS,
salt: '67006738228878699843088602623665307406148487219438534730168799356281242528500',
feeRecipient: NULL_ADDRESS,
exchangeContractAddress: NULL_ADDRESS,
expirationUnixTimestampSec: '42',
};
describe('#orderSchema', () => {
it('should validate valid order', () => {
const testCases = [
order,
];
validateAgainstSchema(testCases, orderSchema);
});
it('should fail for invalid order', () => {
const testCases = [
{
...order,
salt: undefined,
},
{
...order,
salt: 'salt',
},
'order',
];
const shouldFail = true;
validateAgainstSchema(testCases, orderSchema, shouldFail);
});
});
describe('signed order including schemas', () => {
const signedOrder = {
...order,
ecSignature: {
v: 27,
r: '0x61a3ed31b43c8780e905a260a35faefcc527be7516aa11c0256729b5b351bc33',
s: '0x40349190569279751135161d22529dc25add4f6069af05be04cacbda2ace2254',
},
};
describe('#signedOrdersSchema', () => {
it('should validate valid signed orders', () => {
const testCases = [
[signedOrder],
[],
];
validateAgainstSchema(testCases, signedOrdersSchema);
});
it('should fail for invalid signed orders', () => {
const testCases = [
[
signedOrder,
1,
],
];
const shouldFail = true;
validateAgainstSchema(testCases, signedOrdersSchema, shouldFail);
});
});
describe('#signedOrderSchema', () => {
it('should validate valid signed order', () => {
const testCases = [
signedOrder,
];
validateAgainstSchema(testCases, signedOrderSchema);
});
it('should fail for invalid signed order', () => {
const testCases = [
{
...signedOrder,
ecSignature: undefined,
},
];
const shouldFail = true;
validateAgainstSchema(testCases, signedOrderSchema, shouldFail);
});
});
describe('#orderFillOrKillRequestsSchema', () => {
const orderFillOrKillRequests = [
{
signedOrder,
fillTakerAmount: '5',
},
];
it('should validate valid order fill or kill requests', () => {
const testCases = [
orderFillOrKillRequests,
];
validateAgainstSchema(testCases, orderFillOrKillRequestsSchema);
});
it('should fail for invalid order fill or kill requests', () => {
const testCases = [
[
{
...orderFillOrKillRequests[0],
fillTakerAmount: undefined,
},
],
];
const shouldFail = true;
validateAgainstSchema(testCases, orderFillOrKillRequestsSchema, shouldFail);
});
});
describe('#orderCancellationRequestsSchema', () => {
const orderCancellationRequests = [
{
order,
takerTokenCancelAmount: '5',
},
];
it('should validate valid order cancellation requests', () => {
const testCases = [
orderCancellationRequests,
];
validateAgainstSchema(testCases, orderCancellationRequestsSchema);
});
it('should fail for invalid order cancellation requests', () => {
const testCases = [
[
{
...orderCancellationRequests[0],
takerTokenCancelAmount: undefined,
},
],
];
const shouldFail = true;
validateAgainstSchema(testCases, orderCancellationRequestsSchema, shouldFail);
});
});
describe('#orderFillRequestsSchema', () => {
const orderFillRequests = [
{
signedOrder,
takerTokenFillAmount: '5',
},
];
it('should validate valid order fill requests', () => {
const testCases = [
orderFillRequests,
];
validateAgainstSchema(testCases, orderFillRequestsSchema);
});
it('should fail for invalid order fill requests', () => {
const testCases = [
[
{
...orderFillRequests[0],
takerTokenFillAmount: undefined,
},
],
];
const shouldFail = true;
validateAgainstSchema(testCases, orderFillRequestsSchema, shouldFail);
});
});
describe('#relayerApiOrderBookResponseSchema', () => {
it('should validate valid order book responses', () => {
const testCases = [
{
bids: [],
asks: [],
},
{
bids: [signedOrder, signedOrder],
asks: [],
},
{
bids: [],
asks: [signedOrder, signedOrder],
},
{
bids: [signedOrder],
asks: [signedOrder, signedOrder],
},
];
validateAgainstSchema(testCases, relayerApiOrderBookResponseSchema);
});
it('should fail for invalid order fill requests', () => {
const testCases = [
{},
{
bids: [signedOrder, signedOrder],
},
{
asks: [signedOrder, signedOrder],
},
{
bids: signedOrder,
asks: [signedOrder, signedOrder],
},
{
bids: [signedOrder],
asks: signedOrder,
},
];
const shouldFail = true;
validateAgainstSchema(testCases, relayerApiOrderBookResponseSchema, shouldFail);
});
});
describe('#relayerApiOrderbookChannelSubscribeSchema', () => {
it('should validate valid orderbook channel websocket subscribe message', () => {
const testCases = [
{
type: 'subscribe',
channel: 'orderbook',
payload: {
baseTokenAddress: '0x323b5d4c32345ced77393b3530b1eed0f346429d',
quoteTokenAddress: '0x323b5d4c32345ced77393b3530b1eed0f346429d',
snapshot: true,
limit: 100,
},
},
{
type: 'subscribe',
channel: 'orderbook',
payload: {
baseTokenAddress: '0x323b5d4c32345ced77393b3530b1eed0f346429d',
quoteTokenAddress: '0x323b5d4c32345ced77393b3530b1eed0f346429d',
},
},
];
validateAgainstSchema(testCases, relayerApiOrderbookChannelSubscribeSchema);
});
it('should fail for invalid orderbook channel websocket subscribe message', () => {
const checksummedAddress = '0xA2b31daCf30a9C50ca473337c01d8A201ae33e32';
const testCases = [
{
type: 'foo',
channel: 'orderbook',
payload: {
baseTokenAddress: '0x323b5d4c32345ced77393b3530b1eed0f346429d',
quoteTokenAddress: '0x323b5d4c32345ced77393b3530b1eed0f346429d',
},
},
{
type: 'subscribe',
channel: 'bar',
payload: {
baseTokenAddress: '0x323b5d4c32345ced77393b3530b1eed0f346429d',
quoteTokenAddress: '0x323b5d4c32345ced77393b3530b1eed0f346429d',
},
},
{
type: 'subscribe',
channel: 'orderbook',
payload: {
baseTokenAddress: checksummedAddress,
quoteTokenAddress: '0x323b5d4c32345ced77393b3530b1eed0f346429d',
},
},
{
type: 'subscribe',
channel: 'orderbook',
payload: {
baseTokenAddress: '0x323b5d4c32345ced77393b3530b1eed0f346429d',
quoteTokenAddress: checksummedAddress,
},
},
{
type: 'subscribe',
channel: 'orderbook',
payload: {
quoteTokenAddress: '0x323b5d4c32345ced77393b3530b1eed0f346429d',
},
},
{
type: 'subscribe',
channel: 'orderbook',
payload: {
baseTokenAddress: '0x323b5d4c32345ced77393b3530b1eed0f346429d',
},
},
{
type: 'subscribe',
channel: 'orderbook',
payload: {
baseTokenAddress: '0x323b5d4c32345ced77393b3530b1eed0f346429d',
quoteTokenAddress: '0x323b5d4c32345ced77393b3530b1eed0f346429d',
snapshot: 'true',
limit: 100,
},
},
{
type: 'subscribe',
channel: 'orderbook',
payload: {
baseTokenAddress: '0x323b5d4c32345ced77393b3530b1eed0f346429d',
quoteTokenAddress: '0x323b5d4c32345ced77393b3530b1eed0f346429d',
snapshot: true,
limit: '100',
},
},
];
const shouldFail = true;
validateAgainstSchema(testCases, relayerApiOrderbookChannelSubscribeSchema, shouldFail);
});
});
describe('#relayerApiOrderbookChannelSnapshotSchema', () => {
it('should validate valid orderbook channel websocket snapshot message', () => {
const testCases = [
{
type: 'snapshot',
channel: 'orderbook',
channelId: 2,
payload: {
bids: [],
asks: [],
},
},
{
type: 'snapshot',
channel: 'orderbook',
channelId: 2,
payload: {
bids: [
signedOrder,
],
asks: [
signedOrder,
],
},
},
];
validateAgainstSchema(testCases, relayerApiOrderbookChannelSnapshotSchema);
});
it('should fail for invalid orderbook channel websocket snapshot message', () => {
const testCases = [
{
type: 'foo',
channel: 'orderbook',
channelId: 2,
payload: {
bids: [
signedOrder,
],
asks: [
signedOrder,
],
},
},
{
type: 'snapshot',
channel: 'bar',
channelId: 2,
payload: {
bids: [
signedOrder,
],
asks: [
signedOrder,
],
},
},
{
type: 'snapshot',
channel: 'orderbook',
payload: {
bids: [
signedOrder,
],
asks: [
signedOrder,
],
},
},
{
type: 'snapshot',
channel: 'orderbook',
channelId: '2',
payload: {
bids: [
signedOrder,
],
asks: [
signedOrder,
],
},
},
{
type: 'snapshot',
channel: 'orderbook',
channelId: 2,
payload: {
bids: [
signedOrder,
],
},
},
{
type: 'snapshot',
channel: 'orderbook',
channelId: 2,
payload: {
asks: [
signedOrder,
],
},
},
{
type: 'snapshot',
channel: 'orderbook',
channelId: 2,
payload: {
bids: [
signedOrder,
],
asks: [
{},
],
},
},
{
type: 'snapshot',
channel: 'orderbook',
channelId: 2,
payload: {
bids: [
{},
],
asks: [
signedOrder,
],
},
},
];
const shouldFail = true;
validateAgainstSchema(testCases, relayerApiOrderbookChannelSnapshotSchema, shouldFail);
});
});
describe('#relayerApiOrderbookChannelUpdateSchema', () => {
it('should validate valid orderbook channel websocket update message', () => {
const testCases = [
{
type: 'update',
channel: 'orderbook',
channelId: 2,
payload: signedOrder,
},
];
validateAgainstSchema(testCases, relayerApiOrderbookChannelUpdateSchema);
});
it('should fail for invalid orderbook channel websocket update message', () => {
const testCases = [
{
type: 'foo',
channel: 'orderbook',
payload: signedOrder,
},
{
type: 'update',
channel: 'bar',
payload: signedOrder,
},
{
type: 'update',
channel: 'orderbook',
payload: {},
},
];
const shouldFail = true;
validateAgainstSchema(testCases, relayerApiOrderbookChannelUpdateSchema, shouldFail);
});
});
});
});
describe('BigNumber serialization', () => {
it('should correctly serialize BigNumbers', () => {
const testCases = {
'42': '42',
'0': '0',
'1.3': '1.3',
'0.2': '0.2',
'00.00': '0',
'.3': '0.3',
};
forEach(testCases, (serialized: string, input: string) => {
expect(JSON.parse(JSON.stringify(new BigNumber(input)))).to.be.equal(serialized);
});
});
});
describe('#relayerApiErrorResponseSchema', () => {
it('should validate valid errorResponse', () => {
const testCases = [
{
code: 102,
reason: 'Order submission disabled',
},
{
code: 101,
reason: 'Validation failed',
validationErrors: [
{
field: 'maker',
code: 1002,
reason: 'Invalid address',
},
],
},
];
validateAgainstSchema(testCases, relayerApiErrorResponseSchema);
});
it('should fail for invalid error responses', () => {
const testCases = [
{},
{
code: 102,
},
{
code: '102',
reason: 'Order submission disabled',
},
{
reason: 'Order submission disabled',
},
{
code: 101,
reason: 'Validation failed',
validationErrors: [
{
field: 'maker',
reason: 'Invalid address',
},
],
},
{
code: 101,
reason: 'Validation failed',
validationErrors: [
{
field: 'maker',
code: '1002',
reason: 'Invalid address',
},
],
},
];
const shouldFail = true;
validateAgainstSchema(testCases, relayerApiErrorResponseSchema, shouldFail);
});
});
describe('#relayerApiFeesPayloadSchema', () => {
it('should validate valid fees payloads', () => {
const testCases = [
{
exchangeContractAddress: '0x323b5d4c32345ced77393b3530b1eed0f346429d',
maker: '0x323b5d4c32345ced77393b3530b1eed0f346429d',
taker: '0x323b5d4c32345ced77393b3530b1eed0f346429d',
makerTokenAddress: '0x323b5d4c32345ced77393b3530b1eed0f346429d',
takerTokenAddress: '0xef7fff64389b814a946f3e92105513705ca6b990',
makerTokenAmount: '10000000000000000000',
takerTokenAmount: '30000000000000000000',
expirationUnixTimestampSec: '42',
salt: '67006738228878699843088602623665307406148487219438534730168799356281242528500',
},
];
validateAgainstSchema(testCases, relayerApiFeesPayloadSchema);
});
it('should fail for invalid fees payloads', () => {
const checksummedAddress = '0xA2b31daCf30a9C50ca473337c01d8A201ae33e32';
const testCases = [
{},
{
takerTokenAddress: '0xef7fff64389b814a946f3e92105513705ca6b990',
makerTokenAmount: '10000000000000000000',
takerTokenAmount: '30000000000000000000',
},
{
taker: checksummedAddress,
makerTokenAddress: '0x323b5d4c32345ced77393b3530b1eed0f346429d',
takerTokenAddress: '0xef7fff64389b814a946f3e92105513705ca6b990',
makerTokenAmount: '10000000000000000000',
takerTokenAmount: '30000000000000000000',
},
{
makerTokenAddress: '0x323b5d4c32345ced77393b3530b1eed0f346429d',
takerTokenAddress: '0xef7fff64389b814a946f3e92105513705ca6b990',
makerTokenAmount: 10000000000000000000,
takerTokenAmount: 30000000000000000000,
},
];
const shouldFail = true;
validateAgainstSchema(testCases, relayerApiFeesPayloadSchema, shouldFail);
});
});
describe('#relayerApiFeesResponseSchema', () => {
it('should validate valid fees responses', () => {
const testCases = [
{
makerFee: '10000000000000000',
takerFee: '30000000000000000',
feeRecipient: '0x323b5d4c32345ced77393b3530b1eed0f346429d',
},
];
validateAgainstSchema(testCases, relayerApiFeesResponseSchema);
});
it('should fail for invalid fees responses', () => {
const checksummedAddress = '0xA2b31daCf30a9C50ca473337c01d8A201ae33e32';
const testCases = [
{},
{
makerFee: 10000000000000000,
takerFee: 30000000000000000,
},
{
feeRecipient: checksummedAddress,
takerToSpecify: checksummedAddress,
makerFee: '10000000000000000',
takerFee: '30000000000000000',
},
];
const shouldFail = true;
validateAgainstSchema(testCases, relayerApiFeesResponseSchema, shouldFail);
});
});
describe('#relayerApiTokenPairsResponseSchema', () => {
it('should validate valid tokenPairs response', () => {
const testCases = [
[],
[
{
tokenA: {
address: '0x323b5d4c32345ced77393b3530b1eed0f346429d',
minAmount: '0',
maxAmount: '10000000000000000000',
precision: 5,
},
tokenB: {
address: '0xef7fff64389b814a946f3e92105513705ca6b990',
minAmount: '0',
maxAmount: '50000000000000000000',
precision: 5,
},
},
],
[
{
tokenA: {
address: '0x323b5d4c32345ced77393b3530b1eed0f346429d',
},
tokenB: {
address: '0xef7fff64389b814a946f3e92105513705ca6b990',
},
},
],
];
validateAgainstSchema(testCases, relayerApiTokenPairsResponseSchema);
});
it('should fail for invalid tokenPairs responses', () => {
const checksummedAddress = '0xA2b31daCf30a9C50ca473337c01d8A201ae33e32';
const testCases = [
[
{
tokenA: {
address: checksummedAddress,
},
tokenB: {
address: checksummedAddress,
},
},
],
[
{
tokenA: {
address: '0x323b5d4c32345ced77393b3530b1eed0f346429d',
minAmount: 0,
maxAmount: 10000000000000000000,
},
tokenB: {
address: '0xef7fff64389b814a946f3e92105513705ca6b990',
minAmount: 0,
maxAmount: 50000000000000000000,
},
},
],
[
{
tokenA: {
address: '0x323b5d4c32345ced77393b3530b1eed0f346429d',
precision: '5',
},
tokenB: {
address: '0xef7fff64389b814a946f3e92105513705ca6b990',
precision: '5',
},
},
],
];
const shouldFail = true;
validateAgainstSchema(testCases, relayerApiTokenPairsResponseSchema, shouldFail);
});
});
describe('#jsNumberSchema', () => {
it('should validate valid js number', () => {
const testCases = [
1,
42,
];
validateAgainstSchema(testCases, jsNumber);
});
it('should fail for invalid js number', () => {
const testCases = [
NaN,
-1,
new BigNumber(1),
];
const shouldFail = true;
validateAgainstSchema(testCases, jsNumber, shouldFail);
});
});
describe('#txDataSchema', () => {
it('should validate valid txData', () => {
const testCases = [
{
from: NULL_ADDRESS,
},
{
from: NULL_ADDRESS,
gas: new BigNumber(42),
},
{
from: NULL_ADDRESS,
gas: 42,
},
];
validateAgainstSchema(testCases, txDataSchema);
});
it('should fail for invalid txData', () => {
const testCases = [
{
gas: new BigNumber(42),
},
{
from: NULL_ADDRESS,
unknownProp: 'here',
},
{},
[],
new BigNumber(1),
];
const shouldFail = true;
validateAgainstSchema(testCases, txDataSchema, shouldFail);
});
});
});

View File

@ -0,0 +1,17 @@
{
"compilerOptions": {
"module": "commonjs",
"target": "es5",
"lib": [ "es2017", "dom"],
"outDir": "lib",
"sourceMap": true,
"declaration": true,
"noImplicitAny": true,
"strictNullChecks": true
},
"include": [
"./src/**/*",
"./test/**/*",
"../../node_modules/chai-typescript-typings/index.d.ts"
]
}

View File

@ -0,0 +1,5 @@
{
"extends": [
"tslint-config-0xproject"
]
}

View File

@ -2,13 +2,6 @@
# yarn lockfile v1
"0x-json-schemas@^0.6.1", "0x-json-schemas@^0.6.5":
version "0.6.6"
resolved "https://registry.yarnpkg.com/0x-json-schemas/-/0x-json-schemas-0.6.6.tgz#3852e639245474a14daa2f8c454ba83ca5df8a9c"
dependencies:
jsonschema "^1.2.0"
lodash.values "^4.3.0"
"@types/fs-extra@^4.0.0":
version "4.0.4"
resolved "https://registry.yarnpkg.com/@types/fs-extra/-/fs-extra-4.0.4.tgz#72947e108f2cbeda5ab288a927399fdf6d02bd42"
@ -36,7 +29,19 @@
dependencies:
jsonschema "*"
"@types/lodash@^4.14.37", "@types/lodash@^4.14.64", "@types/lodash@^4.14.78":
"@types/lodash.foreach@^4.5.3":
version "4.5.3"
resolved "https://registry.yarnpkg.com/@types/lodash.foreach/-/lodash.foreach-4.5.3.tgz#87c01a0c5d9d17eec936ca3c28897af79440cdfc"
dependencies:
"@types/lodash" "*"
"@types/lodash.values@^4.3.3":
version "4.3.3"
resolved "https://registry.yarnpkg.com/@types/lodash.values/-/lodash.values-4.3.3.tgz#910edc65b391782d65dc4b4d8804a0dabc5370e6"
dependencies:
"@types/lodash" "*"
"@types/lodash@*", "@types/lodash@^4.14.37", "@types/lodash@^4.14.64", "@types/lodash@^4.14.78":
version "4.14.85"
resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.85.tgz#a16fbf942422f6eca5622b6910492c496c35069b"
@ -1119,7 +1124,7 @@ chai-typescript-typings@^0.0.1:
version "0.0.1"
resolved "https://registry.yarnpkg.com/chai-typescript-typings/-/chai-typescript-typings-0.0.1.tgz#433dee303b0b2978ad0dd03129df0a5afb791274"
chai@^4.0.1:
chai@^4.0.1, chai@^4.1.1:
version "4.1.2"
resolved "https://registry.yarnpkg.com/chai/-/chai-4.1.2.tgz#0f64584ba642f0f2ace2806279f4f06ca23ad73c"
dependencies:
@ -3547,6 +3552,10 @@ lodash.assign@^4.0.3, lodash.assign@^4.0.6:
version "4.2.0"
resolved "https://registry.yarnpkg.com/lodash.assign/-/lodash.assign-4.2.0.tgz#0d99f3ccd7a6d261d19bdaeb9245005d285808e7"
lodash.foreach@^4.5.0:
version "4.5.0"
resolved "https://registry.yarnpkg.com/lodash.foreach/-/lodash.foreach-4.5.0.tgz#1a6a35eace401280c7f06dddec35165ab27e3e53"
lodash.get@^4.4.2:
version "4.4.2"
resolved "https://registry.yarnpkg.com/lodash.get/-/lodash.get-4.4.2.tgz#2d177f652fa31e939b4438d5341499dfa3825e99"