Skip to content

Commit

Permalink
feat(extensions): add extra string formats (#19)
Browse files Browse the repository at this point in the history
  • Loading branch information
patrickhulce committed Jul 26, 2016
1 parent 85dcc57 commit f6729df
Show file tree
Hide file tree
Showing 8 changed files with 347 additions and 12 deletions.
10 changes: 10 additions & 0 deletions lib/extensions/builders.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,16 @@ module.exports = function (construct) {

return {
builders: _.assign(typeBuilders, {
object: function (childModel) {
return childModel ?
construct({type: 'object', children: childModel}) :
construct({type: 'object'});
},
array: function (childModel) {
return childModel ?
construct({type: 'array', children: childModel}) :
construct({type: 'array'});
},
enum: function (options) {
if (!_.isArray(options)) {
options = _.slice(arguments);
Expand Down
3 changes: 3 additions & 0 deletions lib/extensions/minmax.js
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,9 @@ module.exports = function () {
assert.ok(typeof max === 'number', 'max must be a number');
return this._with({max: max});
},
length: function (value) {
return this._with({min: value, max: value});
},
}
};
};
74 changes: 74 additions & 0 deletions lib/extensions/strings.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
var _ = require('lodash');
var assert = require('../assert');


var ip = /(\d{1,3}\.){3}\d{1,3}(\/\d{1,2})?/i;
var domain = /([0-9a-z-]+\.?)+(\.)?[0-9a-z-]+/i;
var hostname = new RegExp(`(${domain.source}|${ip.source})`, 'i');
var REGEXES = {
hex: /^[0-9a-f]*$/i,
alphanumeric: /^\w*$/i,
creditCard: /^\d{13,16}$/,
uuid: /^[0-9a-f]{4}([0-9a-f]{4}-){4}[0-9a-f]{12}$/i,
ip: new RegExp(`^${ip.source}$`),
domain: new RegExp(`^${domain.source}$`, 'i'),
email: [/^\S+@/, domain, /$/],
hostname: new RegExp(`^${hostname.source}$`, 'i'),
uri: [
/^[a-z0-9+.-]+:(\/\/)?/, // scheme
/(\S+@)?/, // userinfo
hostname, // hostname
/(:\d+)?/,
],
};

var compose = function (regexes) {
return regexes.reduce(function (regexp, part) {
var first = regexp ? regexp.source : '';
return new RegExp(first + part.source);
}, false);
};

var validation = function (name) {
var regex = _.isArray(REGEXES[name]) ?
compose(REGEXES[name]) : REGEXES[name];
var msg = `value should match ${regex}`;

return value => {
var match = _.get(value, 'match', _.noop);
assert.typeof(match, 'function');
assert.ok(match.call(value, regex), msg);
};
};

module.exports = function () {
var extension = {
builders: true,
formats: {
string: [
'uuid', 'alphanumeric', 'hex',
'ip', 'uri', 'domain', 'email',
'creditCard',
],
},
transformations: {
string: {
creditCard: function (value) {
if (typeof value === 'string') {
return value.replace(/[-\s]+/g, '');
} else {
return value;
}
}
}
}
};

extension.validations = {
string: _.map(extension.formats.string, format => {
return _.set({}, format, validation(format));
}).reduce(_.merge, {})
};

return extension;
};
28 changes: 28 additions & 0 deletions test/bootstrap.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
var path = require('path');
var assert = require('assert');
var chai = require('chai');
var sinon = require('sinon');
chai.should();
Expand All @@ -12,3 +13,30 @@ global.defineTest = (file, func) => {
func(require('../lib/' + file));
});
};


var mapIntoFunctions = function (values, validate, model) {
return values.map(value => [() => {
validate.call(model, value);
}, value]);
};

global.testPassingValues = function () {
mapIntoFunctions.apply(null, arguments).forEach(items => {
try {
items[0].should.not.throw();
} catch (err) {
assert.ok(false, `${items[1]} failed validation: ${err.message}`);
}
});
};

global.testFailingValues = function () {
mapIntoFunctions.apply(null, arguments).forEach(items => {
try {
items[0].should.throw(assert.AssertionError);
} catch (err) {
assert.ok(false, `${items[1]} should have failed validation`);
}
});
};
18 changes: 18 additions & 0 deletions test/extensions/builders.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,24 @@ defineTest('extensions/builders.js', function (buildersFactory) {
model.should.be.instanceof(Model);
model.spec.should.have.property('type', type);
});

if (type === 'object' || type === 'array') {
it('should set spec.children', function () {
var modelSpec = type === 'object' ?
{id: builders.string()} :
{type: 'string'};

var model = builders[type](modelSpec);
model.should.be.instanceof(Model);
model.spec.should.have.property('type', type);
if (type === 'array') {
model.spec.should.have.property('children').an('object');
} else {
model.spec.should.have.property('children').
with.deep.property('0.model').instanceof(Model);
}
});
}
});
});

Expand Down
15 changes: 15 additions & 0 deletions test/extensions/minmax.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -159,5 +159,20 @@ defineTest('extensions/minmax.js', function (minmaxFactory) {
}).should.throw();
});
});

describe('length', function () {
it('should set spec.min and spec.max', function () {
var model = new inst.Model({type: 'string'}).length(10);
model.should.have.deep.property('spec.min', 10);
model.should.have.deep.property('spec.max', 10);
model.validate('0123456789').should.have.property('conforms', true);
});

it('should fail for non-numeric types', function () {
(function () {
new Model({type: 'array'}).length('foo');
}).should.throw();
});
});
});
});
199 changes: 199 additions & 0 deletions test/extensions/strings.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,199 @@
defineTest('extensions/strings.js', function (extensionFactory) {
var extension = extensionFactory();

describe('#transformations', function () {
describe('creditCard', function () {
var transform = extension.transformations.string.creditCard;
it('should remove dashes and spaces', function () {
transform('444-4444-4444').should.equal('44444444444');
transform('444 4444 4444').should.equal('44444444444');
});

it('should not remove dashes and spaces when non-string', function () {
var obj = {};
transform(12).should.equal(12);
transform(obj).should.equal(obj);
});
});
});

describe('#validations', function () {
describe('uuid', function () {
var validate = extension.validations.string.uuid;

it('should pass valid values', function () {
testPassingValues([
'11111111-1111-1111-1111-111111111111',
'1e1af1d1-2111-4111-1121-1a1e111ec111',
'8aebedcd-474d-4a32-bd7e-425b92834fb3',
'f83b75b8-8387-4657-9e56-fb7a501ca578',
'367aa2b0-52e4-11e6-beb8-9e71128cae77',
], validate);
});

it('should fail invalid values', function () {
testFailingValues([
123, null, [], '', false,
' 11111111-1111-1111-1111-111111111111',
'11111111-1111-1111-1111-111111111111 ',
'11111111-1111-1111- 1111-111111111111 ',
'11111111-1111-1111-1111-11111111111g',
'11111111-1111-1111-1111-11111111111',
'1e1af1d1-2111-4111-112-1a1e111ec111',
'8aebedcd-474d-4a3-bd7e-425b92834fb3',
'f83b75b8-887-4657-9e56-fb7a501ca578',
'367aa2b-52e4-11e6-beb8-9e71128cae77',
'367aa2b052e411e6beb89e71128cae77',
], validate);
});
});

describe('alphanumeric', function () {
var validate = extension.validations.string.alphanumeric;

it('should pass valid values', function () {
testPassingValues([
'', 'A', '112', 'DFfa234',
'15cyA7uYR4y4CpLuhMfaQ0tTeKDrf5S',
'g1dNsuxnFYQeZzFyd4vfmNyCGarkzal',
], validate);
});

it('should fail invalid values', function () {
testFailingValues([
123, null, [], false, ' asdf', '12 23', 'foo ',
'asdf-sfasf-sfsdf-sasdf', '1231-123123',
'asfafs14234$%@#sdf', 'FSdioJFEJW\nasdf',
], validate);
});
});

describe('hex', function () {
var validate = extension.validations.string.hex;

it('should pass valid values', function () {
testPassingValues([
'', 'A', '112', 'DFfa234',
'367aa2b052e411e6beb89e71128cae77',
'df42447a701d4d0f9506e9087bb604a0',
], validate);
});

it('should fail invalid values', function () {
testFailingValues([
123, null, [], false, 'asdf fasd',
' abcdef', 'abcdef ', 'abc def', '12 23',
'asdf-sfasf-sfsdf-sasdf', '1231-123123',
'abcdef%@#abcdef', 'FSdioJFEJWasdf',
], validate);
});
});

describe('ip', function () {
var validate = extension.validations.string.ip;

it('should pass valid values', function () {
testPassingValues([
'172.32.0.1', '192.168.0.1', '10.10.0.1',
'255.255.255.0', '8.8.8.8', '75.75.75.75',
'10.0.0.0/8', '172.20.0.0/16', '192.168.0.0/24',
], validate);
});

it('should fail invalid values', function () {
testFailingValues([
123, null, [], false, 'asdf fasd', '12 23',
'172.32.0.1 ', ' 172.32.0.1', '172. 32.0.1',
'asdf-sfasf-sfsdf-sasdf', '1231-123123',
'abcdef%@#abcdef', 'FSdioJFEJWasdf',
], validate);
});
});

describe('uri', function () {
var validate = extension.validations.string.uri;

it('should pass valid values', function () {
testPassingValues([
'http://www.foo-bar.com/asdfsfd?asdfsf#go',
'https://192.168.0.0:3200/something?user=#other',
'ftp://user:password@home.net/my/folder',
'ssh://git@github.com:patrickhulce/klay',
'git+https://github.com/patrickhulce/klay',
], validate);
});

it('should fail invalid values', function () {
testFailingValues([
123, null, [], false, 'www.google.com',
' http://www.foo-bar.com/asdfsfd?asdfsf#go',
'http:/my-something', 'sdf%#://#%@asdf',
'asdfasfasfd', '23423423423', 'asdf asdf',
], validate);
});
});

describe('domain', function () {
var validate = extension.validations.string.domain;

it('should pass valid values', function () {
testPassingValues([
'www.foo-bar.com', 'home.net', 'github.com',
'something.private.domain', 'other-123.local',
], validate);
});

it('should fail invalid values', function () {
testFailingValues([
123, null, [], false, '192.168.0.0/24',
' www.foobar.com', 'home.net ', 'git hub.com',
'http:/my-something', 'sdf%#://#%@asdf',
'asdfas fasfd', '23 423423423', 'asdf asdf',
], validate);
});
});

describe('email', function () {
var validate = extension.validations.string.email;

it('should pass valid values', function () {
testPassingValues([
'me@www.foo-bar.com', 'me+spam@home.net',
'other.special@github.com', 'math123@cool.co.uk',
'admin@localhost', 'foo+.sf@other-123.local',
], validate);
});

it('should fail invalid values', function () {
testFailingValues([
123, null, [], false, '192.168.0.0/24',
' me@foobar.com', 'you@home.net ', 't@git hub.com',
'http:/my-something', 'sdf%#://#%asdf',
'asdfasfasfd', '23423423423', 'asdf asdf',
], validate);
});
});

describe('creditCard', function () {
var validate = extension.validations.string.creditCard;

it('should pass valid values', function () {
testPassingValues([
'4123412341234123',
'4123412341234',
'567256725672562',
], validate);
});

it('should fail invalid values', function () {
testFailingValues([
123, null, [], false,
'567256725672562232',
'41-234-12341-234-123',
'41 234 12341234 123',
'4123-4123', '1234242234',
], validate);
});
});
});
});
Loading

0 comments on commit f6729df

Please sign in to comment.