Add assert package to the monorepo

This commit is contained in:
Brandon Millman 2017-11-13 16:25:21 -05:00
parent 2b26981e3d
commit 1147cb56ba
10 changed files with 537 additions and 9 deletions

View File

@ -87,7 +87,7 @@
},
"dependencies": {
"0x-json-schemas": "^0.6.1",
"bignumber.js": "^4.1.0",
"bignumber.js": "~4.1.0",
"compare-versions": "^3.0.1",
"es6-promisify": "^5.0.0",
"ethereumjs-abi": "^0.6.4",

View File

@ -0,0 +1,5 @@
<img src="https://github.com/0xProject/branding/blob/master/0x_Black_CMYK.png" width="200px" >
---
Standard type and schema assertions to be used across all 0x projects and packages

View File

@ -0,0 +1,14 @@
machine:
node:
version: 6.5.0
dependencies:
override:
- yarn
cache_directories:
- ~/.cache/yarn
test:
override:
- yarn test
- yarn lint

View File

@ -0,0 +1,45 @@
{
"name": "0x-assert",
"version": "0.0.3",
"description": "Provides a standard way of performing type and schema validation across 0x projects",
"main": "lib/src/index.js",
"types": "lib/src/index.d.ts",
"scripts": {
"build": "tsc",
"clean": "shx rm -rf _bundles lib test_temp",
"lint": "tslint src/**/*.ts test/**/*.ts",
"run_mocha": "mocha lib/test/**/*_test.js",
"prepublishOnly": "run-p build",
"test": "run-s clean build run_mocha"
},
"license": "Apache-2.0",
"repository": {
"type": "git",
"url": "https://github.com/0xProject/assert.git"
},
"bugs": {
"url": "https://github.com/0xProject/assert/issues"
},
"homepage": "https://github.com/0xProject/assert#readme",
"devDependencies": {
"@types/lodash": "^4.14.78",
"@types/mocha": "^2.2.42",
"@types/valid-url": "^1.0.2",
"chai": "^4.0.1",
"chai-typescript-typings": "^0.0.1",
"dirty-chai": "^2.0.1",
"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"
},
"dependencies": {
"0x-json-schemas": "^0.6.5",
"bignumber.js": "~4.1.0",
"ethereum-address": "^0.0.4",
"lodash": "^4.17.4",
"valid-url": "^1.0.9"
}
}

5
packages/assert/src/globals.d.ts vendored Normal file
View File

@ -0,0 +1,5 @@
declare module 'dirty-chai';
declare module 'ethereum-address' {
const isAddress: (arg: any) => boolean;
}

View File

@ -0,0 +1,84 @@
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';
const HEX_REGEX = /^0x[0-9A-F]*$/i;
export const assert = {
isBigNumber(variableName: string, value: BigNumber): void {
const isBigNumber = _.isObject(value) && (value as any).isBigNumber;
this.assert(isBigNumber, this.typeAssertionMessage(variableName, 'BigNumber', value));
},
isUndefined(value: any, variableName?: string): void {
this.assert(_.isUndefined(value), this.typeAssertionMessage(variableName, 'undefined', value));
},
isString(variableName: string, value: string): void {
this.assert(_.isString(value), this.typeAssertionMessage(variableName, 'string', value));
},
isFunction(variableName: string, value: any): void {
this.assert(_.isFunction(value), this.typeAssertionMessage(variableName, 'function', value));
},
isHexString(variableName: string, value: string): void {
this.assert(_.isString(value) && HEX_REGEX.test(value),
this.typeAssertionMessage(variableName, 'HexString', value));
},
isETHAddressHex(variableName: string, value: string): void {
this.assert(ethereum_address.isAddress(value), this.typeAssertionMessage(variableName, 'ETHAddressHex', value));
this.assert(
ethereum_address.isAddress(value) && value.toLowerCase() === value,
`Checksummed addresses are not supported. Convert ${variableName} to lower case before passing`,
);
},
doesBelongToStringEnum(variableName: string, value: string,
stringEnum: any /* There is no base type for every string enum */): void {
const doesBelongToStringEnum = !_.isUndefined(stringEnum[value]);
const enumValues = _.keys(stringEnum);
const enumValuesAsStrings = _.map(enumValues, enumValue => `'${enumValue}'`);
const enumValuesAsString = enumValuesAsStrings.join(', ');
assert.assert(
doesBelongToStringEnum,
`Expected ${variableName} to be one of: ${enumValuesAsString}, encountered: ${value}`,
);
},
hasAtMostOneUniqueValue(value: any[], errMsg: string): void {
this.assert(_.uniq(value).length <= 1, errMsg);
},
isNumber(variableName: string, value: number): void {
this.assert(_.isFinite(value), this.typeAssertionMessage(variableName, 'number', value));
},
isBoolean(variableName: string, value: boolean): void {
this.assert(_.isBoolean(value), this.typeAssertionMessage(variableName, 'boolean', value));
},
isWeb3Provider(variableName: string, value: any): void {
const isWeb3Provider = _.isFunction((value as any).send) || _.isFunction((value as any).sendAsync);
this.assert(isWeb3Provider, this.typeAssertionMessage(variableName, 'Web3.Provider', value));
},
doesConformToSchema(variableName: string, value: any, schema: Schema): void {
const schemaValidator = new SchemaValidator();
const validationResult = schemaValidator.validate(value, schema);
const hasValidationErrors = validationResult.errors.length > 0;
const msg = `Expected ${variableName} to conform to schema ${schema.id}
Encountered: ${JSON.stringify(value, null, '\t')}
Validation errors: ${validationResult.errors.join(', ')}`;
this.assert(!hasValidationErrors, msg);
},
isHttpUrl(variableName: string, value: any): void {
const isValidUrl = validUrl.isWebUri(value);
this.assert(isValidUrl, this.typeAssertionMessage(variableName, 'http url', value));
},
isUri(variableName: string, value: any): void {
const isValidUri = validUrl.isUri(value);
this.assert(isValidUri, this.typeAssertionMessage(variableName, 'uri', value));
},
assert(condition: boolean, message: string): void {
if (!condition) {
throw new Error(message);
}
},
typeAssertionMessage(variableName: string, type: string, value: any): string {
return `Expected ${variableName} to be of type ${type}, encountered: ${value}`;
},
};

View File

@ -0,0 +1,338 @@
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 {assert} from '../src/index';
chai.config.includeStack = true;
chai.use(dirtyChai);
const expect = chai.expect;
describe('Assertions', () => {
const variableName = 'variable';
describe('#isBigNumber', () => {
it('should not throw for valid input', () => {
const validInputs = [
new BigNumber(23),
new BigNumber('45'),
];
validInputs.forEach(input => expect(assert.isBigNumber.bind(assert, variableName, input)).to.not.throw());
});
it('should throw for invalid input', () => {
const invalidInputs = [
'test',
42,
false,
{ random: 'test' },
undefined,
];
invalidInputs.forEach(input => expect(assert.isBigNumber.bind(assert, variableName, input)).to.throw());
});
});
describe('#isUndefined', () => {
it('should not throw for valid input', () => {
const validInputs = [
undefined,
];
validInputs.forEach(input => expect(assert.isUndefined.bind(assert, input, variableName)).to.not.throw());
});
it('should throw for invalid input', () => {
const invalidInputs = [
'test',
42,
false,
{ random: 'test' },
];
invalidInputs.forEach(input => expect(assert.isUndefined.bind(assert, input, variableName)).to.throw());
});
});
describe('#isString', () => {
it('should not throw for valid input', () => {
const validInputs = [
'hello',
'goodbye',
];
validInputs.forEach(input => expect(assert.isString.bind(assert, variableName, input)).to.not.throw());
});
it('should throw for invalid input', () => {
const invalidInputs = [
42,
false,
{ random: 'test' },
undefined,
new BigNumber(45),
];
invalidInputs.forEach(input => expect(assert.isString.bind(assert, variableName, input)).to.throw());
});
});
describe('#isFunction', () => {
it('should not throw for valid input', () => {
const validInputs = [
BigNumber,
assert.isString.bind(this),
];
validInputs.forEach(input => expect(assert.isFunction.bind(assert, variableName, input)).to.not.throw());
});
it('should throw for invalid input', () => {
const invalidInputs = [
42,
false,
{ random: 'test' },
undefined,
new BigNumber(45),
];
invalidInputs.forEach(input => expect(assert.isFunction.bind(assert, variableName, input)).to.throw());
});
});
describe('#isHexString', () => {
it('should not throw for valid input', () => {
const validInputs = [
'0x61a3ed31B43c8780e905a260a35faefEc527be7516aa11c0256729b5b351bc33',
'0x40349190569279751135161d22529dc25add4f6069af05be04cacbda2ace2254',
];
validInputs.forEach(input => expect(assert.isHexString.bind(assert, variableName, input)).to.not.throw());
});
it('should throw for invalid input', () => {
const invalidInputs = [
42,
false,
{ random: 'test' },
undefined,
new BigNumber(45),
'0x61a3ed31B43c8780e905a260a35faYfEc527be7516aa11c0256729b5b351bc33',
];
invalidInputs.forEach(input => expect(assert.isHexString.bind(assert, variableName, input)).to.throw());
});
});
describe('#isETHAddressHex', () => {
it('should not throw for valid input', () => {
const validInputs = [
'0x0000000000000000000000000000000000000000',
'0x6fffd0ae3f7d88c9b4925323f54c6e4b2918c5fd',
'0x12459c951127e0c374ff9105dda097662a027093',
];
validInputs.forEach(input =>
expect(assert.isETHAddressHex.bind(assert, variableName, input)).to.not.throw(),
);
});
it('should throw for invalid input', () => {
const invalidInputs = [
42,
false,
{ random: 'test' },
undefined,
new BigNumber(45),
'0x6FFFd0ae3f7d88c9b4925323f54c6e4b2918c5fd',
'0x6FFFd0ae3f7d88c9b4925323f54c6e4',
];
invalidInputs.forEach(input =>
expect(assert.isETHAddressHex.bind(assert, variableName, input)).to.throw(),
);
});
});
describe('#doesBelongToStringEnum', () => {
enum TestEnums {
Test1 = 'Test1',
Test2 = 'Test2',
}
it('should not throw for valid input', () => {
const validInputs = [
TestEnums.Test1,
TestEnums.Test2,
];
validInputs.forEach(input =>
expect(assert.doesBelongToStringEnum.bind(assert, variableName, input, TestEnums)).to.not.throw(),
);
});
it('should throw for invalid input', () => {
const invalidInputs = [
42,
false,
{ random: 'test' },
undefined,
new BigNumber(45),
];
invalidInputs.forEach(input =>
expect(assert.doesBelongToStringEnum.bind(assert, variableName, input, TestEnums)).to.throw(),
);
});
});
describe('#hasAtMostOneUniqueValue', () => {
const errorMsg = 'more than one unique value';
it('should not throw for valid input', () => {
const validInputs = [
['hello'],
['goodbye', 'goodbye', 'goodbye'],
];
validInputs.forEach(input =>
expect(assert.hasAtMostOneUniqueValue.bind(assert, input, errorMsg)).to.not.throw(),
);
});
it('should throw for invalid input', () => {
const invalidInputs = [
['hello', 'goodbye'],
['goodbye', 42, false, false],
];
invalidInputs.forEach(input =>
expect(assert.hasAtMostOneUniqueValue.bind(assert, input, errorMsg)).to.throw(),
);
});
});
describe('#isNumber', () => {
it('should not throw for valid input', () => {
const validInputs = [
42,
0.00,
21e+42,
];
validInputs.forEach(input => expect(assert.isNumber.bind(assert, variableName, input)).to.not.throw());
});
it('should throw for invalid input', () => {
const invalidInputs = [
false,
{ random: 'test' },
undefined,
new BigNumber(45),
];
invalidInputs.forEach(input => expect(assert.isNumber.bind(assert, variableName, input)).to.throw());
});
});
describe('#isBoolean', () => {
it('should not throw for valid input', () => {
const validInputs = [
true,
false,
];
validInputs.forEach(input => expect(assert.isBoolean.bind(assert, variableName, input)).to.not.throw());
});
it('should throw for invalid input', () => {
const invalidInputs = [
42,
{ random: 'test' },
undefined,
new BigNumber(45),
];
invalidInputs.forEach(input => expect(assert.isBoolean.bind(assert, variableName, input)).to.throw());
});
});
describe('#isWeb3Provider', () => {
it('should not throw for valid input', () => {
const validInputs = [
{ send: () => 45 },
{ sendAsync: () => 45 },
];
validInputs.forEach(input =>
expect(assert.isWeb3Provider.bind(assert, variableName, input)).to.not.throw(),
);
});
it('should throw for invalid input', () => {
const invalidInputs = [
42,
{ random: 'test' },
undefined,
new BigNumber(45),
];
invalidInputs.forEach(input =>
expect(assert.isWeb3Provider.bind(assert, variableName, input)).to.throw(),
);
});
});
describe('#doesConformToSchema', () => {
const schema = schemas.addressSchema;
it('should not throw for valid input', () => {
const validInputs = [
'0x6fffd0ae3f7d88c9b4925323f54c6e4b2918c5fd',
'0x12459c951127e0c374ff9105dda097662a027093',
];
validInputs.forEach(input =>
expect(assert.doesConformToSchema.bind(assert, variableName, input, schema)).to.not.throw(),
);
});
it('should throw for invalid input', () => {
const invalidInputs = [
42,
{ random: 'test' },
undefined,
new BigNumber(45),
];
invalidInputs.forEach(input =>
expect(assert.doesConformToSchema.bind(assert, variableName, input, schema)).to.throw(),
);
});
});
describe('#isHttpUrl', () => {
it('should not throw for valid input', () => {
const validInputs = [
'http://www.google.com',
'https://api.example-relayer.net',
'https://api.radarrelay.com/0x/v0/',
'https://zeroex.beta.radarrelay.com:8000/0x/v0/',
];
validInputs.forEach(input =>
expect(assert.isHttpUrl.bind(assert, variableName, input)).to.not.throw(),
);
});
it('should throw for invalid input', () => {
const invalidInputs = [
42,
{ random: 'test' },
undefined,
new BigNumber(45),
'ws://www.api.example-relayer.net',
'www.google.com',
'api.example-relayer.net',
'user:password@api.example-relayer.net',
'//api.example-relayer.net',
];
invalidInputs.forEach(input =>
expect(assert.isHttpUrl.bind(assert, variableName, input)).to.throw(),
);
});
});
describe('#isUri', () => {
it('should not throw for valid input', () => {
const validInputs = [
'http://www.google.com',
'https://api.example-relayer.net',
'https://api.radarrelay.com/0x/v0/',
'https://zeroex.beta.radarrelay.com:8000/0x/v0/',
'ws://www.api.example-relayer.net',
'wss://www.api.example-relayer.net',
'user:password@api.example-relayer.net',
];
validInputs.forEach(input =>
expect(assert.isUri.bind(assert, variableName, input)).to.not.throw(),
);
});
it('should throw for invalid input', () => {
const invalidInputs = [
42,
{ random: 'test' },
undefined,
new BigNumber(45),
'www.google.com',
'api.example-relayer.net',
'//api.example-relayer.net',
];
invalidInputs.forEach(input =>
expect(assert.isUri.bind(assert, variableName, input)).to.throw(),
);
});
});
describe('#assert', () => {
const assertMessage = 'assert not satisfied';
it('should not throw for valid input', () => {
expect(assert.assert.bind(assert, true, assertMessage)).to.not.throw();
});
it('should throw for invalid input', () => {
expect(assert.assert.bind(assert, false, assertMessage)).to.throw();
});
});
describe('#typeAssertionMessage', () => {
it('should render correct message', () => {
expect(assert.typeAssertionMessage('variable', 'string', 'number'))
.to.equal(`Expected variable to be of type string, encountered: number`);
});
});
});

View File

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

View File

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

View File

@ -2,7 +2,7 @@
# yarn lockfile v1
"0x-json-schemas@^0.6.1":
"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:
@ -36,7 +36,7 @@
dependencies:
jsonschema "*"
"@types/lodash@^4.14.37", "@types/lodash@^4.14.64":
"@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"
@ -48,7 +48,7 @@
version "2.0.29"
resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-2.0.29.tgz#5002e14f75e2d71e564281df0431c8c1b4a2a36a"
"@types/mocha@^2.2.41":
"@types/mocha@^2.2.41", "@types/mocha@^2.2.42":
version "2.2.44"
resolved "https://registry.yarnpkg.com/@types/mocha/-/mocha-2.2.44.tgz#1d4a798e53f35212fd5ad4d04050620171cd5b5e"
@ -73,6 +73,10 @@
dependencies:
"@types/node" "*"
"@types/valid-url@^1.0.2":
version "1.0.2"
resolved "https://registry.yarnpkg.com/@types/valid-url/-/valid-url-1.0.2.tgz#60fa435ce24bfd5ba107b8d2a80796aeaf3a8f45"
JSONStream@^1.0.4:
version "1.3.1"
resolved "https://registry.yarnpkg.com/JSONStream/-/JSONStream-1.3.1.tgz#707f761e01dae9e16f1bcf93703b78c70966579a"
@ -802,7 +806,7 @@ big.js@^3.1.3:
version "3.2.0"
resolved "https://registry.yarnpkg.com/big.js/-/big.js-3.2.0.tgz#a5fc298b81b9e0dca2e458824784b65c52ba588e"
bignumber.js@^4.0.2, bignumber.js@^4.1.0:
bignumber.js@^4.0.2, bignumber.js@~4.1.0:
version "4.1.0"
resolved "https://registry.yarnpkg.com/bignumber.js/-/bignumber.js-4.1.0.tgz#db6f14067c140bd46624815a7916c92d9b6c24b1"
@ -1599,7 +1603,7 @@ crypto-browserify@^3.11.0:
randombytes "^2.0.0"
randomfill "^1.0.3"
crypto-js@^3.1.4:
crypto-js@^3.1.4, crypto-js@^3.1.6:
version "3.1.8"
resolved "https://registry.yarnpkg.com/crypto-js/-/crypto-js-3.1.8.tgz#715f070bf6014f2ae992a98b3929258b713f08d5"
@ -2011,6 +2015,12 @@ eth-sig-util@^1.3.0:
ethereumjs-abi "git+https://github.com/ethereumjs/ethereumjs-abi.git"
ethereumjs-util "^5.1.1"
ethereum-address@^0.0.4:
version "0.0.4"
resolved "https://registry.yarnpkg.com/ethereum-address/-/ethereum-address-0.0.4.tgz#91729b2bc8a0044bbee2c05ccf6d0417953e5f95"
dependencies:
crypto-js "^3.1.6"
ethereum-common@0.2.0:
version "0.2.0"
resolved "https://registry.yarnpkg.com/ethereum-common/-/ethereum-common-0.2.0.tgz#13bf966131cce1eeade62a1b434249bb4cb120ca"
@ -3827,7 +3837,7 @@ mkdirp@0.5.1, "mkdirp@>=0.5 0", mkdirp@^0.5.0, mkdirp@^0.5.1, mkdirp@~0.5.0:
dependencies:
minimist "0.0.8"
mocha@^4.0.0:
mocha@^4.0.0, mocha@^4.0.1:
version "4.0.1"
resolved "https://registry.yarnpkg.com/mocha/-/mocha-4.0.1.tgz#0aee5a95cf69a4618820f5e51fa31717117daf1b"
dependencies:
@ -3982,7 +3992,7 @@ normalize-path@^2.0.0, normalize-path@^2.0.1:
dependencies:
remove-trailing-separator "^1.0.1"
npm-run-all@^4.0.2:
npm-run-all@^4.0.2, npm-run-all@^4.1.1:
version "4.1.2"
resolved "https://registry.yarnpkg.com/npm-run-all/-/npm-run-all-4.1.2.tgz#90d62d078792d20669139e718621186656cea056"
dependencies:
@ -5701,7 +5711,7 @@ typescript@2.4.1:
version "2.4.1"
resolved "https://registry.yarnpkg.com/typescript/-/typescript-2.4.1.tgz#c3ccb16ddaa0b2314de031e7e6fee89e5ba346bc"
typescript@^2.4.1:
typescript@^2.4.2, typescript@~2.6.1:
version "2.6.1"
resolved "https://registry.yarnpkg.com/typescript/-/typescript-2.6.1.tgz#ef39cdea27abac0b500242d6726ab90e0c846631"
@ -5813,6 +5823,10 @@ uuid@^3.0.0, uuid@^3.0.1, uuid@^3.1.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.1.0.tgz#3dd3d3e790abc24d7b0d3a034ffababe28ebbc04"
valid-url@^1.0.9:
version "1.0.9"
resolved "https://registry.yarnpkg.com/valid-url/-/valid-url-1.0.9.tgz#1c14479b40f1397a75782f115e4086447433a200"
validate-npm-package-license@^3.0.1:
version "3.0.1"
resolved "https://registry.yarnpkg.com/validate-npm-package-license/-/validate-npm-package-license-3.0.1.tgz#2804babe712ad3379459acfbe24746ab2c303fbc"