Comparing version 1.0.4 to 1.1.0
96
index.js
'use strict' | ||
function isArguments (thingy) { | ||
return typeof thingy === 'object' && thingy.hasOwnProperty('callee') | ||
return thingy != null && typeof thingy === 'object' && thingy.hasOwnProperty('callee') | ||
} | ||
var types = { | ||
'*': ['any', function () { return true }], | ||
A: ['array', function (thingy) { return Array.isArray(thingy) || isArguments(thingy) }], | ||
S: ['string', function (thingy) { return typeof thingy === 'string' }], | ||
N: ['number', function (thingy) { return typeof thingy === 'number' }], | ||
F: ['function', function (thingy) { return typeof thingy === 'function' }], | ||
O: ['object', function (thingy) { return typeof thingy === 'object' && !types.A[1](thingy) && !types.E[1](thingy) }], | ||
B: ['boolean', function (thingy) { return typeof thingy === 'boolean' }], | ||
E: ['error', function (thingy) { return thingy instanceof Error }] | ||
'*': {label: 'any', check: function () { return true }}, | ||
A: {label: 'array', check: function (thingy) { return Array.isArray(thingy) || isArguments(thingy) }}, | ||
S: {label: 'string', check: function (thingy) { return typeof thingy === 'string' }}, | ||
N: {label: 'number', check: function (thingy) { return typeof thingy === 'number' }}, | ||
F: {label: 'function', check: function (thingy) { return typeof thingy === 'function' }}, | ||
O: {label: 'object', check: function (thingy) { return typeof thingy === 'object' && thingy != null && !types.A.check(thingy) && !types.E.check(thingy) }}, | ||
B: {label: 'boolean', check: function (thingy) { return typeof thingy === 'boolean' }}, | ||
E: {label: 'error', check: function (thingy) { return thingy instanceof Error }}, | ||
Z: {label: 'null', check: function (thingy) { return thingy == null }} | ||
} | ||
var validate = module.exports = function (schema, args) { | ||
if (!schema) throw missingRequiredArg(0, 'schema') | ||
var validate = module.exports = function (rawSchemas, args) { | ||
if (arguments.length !== 2) throw wrongNumberOfArgs(['SA'], arguments.length) | ||
if (!rawSchemas) throw missingRequiredArg(0, 'rawSchemas') | ||
if (!args) throw missingRequiredArg(1, 'args') | ||
if (!types.S[1](schema)) throw invalidType(0, 'string', schema) | ||
if (!types.A[1](args)) throw invalidType(1, 'array', args) | ||
for (var ii = 0; ii < schema.length; ++ii) { | ||
var type = schema[ii] | ||
if (!types[type]) throw unknownType(ii, type) | ||
var typeLabel = types[type][0] | ||
var typeCheck = types[type][1] | ||
if (type === 'E' && args[ii] == null) continue | ||
if (args[ii] == null) throw missingRequiredArg(ii) | ||
if (!typeCheck(args[ii])) throw invalidType(ii, typeLabel, args[ii]) | ||
if (type === 'E') return | ||
if (!types.S.check(rawSchemas)) throw invalidType(0, ['string'], rawSchemas) | ||
if (!types.A.check(args)) throw invalidType(1, ['array'], args) | ||
var schemas = rawSchemas.split('|') | ||
var arity = {} | ||
function addSchema (schema) { | ||
var group = arity[schema.length] = arity[schema.length] || [] | ||
if (group.indexOf(schema) === -1) group.push(schema) | ||
} | ||
if (schema.length < args.length) throw tooManyArgs(schema.length, args.length) | ||
schemas.forEach(function (schema) { | ||
for (var ii = 0; ii < schema.length; ++ii) { | ||
var type = schema[ii] | ||
if (!types[type]) throw unknownType(ii, type) | ||
} | ||
if (/E.*E/.test(schema)) throw moreThanOneError(schema) | ||
addSchema(schema) | ||
if (/E/.test(schema)) { | ||
addSchema(schema.replace(/E.*$/, 'E')) | ||
addSchema(schema.replace(/E/, 'Z')) | ||
} | ||
}) | ||
var matching = arity[args.length] | ||
if (!matching) { | ||
throw wrongNumberOfArgs(Object.keys(arity), args.length) | ||
} | ||
for (var ii = 0; ii < args.length; ++ii) { | ||
var newMatching = matching.filter(function (schema) { | ||
var type = schema[ii] | ||
var typeCheck = types[type].check | ||
return typeCheck(args[ii]) | ||
}) | ||
if (!newMatching.length) { | ||
var labels = matching.map(function (schema) { | ||
return types[schema[ii]].label | ||
}).filter(function (schema) { return schema != null }) | ||
throw invalidType(ii, labels, args[ii]) | ||
} | ||
matching = newMatching | ||
} | ||
} | ||
@@ -44,15 +71,28 @@ | ||
function invalidType (num, expectedType, value) { | ||
function invalidType (num, expectedTypes, value) { | ||
var valueType | ||
Object.keys(types).forEach(function (typeCode) { | ||
if (types[typeCode][1](value)) valueType = types[typeCode][0] | ||
if (types[typeCode].check(value)) valueType = types[typeCode].label | ||
}) | ||
return newException('EINVALIDTYPE', 'Argument #' + (num + 1) + ': Expected ' + | ||
expectedType + ' but got ' + valueType) | ||
englishList(expectedTypes) + ' but got ' + valueType) | ||
} | ||
function tooManyArgs (expected, got) { | ||
return newException('ETOOMANYARGS', 'Too many arguments, expected ' + expected + ' and got ' + got) | ||
function englishList (list) { | ||
return list.join(', ').replace(/, ([^,]+)$/, ' or $1') | ||
} | ||
function wrongNumberOfArgs (expected, got) { | ||
var english = englishList(expected) | ||
var args = expected.every(function (ex) { return ex.length === 1 }) | ||
? 'argument' | ||
: 'arguments' | ||
return newException('EWRONGARGCOUNT', 'Expected ' + english + ' ' + args + ' but got ' + got) | ||
} | ||
function moreThanOneError (schema) { | ||
return newException('ETOOMANYERRORTYPES', | ||
'Only one error type per argument signature is allowed, more than one found in "' + schema + '"') | ||
} | ||
function newException (code, msg) { | ||
@@ -59,0 +99,0 @@ var e = new Error(msg) |
{ | ||
"name": "aproba", | ||
"version": "1.0.4", | ||
"version": "1.1.0", | ||
"description": "A rediculously light-weight argument validator", | ||
@@ -11,4 +11,4 @@ "main": "index.js", | ||
"devDependencies": { | ||
"standard": "^7.1.2", | ||
"tap": "^5.7.3" | ||
"standard": "^8.6.0", | ||
"tap": "^10.0.2" | ||
}, | ||
@@ -19,3 +19,3 @@ "files": [ | ||
"scripts": { | ||
"test": "standard && tap test/*.js" | ||
"test": "standard && tap -j3 test/*.js" | ||
}, | ||
@@ -22,0 +22,0 @@ "repository": { |
@@ -32,3 +32,4 @@ aproba | ||
B | typeof == boolean | ||
E | instanceof Error OR null | ||
E | instanceof Error OR null **(special: see below)** | ||
Z | == null | ||
@@ -41,5 +42,34 @@ Validation failures throw one of three exception types, distinguished by a | ||
If an error argument is found and is not null then the remaining arguments | ||
will not be validated. | ||
If an **error** argument is found and is not null then the remaining | ||
arguments are optional. That is, if you say `ESO` then that's like using a | ||
non-magical `E` in: `E|ESO|ZSO`. | ||
### But I have optional arguments?! | ||
You can provide more than one signature by separating them with pipes `|`. | ||
If any signature matches the arguments then they'll be considered valid. | ||
So for example, say you wanted to write a signature for | ||
`fs.createWriteStream`. The docs for it describe it thusly: | ||
``` | ||
fs.createWriteStream(path[, options]) | ||
``` | ||
This would be a signature of `SO|S`. That is, a string and and object, or | ||
just a string. | ||
Now, if you read the full `fs` docs, you'll see that actually path can ALSO | ||
be a buffer. And options can be a string, that is: | ||
``` | ||
path <String> | <Buffer> | ||
options <String> | <Object> | ||
``` | ||
To reproduce this you have to fully enumerate all of the possible | ||
combinations and that implies a signature of `SO|SS|OO|OS|S|O`. The | ||
awkwardness is a feature: It reminds you of the complexity you're adding to | ||
your API when you do this sort of thing. | ||
### Why this exists | ||
@@ -46,0 +76,0 @@ |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
7905
92
90