the-thing-is
Advanced tools
Comparing version 0.3.0 to 0.4.0
{ | ||
"name": "the-thing-is", | ||
"version": "0.3.0", | ||
"version": "0.4.0", | ||
"description": "Configuration drivin validation", | ||
"main": "the-thing-is.js", | ||
"scripts": { | ||
"test": "mocha -R spec test/the-thing-is.js" | ||
"test": "mocha -R spec" | ||
}, | ||
@@ -22,4 +22,7 @@ "repository": { | ||
"dependencies": { | ||
"is-too": "^2.0.0" | ||
"is-too": "^2.2.0" | ||
}, | ||
"devDependencies": { | ||
"mocha": "^2.1.0" | ||
} | ||
} |
# the-thing-is | ||
Configuration Driven Validation with a cute name. | ||
> _"You see, the thing is..."_ | ||
Uses [`is-it`](https://github.com/mrDarcyMurphy/is) behind the scenes for the comparisons. | ||
...now you can thoroughly and precisely measure your subjects against a series of standards. | ||
`the('16').is(['present', 'numberString', {greaterThanOrEqualTo:0}]) // true` | ||
This thing is _great_ for front-end form validation. | ||
[`is-too`](https://github.com/LoudBit/is-too) does comparisons behind the scenes. `the-thing-is` handles the organization of multiple checks. See `is-too`'s README for a list of what's available. | ||
## Basic usage | ||
``` javascript | ||
var the = require('the-thing-is') | ||
var thing = 16 | ||
the('thing').is('present') // true | ||
``` | ||
// describe your object with an array to ensure the order of evaluation | ||
### Array of Standards | ||
Use an array to describe your subject with a series of attributes that will be evaluated in order. | ||
``` javascript | ||
var whatYouExpect = ['present', 'integer', {greaterThan:0, lessThan:256}] | ||
if (the(thing).is(whatYouExpect)) { | ||
fuckYeah() | ||
the(16).is(whatYouExpect) // true | ||
the(640).is(whatYouExpect) // false | ||
``` | ||
### Tree of Standards | ||
This is where the true value of `the-thing-is` is. Trees can be as deep as you want, `the-thing-is` works through it recursively. | ||
Should the check fail, it'll record the path to the offending branch of the tree and store it as an error. | ||
``` javascript | ||
var userSchema = { | ||
name: ['string'], | ||
address: { | ||
street1: ['present', 'string', {matches: /.*/}], | ||
street2: ['string'], | ||
city: ['present', 'string'], | ||
state: ['present', 'string', {matches: /^[A-Z]{2}$/}], | ||
zip: ['present', 'string', {matches: /^[0-9]{5}$/}] | ||
} | ||
} | ||
if (the(thing).isnt(whatYouExpect)) { | ||
throwSomething(the.last.error) | ||
var user = { | ||
name: "Joe Bob", | ||
address: { | ||
street1: '123 Any St.', | ||
street2: '', | ||
city: 'Anytown', | ||
state: 'NE', | ||
zip: 12345 | ||
} | ||
} | ||
the(user).is(userSchema) // false | ||
the.last.error // [{ 'address.zip': ['string'] }] | ||
``` | ||
### Negative Standard | ||
If your subject is like an unruly teenager and you expect it to fail to live up to your standards all the time, then just say so. | ||
``` javascript | ||
var teenager = undefined | ||
the(teenager).isnt('present') // true | ||
the.last.error // ['present'] | ||
``` | ||
## Errors | ||
If your subject fails to live up to the standard you've set then `the-thing-is` will dutifully list all the ways it does so, in a way intended to be useful enough for you to deliver a useful message to your users. | ||
- Failed simple checks will return a string. | ||
- Failed objects will return an object with a key referring to the branch in the tree, and an array of failures. | ||
Altogether, `the-thing-is` will return an array of all your subject's failures. | ||
``` javascript | ||
the.last.error == ['number'] | ||
the.last.error == [{greaterThan:0}] | ||
the.last.error == [{'foo.bar': ['number']}] | ||
the.last.error == [{'foo.bar': ['number', {greaterThan:0}]}] | ||
``` | ||
Additionally, if you describe your object using standards that don't exist in `is-too` then `the-thing-is` will throw a TypeError. | ||
``` javascript | ||
the('thing').is('gonnaThrowUp') | ||
// => TypeError("`gonnaThrowUp` isn't a valid comparison method.") | ||
``` | ||
@@ -0,1 +1,5 @@ | ||
/* jshint quotmark:false */ | ||
/* global describe, it */ | ||
'use strict' | ||
var assert = require('assert') | ||
@@ -39,3 +43,3 @@ var the = require('../the-thing-is') | ||
assert(the.last.thing === 0) | ||
assert(!the.last.error) | ||
assert(!the.last.error.length) | ||
}) | ||
@@ -51,3 +55,3 @@ }) | ||
assert.equal(the.last.thing, 'thing') | ||
assert(!the.last.error) | ||
assert(!the.last.error.length) | ||
}) | ||
@@ -78,3 +82,3 @@ }) | ||
it('sets the.last.error', function() { | ||
assert(the.last.error) | ||
assert.deepEqual(the.last.error, ['number', 'integer']) | ||
}) | ||
@@ -86,7 +90,7 @@ }) | ||
describe("a test of a standard that doesn't exist", function(){ | ||
describe("the('thing').is([{gonnaGetThrown:true}])", function(){ | ||
it("should throw an error", function(){ | ||
var whatIExpect = [{gonnaGetThrown:true}] | ||
assert.throws(function(){ | ||
the(0).is(whatIExpect) | ||
the('thing').is(whatIExpect) | ||
}, TypeError) | ||
@@ -96,3 +100,3 @@ }) | ||
describe("an incorrect test of a standard", function(){ | ||
describe("the('0').is([{number:'wrong'}])", function(){ | ||
it("shouldn't throw an error", function(){ | ||
@@ -106,26 +110,37 @@ var whatIExpect = [{number:'wrong'}] | ||
describe("a complex expectation that passes", function(){ | ||
it('passes all the conditions', function(){ | ||
describe("the('0')", function(){ | ||
it(".is(['string', 'integerString', {gte: 0, lte:100}])", function(){ | ||
var whatIExpect = [ | ||
'string', 'integerString', | ||
{ | ||
// this is a trick - `number:false` is true | ||
// because `number` isn't a check against a standard | ||
number: false, | ||
integerString: true, | ||
'string', 'integerString', { | ||
gte: 0, | ||
lte: 100 | ||
} | ||
] | ||
}] | ||
assert( the('0').is(whatIExpect) ) | ||
assert( the.last.error.length ) | ||
}) | ||
it(".is([{ equal:0 }])", function(){ | ||
assert( the('0').is([{ equal: 0 }]) ) | ||
}) | ||
it(".is([{ exactly: '0' }])", function(){ | ||
assert( the('0').is([{ exactly: '0' }]) ) | ||
}) | ||
it(".isnt([{ equal: 1 }])", function(){ | ||
assert( the('0').isnt([{ equal: 1 }]) ) | ||
assert.deepEqual( the.last.error, [{equal:'1'}]) | ||
}) | ||
it(".isnt([{ equal: '1' }])", function(){ | ||
assert( the('0').isnt([{ equal: '1' }]) ) | ||
assert.deepEqual( the.last.error, [{equal:'1'}]) | ||
}) | ||
}) | ||
describe("a complex expectation that's not met", function(){ | ||
it("doesn't meet the expectations", function(){ | ||
the('0').is(['string', 'integerString', {gte:0, lte:100}]) | ||
assert( the.last.error.length ) | ||
describe("the('101')", function(){ | ||
it(".isnt(['string', 'integerString', {gte: 0, lte:100}])", function(){ | ||
var whatIExpect = [ | ||
'string', 'integerString', { | ||
gte: 0, | ||
lte: 100 | ||
}] | ||
assert( the('101').isnt(whatIExpect), false ) | ||
}) | ||
it("sets a meaningful (enough) error message", function(){ | ||
assert.equal( the.last.error[0], "See, the thing is, 0 (string) isn't gte 0.") | ||
it("creates a meaningful error array", function(){ | ||
assert.deepEqual( the.last.error, [{lte:100}]) | ||
}) | ||
@@ -167,5 +182,163 @@ }) | ||
assert.equal(the.last.thing, 'thing') | ||
assert(!the.last.error) | ||
assert(!the.last.error.length) | ||
}) | ||
}) | ||
}) | ||
describe('testing objects', function() { | ||
describe('{foo:"bar"}', function() { | ||
var subject = { foo: "bar" } | ||
it('is("plainObject")', function() { | ||
assert.equal(the(subject).is('plainObject'), true) | ||
}) | ||
it('is({ foo: "string" })', function() { | ||
assert.equal(the(subject).is({ foo: "string" }), true) | ||
}) | ||
it('is({foo:["string"]})', function() { | ||
assert.equal(the(subject).is({ foo: ["string"] }), true) | ||
}) | ||
it('is({bar:["string"]}) == false', function() { | ||
assert.equal(the(subject).is({ bar:["string"] }), false) | ||
}) | ||
it('isnt({bar:["string"]})', function() { | ||
assert.equal(the(subject).isnt({ bar:["string"] }), true) | ||
}) | ||
}) | ||
describe('{ foo:"bar", bar:"fizz", baz:666 }', function() { | ||
var subject = { | ||
foo: 'bar', | ||
bar: 'fizz', | ||
baz: 666 | ||
} | ||
it('is("plainObject")', function() { | ||
assert.equal(the(subject).is('plainObject'), true) | ||
}) | ||
it('is({ foo:"string", bar:"string", baz:["number", {greaterThan:0}, {lessThan:777}] })', function() { | ||
assert.equal(the(subject).is({ | ||
foo: 'string', | ||
bar: 'string', | ||
baz: ['number', {greaterThan:0}, {lessThan:777}] | ||
}), true) | ||
}) | ||
it('isnt({ flop:"string", bar:"string", baz:["number", {greaterThan:0}, {lessThan:777}] })', function() { | ||
assert.equal(the(subject).isnt({ | ||
flop: 'string', | ||
bar: 'string', | ||
baz: ['number', {greaterThan:0}, {lessThan:777}] | ||
}), true) | ||
}) | ||
it('isnt({ foo:"string", bar:"string", baz:["number", {greaterThan:666}] })', function() { | ||
assert.equal(the(subject).isnt({ | ||
foo: 'string', | ||
bar: 'string', | ||
baz: ['number', {greaterThan:666}] | ||
}), true) | ||
}) | ||
}) | ||
describe('{foo: {bar: {baz: {buz: "fiz"}}}}', function() { | ||
var subject = { | ||
foo: { | ||
bar: { | ||
baz: { | ||
buz: 'fiz' | ||
} | ||
} | ||
} | ||
} | ||
it('is("plainObject")', function() { | ||
assert.equal(the(subject).is('plainObject'), true) | ||
}) | ||
it('is({foo: {bar: {baz: {buz: ["string"]}}}})', function() { | ||
assert.equal(the(subject).is({foo: {bar: {baz: {buz: ['string']}}}}), true) | ||
}) | ||
it('isnt({foo: {bar: {baz: {buz: ["number"]}}}}) - wrong type', function() { | ||
assert.equal(the(subject).isnt({foo: {bar: {baz: {buz: ['number']}}}}), true) | ||
}) | ||
it('isnt({foo: {bar: {baz: {buz: {fiz: ["string"]}}}}}) - nested too deep', function() { | ||
assert.throws(function(){ | ||
the(subject).isnt({foo: {bar: {baz: {buz: {fiz: ['string']}}}}}) | ||
}, TypeError) | ||
}) | ||
}) | ||
describe('{foo: {bar: {baz: {buz: "fiz"}}}}', function() { | ||
var subject = { | ||
fo: { | ||
foo: { | ||
fooo: 'fooo' | ||
}, | ||
oof: { | ||
ooof: 'ooof' | ||
} | ||
}, | ||
bar: { | ||
baz: { | ||
buz: 'buz' | ||
}, | ||
rab: { | ||
zub: 'zub' | ||
}, | ||
zab: 'zab' | ||
} | ||
} | ||
it('is("plainObject")', function() { | ||
assert.equal(the(subject).is('plainObject'), true) | ||
}) | ||
it('is({foo: {bar: {baz: {buz: ["string"]}}}})', function() { | ||
var whatIExpect = { | ||
fo: { | ||
foo: { | ||
fooo: 'string' | ||
}, | ||
oof: { | ||
ooof: 'string' | ||
} | ||
}, | ||
bar: { | ||
baz: { | ||
buz: 'string' | ||
} | ||
} | ||
} | ||
assert.equal(the(subject).is(whatIExpect), true) | ||
}) | ||
it('isnt({foo: {bar: {baz: {buz: ["number"]}}}}) - foo not at top level', function() { | ||
assert.equal(the(subject).isnt({foo: {bar: {baz: {buz: ['number']}}}}), true) | ||
assert.deepEqual(the.last.error, [{'foo':['present']}]) | ||
}) | ||
}) | ||
describe('things with multiple errors', function() { | ||
it('report multiple errors', function(){ | ||
the('thing').is(['number', {greaterThan:0}, {lessThan:1000}]) | ||
assert.deepEqual( the.last.error, ['number', {greaterThan:0}, {lessThan:1000}]) | ||
}) | ||
it('objects report multiple errors too', function(){ | ||
the({ | ||
foo: 1, | ||
bar: 2, | ||
fizz: { | ||
buzz: 'fizzbuzz' | ||
} | ||
}).is({ | ||
foo: ['number'], | ||
bar: ['string'], | ||
fizz: { | ||
buzz: ['number', {greaterThan:0}] | ||
} | ||
}) | ||
assert.deepEqual( the.last.error, [{bar:['string']}, {'fizz.buzz': ['number', {greaterThan:0}]}] ) | ||
}) | ||
}) | ||
}) | ||
// The Thing Is | ||
// Configuration driven validation | ||
// with the goal of a near-english syntax (for consumers) | ||
// | ||
// Detailed Object Descriptions | ||
// ...with a sort of _artistic_ quality of code | ||
// | ||
// Usage: | ||
@@ -10,7 +11,6 @@ // | ||
// var whatYouExpect = ['present', 'integer', {greaterThan:0, lessThan:256}] -- combo v2, maintains order, groups comparisons that are chaotic | ||
// var whatYouExpect = {present:true, number:true, greaterThan:0} -- object (verbose, unreliable order) | ||
// var whatYouExpect = [{present:true}, {number:true}, {greaterThan:0}] -- array of objects (very verbose, reliable order) | ||
// var whatYouExpect = [{present:true}, {number:true}, {greaterThan:0}] -- array of objects (very verbose, reliable order, overkill) | ||
// | ||
// if (the(thing).is(whatYouExpect)) { | ||
// fuckYeah() | ||
// ohYeah() | ||
// } | ||
@@ -22,88 +22,132 @@ // | ||
'use strict' | ||
// Dependencies | ||
var is = require('is-too') | ||
// the -- function you care about | ||
function the(thing) { | ||
the.past.push({ | ||
thing: thing, | ||
errors: [] | ||
}) | ||
the.last = the.past[the.past.length-1 || 0] | ||
return comparisons | ||
function the (thing) { | ||
the.path = [] | ||
the.last = { | ||
thing: thing, | ||
error: [] | ||
} | ||
return { | ||
is: what, | ||
isnt: function (expected, thing) { | ||
return !this.is(expected, thing) | ||
} | ||
} | ||
} | ||
the.past = [] | ||
the.last = null | ||
// is, isnt | ||
var comparisons = { | ||
is: function(whatYouExpect) { | ||
// will recursively see if the(thing).is(whatYouExpect) | ||
function what (expected, thing) { | ||
var its = true | ||
var expectation = null | ||
thing = thing || the.last.thing | ||
// the(thing).is() | ||
// whatYouExpect is undefined or null -- so check mere presence of a thing | ||
if ( is.not.present(whatYouExpect) ) | ||
its = is.present(the.last.thing) | ||
// the(thing).is() | ||
// expected is undefined or null -- so check mere presence of a thing | ||
if ( is.not.present(expected) ) | ||
return see('present', thing) | ||
// 'present' -- single boolean check | ||
// the(thing).is('integer') // true/false | ||
// the(thing).is('borkborkbork') // throw | ||
else | ||
if ( is.string(whatYouExpect) ) | ||
its = booleanCheck(whatYouExpect) | ||
// 'present' -- single boolean check | ||
// the(thing).is('integer') // true/false | ||
// the(thing).is('borkborkbork') // throw | ||
if ( is.string(expected) ) | ||
return see(expected, thing) | ||
// ['present', 'number'] -- array of boolean | ||
// the(thing).is(['present', 'integer']) | ||
// ['present', 'number'] -- array of boolean comparisons | ||
// ['present', 'number', {greaterThan:0}, {lessThanorEqualTo:100}] -- separated objects | ||
// ['present', 'number', {greaterThan:0, lessThanorEqualTo:100}] -- combined object | ||
// { foo: ['present', { bar: ['present'] }] } | ||
// the(thing).is(['present', 'integer']) | ||
if ( is.array(expected) ) | ||
expected.forEach(function(expected){ | ||
return what(expected, thing) | ||
}) | ||
// { foo: ['bar'] } -- dictionary describing complex or deep objects | ||
// the(thing).is({ | ||
// name: ['present', 'string'], | ||
// address: { | ||
// street: 'string', | ||
// city: 'string', | ||
// state: 'string', | ||
// zip: 'string' | ||
// } | ||
// }) | ||
if ( is.plainObject(expected) ) | ||
if (is.plainObject(thing)) { | ||
// stash the path to branch the tree | ||
var pathStash = the.path.slice() | ||
Object.keys(expected).forEach(function(key, i){ | ||
var nexThing = thing[key], | ||
nexPectation = expected[key] | ||
if (i > 0) | ||
the.path.pop() | ||
the.path.push(key) | ||
if ( is.present(nexPectation) && is.present(nexThing) ) | ||
return what(nexPectation, nexThing) | ||
else | ||
return see('present', nexThing) | ||
}) | ||
the.path = pathStash.slice(); | ||
} | ||
else | ||
if ( is.array(whatYouExpect) ) | ||
for (var i = 0; i < whatYouExpect.length && its == true; i++) { | ||
expectation = whatYouExpect[i] | ||
if (is.string(expectation)) | ||
its = booleanCheck(expectation) | ||
else | ||
its = subjectCheck(expectation) | ||
Object.keys(expected).forEach(function(key, i, arr){ | ||
var standard = expected[key]; | ||
return see(key, thing, standard); | ||
}) | ||
if (!its) | ||
the.last.error = '' + the.last.thing + ' is not ' + whatYouExpect[i]; | ||
} | ||
return !the.last.error.length; | ||
// {greaterThan:0} -- configuration object with single condition | ||
// {present:true, integer:true, greaterThan:0} -- unreliable due to unreliable hash key order | ||
// [{present:true}, {integer:true}, {greaterThan:0}] -- reliable, but verbose | ||
// ['present', 'integer', {greaterThanOrEqualTo:0}] -- preferred | ||
return its | ||
}, | ||
isnt: function(whatYouExpect) { | ||
return !this.is(whatYouExpect) | ||
} | ||
} | ||
// simple yes/no type comparisons against an expectation | ||
function booleanCheck(expectation) { | ||
if (is[expectation]) | ||
return is[expectation](the.last.thing) | ||
function see (expected, thing, standard) { | ||
var err = {}, | ||
fail = {}, | ||
failPath = the.path.join('.') | ||
if ( is.not.string(expected) || is.not.present(is[expected]) ) | ||
throw new TypeError('`' + expected + '` isn\'t a valid comparison method.') | ||
// good to go, so go | ||
if ( is[expected](thing, standard) ) | ||
return true | ||
// needs to be stored as an object | ||
if ( is.present(standard) ) | ||
fail[expected] = standard // eg. {greaterThan:0} | ||
else | ||
throw new TypeError("`"+expectation+"` isn't a valid comparison method.") | ||
} | ||
fail = expected // eg. 'present' | ||
function subjectCheck(expectation) { | ||
the.last.error = [] | ||
// needs to be stored as the value of the path | ||
if (the.path.length) | ||
err[failPath] = [fail] // eg. {'foo.bar': ['present']} | ||
else | ||
err = fail // eg. 'present' | ||
// loop through the keys in the object to | ||
Object.keys(expectation).forEach(function(key, value){ | ||
if ( !is[key] ) | ||
throw new TypeError("`"+key+"` isn't a valid comparison method.") | ||
if ( is[key](the.last.thing, expectation[key]) ) { | ||
the.last.error.push(['See, the thing is, ', the.last.thing, ' (', typeof the.last.thing, ') isn\'t ', key, ' ', value, '.'].join('')); | ||
// need to group the fails for a key in the same array | ||
var isExisting = the.last.error.some(function something (last) { | ||
if (is.plainObject(last) && failPath in last) { | ||
last[failPath].push(fail) | ||
return true | ||
} | ||
}) | ||
return !!the.last.error.length | ||
if (!isExisting) | ||
the.last.error.push(err) | ||
return false | ||
} | ||
@@ -110,0 +154,0 @@ |
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
New author
Supply chain riskA new npm collaborator published a version of the package for the first time. New collaborators are usually benign additions to a project, but do indicate a change to the security surface area of a package.
Found 1 instance in 1 package
18132
7
434
102
1
Updatedis-too@^2.2.0