Comparing version 3.1.0 to 4.0.0
@@ -14,18 +14,9 @@ // Load modules | ||
module.exports = internals.Alternatives = function (alternatives) { | ||
internals.Alternatives = function () { | ||
var self = this; | ||
Any.call(this); | ||
this._type = 'alternatives'; | ||
this._invalids.remove(null); | ||
Hoek.assert(alternatives && alternatives.length, 'Missing alternatives'); | ||
Hoek.assert(alternatives.length > 1, 'Alternatives require more than one'); | ||
this._inner = internals.compile(alternatives); | ||
this._base(function (value, state, options) { | ||
return self._traverse(value, state, options); | ||
}); | ||
this._inner = []; | ||
}; | ||
@@ -36,47 +27,78 @@ | ||
internals.Alternatives.create = function (/* alternatives */) { | ||
internals.Alternatives.prototype._base = function (value, state, options) { | ||
var alt = new internals.Alternatives(Hoek.flatten(Array.prototype.slice.call(arguments))); | ||
var errors = []; | ||
for (var i = 0, il = this._inner.length; i < il; ++i) { | ||
var item = this._inner[i]; | ||
var schema = item.schema; | ||
if (!schema) { | ||
var failed = item.is._validate(item.ref(state.parent), null, options, state.parent).errors; | ||
schema = failed ? item.otherwise : item.then; | ||
if (!schema) { | ||
continue; | ||
} | ||
} | ||
for (var i = 0, il = alt._inner.length; i < il; ++i) { | ||
alt._allow(alt._inner[i]._valids.values()); | ||
var result = schema._validate(value, state, options); | ||
if (!result.errors) { // Found a valid match | ||
return result; | ||
} | ||
errors = errors.concat(result.errors); | ||
} | ||
return alt; | ||
return { errors: errors.length ? errors : Errors.create('alternatives.base', null, state, options) }; | ||
}; | ||
internals.Alternatives._create = function (/* alternatives */) { // Used to cast [] | ||
internals.Alternatives.prototype.try = function (/* schemas */) { | ||
var alt = internals.Alternatives.create.apply(null, arguments); | ||
alt._invalids.remove(null); | ||
alt._valids.remove(undefined); | ||
return alt; | ||
}; | ||
var schemas = Hoek.flatten(Array.prototype.slice.call(arguments)); | ||
Hoek.assert(schemas.length, 'Cannot add other alternatives without at least one schema'); | ||
internals.compile = function (alternatives) { | ||
var obj = this.clone(); | ||
var compiled = []; | ||
for (var i = 0, il = alternatives.length; i < il; ++i) { | ||
compiled.push(Cast.schema(alternatives[i])); | ||
for (var i = 0, il = schemas.length; i < il; ++i) { | ||
var cast = Cast.schema(schemas[i]); | ||
if (cast._refs.length) { | ||
obj._refs = obj._refs.concat(cast._refs) | ||
} | ||
obj._inner.push({ schema: cast }); | ||
} | ||
return compiled; | ||
return obj; | ||
}; | ||
internals.Alternatives.prototype._traverse = function (value, state, options) { | ||
internals.Alternatives.prototype.when = function (ref, options) { | ||
var errors = []; | ||
for (var i = 0, il = this._inner.length; i < il; ++i) { | ||
var err = this._inner[i]._validate(value, state, options); | ||
if (!err) { // Found a valid match | ||
return null; | ||
} | ||
Hoek.assert(ref, 'Missing reference'); | ||
Hoek.assert(options, 'Missing options'); | ||
Hoek.assert(typeof options === 'object', 'Invalid options'); | ||
Hoek.assert(options.is, 'Missing "is" directive'); | ||
Hoek.assert(options.then !== undefined || options.otherwise !== undefined, 'options must have at least one of "then" or "otherwise"'); | ||
errors = errors.concat(err); | ||
var obj = this.clone(); | ||
var item = { | ||
ref: Cast.ref(ref), | ||
is: Cast.schema(options.is), | ||
then: options.then !== undefined ? Cast.schema(options.then) : undefined, | ||
otherwise: options.otherwise !== undefined ? Cast.schema(options.otherwise) : undefined | ||
}; | ||
obj._refs = obj._refs.concat(item.ref.root, item.is._refs); | ||
if (item.then && item.then._refs) { | ||
obj._refs = obj._refs.concat(item.then._refs); | ||
} | ||
return errors; | ||
if (item.otherwise && item.otherwise._refs) { | ||
obj._refs = obj._refs.concat(item.otherwise._refs); | ||
} | ||
obj._inner.push(item); | ||
return obj; | ||
}; | ||
@@ -89,3 +111,28 @@ | ||
for (var i = 0, il = this._inner.length; i < il; ++i) { | ||
descriptions.push(this._inner[i].describe()); | ||
var item = this._inner[i]; | ||
if (item.schema) { | ||
// try() | ||
descriptions.push(item.schema.describe()); | ||
} | ||
else { | ||
// when() | ||
var when = { | ||
ref: item.ref.key, | ||
is: item.is.describe() | ||
}; | ||
if (item.then) { | ||
when.then = item.then.describe(); | ||
} | ||
if (item.otherwise) { | ||
when.otherwise = item.otherwise.describe(); | ||
} | ||
descriptions.push(when); | ||
} | ||
} | ||
@@ -95,1 +142,4 @@ | ||
}; | ||
module.exports = new internals.Alternatives(); |
572
lib/any.js
// Load modules | ||
var Sys = require('sys'); | ||
var Path = require('path'); | ||
var Hoek = require('hoek'); | ||
var Ref = require('./ref'); | ||
var Errors = require('./errors'); | ||
var Hoek = require('hoek'); | ||
var Alternatives = null; // Delay-loaded to prevent circular dependencies | ||
var Cast = null; | ||
@@ -18,3 +19,2 @@ | ||
convert: true, | ||
modify: false, | ||
allowUnknown: false, | ||
@@ -32,27 +32,20 @@ skipFunctions: false, | ||
this._settings = null; | ||
this._tests = []; | ||
this._dependencies = []; | ||
this._mutators = []; | ||
this._valids = new internals.Set([undefined]); | ||
this._invalids = new internals.Set([null]); | ||
this._flags = { | ||
insensitive: false, | ||
allowOnly: false, | ||
default: undefined | ||
}; | ||
this._tests = []; | ||
this._flags = {}; // insensitive (false), allowOnly (false), default (undefined), encoding (undefined) | ||
this._description = null; | ||
this._unit = null; | ||
this._notes = []; | ||
this._tags = []; | ||
this._examples = []; | ||
this._inner = null; // Immutable | ||
this._inner = null; | ||
this._renames = []; | ||
this._dependencies = []; | ||
this._refs = []; | ||
}; | ||
internals.Any.create = function () { | ||
return new internals.Any(); | ||
}; | ||
internals.Any.prototype.clone = function () { | ||
@@ -66,18 +59,17 @@ | ||
obj._settings = Hoek.clone(this._settings); | ||
obj._tests = this._tests.slice(); | ||
obj._dependencies = this._dependencies.slice(); | ||
obj._mutators = this._mutators.slice(); | ||
obj._valids = Hoek.clone(this._valids); | ||
obj._invalids = Hoek.clone(this._invalids); | ||
obj._flags = { | ||
insensitive: this._flags.insensitive, | ||
allowOnly: this._flags.allowOnly, | ||
default: this._flags.default | ||
}; | ||
obj._tests = this._tests.slice(); | ||
obj._flags = Hoek.clone(this._flags); | ||
obj._description = this._description; | ||
obj._unit = this._unit; | ||
obj._notes = this._notes.slice(); | ||
obj._tags = this._tags.slice(); | ||
obj._examples = this._examples.slice(); | ||
obj._inner = this._inner; | ||
obj._inner = this._inner ? this._inner.slice() : null; | ||
obj._renames = this._renames.slice(); | ||
obj._dependencies = this._dependencies.slice(); | ||
obj._refs = this._refs.slice(); | ||
@@ -88,20 +80,26 @@ return obj; | ||
internals.Any.prototype._base = function (func) { | ||
internals.Any.prototype.concat = function (schema) { | ||
this._tests.push({ func: func }); | ||
}; | ||
Hoek.assert(schema && schema.isJoi, 'Invalid schema object'); | ||
Hoek.assert(schema._type === 'any' || schema._type === this._type, 'Cannot merge with another type:', schema._type); | ||
var obj = this.clone(); | ||
internals.Any.prototype._test = function (name, arg, func) { | ||
obj._settings = obj._settings ? Hoek.merge(obj._settings, schema._settings) : schema._settings; | ||
obj._valids.merge(schema._valids); | ||
obj._invalids.merge(schema._invalids); | ||
obj._tests = obj._tests.concat(schema._tests); | ||
Hoek.merge(obj._flags, schema._flags); | ||
var obj = this.clone(); | ||
obj._tests.push({ func: func, name: name, arg: arg }); | ||
return obj; | ||
}; | ||
obj._description = schema._description || obj._description; | ||
obj._unit = schema._unit || obj._unit; | ||
obj._notes = obj._notes.concat(schema._notes); | ||
obj._tags = obj._tags.concat(schema._tags); | ||
obj._examples = obj._examples.concat(schema._examples); | ||
obj._inner = obj._inner ? (schema._inner ? obj._inner.concat(schema._inner) : obj._inner) : schema._inner; | ||
obj._renames = obj._renames.concat(schema._renames); | ||
obj._dependencies = obj._dependencies.concat(schema._dependencies); | ||
obj._refs = obj._refs.concat(schema._refs); | ||
internals.Any.prototype._dependency = function (name, arg, func) { | ||
var obj = this.clone(); | ||
obj._dependencies.push({ func: func, name: name, arg: arg }); | ||
return obj; | ||
@@ -111,6 +109,8 @@ }; | ||
internals.Any.prototype._mutate = function (value) { | ||
internals.Any.prototype._test = function (name, arg, func) { | ||
Hoek.assert(!this._flags.allowOnly, 'Cannot define rules when valid values specified'); | ||
var obj = this.clone(); | ||
obj._mutators.push(value); | ||
obj._tests.push({ func: func, name: name, arg: arg }); | ||
return obj; | ||
@@ -143,3 +143,3 @@ }; | ||
this._invalids.remove(value); | ||
this._valids.add(value); | ||
this._valids.add(value, this._refs); | ||
} | ||
@@ -157,4 +157,6 @@ }; | ||
internals.Any.prototype.valid = function () { | ||
internals.Any.prototype.valid = internals.Any.prototype.equal = function () { | ||
Hoek.assert(!this._tests.length, 'Cannot set valid values when rules specified'); | ||
var obj = this.allow.apply(this, arguments); | ||
@@ -166,3 +168,3 @@ obj._flags.allowOnly = true; | ||
internals.Any.prototype.invalid = function (value) { | ||
internals.Any.prototype.invalid = internals.Any.prototype.not = function (value) { | ||
@@ -174,3 +176,3 @@ var obj = this.clone(); | ||
obj._valids.remove(value); | ||
obj._invalids.add(value); | ||
obj._invalids.add(value, this._refs); | ||
} | ||
@@ -200,172 +202,43 @@ | ||
internals.Any.prototype.with = function () { | ||
internals.Any.prototype.default = function (value) { | ||
var peers = Hoek.flatten(Array.prototype.slice.call(arguments)); | ||
for (var i = 0, li = peers.length; i < li; i++) { | ||
Hoek.assert(typeof peers[i] === 'string', 'peers must be a string'); | ||
var obj = this.clone(); | ||
obj._flags.default = value; | ||
if (Ref.isRef(value)) { | ||
obj._refs.push(value.root); | ||
} | ||
return this._dependency('with', peers, function (value, state, options) { | ||
if (!state.parent) { | ||
return Errors.create('any.with.parent', null, state, options); | ||
} | ||
if (value === undefined) { | ||
return null; | ||
} | ||
for (var i = 0, il = peers.length; i < il; ++i) { | ||
var peer = peers[i]; | ||
if (!state.parent.hasOwnProperty(peer) || | ||
state.parent[peer] === undefined) { | ||
return Errors.create('any.with.peer', { value: peer }, state, options); | ||
} | ||
} | ||
return null; | ||
}); | ||
return obj; | ||
}; | ||
internals.Any.prototype.without = function () { | ||
internals.Any.prototype.when = function (ref, options) { | ||
var peers = Hoek.flatten(Array.prototype.slice.call(arguments)); | ||
for (var i = 0, li = peers.length; i < li; i++) { | ||
Hoek.assert(typeof peers[i] === 'string', 'peers must be strings'); | ||
} | ||
Hoek.assert(options && typeof options === 'object', 'Invalid options'); | ||
Hoek.assert(options.then !== undefined || options.otherwise !== undefined, 'options must have at least one of "then" or "otherwise"'); | ||
return this._dependency('without', peers, function (value, state, options) { | ||
Cast = Cast || require('./cast'); | ||
var then = options.then ? this.concat(Cast.schema(options.then)) : this; | ||
var otherwise = options.otherwise ? this.concat(Cast.schema(options.otherwise)) : this; | ||
if (!state.parent) { | ||
return Errors.create('any.without.parent', null, state, options); | ||
} | ||
if (value === undefined) { | ||
return null; | ||
} | ||
for (var i = 0, il = peers.length; i < il; ++i) { | ||
var peer = peers[i]; | ||
if (state.parent.hasOwnProperty(peer) && | ||
state.parent[peer] !== undefined) { | ||
return Errors.create('any.without.peer', { value: peer }, state, options); | ||
} | ||
} | ||
return null; | ||
}); | ||
Alternatives = Alternatives || require('./alternatives'); | ||
return Alternatives.when(ref, { is: options.is, then: then, otherwise: otherwise }); | ||
}; | ||
internals.Any.prototype.xor = function () { | ||
internals.Any.prototype.description = function (desc) { | ||
var peers = Hoek.flatten(Array.prototype.slice.call(arguments)); | ||
Hoek.assert(desc && typeof desc === 'string', 'Description must be a non-empty string'); | ||
return this._dependency('xor', peers, function (value, state, options) { | ||
if (!state.parent) { | ||
return Errors.create('any.xor.parent', null, state, options); | ||
} | ||
var present = (value !== undefined && value !== null && value !== '' ? 1 : 0); | ||
for (var i = 0, il = peers.length; i < il && present < 2; ++i) { | ||
var peer = peers[i]; | ||
if (state.parent.hasOwnProperty(peer) && | ||
state.parent[peer] !== undefined) { | ||
++present; | ||
} | ||
} | ||
if (present === 1) { | ||
return null; | ||
} | ||
if (present === 0) { | ||
return Errors.create('any.xor.missing', { value: peers }, state, options); | ||
} | ||
return Errors.create('any.xor.peer', { value: peers }, state, options); | ||
}); | ||
var obj = this.clone(); | ||
obj._description = desc; | ||
return obj; | ||
}; | ||
internals.Any.prototype.or = function () { | ||
internals.Any.prototype.notes = function (notes) { | ||
var peers = Hoek.flatten(Array.prototype.slice.call(arguments)); | ||
for (var i = 0, li = peers.length; i < li; i++) { | ||
Hoek.assert(typeof peers[i] === 'string', 'peers must be a string'); | ||
} | ||
Hoek.assert(notes && (typeof notes === 'string' || Array.isArray(notes)), 'Notes must be a non-empty string or array'); | ||
return this._dependency('or', peers, function (value, state, options) { | ||
if (!state.parent) { | ||
return Errors.create('any.or.parent', null, state, options); | ||
} | ||
if (value !== undefined) { | ||
return null; | ||
} | ||
for (var i = 0, il = peers.length; i < il; ++i) { | ||
var peer = peers[i]; | ||
if (state.parent.hasOwnProperty(peer) && | ||
state.parent[peer] !== undefined) { | ||
return null; | ||
} | ||
} | ||
return Errors.create('any.or.peer', { value: peers }, state, options); | ||
}); | ||
}; | ||
internals.Any.prototype.rename = function (to, renameOptions) { | ||
var defaults = { | ||
move: false, | ||
multiple: false, | ||
override: false | ||
}; | ||
renameOptions = Hoek.merge(defaults, renameOptions); | ||
return this._mutate(function (value, state, options) { | ||
if (!state.parent) { | ||
return Errors.create('any.rename.parent', null, state, options); | ||
} | ||
if (!renameOptions.multiple && | ||
state.renamed[to]) { | ||
return Errors.create('any.rename.multiple', { value: to }, state, options); | ||
} | ||
if (state.parent.hasOwnProperty(to) && | ||
!renameOptions.override && | ||
!state.renamed[to]) { | ||
return Errors.create('any.rename.override', { value: to }, state, options); | ||
} | ||
state.parent[to] = state.parent[state.key]; | ||
state.renamed[to] = true; | ||
if (renameOptions.move) { | ||
delete state.parent[state.key]; | ||
} | ||
return null; | ||
}); | ||
}; | ||
internals.Any.prototype.default = function (value) { | ||
var obj = this.clone(); | ||
obj._flags.default = value; | ||
obj._notes = obj._notes.concat(notes); | ||
return obj; | ||
@@ -375,8 +248,8 @@ }; | ||
internals.Any.prototype.description = function (desc) { | ||
internals.Any.prototype.tags = function (tags) { | ||
Hoek.assert(desc && typeof desc === 'string', 'Description must be a non-empty string'); | ||
Hoek.assert(tags && (typeof tags === 'string' || Array.isArray(tags)), 'Tags must be a non-empty string or array'); | ||
var obj = this.clone(); | ||
obj._description = desc; | ||
obj._tags = obj._tags.concat(tags); | ||
return obj; | ||
@@ -386,8 +259,10 @@ }; | ||
internals.Any.prototype.notes = function (notes) { | ||
internals.Any.prototype.example = function (value) { | ||
Hoek.assert(notes && (typeof notes === 'string' || Array.isArray(notes)), 'Notes must be a non-empty string or array'); | ||
Hoek.assert(arguments.length, 'Missing example'); | ||
var result = this._validate(value, null, internals.defaults); | ||
Hoek.assert(!result.errors, 'Bad example:', result.errors && Errors.process(result.errors, value)); | ||
var obj = this.clone(); | ||
obj._notes = obj._notes.concat(notes); | ||
obj._examples = obj._examples.concat(value); | ||
return obj; | ||
@@ -397,8 +272,8 @@ }; | ||
internals.Any.prototype.tags = function (tags) { | ||
internals.Any.prototype.unit = function (name) { | ||
Hoek.assert(tags && (typeof tags === 'string' || Array.isArray(tags)), 'Tags must be a non-empty string or array'); | ||
Hoek.assert(name && typeof name === 'string', 'Unit name must be a non-empty string'); | ||
var obj = this.clone(); | ||
obj._tags = obj._tags.concat(tags); | ||
obj._unit = name; | ||
return obj; | ||
@@ -408,3 +283,3 @@ }; | ||
internals.Any.prototype._validate = function (value, state, options) { | ||
internals.Any.prototype._validate = function (value, state, options, reference) { | ||
@@ -415,3 +290,3 @@ var self = this; | ||
state = state || { parent: null, key: '', path: '', renamed: {} }; | ||
state = state || { key: '', path: '', parent: null, reference: reference }; | ||
@@ -423,139 +298,108 @@ if (this._settings) { | ||
var errors = []; | ||
var finish = function () { | ||
var process = function () { | ||
return { | ||
value: (value !== undefined) ? value : (Ref.isRef(self._flags.default) ? self._flags.default(state.parent) : self._flags.default), | ||
errors: errors.length ? errors : null | ||
}; | ||
}; | ||
// Validate dependencies | ||
// Check allowed and denied values using the original value | ||
for (var i = 0, il = self._dependencies.length; i < il; ++i) { | ||
var err = self._dependencies[i].func(value, state, options); | ||
if (err) { | ||
errors.push(err); | ||
if (options.abortEarly) { | ||
return errors; | ||
} | ||
} | ||
} | ||
if (this._valids.has(value, state, this._flags.insensitive)) { | ||
return finish(); | ||
} | ||
// Check allowed and denied values using the original value | ||
if (this._invalids.has(value, state, this._flags.insensitive)) { | ||
errors.push(Errors.create(value === '' ? 'any.empty' : (value === undefined ? 'any.required' : 'any.invalid'), null, state, options)); | ||
if (options.abortEarly || | ||
value === undefined) { // No reason to keep validating missing value | ||
if (self._valids.has(value, self._flags.insensitive)) { | ||
return finish(); | ||
} | ||
} | ||
if (self._invalids.has(value, self._flags.insensitive)) { | ||
errors.push(Errors.create(value === '' ? 'any.empty' : 'any.invalid', { value: (value === '' ? '' : self._invalids.key(value)) }, state, options)); | ||
if (options.abortEarly) { | ||
return errors; | ||
} | ||
// Convert value and validate type | ||
if (this._base) { | ||
var base = this._base.call(this, value, state, options); | ||
if (base.errors) { | ||
value = base.value; | ||
errors = errors.concat(base.errors); | ||
return finish(); // Base error always aborts early | ||
} | ||
// Convert value | ||
if (base.value !== value) { | ||
value = base.value; | ||
if (options.convert && | ||
self._convert) { | ||
// Check allowed and denied values using the converted value | ||
var original = value; | ||
value = self._convert(original); | ||
if (original !== value) { | ||
if (this._valids.has(value, state, this._flags.insensitive)) { | ||
return finish(); | ||
} | ||
// Check allowed and denied values using the converted value | ||
if (self._valids.has(value, self._flags.insensitive)) { | ||
if (this._invalids.has(value, state, this._flags.insensitive)) { | ||
errors.push(Errors.create('any.invalid', null, state, options)); | ||
if (options.abortEarly) { | ||
return finish(); | ||
} | ||
if (self._invalids.has(value, self._flags.insensitive)) { | ||
errors.push(Errors.create('any.invalid', { value: self._invalids.key(value) }, state, options)); | ||
if (options.abortEarly) { | ||
return errors; | ||
} | ||
} | ||
} | ||
} | ||
} | ||
// Required values did not match | ||
// Required values did not match | ||
if (self._flags.allowOnly) { | ||
errors.push(Errors.create('any.validate.allowOnly', { value: self._valids.toString(false) }, state, options)); | ||
if (options.abortEarly) { | ||
return errors; | ||
} | ||
if (this._flags.allowOnly) { | ||
errors.push(Errors.create('any.allowOnly', { valids: this._valids.toString(false) }, state, options)); | ||
if (options.abortEarly) { | ||
return finish(); | ||
} | ||
} | ||
// Validate tests | ||
// Validate tests | ||
for (i = 0, il = self._tests.length; i < il; ++i) { | ||
var test = self._tests[i]; | ||
var err = test.func(value, state, options); | ||
if (err) { | ||
if (Array.isArray(err)) { | ||
errors = errors.concat(err); | ||
} | ||
else { | ||
errors.push(err); | ||
} | ||
if (!test.name || // Base test error always stops | ||
options.abortEarly) { | ||
return errors; | ||
} | ||
for (var i = 0, il = this._tests.length; i < il; ++i) { | ||
var test = this._tests[i]; | ||
var err = test.func.call(this, value, state, options); | ||
if (err) { | ||
errors.push(err); | ||
if (options.abortEarly) { | ||
return finish(); | ||
} | ||
} | ||
} | ||
return finish(); | ||
}; | ||
return finish(); | ||
}; | ||
var finish = function () { | ||
if (!errors.length && | ||
state.parent && | ||
((options.modify && state.parent.hasOwnProperty(state.key)) || (value === undefined && self._flags.default !== undefined))) { | ||
internals.Any.prototype._validateWithOptions = function (value, options, callback) { | ||
state.parent[state.key] = (value !== undefined ? value : self._flags.default); | ||
} | ||
var settings = Hoek.applyToDefaults(internals.defaults, options); | ||
var result = this._validate(value, null, settings); | ||
var errors = Errors.process(result.errors, value); | ||
// Apply mutators as long as there are no errors | ||
for (var m = 0, ml = self._mutators.length; m < ml && !errors.length; ++m) { | ||
var err = self._mutators[m](value, state, options); | ||
if (err) { | ||
errors.push(err); | ||
} | ||
} | ||
// Return null or errors | ||
return errors.length ? errors : null; | ||
}; | ||
return process(); | ||
return callback(errors, result.value); | ||
}; | ||
internals.Any.prototype.validate = function (object, options) { | ||
internals.Any.prototype.validate = function (value, callback) { | ||
var settings = Hoek.applyToDefaults(internals.defaults, options || {}); | ||
var result = this._validate(value, null, internals.defaults); | ||
var errors = Errors.process(result.errors, value); | ||
Hoek.assert(!settings.modify || settings.convert, 'Cannot save and skip conversions at the same time'); | ||
var errors = this._validate(object, null, settings); | ||
return Errors.process(errors, object); | ||
return callback(errors, result.value); | ||
}; | ||
internals.Any.prototype.validateCallback = function (object, options, callback) { // Not actually async, just callback interface | ||
var err = this.validate(object, options); | ||
return callback(err); | ||
}; | ||
internals.Any.prototype.describe = function () { | ||
var description = { | ||
type: this._type, | ||
flags: this._flags | ||
type: this._type | ||
}; | ||
if (Object.keys(this._flags).length) { | ||
description.flags = this._flags; | ||
} | ||
if (this._description) { | ||
@@ -573,4 +417,12 @@ description.description = this._description; | ||
if (this._examples.length) { | ||
description.examples = this._examples; | ||
} | ||
if (this._unit) { | ||
description.unit = this._unit; | ||
} | ||
var valids = this._valids.values(); | ||
if (valids) { | ||
if (valids.length) { | ||
description.valids = valids; | ||
@@ -580,3 +432,3 @@ } | ||
var invalids = this._invalids.values(); | ||
if (invalids) { | ||
if (invalids.length) { | ||
description.invalids = invalids; | ||
@@ -587,12 +439,9 @@ } | ||
var validators = [].concat(this._dependencies, this._tests); | ||
for (var i = 0, il = validators.length; i < il; ++i) { | ||
var validator = validators[i]; | ||
if (validator.name) { | ||
var item = { name: validator.name }; | ||
if (validator.arg) { | ||
item.arg = validator.arg; | ||
} | ||
description.rules.push(item); | ||
for (var i = 0, il = this._tests.length; i < il; ++i) { | ||
var validator = this._tests[i]; | ||
var item = { name: validator.name }; | ||
if (validator.arg) { | ||
item.arg = validator.arg; | ||
} | ||
description.rules.push(item); | ||
} | ||
@@ -612,4 +461,3 @@ | ||
this._set = {}; | ||
this._lowercase = {}; | ||
this._set = []; | ||
@@ -622,51 +470,75 @@ for (var i = 0, il = values.length; i < il; ++i) { | ||
internals.Set.prototype.add = function (value) { | ||
internals.Set.prototype.add = function (value, refs) { | ||
var key = this.key(value); | ||
this._set[key] = value; | ||
this._lowercase[key.toLowerCase()] = value; | ||
}; | ||
Hoek.assert(value === null || value === undefined || value instanceof Date || Ref.isRef(value) || (typeof value !== 'function' && typeof value !== 'object'), 'Value cannot be an object or function'); | ||
if (typeof value !== 'function' && | ||
this.has(value, null, false)) { | ||
internals.Set.prototype.remove = function (value) { | ||
return; | ||
} | ||
var key = this.key(value); | ||
delete this._set[key]; | ||
delete this._lowercase[key.toLowerCase()]; | ||
if (Ref.isRef(value)) { | ||
refs.push(value.root); | ||
} | ||
this._set.push(value); | ||
}; | ||
internals.Set.prototype.has = function (value, insensitive) { | ||
internals.Set.prototype.merge = function (set) { | ||
var key = this.key(value); | ||
return (insensitive === true ? this._lowercase.hasOwnProperty(key.toLowerCase()) : this._set.hasOwnProperty(key)); | ||
for (var i = 0, il = set._set.length; i < il; ++i) { | ||
this.add(set._set[i]); | ||
} | ||
}; | ||
internals.Set.prototype.key = function (value) { | ||
internals.Set.prototype.remove = function (value) { | ||
return Sys.inspect(value); | ||
this._set = this._set.filter(function (item) { | ||
return value !== item; | ||
}); | ||
}; | ||
internals.Set.prototype.values = function () { | ||
internals.Set.prototype.has = function (value, state, insensitive) { | ||
var list = []; | ||
var values = Object.keys(this._set); | ||
for (var i = 0, il = values.length; i < il; ++i) { | ||
list.push(this._set[values[i]]); | ||
for (var i = 0, il = this._set.length; i < il; ++i) { | ||
var item = this._set[i]; | ||
if (Ref.isRef(item)) { | ||
item = item(state.reference || state.parent); | ||
} | ||
if (typeof value !== typeof item) { | ||
continue; | ||
} | ||
if (value === item || | ||
(value instanceof Date && item instanceof Date && value.getTime() === item.getTime()) || | ||
(insensitive && typeof value === 'string' && value.toLowerCase() === item.toLowerCase())) { | ||
return true; | ||
} | ||
} | ||
return list.length ? list : null; | ||
return false; | ||
}; | ||
internals.Set.prototype.values = function () { | ||
return this._set.slice(); | ||
}; | ||
internals.Set.prototype.toString = function (includeUndefined) { | ||
var list = ''; | ||
var values = Object.keys(this._set); | ||
for (var i = 0, il = values.length; i < il; ++i) { | ||
var value = this._set[values[i]]; | ||
if (value !== undefined || includeUndefined) { | ||
list += (list ? ', ' : '') + (value === undefined ? 'undefined' : (value === null ? 'null' : value)); | ||
for (var i = 0, il = this._set.length; i < il; ++i) { | ||
var item = this._set[i]; | ||
if (item !== undefined || includeUndefined) { | ||
list += (list ? ', ' : '') + internals.stringify(item); | ||
} | ||
@@ -676,2 +548,20 @@ } | ||
return list; | ||
}; | ||
}; | ||
internals.stringify = function (value) { | ||
if (value === undefined) { | ||
return 'undefined'; | ||
} | ||
if (value === null) { | ||
return 'null'; | ||
} | ||
if (typeof value === 'string') { | ||
return value; | ||
} | ||
return value.toString(); | ||
}; |
@@ -5,2 +5,3 @@ // Load modules | ||
var Any = require('./any'); | ||
var Cast = require('./cast'); | ||
var Errors = require('./errors'); | ||
@@ -15,15 +16,6 @@ var Hoek = require('hoek'); | ||
module.exports = internals.Array = function () { | ||
internals.Array = function () { | ||
Any.call(this); | ||
this._type = 'array'; | ||
this._base(function (value, state, options) { | ||
if (Array.isArray(value)) { | ||
return null; | ||
} | ||
return Errors.create('array.base', { value: value }, state, options); | ||
}); | ||
}; | ||
@@ -34,30 +26,22 @@ | ||
internals.Array.create = function () { | ||
internals.Array.prototype._base = function (value, state, options) { | ||
return new internals.Array(); | ||
}; | ||
var result = { | ||
value: value | ||
}; | ||
if (typeof value === 'string' && | ||
options.convert) { | ||
internals.Array.prototype._convert = function (value) { | ||
if (typeof value !== 'string') { | ||
return value; | ||
try { | ||
var converted = JSON.parse(value); | ||
if (Array.isArray(converted)) { | ||
result.value = converted; | ||
} | ||
} | ||
catch (e) { } | ||
} | ||
if (!isNaN(value)) { // Check with isNaN, because JSON.parse converts number string to number | ||
return value; | ||
} | ||
try { | ||
var converted = JSON.parse(value); | ||
if (Array.isArray(converted)) { | ||
return converted; | ||
} | ||
else { | ||
return [converted]; | ||
} | ||
} | ||
catch (e) { | ||
return value; | ||
} | ||
result.errors = Array.isArray(result.value) ? null : Errors.create('array.base', null, state, options); | ||
return result; | ||
}; | ||
@@ -68,4 +52,7 @@ | ||
var inclusions = Hoek.flatten(Array.prototype.slice.call(arguments)); | ||
var inclusions = Hoek.flatten(Array.prototype.slice.call(arguments)).map(function (type) { | ||
return Cast.schema(type); | ||
}); | ||
return this._test('includes', inclusions, function (value, state, options) { | ||
@@ -76,5 +63,8 @@ | ||
var isValid = false; | ||
var localState = { key: v, path: (state.path ? state.path + '.' : '') + v, parent: value, reference: state.reference }; | ||
for (var i = 0, il = inclusions.length; i < il; ++i) { | ||
var err = inclusions[i]._validate(item, { parent: value, key: v, path: (state.path ? state.path + '.' : '') + v, renamed: state.renamed }, options); | ||
if (!err) { | ||
var result = inclusions[i]._validate(item, localState, options); | ||
if (!result.errors) { | ||
value[v] = result.value; | ||
isValid = true; | ||
@@ -87,3 +77,3 @@ break; | ||
if (il === 1) { | ||
return Errors.create('array.includes-single', { value: item, pos: v, reason: err }, state, options); | ||
return Errors.create('array.includesOne', { pos: v, reason: result.errors }, { key: state.key, path: localState.path }, options); | ||
} | ||
@@ -93,3 +83,3 @@ } | ||
if (!isValid) { | ||
return Errors.create('array.includes', { value: item, pos: v }, state, options); | ||
return Errors.create('array.includes', { pos: v }, { key: state.key, path: localState.path }, options); | ||
} | ||
@@ -105,4 +95,7 @@ } | ||
var exclusions = Hoek.flatten(Array.prototype.slice.call(arguments)); | ||
var exclusions = Hoek.flatten(Array.prototype.slice.call(arguments)).map(function (type) { | ||
return Cast.schema(type); | ||
}); | ||
return this._test('excludes', exclusions, function (value, state, options) { | ||
@@ -112,6 +105,8 @@ | ||
var item = value[v]; | ||
var localState = { key: v, path: (state.path ? state.path + '.' : '') + v, parent: value, reference: state.reference }; | ||
for (var i = 0, il = exclusions.length; i < il; ++i) { | ||
var err = exclusions[i].validate(item, options); | ||
if (!err) { | ||
return Errors.create('array.excludes', { value: item, pos: i + 1 }, state, options); | ||
var result = exclusions[i]._validate(item, localState, {}); // Not passing options to use defaults | ||
if (!result.errors) { | ||
return Errors.create('array.excludes', { pos: v }, { key: state.key, path: localState.path }, options); | ||
} | ||
@@ -128,3 +123,3 @@ } | ||
Hoek.assert(!isNaN(limit) && ((limit | 0) === parseFloat(limit)) && limit >= 0, 'limit must be a positive integer'); | ||
Hoek.assert(Hoek.isInteger(limit) && limit >= 0, 'limit must be a positive integer'); | ||
@@ -137,3 +132,3 @@ return this._test('min', limit, function (value, state, options) { | ||
return Errors.create('array.min', { value: limit }, state, options); | ||
return Errors.create('array.min', { limit: limit }, state, options); | ||
}); | ||
@@ -145,3 +140,3 @@ }; | ||
Hoek.assert(!isNaN(limit) && ((limit | 0) === parseFloat(limit)) && limit >= 0, 'limit must be a positive integer'); | ||
Hoek.assert(Hoek.isInteger(limit) && limit >= 0, 'limit must be a positive integer'); | ||
@@ -154,3 +149,3 @@ return this._test('max', limit, function (value, state, options) { | ||
return Errors.create('array.max', { value: limit }, state, options); | ||
return Errors.create('array.max', { limit: limit }, state, options); | ||
}); | ||
@@ -162,3 +157,3 @@ }; | ||
Hoek.assert(!isNaN(limit) && ((limit | 0) === parseFloat(limit)) && limit >= 0, 'limit must be a positive integer'); | ||
Hoek.assert(Hoek.isInteger(limit) && limit >= 0, 'limit must be a positive integer'); | ||
@@ -171,4 +166,7 @@ return this._test('length', limit, function (value, state, options) { | ||
return Errors.create('array.length', { value: limit }, state, options); | ||
return Errors.create('array.length', { limit: limit }, state, options); | ||
}); | ||
}; | ||
module.exports = new internals.Array(); |
@@ -13,15 +13,6 @@ // Load modules | ||
module.exports = internals.Boolean = function () { | ||
internals.Boolean = function () { | ||
Any.call(this); | ||
this._type = 'boolean'; | ||
this._base(function (value, state, options) { | ||
if (typeof value === 'boolean') { | ||
return null; | ||
} | ||
return Errors.create('boolean.base', null, state, options); | ||
}); | ||
}; | ||
@@ -32,17 +23,21 @@ | ||
internals.Boolean.create = function () { | ||
internals.Boolean.prototype._base = function (value, state, options) { | ||
return new internals.Boolean(); | ||
}; | ||
var result = { | ||
value: value | ||
}; | ||
if (typeof value === 'string' && | ||
options.convert) { | ||
internals.Boolean.prototype._convert = function (value) { | ||
if (typeof value !== 'string') { | ||
return value; | ||
var lower = value.toLowerCase(); | ||
result.value = (lower === 'true' || lower === 'yes' || lower === 'on' ? true | ||
: (lower === 'false' || lower === 'no' || lower === 'off' ? false : value)); | ||
} | ||
value = value.toLowerCase(); | ||
return (value === 'true' || value === 'yes' || value === 'on' ? true | ||
: (value === 'false' || value === 'no' || value === 'off' ? false : value)); | ||
result.errors = (typeof result.value === 'boolean') ? null : Errors.create('boolean.base', null, state, options); | ||
return result; | ||
}; | ||
module.exports = new internals.Boolean(); |
// Load modules | ||
var Hoek = require('hoek'); | ||
var Ref = require('./ref'); | ||
// Type modules are delay-loaded to prevent circular dependencies | ||
@@ -10,9 +11,9 @@ | ||
var internals = { | ||
any: require('./any').create(), | ||
date: require('./date').create(), | ||
string: require('./string').create(), | ||
number: require('./number').create(), | ||
boolean: require('./boolean').create(), | ||
Alternatives: null, | ||
Object: null | ||
any: null, | ||
date: require('./date'), | ||
string: require('./string'), | ||
number: require('./number'), | ||
boolean: require('./boolean'), | ||
alt: null, | ||
object: null | ||
}; | ||
@@ -23,4 +24,5 @@ | ||
internals.Alternatives = internals.Alternatives || require('./alternatives'); | ||
internals.Object = internals.Object || require('./object'); | ||
internals.any = internals.any || new (require('./any'))(); | ||
internals.alt = internals.alt || require('./alternatives'); | ||
internals.object = internals.object || require('./object'); | ||
@@ -35,3 +37,3 @@ if (config && | ||
if (Array.isArray(config)) { | ||
return internals.Alternatives._create(config); | ||
return internals.alt.try(config); | ||
} | ||
@@ -47,3 +49,3 @@ | ||
return internals.Object.create(config); | ||
return internals.object.keys(config); | ||
} | ||
@@ -63,2 +65,6 @@ | ||
if (Ref.isRef(config)) { | ||
return internals.any.valid(config); | ||
} | ||
Hoek.assert(config === null, 'Invalid schema content:', config); | ||
@@ -68,1 +74,7 @@ | ||
}; | ||
exports.ref = function (id) { | ||
return Ref.isRef(id) ? id : Ref.create(id); | ||
}; |
@@ -13,15 +13,6 @@ // Load modules | ||
module.exports = internals.Date = function () { | ||
internals.Date = function () { | ||
Any.call(this); | ||
this._type = 'date'; | ||
this._base(function (value, state, options) { | ||
if (value instanceof Date) { | ||
return null; | ||
} | ||
return Errors.create('date.base', { value: value }, state, options); | ||
}); | ||
}; | ||
@@ -32,5 +23,10 @@ | ||
internals.Date.create = function () { | ||
internals.Date.prototype._base = function (value, state, options) { | ||
return new internals.Date(); | ||
var result = { | ||
value: (options.convert && internals.toDate(value)) || value | ||
}; | ||
result.errors = (result.value instanceof Date) ? null : Errors.create('date.base', null, state, options); | ||
return result; | ||
}; | ||
@@ -41,10 +37,13 @@ | ||
var number = Number(value); | ||
if (!isNaN(number)) { | ||
value = number; | ||
if (value instanceof Date) { | ||
return value; | ||
} | ||
var date = new Date(value); | ||
if (!isNaN(date.getTime())) { | ||
return date; | ||
if (typeof value === 'string' || | ||
Hoek.isInteger(value)) { | ||
var date = new Date(value); | ||
if (!isNaN(date.getTime())) { | ||
return date; | ||
} | ||
} | ||
@@ -56,8 +55,2 @@ | ||
internals.Date.prototype._convert = function (value) { | ||
return (internals.toDate(value) || value); | ||
}; | ||
internals.Date.prototype.min = function (date) { | ||
@@ -74,3 +67,3 @@ | ||
return Errors.create('date.min', { value: date }, state, options); | ||
return Errors.create('date.min', { limit: date }, state, options); | ||
}); | ||
@@ -91,4 +84,7 @@ }; | ||
return Errors.create('date.max', { value: date }, state, options); | ||
return Errors.create('date.max', { limit: date }, state, options); | ||
}); | ||
}; | ||
module.exports = new internals.Date(); |
@@ -16,3 +16,3 @@ // Load modules | ||
this.context = context || {}; | ||
this.context.key = state.key || '<root>'; | ||
this.context.key = state.key; | ||
this.path = state.path; | ||
@@ -28,11 +28,14 @@ this.options = options; | ||
var localized = this.options.language; | ||
this.context.key = this.context.key || localized.root || Language.errors.root; | ||
var format = Hoek.reach(localized, this.type) || Hoek.reach(Language.errors, this.type); | ||
if (!format) { | ||
return this.context.key; | ||
} | ||
var hasKey = false; | ||
var message = format.replace(/\{\{\s*([^\s}]+?)\s*\}\}/ig, function (match, name) { | ||
return format.replace(/\{\{\s*([^\s}]+?)\s*\}\}/ig, function (match, name) { | ||
hasKey = hasKey || name === 'key'; | ||
var value = Hoek.reach(self.context, name); | ||
return Array.isArray(value) ? value.join(', ') : value.toString(); | ||
}); | ||
return Hoek.reach(self.context, name).toString(); | ||
}); | ||
return hasKey ? message : this.context.key + ' ' + message; | ||
}; | ||
@@ -56,25 +59,13 @@ | ||
var item = errors[i]; | ||
details.push({ message: item.toString(), path: item.path || item.context.key, type: item.type }); | ||
details.push({ | ||
message: item.toString(), | ||
path: item.path || item.context.key, | ||
type: item.type | ||
}); | ||
} | ||
return new internals.ValidationError(details, object); | ||
}; | ||
// Construct error | ||
internals.ValidationError = function (details, object) { | ||
Error.call(this); | ||
this.details = details; | ||
this._object = object; | ||
return this.simple(); | ||
}; | ||
Hoek.inherits(internals.ValidationError, Error); | ||
internals.ValidationError.prototype.simple = function () { | ||
var message = ''; | ||
this.details.forEach(function (error) { | ||
details.forEach(function (error) { | ||
@@ -84,14 +75,18 @@ message += (message ? '. ' : '') + error.message; | ||
this.message = message; | ||
return this; | ||
var error = new Error(message); | ||
error.details = details; | ||
error._object = object; | ||
error.annotate = internals.annotate; | ||
return error; | ||
}; | ||
internals.ValidationError.prototype.annotated = function () { | ||
internals.annotate = function () { | ||
var obj = Hoek.clone(this._object || {}); | ||
var lookup = {}; | ||
var el = this.details.length; | ||
for (var e = el - 1; e >= 0; --e) { // Reverse order to process deepest child first | ||
var pos = el - e; | ||
var error = this.details[e]; | ||
@@ -109,6 +104,15 @@ var path = error.path.split('.'); | ||
delete ref[seg]; | ||
ref[seg + '_$key$_' + (e + 1) + '_$end$_'] = value; | ||
var label = seg + '_$key$_' + pos + '_$end$_'; | ||
ref[label] = value; | ||
lookup[error.path] = label; | ||
} | ||
else if (lookup[error.path]) { | ||
var replacement = lookup[error.path]; | ||
var appended = replacement.replace('_$end$_', ', ' + pos + '_$end$_'); | ||
ref[appended] = ref[replacement]; | ||
lookup[error.path] = appended; | ||
delete ref[replacement]; | ||
} | ||
else { | ||
ref['_$miss$_' + seg + '|' + (e + 1) + '_$end$_'] = '__missing__'; | ||
ref['_$miss$_' + seg + '|' + pos + '_$end$_'] = '__missing__'; | ||
} | ||
@@ -121,3 +125,3 @@ } | ||
annotated = annotated.replace(/_\$key\$_(\d+)_\$end\$_\"/g, function ($0, $1) { | ||
annotated = annotated.replace(/_\$key\$_([, \d]+)_\$end\$_\"/g, function ($0, $1) { | ||
@@ -127,3 +131,3 @@ return '" \u001b[31m[' + $1 + ']\u001b[0m'; | ||
this.message = annotated.replace(/\"_\$miss\$_([^\|]+)\|(\d+)_\$end\$_\"\: \"__missing__\"/g, function ($0, $1, $2) { | ||
var message = annotated.replace(/\"_\$miss\$_([^\|]+)\|(\d+)_\$end\$_\"\: \"__missing__\"/g, function ($0, $1, $2) { | ||
@@ -133,12 +137,12 @@ return '\u001b[41m"' + $1 + '"\u001b[0m\u001b[31m [' + $2 + ']: -- missing --\u001b[0m'; | ||
this.message += '\n\u001b[31m'; | ||
message += '\n\u001b[31m'; | ||
for (e = 0; e < el; ++e) { | ||
this.message += '\n[' + (e + 1) + '] ' + this.details[e].message; | ||
message += '\n[' + (e + 1) + '] ' + this.details[e].message; | ||
} | ||
this.message += '\u001b[0m'; | ||
message += '\u001b[0m'; | ||
return this; | ||
return message; | ||
}; | ||
@@ -13,15 +13,6 @@ // Load modules | ||
module.exports = internals.Function = function () { | ||
internals.Function = function () { | ||
Any.call(this); | ||
this._type = 'func'; | ||
this._base(function (value, state, options) { | ||
if (typeof value === 'function') { | ||
return null; | ||
} | ||
return Errors.create('function.base', null, state, options); | ||
}); | ||
}; | ||
@@ -32,5 +23,11 @@ | ||
internals.Function.create = function () { | ||
internals.Function.prototype._base = function (value, state, options) { | ||
return new internals.Function(); | ||
return { | ||
value: value, | ||
errors: (typeof value === 'function') ? null : Errors.create('function.base', null, state, options) | ||
}; | ||
}; | ||
module.exports = new internals.Function(); |
114
lib/index.js
// Load modules | ||
var Hoek = require('hoek'); | ||
var Any = require('./any'); | ||
var Cast = require('./cast'); | ||
var Ref = require('./ref'); | ||
@@ -10,44 +10,104 @@ | ||
var internals = {}; | ||
var internals = { | ||
alternatives: require('./alternatives'), | ||
array: require('./array'), | ||
boolean: require('./boolean'), | ||
binary: require('./binary'), | ||
date: require('./date'), | ||
func: require('./function'), | ||
number: require('./number'), | ||
object: require('./object'), | ||
string: require('./string') | ||
}; | ||
exports.any = Any.create; | ||
exports.alternatives = exports.alt = require('./alternatives').create; | ||
exports.array = require('./array').create; | ||
exports.boolean = exports.bool = require('./boolean').create; | ||
exports.date = require('./date').create; | ||
exports.func = require('./function').create; | ||
exports.number = require('./number').create; | ||
exports.object = require('./object').create; | ||
exports.string = require('./string').create; | ||
internals.root = function () { | ||
var any = new Any(); | ||
exports.validate = function (object, schema, options) { | ||
var root = any.clone(); | ||
root.any = function () { | ||
return Cast.schema(schema).validate(object, options); | ||
}; | ||
return any; | ||
}; | ||
root.alternatives = root.alt = function () { | ||
exports.validateCallback = function (object, schema, options, callback) { // Not actually async, just callback interface | ||
return arguments.length ? internals.alternatives.try.apply(internals.alternatives, arguments) : internals.alternatives; | ||
}; | ||
var err = exports.validate(object, schema, options); | ||
return callback(err); | ||
}; | ||
root.array = function () { | ||
return internals.array; | ||
}; | ||
exports.describe = function (schema) { | ||
root.boolean = root.bool = function () { | ||
Hoek.assert(typeof schema === 'object', 'Schema must be an object'); | ||
return internals.boolean; | ||
}; | ||
if (typeof schema.describe === 'function') { | ||
root.binary = function () { | ||
return internals.binary; | ||
}; | ||
root.date = function () { | ||
return internals.date; | ||
}; | ||
root.func = function () { | ||
return internals.func; | ||
}; | ||
root.number = function () { | ||
return internals.number; | ||
}; | ||
root.object = function () { | ||
return arguments.length ? internals.object.keys.apply(internals.object, arguments) : internals.object; | ||
}; | ||
root.string = function () { | ||
return internals.string; | ||
}; | ||
root.ref = function () { | ||
return Ref.create.apply(null, arguments); | ||
}; | ||
root.validate = function (value /*, [schema], [options], callback */) { | ||
var callback = arguments[arguments.length - 1]; | ||
if (arguments.length === 2) { | ||
return any.validate(value, callback); | ||
} | ||
var options = arguments.length === 4 ? arguments[2] : {}; | ||
var schema = Cast.schema(arguments[1]); | ||
return schema._validateWithOptions(value, options, callback); | ||
}; | ||
root.describe = function () { | ||
var schema = arguments.length ? Cast.schema(arguments[0]) : any; | ||
return schema.describe(); | ||
} | ||
}; | ||
return exports.object(schema).describe(); | ||
root.compile = function (schema) { | ||
return Cast.schema(schema); | ||
}; | ||
return root; | ||
}; | ||
exports.compile = function (schema) { | ||
return Cast.schema(schema); | ||
}; | ||
module.exports = internals.root(); |
@@ -10,75 +10,79 @@ // Load modules | ||
exports.errors = { | ||
root: 'value', | ||
any: { | ||
unknown: 'the key {{key}} is not allowed', | ||
invalid: 'the value of {{key}} is not allowed to be {{value}}', | ||
empty: 'the value of {{key}} is not allowed to be empty', | ||
with: { | ||
parent: 'cannot specify peer when key is not part of an object', | ||
peer: 'missing required peer {{value}}' | ||
}, | ||
without: { | ||
parent: 'cannot specify peer when key is not part of an object', | ||
peer: 'conflict with forbidden peer {{value}}' | ||
}, | ||
xor: { | ||
parent: 'cannot specify peer when key is not part of an object', | ||
peer: '{{key}} conflict with exclusive peer {{value}}', | ||
missing: 'at least one of {{key}} {{value}} is required' | ||
}, | ||
or: { | ||
parent: 'cannot specify peer when key is not part of an object', | ||
peer: 'missing alternative peers {{value}}' | ||
}, | ||
rename: { | ||
parent: 'cannot rename when key is not part of an object', | ||
multiple: 'cannot rename {{key}} because multiple renames are disabled and target {{value}} already used in a rename', | ||
override: 'cannot rename {{key}} because override is disabled and target {{value}} exists' | ||
}, | ||
validate: { | ||
allowOnly: 'the value of {{key}} must be one of {{value}}' | ||
} | ||
unknown: 'is not allowed', | ||
invalid: 'contains an invalid value', | ||
empty: 'is not allowed to be empty', | ||
required: 'is required', | ||
allowOnly: 'must be one of {{valids}}' | ||
}, | ||
alternatives: { | ||
base: 'not matching any of the allowed alternatives' | ||
}, | ||
array: { | ||
base: 'the value of {{key}} must be an array', | ||
includes: 'the {{key}} array value in position {{pos}} does not match any of the allowed types', | ||
'includes-single': 'the {{key}} array value in position {{pos}} fails because {{reason}}', | ||
excludes: 'the value {{value}} in {{key}} cannot violates exclusion rule {{pos}}', | ||
min: 'the length of {{key}} must include at least {{value}} items', | ||
max: 'the length of {{key}} must include less than (or equal to) {{value}} items', | ||
length: 'the length of {{key}} must include {{value}} items' | ||
base: 'must be an array', | ||
includes: 'position {{pos}} does not match any of the allowed types', | ||
includesOne: 'position {{pos}} fails because {{reason}}', | ||
excludes: 'position {{pos}} contains an excluded value', | ||
min: 'must contain at least {{limit}} items', | ||
max: 'must contain less than or equal to {{limit}} items', | ||
length: 'must contain {{limit}} items' | ||
}, | ||
boolean: { | ||
base: 'the value of {{key}} must be a boolean' | ||
base: 'must be a boolean' | ||
}, | ||
binary: { | ||
base: 'must be a buffer or a string', | ||
min: 'must be at least {{limit}} bytes', | ||
max: 'must be less than or equal to {{limit}} bytes', | ||
length: 'must be {{limit}} bytes' | ||
}, | ||
date: { | ||
base: 'the value of {{key}} must be a number of milliseconds or valid date string', | ||
min: 'the value of {{key}} must be larger than or equal to {{value}}', | ||
max: 'the value of {{key}} must be less than or equal to {{value}}' | ||
base: 'must be a number of milliseconds or valid date string', | ||
min: 'must be larger than or equal to {{limit}}', | ||
max: 'must be less than or equal to {{limit}}' | ||
}, | ||
function: { | ||
base: 'the value of {{key}} must be a Function' | ||
base: 'must be a Function' | ||
}, | ||
object: { | ||
base: 'the value of {{key}} must be an object', | ||
allowUnknown: 'the key {{key}} is not allowed' | ||
base: 'must be an object', | ||
min: 'must have at least {{limit}} children', | ||
max: 'must have less than or equal to {{limit}} children', | ||
length: 'must have {{limit}} children', | ||
allowUnknown: 'is not allowed', | ||
with: 'missing required peer {{peer}}', | ||
without: 'conflict with forbidden peer {{peer}}', | ||
missing: 'must contain at least one of {{peers}}', | ||
xor: 'contains a conflict between exclusive peers {{peers}}', | ||
or: 'must contain at least one of {{peers}}', | ||
and: 'contains {{present}} without its required peers {{missing}}', | ||
assert: 'validation failed because {{ref}} failed to {{message}}', | ||
rename: { | ||
multiple: 'cannot rename child {{from}} because multiple renames are disabled and another key was already renamed to {{to}}', | ||
override: 'cannot rename child {{from}} because override is disabled and target {{to}} exists' | ||
} | ||
}, | ||
number: { | ||
base: 'the value of {{key}} must be a number', | ||
min: 'the value of {{key}} must be larger than or equal to {{value}}', | ||
max: 'the value of {{key}} must be less than or equal to {{value}}', | ||
float: 'the value of {{key}} must be a float or double', | ||
int: 'the value of {{key}} must be an integer' | ||
base: 'must be a number', | ||
min: 'must be larger than or equal to {{limit}}', | ||
max: 'must be less than or equal to {{limit}}', | ||
float: 'must be a float or double', | ||
integer: 'must be an integer', | ||
negative: 'must be a negative number', | ||
positive: 'must be a positive number' | ||
}, | ||
string: { | ||
base: 'the value of {{key}} must be a string', | ||
min: 'the length of {{key}} must be at least {{value}} characters long', | ||
max: 'the length of {{key}} must be less than or equal to {{value}} characters long', | ||
length: 'the length of {{key}} must be {{value}} characters long', | ||
alphanum: 'the value of {{key}} must only contain alpha-numeric characters', | ||
token: 'the value of {{key}} must only contain alpha-numeric and underscore characters', | ||
regex: 'the value of {{key}} must match the regular expression {{value}}', | ||
email: 'The value of {{key}} must be a valid email', | ||
isoDate: 'the value of {{key}} must be a valid ISO 8601 date', | ||
guid: 'the value of {{key}} must be a valid GUID' | ||
base: 'must be a string', | ||
min: 'length must be at least {{limit}} characters long', | ||
max: 'length must be less than or equal to {{limit}} characters long', | ||
length: 'length must be {{limit}} characters long', | ||
alphanum: 'must only contain alpha-numeric characters', | ||
token: 'must only contain alpha-numeric and underscore characters', | ||
regex: 'fails to match the required pattern', | ||
email: 'must be a valid email', | ||
isoDate: 'must be a valid ISO 8601 date', | ||
guid: 'must be a valid GUID', | ||
hostname: 'must be a valid hostname' | ||
} | ||
}; |
@@ -13,15 +13,6 @@ // Load modules | ||
module.exports = internals.Number = function () { | ||
internals.Number = function () { | ||
Any.call(this); | ||
this._type = 'number'; | ||
this._base(function (value, state, options) { | ||
if (typeof value === 'number' && !isNaN(value)) { | ||
return null; | ||
} | ||
return Errors.create('number.base', { value: value }, state, options); | ||
}); | ||
}; | ||
@@ -32,20 +23,18 @@ | ||
internals.Number.create = function () { | ||
internals.Number.prototype._base = function (value, state, options) { | ||
return new internals.Number(); | ||
}; | ||
var result = { | ||
errors: null, | ||
value: value | ||
}; | ||
if (typeof value === 'string' && | ||
options.convert) { | ||
internals.Number.prototype._convert = function (value) { | ||
if (typeof value === 'string') { | ||
var number = parseFloat(value); | ||
if (isNaN(number) || !isFinite(value)) { | ||
return NaN; | ||
} | ||
return number; | ||
result.value = (isNaN(number) || !isFinite(value)) ? NaN : number; | ||
} | ||
return value; | ||
result.errors = (typeof result.value === 'number' && !isNaN(result.value)) ? null : Errors.create('number.base', null, state, options); | ||
return result; | ||
}; | ||
@@ -56,3 +45,3 @@ | ||
Hoek.assert((!isNaN(limit) && ((limit | 0) === parseFloat(limit))), 'limit must be an integer'); | ||
Hoek.assert(Hoek.isInteger(limit), 'limit must be an integer'); | ||
@@ -65,3 +54,3 @@ return this._test('min', limit, function (value, state, options) { | ||
return Errors.create('number.min', { value: limit }, state, options); | ||
return Errors.create('number.min', { limit: limit }, state, options); | ||
}); | ||
@@ -73,3 +62,3 @@ }; | ||
Hoek.assert((!isNaN(limit) && ((limit | 0) === parseFloat(limit))), 'limit must be an integer'); | ||
Hoek.assert(Hoek.isInteger(limit), 'limit must be an integer'); | ||
@@ -82,3 +71,3 @@ return this._test('max', limit, function (value, state, options) { | ||
return Errors.create('number.max', { value: limit }, state, options); | ||
return Errors.create('number.max', { limit: limit }, state, options); | ||
}); | ||
@@ -92,7 +81,3 @@ }; | ||
if ((value | 0) === parseFloat(value)) { | ||
return null; | ||
} | ||
return Errors.create('number.int', { value: value }, state, options); | ||
return Hoek.isInteger(value) ? null : Errors.create('number.integer', null, state, options); | ||
}); | ||
@@ -110,3 +95,3 @@ }; | ||
return Errors.create('number.negative', { value: value }, state, options); | ||
return Errors.create('number.negative', null, state, options); | ||
}); | ||
@@ -124,4 +109,7 @@ }; | ||
return Errors.create('number.positive', { value: value }, state, options); | ||
return Errors.create('number.positive', null, state, options); | ||
}); | ||
}; | ||
module.exports = new internals.Number(); |
// Load modules | ||
var Hoek = require('hoek'); | ||
var Topo = require('topo'); | ||
var Any = require('./any'); | ||
var Cast = require('./cast'); | ||
var Ref = require('./ref'); | ||
var Errors = require('./errors'); | ||
var Hoek = require('hoek'); | ||
@@ -14,85 +16,207 @@ | ||
module.exports = internals.Object = function (schema) { | ||
internals.Object = function () { | ||
var self = this; | ||
Hoek.assert(schema === null || schema === undefined || (typeof schema === 'object' && !schema.isJoi), 'Object schema must be a valid object and cannot be a joi schema'); | ||
Any.call(this); | ||
this._type = 'object'; | ||
this._inner = internals.compile(schema); | ||
this._inner = null; | ||
}; | ||
this._base(function (value, state, options) { | ||
Hoek.inherits(internals.Object, Any); | ||
if (value && | ||
typeof value === 'object' && | ||
!Array.isArray(value)) { | ||
return self._traverse(value, state, options); | ||
internals.Object.prototype._base = function (value, state, options) { | ||
var target = value; | ||
var errors = []; | ||
var finish = function () { | ||
return { | ||
value: target, | ||
errors: errors.length ? errors : null | ||
}; | ||
}; | ||
if (typeof value === 'string' && | ||
options.convert) { | ||
try { | ||
value = JSON.parse(value); | ||
} | ||
catch (err) { } | ||
} | ||
return Errors.create('object.base', null, state, options); | ||
}); | ||
}; | ||
if (!value || | ||
typeof value !== 'object' || | ||
Array.isArray(value)) { | ||
Hoek.inherits(internals.Object, Any); | ||
errors.push(Errors.create('object.base', null, state, options)); | ||
return finish(); | ||
} | ||
// Ensure target is a local copy (parsed) or shallow copy | ||
internals.Object.create = function (schema) { | ||
if (target === value) { | ||
target = {}; | ||
target.__proto__ = Object.getPrototypeOf(value); | ||
var valueKeys = Object.keys(value); | ||
for (var t = 0, tl = valueKeys.length; t < tl; ++t) { | ||
target[valueKeys[t]] = value[valueKeys[t]]; | ||
} | ||
} | ||
else { | ||
target = value; | ||
} | ||
return new internals.Object(schema); | ||
}; | ||
// Rename keys | ||
var renamed = {}; | ||
for (var r = 0, rl = this._renames.length; r < rl; ++r) { | ||
var item = this._renames[r]; | ||
internals.compile = function (schema) { | ||
if (!item.options.multiple && | ||
renamed[item.to]) { | ||
if (!schema) { | ||
return schema; | ||
errors.push(Errors.create('object.rename.multiple', { from: item.from, to: item.to }, state, options)); | ||
if (options.abortEarly) { | ||
return finish(); | ||
} | ||
} | ||
if (target.hasOwnProperty(item.to) && | ||
!item.options.override && | ||
!renamed[item.to]) { | ||
errors.push(Errors.create('object.rename.override', { from: item.from, to: item.to }, state, options)); | ||
if (options.abortEarly) { | ||
return finish(); | ||
} | ||
} | ||
target[item.to] = target[item.from]; | ||
renamed[item.to] = true; | ||
if (!item.options.alias) { | ||
delete target[item.from]; | ||
} | ||
} | ||
var compiled = {}; | ||
var children = Object.keys(schema); | ||
for (var c = 0, cl = children.length; c < cl; ++c) { | ||
var key = children[c]; | ||
var child = schema[key]; | ||
compiled[key] = Cast.schema(child); | ||
// Validate dependencies | ||
for (var d = 0, dl = this._dependencies.length; d < dl; ++d) { | ||
var dep = this._dependencies[d]; | ||
var err = internals[dep.type](dep.key && value[dep.key], dep.peers, target, { key: dep.key, path: (state.path ? state.path + '.' : '') + dep.key }, options); | ||
if (err) { | ||
errors.push(err); | ||
if (options.abortEarly) { | ||
return finish(); | ||
} | ||
} | ||
} | ||
return compiled; | ||
}; | ||
// Validate schema | ||
if (!this._inner) { // null allows any keys | ||
return finish(); | ||
} | ||
internals.Object.prototype._convert = function (value) { | ||
var unprocessed = Hoek.mapToObject(Object.keys(target)); | ||
var key; | ||
if (typeof value === 'object' || | ||
value === undefined) { | ||
for (var i = 0, il = this._inner.length; i < il; ++i) { | ||
var child = this._inner[i]; | ||
var key = child.key; | ||
var item = target[key]; | ||
return value; | ||
delete unprocessed[key]; | ||
var localState = { key: key, path: (state.path ? state.path + '.' : '') + key, parent: target, reference: state.reference }; | ||
var result = child.schema._validate(item, localState, options); | ||
if (result.errors) { | ||
errors = errors.concat(result.errors); | ||
if (options.abortEarly) { | ||
return finish(); | ||
} | ||
} | ||
if (result.value !== undefined) { | ||
target[key] = result.value; | ||
} | ||
} | ||
try { | ||
return JSON.parse(value); | ||
var unprocessedKeys = Object.keys(unprocessed); | ||
if (unprocessedKeys.length) { | ||
if (options.stripUnknown || | ||
options.skipFunctions) { | ||
var hasFunctions = false; | ||
for (var k = 0, kl = unprocessedKeys.length; k < kl; ++k) { | ||
key = unprocessedKeys[k]; | ||
if (options.stripUnknown) { | ||
delete target[key]; | ||
} | ||
else if (typeof target[key] === 'function') { | ||
delete unprocessed[key]; | ||
hasFunctions = true; | ||
} | ||
} | ||
if (options.stripUnknown) { | ||
return finish(); | ||
} | ||
if (hasFunctions) { | ||
unprocessedKeys = Object.keys(unprocessed); | ||
} | ||
} | ||
if (unprocessedKeys.length && | ||
!options.allowUnknown) { | ||
for (var e = 0, el = unprocessedKeys.length; e < el; ++e) { | ||
errors.push(Errors.create('object.allowUnknown', null, { key: unprocessedKeys[e], path: state.path }, options)); | ||
} | ||
} | ||
} | ||
catch (err) { | ||
return value; | ||
} | ||
return finish(); | ||
}; | ||
internals.Object.prototype.describe = function () { | ||
internals.Object.prototype.keys = function (schema) { | ||
var description = Any.prototype.describe.call(this); | ||
Hoek.assert(schema === null || schema === undefined || typeof schema === 'object', 'Object schema must be a valid object'); | ||
Hoek.assert(!schema || !schema.isJoi, 'Object schema cannot be a joi schema'); | ||
if (this._inner) { | ||
description.children = {}; | ||
var children = Object.keys(this._inner); | ||
var obj = this.clone(); | ||
for (var c = 0, cl = children.length; c < cl; ++c) { | ||
var key = children[c]; | ||
var child = this._inner[key]; | ||
description.children[key] = child.describe(); | ||
if (!schema) { | ||
obj._inner = null; | ||
return obj; | ||
} | ||
var children = Object.keys(schema); | ||
if (!children.length) { | ||
obj._inner = []; | ||
return obj; | ||
} | ||
var topo = new Topo(); | ||
if (obj._inner) { | ||
for (var i = 0, il = obj._inner.length; i < il; ++i) { | ||
var child = obj._inner[i]; | ||
topo.add(child, { after: child._refs, group: child.key }); | ||
} | ||
} | ||
return description; | ||
for (var c = 0, cl = children.length; c < cl; ++c) { | ||
var key = children[c]; | ||
var child = schema[key]; | ||
var cast = Cast.schema(child); | ||
topo.add({ key: key, schema: cast }, { after: cast._refs, group: key }); | ||
} | ||
obj._inner = topo.nodes; | ||
return obj; | ||
}; | ||
@@ -103,3 +227,3 @@ | ||
Hoek.assert((!isNaN(limit) && ((limit | 0) === parseFloat(limit))), 'limit must be an integer'); | ||
Hoek.assert(Hoek.isInteger(limit) && limit >= 0, 'limit must be a positive integer'); | ||
@@ -112,3 +236,3 @@ return this._test('length', limit, function (value, state, options) { | ||
return Errors.create('object.length', { value: limit }, state, options); | ||
return Errors.create('object.length', { limit: limit }, state, options); | ||
}); | ||
@@ -120,3 +244,3 @@ }; | ||
Hoek.assert((!isNaN(limit) && ((limit | 0) === parseFloat(limit))), 'limit must be an integer'); | ||
Hoek.assert(Hoek.isInteger(limit) && limit >= 0, 'limit must be a positive integer'); | ||
@@ -129,3 +253,3 @@ return this._test('min', limit, function (value, state, options) { | ||
return Errors.create('object.min', { value: limit }, state, options); | ||
return Errors.create('object.min', { limit: limit }, state, options); | ||
}); | ||
@@ -137,3 +261,3 @@ }; | ||
Hoek.assert((!isNaN(limit) && ((limit | 0) === parseFloat(limit))), 'limit must be an integer'); | ||
Hoek.assert(Hoek.isInteger(limit) && limit >= 0, 'limit must be a positive integer'); | ||
@@ -146,3 +270,3 @@ return this._test('max', limit, function (value, state, options) { | ||
return Errors.create('object.max', { value: limit }, state, options); | ||
return Errors.create('object.max', { limit: limit }, state, options); | ||
}); | ||
@@ -152,66 +276,212 @@ }; | ||
internals.Object.prototype._traverse = function (value, state, options) { | ||
internals.Object.prototype.with = function (key, peers) { | ||
if (!this._inner) { // Object() null allows any keys | ||
return this._dependency('with', key, peers); | ||
}; | ||
internals.Object.prototype.without = function (key, peers) { | ||
return this._dependency('without', key, peers); | ||
}; | ||
internals.Object.prototype.xor = function () { | ||
var peers = Hoek.flatten(Array.prototype.slice.call(arguments)); | ||
return this._dependency('xor', null, peers); | ||
}; | ||
internals.Object.prototype.or = function () { | ||
var peers = Hoek.flatten(Array.prototype.slice.call(arguments)); | ||
return this._dependency('or', null, peers); | ||
}; | ||
internals.Object.prototype.and = function () { | ||
var peers = Hoek.flatten(Array.prototype.slice.call(arguments)); | ||
return this._dependency('and', null, peers); | ||
}; | ||
internals.renameDefaults = { | ||
alias: false, // Keep old value in place | ||
multiple: false, // Allow renaming multiple keys into the same target | ||
override: false // Overrides an existing key | ||
}; | ||
internals.Object.prototype.rename = function (from, to, options) { | ||
Hoek.assert(from, 'Rename missing the from argument'); | ||
Hoek.assert(to, 'Rename missing the to argument'); | ||
Hoek.assert(to !== from, 'Cannot rename key to same name:', from); | ||
for (var i = 0, il = this._renames.length; i < il; ++i) { | ||
Hoek.assert(this._renames[i].from !== from, 'Cannot rename the same key multiple times'); | ||
} | ||
var obj = this.clone(); | ||
obj._renames.push({ | ||
from: from, | ||
to: to, | ||
options: Hoek.applyToDefaults(internals.renameDefaults, options || {}) | ||
}); | ||
return obj; | ||
}; | ||
internals.Object.prototype._dependency = function (type, key, peers) { | ||
peers = [].concat(peers); | ||
for (var i = 0, li = peers.length; i < li; i++) { | ||
Hoek.assert(typeof peers[i] === 'string', type, 'peers must be a string or array of strings'); | ||
} | ||
var obj = this.clone(); | ||
obj._dependencies.push({ type: type, key: key, peers: peers }); | ||
return obj; | ||
}; | ||
internals.with = function (value, peers, parent, state, options) { | ||
if (value === undefined) { | ||
return null; | ||
} | ||
var keys = Object.keys(this._inner); | ||
var unprocessed = Hoek.mapToObject(Object.keys(value)); | ||
var errors = []; | ||
var key; | ||
for (var i = 0, il = peers.length; i < il; ++i) { | ||
var peer = peers[i]; | ||
if (!parent.hasOwnProperty(peer) || | ||
parent[peer] === undefined) { | ||
return Errors.create('object.with', { peer: peer }, state, options); | ||
} | ||
} | ||
for (var i = 0, il = keys.length; i < il; ++i) { | ||
key = keys[i]; | ||
var child = this._inner[key]; | ||
var item = value[key]; | ||
return null; | ||
}; | ||
var localState = { parent: value, key: key, path: (state.path ? state.path + '.' : '') + key, renamed: state.renamed }; | ||
var err = child._validate(item, localState, options); | ||
if (err) { | ||
errors = errors.concat(err); | ||
if (options.abortEarly) { | ||
return errors; | ||
} | ||
internals.without = function (value, peers, parent, state, options) { | ||
if (value === undefined) { | ||
return null; | ||
} | ||
for (var i = 0, il = peers.length; i < il; ++i) { | ||
var peer = peers[i]; | ||
if (parent.hasOwnProperty(peer) && | ||
parent[peer] !== undefined) { | ||
return Errors.create('object.without', { peer: peer }, state, options); | ||
} | ||
} | ||
delete unprocessed[key]; | ||
return null; | ||
}; | ||
internals.xor = function (value, peers, parent, state, options) { | ||
var present = []; | ||
for (var i = 0, il = peers.length; i < il; ++i) { | ||
var peer = peers[i]; | ||
if (parent.hasOwnProperty(peer) && | ||
parent[peer] !== undefined) { | ||
present.push(peer); | ||
} | ||
} | ||
var unprocessedKeys = Object.keys(unprocessed); | ||
if (unprocessedKeys.length) { | ||
if (options.stripUnknown || | ||
options.skipFunctions) { | ||
if (present.length === 1) { | ||
return null; | ||
} | ||
var hasFunctions = false; | ||
for (var k = 0, kl = unprocessedKeys.length; k < kl; ++k) { | ||
key = unprocessedKeys[k]; | ||
if (options.stripUnknown) { | ||
delete value[key]; | ||
} | ||
else if (typeof value[key] === 'function') { | ||
delete unprocessed[key]; | ||
hasFunctions = true; | ||
} | ||
} | ||
if (present.length === 0) { | ||
return Errors.create('object.missing', { peers: peers }, state, options); | ||
} | ||
if (options.stripUnknown) { | ||
return errors.length ? errors : null; | ||
} | ||
return Errors.create('object.xor', { peers: peers }, state, options); | ||
}; | ||
if (hasFunctions) { | ||
unprocessedKeys = Object.keys(unprocessed); | ||
} | ||
internals.or = function (value, peers, parent, state, options) { | ||
for (var i = 0, il = peers.length; i < il; ++i) { | ||
var peer = peers[i]; | ||
if (parent.hasOwnProperty(peer) && | ||
parent[peer] !== undefined) { | ||
return null; | ||
} | ||
} | ||
if (unprocessedKeys.length && | ||
!options.allowUnknown) { | ||
return Errors.create('object.missing', { peers: peers }, state, options); | ||
}; | ||
for (var e = 0, el = unprocessedKeys.length; e < el; ++e) { | ||
errors.push(Errors.create('object.allowUnknown', null, { key: unprocessedKeys[e], path: state.path }, options)); | ||
} | ||
internals.and = function (value, peers, parent, state, options) { | ||
var missing = []; | ||
var present = []; | ||
for (var i = 0, il = peers.length; i < il; ++i) { | ||
var peer = peers[i]; | ||
if (!parent.hasOwnProperty(peer) || | ||
parent[peer] === undefined) { | ||
missing.push(peer); | ||
} | ||
else { | ||
present.push(peer); | ||
} | ||
} | ||
return errors.length ? errors : null; | ||
}; | ||
return missing.length ? Errors.create('object.and', { present: present, missing: missing }, state, options) : null; | ||
}; | ||
internals.Object.prototype.describe = function () { | ||
var description = Any.prototype.describe.call(this); | ||
if (this._inner) { | ||
description.children = {}; | ||
for (var i = 0, il = this._inner.length; i < il; ++i) { | ||
var child = this._inner[i]; | ||
description.children[child.key] = child.schema.describe(); | ||
} | ||
} | ||
if (this._dependencies.length) { | ||
description.dependencies = Hoek.clone(this._dependencies); | ||
} | ||
return description; | ||
}; | ||
internals.Object.prototype.assert = function (ref, schema, message) { | ||
ref = Cast.ref(ref); | ||
Hoek.assert(ref.depth > 1, 'Cannot use assertions for root level references - use direct key rules instead'); | ||
var cast = Cast.schema(schema); | ||
cast = cast.options({ allowUnknown: true }); | ||
return this._test('assert', { cast: cast, ref: ref }, function (value, state, options) { | ||
var result = cast._validate(ref(value), null, options, value); | ||
if (!result.errors) { | ||
return null; | ||
} | ||
return Errors.create('object.assert', { ref: ref.path.join('.'), message: message }, state, options); | ||
}); | ||
}; | ||
module.exports = new internals.Object(); |
// Load modules | ||
var Net = require('net'); | ||
var Any = require('./any'); | ||
@@ -13,3 +14,3 @@ var Errors = require('./errors'); | ||
module.exports = internals.String = function () { | ||
internals.String = function () { | ||
@@ -19,13 +20,2 @@ Any.call(this); | ||
this._invalids.add(''); | ||
this._base(function (value, state, options) { | ||
if (value && | ||
typeof value === 'string') { | ||
return null; | ||
} | ||
return Errors.create('string.base', null, state, options); | ||
}); | ||
}; | ||
@@ -36,5 +26,8 @@ | ||
internals.String.create = function () { | ||
internals.String.prototype._base = function (value, state, options) { | ||
return new internals.String(); | ||
return { | ||
value: value, | ||
errors: (value && typeof value === 'string') ? null : Errors.create('string.base', null, state, options) | ||
}; | ||
}; | ||
@@ -51,13 +44,14 @@ | ||
internals.String.prototype.min = function (limit) { | ||
internals.String.prototype.min = function (limit, encoding) { | ||
Hoek.assert(!isNaN(limit) && ((limit | 0) === parseFloat(limit)) && limit >= 0, 'limit must be a positive integer'); | ||
Hoek.assert(Hoek.isInteger(limit) && limit >= 0, 'limit must be a positive integer'); | ||
return this._test('min', limit, function (value, state, options) { | ||
if (value.length >= limit) { | ||
var length = encoding ? Buffer.byteLength(value, encoding) : value.length; | ||
if (length >= limit) { | ||
return null; | ||
} | ||
return Errors.create('string.min', { value: limit }, state, options); | ||
return Errors.create('string.min', { limit: limit }, state, options); | ||
}); | ||
@@ -67,13 +61,14 @@ }; | ||
internals.String.prototype.max = function (limit) { | ||
internals.String.prototype.max = function (limit, encoding) { | ||
Hoek.assert(!isNaN(limit) && ((limit | 0) === parseFloat(limit)) && limit >= 0, 'limit must be a positive integer'); | ||
Hoek.assert(Hoek.isInteger(limit) && limit >= 0, 'limit must be a positive integer'); | ||
return this._test('max', limit, function (value, state, options) { | ||
if (value.length <= limit) { | ||
var length = encoding ? Buffer.byteLength(value, encoding) : value.length; | ||
if (length <= limit) { | ||
return null; | ||
} | ||
return Errors.create('string.max', { value: limit }, state, options); | ||
return Errors.create('string.max', { limit: limit }, state, options); | ||
}); | ||
@@ -83,13 +78,14 @@ }; | ||
internals.String.prototype.length = function (limit) { | ||
internals.String.prototype.length = function (limit, encoding) { | ||
Hoek.assert(!isNaN(limit) && ((limit | 0) === parseFloat(limit)) && limit >= 0, 'limit must be a positive integer'); | ||
Hoek.assert(Hoek.isInteger(limit) && limit >= 0, 'limit must be a positive integer'); | ||
return this._test('length', limit, function (value, state, options) { | ||
if (value.length === limit) { | ||
var length = encoding ? Buffer.byteLength(value, encoding) : value.length; | ||
if (length === limit) { | ||
return null; | ||
} | ||
return Errors.create('string.length', { value: limit }, state, options); | ||
return Errors.create('string.length', { limit: limit }, state, options); | ||
}); | ||
@@ -109,3 +105,3 @@ }; | ||
return Errors.create('string.regex', { value: pattern.toString() }, state, options); | ||
return Errors.create('string.regex', null, state, options); | ||
}); | ||
@@ -185,1 +181,21 @@ }; | ||
}; | ||
internals.String.prototype.hostname = function () { | ||
var regex = /^(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9])\.)*([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9\-]*[A-Za-z0-9])$/; | ||
return this._test('hostname', undefined, function (value, state, options) { | ||
if ((value.length <= 255 && regex.test(value)) || | ||
Net.isIPv6(value)) { | ||
return null; | ||
} | ||
return Errors.create("string.hostname", null, state, options); | ||
}); | ||
}; | ||
module.exports = new internals.String(); |
{ | ||
"name": "joi", | ||
"description": "Object schema validation", | ||
"version": "3.1.0", | ||
"version": "4.0.0", | ||
"repository": "git://github.com/spumko/joi", | ||
@@ -15,3 +15,4 @@ "main": "index", | ||
"dependencies": { | ||
"hoek": "2.x.x" | ||
"hoek": "^2.1.x", | ||
"topo": "1.x.x" | ||
}, | ||
@@ -24,2 +25,15 @@ "devDependencies": { | ||
}, | ||
"testling": { | ||
"files": "test/*.js", | ||
"browsers": [ | ||
"ie/6..latest", | ||
"chrome/22..latest", | ||
"firefox/16..latest", | ||
"safari/latest", | ||
"opera/11.0..latest", | ||
"iphone/6", | ||
"ipad/6", | ||
"android-browser/latest" | ||
] | ||
}, | ||
"licenses": [ | ||
@@ -26,0 +40,0 @@ { |
673
README.md
@@ -6,6 +6,7 @@ <a href="https://github.com/spumko"><img src="https://raw.github.com/spumko/spumko/master/images/from.png" align="right" /></a> | ||
Current version: **3.1.x** | ||
Current version: **4.0.x** | ||
[![Build Status](https://secure.travis-ci.org/spumko/joi.png)](http://travis-ci.org/spumko/joi) | ||
[![Browser Support](https://ci.testling.com/spumko/joi.png)](https://ci.testling.com/spumko/joi) | ||
@@ -17,5 +18,5 @@ ## Table of Contents | ||
- [Usage](#usage) | ||
- [`validate(value, schema, options)`](#validatevalue-schema-options) | ||
- [`validate(value, schema, [options], callback)`](#validatevalue-schema-options-callback) | ||
- [`compile(schema)`](#compileschema) | ||
- [`any()`](#any) | ||
- [`any`](#any) | ||
- [`any.allow(value)`](#anyallowvalue) | ||
@@ -26,14 +27,13 @@ - [`any.valid(value)`](#anyvalidvalue) | ||
- [`any.optional()`](#anyoptional) | ||
- [`any.with(peer)`](#anywithpeer) | ||
- [`any.without(peer)`](#anywithoutpeer) | ||
- [`any.xor(peer)`](#anyxorpeer) | ||
- [`any.or(peer)`](#anyorpeer) | ||
- [`description(desc)`](#descriptiondesc) | ||
- [`any.notes(notes)`](#anynotesnotes) | ||
- [`any.tags(tags)`](#anytagstags) | ||
- [`any.example(value)`](#anyexamplevalue) | ||
- [`any.unit(name)`](#anyunitname) | ||
- [`any.options(options)`](#anyoptionsoptions) | ||
- [`any.strict()`](#anystrict) | ||
- [`any.rename(to, [options])`](#anyrenameto-options) | ||
- [`any.default(value)`](#anydefault) | ||
- [`array()`](#array) | ||
- [`any.concat(schema)`](#anyconcatschema) | ||
- [`any.when(ref, options)`](#anywhenref-options) | ||
- [`array`](#array) | ||
- [`array.includes(type)`](#arrayincludestype) | ||
@@ -44,17 +44,33 @@ - [`array.excludes(type)`](#arrayexcludestype) | ||
- [`array.length(limit)`](#arraylengthlimit) | ||
- [`binary`](#binary) | ||
- [`binary.encoding(encoding)`](#binaryencodingencoding) | ||
- [`binary.min(limit)`](#binaryminlimit) | ||
- [`binary.max(limit)`](#binarymaxlimit) | ||
- [`binary.length(limit)`](#binarylengthlimit) | ||
- [`boolean()`](#boolean) | ||
- [`date()`](#date) | ||
- [`date`](#date) | ||
- [`date.min(date)`](#datemindate) | ||
- [`date.max(date)`](#datemaxdate) | ||
- [`func()`](#func) | ||
- [`number()`](#number) | ||
- [`func`](#func) | ||
- [`number`](#number) | ||
- [`number.min(limit)`](#numberminlimit) | ||
- [`number.max(limit)`](#numbermaxlimit) | ||
- [`number.integer()`](#numberinteger) | ||
- [`object(schema)`](#objectschema) | ||
- [`string()`](#string) | ||
- [`object`](#object) | ||
- [`object.keys([schema])`](#objectkeysschema) | ||
- [`object.min(limit)`](#objectminlimit) | ||
- [`object.max(limit)`](#objectmaxlimit) | ||
- [`object.length(limit)`](#objectlengthlimit) | ||
- [`object.and(peers)`](#objectandpeers) | ||
- [`object.or(peers)`](#objectorpeers) | ||
- [`object.xor(peers)`](#objectxorpeers) | ||
- [`object.with(key, peers)`](#objectwithkey-peers) | ||
- [`object.without(key, peers)`](#objectwithoutkey-peers) | ||
- [`object.rename(from, to, [options])`](#objectrenamefrom-to-options) | ||
- [`object.assert(ref, schema, message)`](#objectassertref-schema-message) | ||
- [`string`](#string) | ||
- [`string.insensitive()`](#stringinsensitive) | ||
- [`string.min(limit)`](#stringminlimit) | ||
- [`string.max(limit)`](#stringmaxlimit) | ||
- [`string.length(limit)`](#stringlengthlimit) | ||
- [`string.min(limit, [encoding])`](#stringminlimit-encoding) | ||
- [`string.max(limit, [encoding])`](#stringmaxlimit-encoding) | ||
- [`string.length(limit, [encoding])`](#stringlengthlimit-encoding) | ||
- [`string.regex(pattern)`](#stringregexpattern) | ||
@@ -66,6 +82,8 @@ - [`string.alphanum()`](#stringalphanum) | ||
- [`string.isoDate()`](#stringisodate) | ||
- [`alternatives(types)`](#alternativestypes) | ||
- [Migration notes](#migration-notes) | ||
- [`string.hostname()`](#stringhostname) | ||
- [`alternatives`](#alternatives) | ||
- [`alternatives.try(schemas)`](#alternativestryschemas) | ||
- [`alternatives.when(ref, options)`](#alternativeswhenref-options) | ||
- [`ref(key, [options])`](#refkey-options) | ||
# Example | ||
@@ -76,11 +94,11 @@ | ||
var schema = { | ||
username: Joi.string().alphanum().min(3).max(30).with('birthyear').required(), | ||
password: Joi.string().regex(/[a-zA-Z0-9]{3,30}/).without('access_token'), | ||
access_token: [Joi.string(), Joi.number()], | ||
birthyear: Joi.number().integer().min(1900).max(2013), | ||
email: Joi.string().email() | ||
}; | ||
var schema = Joi.object.keys({ | ||
username: Joi.string.alphanum().min(3).max(30).required(), | ||
password: Joi.string.regex(/[a-zA-Z0-9]{3,30}/), | ||
access_token: [Joi.string, Joi.number], | ||
birthyear: Joi.number.integer().min(1900).max(2013), | ||
email: Joi.string.email() | ||
}).with('username', 'birthyear').without('password', 'access_token'); | ||
var err = Joi.validate({ username: 'abc', birthyear: 1994 }, schema); // err === null -> valid | ||
Joi.validate({ username: 'abc', birthyear: 1994 }, schema, function (err) { }); // err === null -> valid | ||
``` | ||
@@ -111,3 +129,3 @@ | ||
var schema = { | ||
a: Joi.string() | ||
a: Joi.string | ||
}; | ||
@@ -122,3 +140,3 @@ ``` | ||
```javascript | ||
var err = Joi.validate({ a: 'a string' }, schema); | ||
Joi.validate({ a: 'a string' }, schema, function (err) { }); | ||
``` | ||
@@ -131,11 +149,11 @@ | ||
```javascript | ||
var schema = Joi.string().min(10); | ||
var schema = Joi.string.min(10); | ||
``` | ||
If the schema is a **joi** type, the `schema.validate(value)` can be called directly on the type. When passing a non-type schema object, | ||
If the schema is a **joi** type, the `schema.validate(value, callback)` can be called directly on the type. When passing a non-type schema object, | ||
the module converts it internally to an object() type equivalent to: | ||
```javascript | ||
var schema = Joi.object({ | ||
a: Joi.string() | ||
var schema = Joi.object.keys({ | ||
a: Joi.string | ||
}); | ||
@@ -149,3 +167,3 @@ ``` | ||
### `validate(value, schema, options)` | ||
### `validate(value, schema, [options], callback)` | ||
@@ -158,3 +176,2 @@ Validates a value using the given schema and options where: | ||
- `convert` - when `true`, attempts to cast values to the required types (e.g. a string to a number). Defaults to `true`. | ||
- `modify` - when `true`, converted values are written back to the provided value (only when value is an object). Defaults to `false`. | ||
- `allowUnknown` - when `true`, allows object to contain unknown keys which are ignored. Defaults to `false`. | ||
@@ -164,6 +181,10 @@ - `skipFunctions` - when `true`, ignores unknown keys with a function value. Defaults to `false`. | ||
- `language` - overrides individual error messages. Defaults to no override (`{}`). | ||
- `callback` - the callback method using the signature `function(err, callback)` where: | ||
- `err` - if validation failed, the error reason, otherwise `null`. | ||
- `value` - the validated value with any type conversions and other modifiers applied (the input is left unchanged). `value` can be | ||
incomplete if validation failed and `abortEarly` is `true`. | ||
```javascript | ||
var schema = { | ||
a: Joi.number() | ||
a: Joi.number | ||
}; | ||
@@ -175,4 +196,3 @@ | ||
var err = Joi.validate(value, schema, { modify: true }); | ||
Joi.validate(value, schema, function (err) { }); | ||
// err -> null | ||
@@ -193,10 +213,10 @@ // value.a -> 123 (number, not string) | ||
var schema = Joi.alternatives([ | ||
Joi.string().valid('key'), | ||
Joi.number().valid(5), | ||
Joi.object({ | ||
a: Joi.boolean().valid(true), | ||
b: Joi.alternatives([ | ||
Joi.string().regex(/^a/), | ||
Joi.string().valid('boom') | ||
var schema = Joi.alternatives.try([ | ||
Joi.string.valid('key'), | ||
Joi.number.valid(5), | ||
Joi.object.keys({ | ||
a: Joi.boolean.valid(true), | ||
b: Joi.alternatives.try([ | ||
Joi.string.regex(/^a/), | ||
Joi.string.valid('boom') | ||
]) | ||
@@ -207,3 +227,3 @@ }) | ||
### `any()` | ||
### `any` | ||
@@ -213,6 +233,6 @@ Generates a schema object that matches any data type. | ||
```javascript | ||
var any = Joi.any(); | ||
var any = Joi.any; | ||
any.valid('a'); | ||
var err = any.validate('a'); | ||
any.validate('a', function (err) { }); | ||
``` | ||
@@ -224,9 +244,9 @@ | ||
- `value` - the allowed value which can be of any type and will be matched against the validated value before applying any other rules. | ||
`value` can be an array of values, or multiple values can be passed as individual arguments. | ||
`value` can be an array of values, or multiple values can be passed as individual arguments. `value` supports [references](#refkey-options). | ||
```javascript | ||
var schema = { | ||
a: Joi.any().allow('a'), | ||
b: Joi.any().allow('b', 'B'), | ||
c: Joi.any().allow(['c', 'C']) | ||
a: Joi.any.allow('a'), | ||
b: Joi.any.allow('b', 'B'), | ||
c: Joi.any.allow(['c', 'C']) | ||
}; | ||
@@ -239,9 +259,9 @@ ``` | ||
- `value` - the allowed value which can be of any type and will be matched against the validated value before applying any other rules. | ||
`value` can be an array of values, or multiple values can be passed as individual arguments. | ||
`value` can be an array of values, or multiple values can be passed as individual arguments. `value` supports [references](#refkey-options). | ||
```javascript | ||
var schema = { | ||
a: Joi.any().valid('a'), | ||
b: Joi.any().valid('b', 'B'), | ||
c: Joi.any().valid(['c', 'C']) | ||
a: Joi.any.valid('a'), | ||
b: Joi.any.valid('b', 'B'), | ||
c: Joi.any.valid(['c', 'C']) | ||
}; | ||
@@ -254,9 +274,9 @@ ``` | ||
- `value` - the forbidden value which can be of any type and will be matched against the validated value before applying any other rules. | ||
`value` can be an array of values, or multiple values can be passed as individual arguments. | ||
`value` can be an array of values, or multiple values can be passed as individual arguments. `value` supports [references](#refkey-options). | ||
```javascript | ||
var schema = { | ||
a: Joi.any().invalid('a'), | ||
b: Joi.any().invalid('b', 'B'), | ||
c: Joi.any().invalid(['c', 'C']) | ||
a: Joi.any.invalid('a'), | ||
b: Joi.any.invalid('b', 'B'), | ||
c: Joi.any.invalid(['c', 'C']) | ||
}; | ||
@@ -270,4 +290,4 @@ ``` | ||
```javascript | ||
var schema = { | ||
a: Joi.any().required() | ||
var schema = { | ||
a: Joi.any.required() | ||
}; | ||
@@ -282,83 +302,51 @@ ``` | ||
var schema = { | ||
a: Joi.any().optional() | ||
a: Joi.any.optional() | ||
}; | ||
``` | ||
#### `any.with(peer)` | ||
#### `any.description(desc)` | ||
Requires the presence of another key whenever this value is present where: | ||
- `peer` - the required key name that must appear together with the current value. `peer` can be an array of values, or multiple values can be | ||
passed as individual arguments. | ||
Annotates the key where: | ||
- `desc` - the description string. | ||
```javascript | ||
var schema = { | ||
a: Joi.any().with('b'), | ||
b: Joi.any() | ||
}; | ||
var schema = Joi.any.description('this key will match anything you give it'); | ||
``` | ||
#### `any.without(peer)` | ||
#### `any.notes(notes)` | ||
Forbids the presence of another key whenever this value is present where: | ||
- `peer` - the forbidden key name that must not appear together with the current value. `peer` can be an array of values, or multiple values can be | ||
passed as individual arguments. | ||
Annotates the key where: | ||
- `notes` - the notes string or array of strings. | ||
```javascript | ||
var schema = { | ||
a: Joi.any().without('b'), | ||
b: Joi.any() | ||
}; | ||
var schema = Joi.any.notes(['this is special', 'this is important']); | ||
``` | ||
#### `any.xor(peer)` | ||
#### `any.tags(tags)` | ||
Defines an exclusive relationship with another key where this or one of the peers is required but not at the same time where: | ||
- `peer` - the exclusive key name that must not appear together with the current value but where one of them is required. `peer` can be an array | ||
of values, or multiple values can be passed as individual arguments. | ||
Annotates the key where: | ||
- `tags` - the tag string or array of strings. | ||
```javascript | ||
var schema = { | ||
a: Joi.any().xor('b'), | ||
b: Joi.any() | ||
}; | ||
var schema = Joi.any.tags(['api', 'user']); | ||
``` | ||
#### `any.or(peer)` | ||
#### `any.example(value)` | ||
Defines a relationship with another key where this or one of the peers is required (and more than one is allowed) where: | ||
- `peer` - the key name that must appear if the current value is missing. `peer` can be an array of values, or multiple | ||
values can be passed as individual arguments. | ||
```javascript | ||
var schema = { | ||
a: Joi.any().or('b'), | ||
b: Joi.any() | ||
}; | ||
``` | ||
#### `any.description(desc)` | ||
Annotates the key where: | ||
- `desc` - the description string. | ||
- `value` - an example value. | ||
```javascript | ||
var schema = Joi.any().description('this key will match anything you give it'); | ||
``` | ||
If the example fails to pass validation, the function will throw. | ||
#### `any.notes(notes)` | ||
Annotates the key where: | ||
- `notes` - the notes string or array of strings. | ||
```javascript | ||
var schema = Joi.any().notes(['this is special', 'this is important']); | ||
var schema = Joi.string.min(4).example('abcd'); | ||
``` | ||
#### `any.tags(tags)` | ||
#### `any.unit(name)` | ||
Annotates the key where: | ||
- `tags` - the tag string or array of strings. | ||
- `name` - the unit name of the value. | ||
```javascript | ||
var schema = Joi.any().tags(['api', 'user']); | ||
var schema = Joi.number.unit('milliseconds'); | ||
``` | ||
@@ -369,7 +357,7 @@ | ||
Overrides the global `validate()` options for the current key and any sub-key where: | ||
- `options` - an object with the same optional keys as [`Joi.validate(value, schema, options)`](#joivalidatevalue-schema-options). | ||
- `options` - an object with the same optional keys as [`Joi.validate(value, schema, options, callback)`](#joivalidatevalue-schema-options-callback). | ||
```javascript | ||
var schema = { | ||
a: Joi.any().options({ modify: true }) | ||
a: Joi.any.options({ convert: false }) | ||
}; | ||
@@ -384,31 +372,49 @@ ``` | ||
var schema = { | ||
a: Joi.any().strict() | ||
a: Joi.any.strict() | ||
}; | ||
``` | ||
#### `any.rename(to, [options])` | ||
Renames a key to another name where: | ||
- `to` - the new key name. | ||
- `options` - an optional object with the following optional keys: | ||
- `move` - if `true`, deletes the old key name, otherwise both old and new keys are kept. Defaults to `false`. | ||
- `multiple` - if `true`, allows renaming multiple keys to the same destination where the last rename wins. Defaults to `false`. | ||
- `override` - if `true`, allows renaming a key over an existing key. Defaults to `false`. | ||
#### `any.default(value)` | ||
Sets a default value if the original value is undefined where: | ||
- `value` - the value. | ||
- `value` - the value. `value` supports [references](#refkey-options). | ||
```javascript | ||
var schema = { | ||
username: Joi.string().default('new_user') | ||
username: Joi.string.default('new_user') | ||
}; | ||
var input = {}; | ||
Joi.validate(input, schema); | ||
Joi.validate(input, schema, function (err) { }); | ||
// input === { username: "new_user" } | ||
``` | ||
### `array()` | ||
#### `any.concat(schema)` | ||
Returns a new type that is the result of adding the rules of one type to another where: | ||
- `schema` - a **joi** type to merge into the current schema. Can only be of the same type as the context type or `any`. | ||
```javascript | ||
var a = Joi.string.valid('a'); | ||
var b = Joi.string.valid('b'); | ||
var ab = a.concat(b); | ||
``` | ||
#### `any.when(ref, options)` | ||
Converts the type into an [`alternatives`](#alternatives) type where the conditions are merged into the type definition where: | ||
- `ref` - the key name or [reference](#refkey-options). | ||
- `options` - an object with: | ||
- `is` - the required condition **joi** type. | ||
- `then` - the alternative schema type if the condition is true. Required if `otherwise` is missing. | ||
- `otherwise` - the alternative schema type if the condition is false. Required if `then` is missing. | ||
```javascript | ||
var schema = { | ||
a: Joi.any().valid('x').when('b', { is: 5, then: Joi.valid('y'), otherwise: Joi.valid('z') }), | ||
b: Joi.any() | ||
}; | ||
``` | ||
### `array` | ||
Generates a schema object that matches an array data type. | ||
@@ -419,6 +425,6 @@ | ||
```javascript | ||
var array = Joi.array(); | ||
array.includes(Joi.string().valid('a', 'b')); | ||
var array = Joi.array; | ||
array.includes(Joi.string.valid('a', 'b')); | ||
var err = array.validate(['a', 'b', 'a']); | ||
array.validate(['a', 'b', 'a'], function (err) { }); | ||
``` | ||
@@ -433,3 +439,3 @@ | ||
var schema = { | ||
a: Joi.array().includes(Joi.string(), Joi.number()) | ||
a: Joi.array.includes(Joi.string, Joi.number) | ||
}; | ||
@@ -445,3 +451,3 @@ ``` | ||
var schema = { | ||
a: Joi.array().excludes(Joi.object()) | ||
a: Joi.array.excludes(Joi.object) | ||
}; | ||
@@ -457,3 +463,3 @@ ``` | ||
var schema = { | ||
a: Joi.array().min(2) | ||
a: Joi.array.min(2) | ||
}; | ||
@@ -469,3 +475,3 @@ ``` | ||
var schema = { | ||
a: Joi.array().max(10) | ||
a: Joi.array.max(10) | ||
}; | ||
@@ -481,7 +487,7 @@ ``` | ||
var schema = { | ||
a: Joi.array().length(5) | ||
a: Joi.array.length(5) | ||
}; | ||
``` | ||
### `boolean()` | ||
### `boolean` | ||
@@ -493,10 +499,64 @@ Generates a schema object that matches a boolean data type (as well as the strings 'true', 'false', 'yes', and 'no'). Can also be called via `bool()`. | ||
```javascript | ||
var boolean = Joi.boolean(); | ||
var boolean = Joi.boolean; | ||
boolean.allow(null); | ||
var err = boolean.validate(true); | ||
boolean.validate(true, function (err) { }); | ||
``` | ||
### `date()` | ||
### `binary` | ||
Generates a schema object that matches a Buffer data type (as well as the strings which will be converted to Buffers). | ||
Supports the same methods of the [`any()`](#any) type. | ||
```javascript | ||
var schema = { | ||
a: Joi.binary | ||
}; | ||
``` | ||
#### `binary.encoding(encoding)` | ||
Sets the string encoding format if a string input is converted to a buffer where: | ||
- `encoding` - the encoding scheme. | ||
```javascript | ||
var schema = Joi.binary.encoding('base64'); | ||
``` | ||
#### `binary.min(limit)` | ||
Specifies the minimum length of the buffer where: | ||
- `limit` - the lowest size of the buffer. | ||
```javascript | ||
var schema = { | ||
a: Joi.binary.min(2) | ||
}; | ||
``` | ||
#### `binary.max(limit)` | ||
Specifies the maximum length of the buffer where: | ||
- `limit` - the highest size of the buffer. | ||
```javascript | ||
var schema = { | ||
a: Joi.binary.max(10) | ||
}; | ||
``` | ||
#### `binary.length(limit)` | ||
Specifies the exact length of the buffer: | ||
- `limit` - the size of buffer allowed. | ||
```javascript | ||
var schema = { | ||
a: Joi.binary.length(5) | ||
}; | ||
``` | ||
### `date` | ||
Generates a schema object that matches a date type (as well as a JavaScript date string or number of milliseconds). | ||
@@ -507,6 +567,6 @@ | ||
```javascript | ||
var date = Joi.date(); | ||
var date = Joi.date; | ||
date.min('12-20-2012'); | ||
var err = date.validate('12-21-2012'); | ||
date.validate('12-21-2012', function (err) { }); | ||
``` | ||
@@ -521,3 +581,3 @@ | ||
var schema = { | ||
a: Joi.date().min('1-1-1974') | ||
a: Joi.date.min('1-1-1974') | ||
}; | ||
@@ -533,7 +593,7 @@ ``` | ||
var schema = { | ||
a: Joi.date().max('12-31-2020') | ||
a: Joi.date.max('12-31-2020') | ||
}; | ||
``` | ||
### `func()` | ||
### `func` | ||
@@ -545,9 +605,9 @@ Generates a schema object that matches a function type. | ||
```javascript | ||
var func = Joi.func(); | ||
var func = Joi.func; | ||
func.allow(null); | ||
var err = func.validate(function () {}); | ||
func.validate(function () {}, function (err) { }); | ||
``` | ||
### `number()` | ||
### `number` | ||
@@ -559,6 +619,6 @@ Generates a schema object that matches a number data type (as well as strings that can be converted to numbers). | ||
```javascript | ||
var number = Joi.number(); | ||
var number = Joi.number; | ||
number.min(1).max(10).integer(); | ||
var err = number.validate(5); | ||
number.validate(5, function (err) { }); | ||
``` | ||
@@ -573,3 +633,3 @@ | ||
var schema = { | ||
a: Joi.number().min(2) | ||
a: Joi.number.min(2) | ||
}; | ||
@@ -585,3 +645,3 @@ ``` | ||
var schema = { | ||
a: Joi.number().max(10) | ||
a: Joi.number.max(10) | ||
}; | ||
@@ -596,11 +656,10 @@ ``` | ||
var schema = { | ||
a: Joi.number().integer() | ||
a: Joi.number.integer() | ||
}; | ||
``` | ||
### `object(schema)` | ||
### `object` | ||
Generates a schema object that matches an object data type (as well as JSON strings that parsed into objects) where: | ||
- `schema` - optional object where each key is assinged a **joi** type object. If the schema is `{}` no keys allowed. | ||
Defaults to 'undefined' which allows any child key. | ||
Generates a schema object that matches an object data type (as well as JSON strings that parsed into objects). Defaults | ||
to allowing any child key. | ||
@@ -610,11 +669,169 @@ Supports the same methods of the [`any()`](#any) type. | ||
```javascript | ||
var object = Joi.object({ | ||
a: Joi.number.min(1).max(10).integer() | ||
var object = Joi.object.keys({ | ||
a: Joi.number.min(1).max(10).integer(), | ||
b: 'some string' | ||
}); | ||
var err = object.validate({ a: 5 }); | ||
object.validate({ a: 5 }, function (err) { }); | ||
``` | ||
### `string()` | ||
#### `object.keys([schema])` | ||
Sets the allowed object keys where: | ||
- `schema` - optional object where each key is assinged a **joi** type object. If `schema` is `{}` no keys allowed. | ||
If `schema` is `null` or `undefined`, any key allowed. If `schema` is an object with keys, the keys are added to any | ||
previously defined keys (but narrows the selection if all keys previously allowed). Defaults to 'undefined' which | ||
allows any child key. | ||
```javascript | ||
var object = Joi.object.keys({ | ||
a: Joi.number | ||
b: Joi.string | ||
}); | ||
``` | ||
#### `object.min(limit)` | ||
Specifies the minimum number of keys in the object where: | ||
- `limit` - the lowest number of keys allowed. | ||
```javascript | ||
var schema = { | ||
a: Joi.object.min(2) | ||
}; | ||
``` | ||
#### `object.max(limit)` | ||
Specifies the maximum number of keys in the object where: | ||
- `limit` - the highest number of object keys allowed. | ||
```javascript | ||
var schema = { | ||
a: Joi.object.max(10) | ||
}; | ||
``` | ||
#### `object.length(limit)` | ||
Specifies the exact number of keys in the object where: | ||
- `limit` - the number of object keys allowed. | ||
```javascript | ||
var schema = { | ||
a: Joi.object.length(5) | ||
}; | ||
``` | ||
#### `object.and(peers)` | ||
Defines am all-or-nothing relationship between keys where if one of the peers is present, all of them are required as | ||
well where: | ||
- `peers` - the key names of which if one present, all are required. `peers` can be a single string value, an | ||
array of string values, or each peer provided as an argument. | ||
```javascript | ||
var schema = Joi.object.keys({ | ||
a: Joi.any, | ||
b: Joi.any | ||
}).or('a', 'b'); | ||
``` | ||
#### `object.or(peers)` | ||
Defines a relationship between keys where one of the peers is required (and more than one is allowed) where: | ||
- `peers` - the key names of which at least one must appear. `peers` can be a single string value, an | ||
array of string values, or each peer provided as an argument. | ||
```javascript | ||
var schema = Joi.object.keys({ | ||
a: Joi.any, | ||
b: Joi.any | ||
}).or('a', 'b'); | ||
``` | ||
#### `object.xor(peers)` | ||
Defines an exclusive relationship between a set of keys where one of them is required but not at the same time where: | ||
- `peers` - the exclusive key names that must not appear together but where one of them is required. `peers` can be a single string value, an | ||
array of string values, or each peer provided as an argument. | ||
```javascript | ||
var schema = Joi.object.keys({ | ||
a: Joi.any, | ||
b: Joi.any | ||
}).xor('a', 'b'); | ||
``` | ||
#### `object.with(key, peers)` | ||
Requires the presence of other keys whenever the specified key is present where: | ||
- `key` - the reference key. | ||
- `peers` - the required peer key names that must appear together with `key`. `peers` can be a single string value or an array of string values. | ||
Note that unlike [`object.and()`](#objectandpeers), `with()` creates a dependency only between the `key` and each of the `peers`, not | ||
between the `peers` themselves. | ||
```javascript | ||
var schema = Joi.object.keys({ | ||
a: Joi.any, | ||
b: Joi.any | ||
}).with('a', 'b'); | ||
``` | ||
#### `object.without(key, peers)` | ||
Forbids the presence of other keys whenever the specified is present where: | ||
- `key` - the reference key. | ||
- `peers` - the forbidden peer key names that must not appear together with `key`. `peers` can be a single string value or an array of string values. | ||
```javascript | ||
var schema = Joi.object.keys({ | ||
a: Joi.any, | ||
b: Joi.any | ||
}).without('a', ['b']); | ||
``` | ||
#### `object.rename(from, to, [options])` | ||
Renames a key to another name (deletes the renamed key) where: | ||
- `from` - the original key name. | ||
- `to` - the new key name. | ||
- `options` - an optional object with the following optional keys: | ||
- `alias` - if `true`, does not delete the old key name, keeping both the new and old keys in place. Defaults to `false`. | ||
- `multiple` - if `true`, allows renaming multiple keys to the same destination where the last rename wins. Defaults to `false`. | ||
- `override` - if `true`, allows renaming a key over an existing key. Defaults to `false`. | ||
Keys are renamed before any other validation rules are applied. | ||
```javascript | ||
var object = Joi.object.keys({ | ||
a: Joi.number | ||
}).rename('b', 'a'); | ||
object.validate({ b: 5 }, function (err) { }); | ||
``` | ||
#### `object.assert(ref, schema, message)` | ||
Verifies an assertion where: | ||
- `ref` - the key name or [reference](#refkey-options). | ||
- `schema` - the validation rules required to satisfy the assertion. If the `schema` includes references, they are resolved against | ||
the object value, not the value of the `ref` target. | ||
- `message` - human-readable message used when the assertion fails. | ||
```javascript | ||
var schema = Joi.object.keys({ | ||
a: { | ||
b: Joi.string, | ||
c: Joi.number | ||
}, | ||
d: { | ||
e: Joi.any | ||
} | ||
}).assert('d.e', Joi.ref('a.c'), 'equal to a.c'); | ||
``` | ||
### `string` | ||
Generates a schema object that matches a string data type. Note that empty strings are not allowed by default and must be enabled with `allow('')`. | ||
@@ -625,6 +842,5 @@ | ||
```javascript | ||
var string = Joi.string(); | ||
string.min(1).max(10); | ||
Joi.string.min(1).max(10); | ||
var err = string.validate('12345'); | ||
string.validate('12345', function (err) { }); | ||
``` | ||
@@ -637,38 +853,33 @@ | ||
```javascript | ||
var schema = { | ||
a: Joi.string().valid('a').insensitive() | ||
}; | ||
var schema = Joi.string.valid('a').insensitive(); | ||
``` | ||
#### `string.min(limit)` | ||
#### `string.min(limit, [encoding])` | ||
Specifies the minimum number string characters where: | ||
- `limit` - the minimum number of string characters required. | ||
- `encoding` - is specified, the string length is calculated in bytes using the provided encoding. | ||
```javascript | ||
var schema = { | ||
a: Joi.string().min(2) | ||
}; | ||
var schema = Joi.string.min(2); | ||
``` | ||
#### `string.max(limit)` | ||
#### `string.max(limit, [encoding])` | ||
Specifies the maximum number of string characters where: | ||
- `limit` - the maximum number of string characters allowed. | ||
- `encoding` - is specified, the string length is calculated in bytes using the provided encoding. | ||
```javascript | ||
var schema = { | ||
a: Joi.string().max(10) | ||
}; | ||
var schema = Joi.string.max(10); | ||
``` | ||
#### `string.length(limit)` | ||
#### `string.length(limit, [encoding])` | ||
Specifies the exact string length required where: | ||
- `limit` - the required string length. | ||
- `encoding` - is specified, the string length is calculated in bytes using the provided encoding. | ||
```javascript | ||
var schema = { | ||
a: Joi.string().length(5) | ||
}; | ||
var schema = Joi.string.length(5); | ||
``` | ||
@@ -682,5 +893,3 @@ | ||
```javascript | ||
var schema = { | ||
a: Joi.string().regex(/^[abc]+$/) | ||
}; | ||
var schema = Joi.string.regex(/^[abc]+$/); | ||
``` | ||
@@ -693,5 +902,3 @@ | ||
```javascript | ||
var schema = { | ||
a: Joi.string().alphanum() | ||
}; | ||
var schema = Joi.string.alphanum(); | ||
``` | ||
@@ -704,5 +911,3 @@ | ||
```javascript | ||
var schema = { | ||
a: Joi.string().token() | ||
}; | ||
var schema = Joi.string.token(); | ||
``` | ||
@@ -715,5 +920,3 @@ | ||
```javascript | ||
var schema = { | ||
a: Joi.string().email() | ||
}; | ||
var schema = Joi.string.email(); | ||
``` | ||
@@ -726,5 +929,3 @@ | ||
```javascript | ||
var schema = { | ||
a: Joi.string().guid() | ||
}; | ||
var schema = Joi.string.guid(); | ||
``` | ||
@@ -737,21 +938,75 @@ | ||
```javascript | ||
var schema = Joi.string.isoDate(); | ||
``` | ||
#### `string.hostname()` | ||
Requires the string value to be a valid hostname as per [RFC1123](http://tools.ietf.org/html/rfc1123). | ||
```javascript | ||
var schema = Joi.string().hostname(); | ||
``` | ||
### `alternatives` | ||
Generates a type that will match one of the provided alternative schemas via the [`try()`](#alternativestryschemas) | ||
method. If no schemas are added, the type will not match any value except for `undefined`. | ||
Supports the same methods of the [`any()`](#any) type. | ||
Alternatives can be expressed using the shorter `[]` notation. | ||
```javascript | ||
var alt = Joi.alternatives.try(Joi.number, Joi.string); | ||
// Same as [Joi.number, Joi.string] | ||
``` | ||
#### `alternatives.try(schemas) | ||
Adds an alternative schema type for attempting to match against the validated value where: | ||
- `schema` - an array of alternaitve **joi** types. Also supports providing each type as a separate argument. | ||
```javascript | ||
var alt = Joi.alternatives.try(Joi.number, Joi.string); | ||
alt.validate('a', function (err) { }); | ||
``` | ||
#### `alternatives.when(ref, options)` | ||
Adds a conditional alternative schema type based on another key value where: | ||
- `ref` - the key name or [reference](#refkey-options). | ||
- `options` - an object with: | ||
- `is` - the required condition **joi** type. | ||
- `then` - the alternative schema type if the condition is true. Required if `otherwise` is missing. | ||
- `otherwise` - the alternative schema type if the condition is false. Required if `then` is missing. | ||
```javascript | ||
var schema = { | ||
a: Joi.string().isoDate() | ||
a: Joi.alternatives().when('b', { is: 5, then: Joi.string(), otherwise: Joi.number() }), | ||
b: Joi.any() | ||
}; | ||
``` | ||
### `alternatives(types)` | ||
### `ref(key, [options])` | ||
Generates a type that will match one of the provided alternative schemas where: | ||
- `types` - an array of alternaitve **joi** types. Also supports providing each type as a separate argument. | ||
Generates a reference to the value of the named key. References are resolved at validation time and in order of dependency | ||
so that if one key validation depends on another, the dependent key is validated second after the reference is validated. | ||
References support the following arguments: | ||
- `key` - the reference target. References cannot point up the object tree, only to siebling keys, but they can point to | ||
children (e.g. 'a.b.c') using the `.` separator. | ||
- `options` - optional settings: | ||
- `separator` - override the default `.` hierarchy separator. | ||
Supports the same methods of the [`any()`](#any) type. | ||
Note that references can only be used where explicitly supported such as in `valid()` or `invalid()` rules. If upwards | ||
(parents) references are needed, use [`object.assert()`](#objectassertref-schema-message). | ||
```javascript | ||
var alt = Joi.alternatives(Joi.number(), Joi.string()); | ||
var err = alt.validate('a'); | ||
var schema = Joi.object.keys({ | ||
a: Joi.ref('b.c'), | ||
b: { | ||
c: Joi.any | ||
} | ||
}); | ||
schema.validate({ a: 5, b: { c: 5 } }, function (err, value) {}); | ||
``` | ||
Note that the `alternatives()` type does not behave the same way as passing multiple alternatives directly using an | ||
array of types (e.g. `{ a: [Joi.number(), Joi.string()] }`). When passing an array directly, the value must match one | ||
of the provided types while when using the `alternatives()` type, the key is optional by default. |
// Load modules | ||
var Lab = require('lab'); | ||
var Alternatives = require('../lib/alternatives'); | ||
var Joi = require('..'); | ||
var Validate = require('./helper'); | ||
@@ -21,6 +22,77 @@ | ||
describe('Types', function () { | ||
describe('alternatives', function () { | ||
describe('Alternatives', function () { | ||
it('fails when no alternatives are provided', function (done) { | ||
Joi.alternatives().validate('a', function (err, value) { | ||
expect(err).to.exist; | ||
expect(err.message).to.equal('value not matching any of the allowed alternatives'); | ||
done(); | ||
}); | ||
}); | ||
it('allows undefined when no alternatives are provided', function (done) { | ||
Joi.alternatives().validate(undefined, function (err, value) { | ||
expect(err).to.not.exist; | ||
done(); | ||
}); | ||
}); | ||
it('applies modifiers when higher priority converts', function (done) { | ||
var schema = Joi.object({ | ||
a: [ | ||
Joi.number(), | ||
Joi.string() | ||
] | ||
}); | ||
schema.validate({ a: '5' }, function (err, value) { | ||
expect(err).to.not.exist; | ||
expect(value.a).to.equal(5); | ||
done(); | ||
}); | ||
}); | ||
it('applies modifiers when lower priority valid is a match', function (done) { | ||
var schema = Joi.object({ | ||
a: [ | ||
Joi.number(), | ||
Joi.valid('5') | ||
] | ||
}); | ||
schema.validate({ a: '5' }, function (err, value) { | ||
expect(err).to.not.exist; | ||
expect(value.a).to.equal(5); | ||
done(); | ||
}); | ||
}); | ||
it('does not apply modifier if alternative fails', function (done) { | ||
var schema = Joi.object({ | ||
a: [ | ||
Joi.object({ c: Joi.any(), d: Joi.number() }).rename('b', 'c'), | ||
{ b: Joi.any(), d: Joi.string() } | ||
] | ||
}); | ||
var input = { a: { b: 'any', d: 'string' } }; | ||
schema.validate(input, function (err, value) { | ||
expect(err).to.not.exist; | ||
expect(value.a.b).to.equal('any'); | ||
done(); | ||
}); | ||
}); | ||
describe('#try', function () { | ||
it('throws when missing alternatives', function (done) { | ||
@@ -30,7 +102,299 @@ | ||
new Alternatives(); | ||
}).to.throw('Missing alternatives'); | ||
Joi.alternatives().try(); | ||
}).to.throw('Cannot add other alternatives without at least one schema'); | ||
done(); | ||
}); | ||
}); | ||
describe('#when', function () { | ||
it('validates conditional alternatives', function (done) { | ||
var schema = { | ||
a: Joi.alternatives().when('b', { is: 5, then: 'x', otherwise: 'y' }) | ||
.try('z'), | ||
b: Joi.any() | ||
}; | ||
Validate(schema, [ | ||
[{ a: 'x', b: 5 }, true], | ||
[{ a: 'x', b: 6 }, false], | ||
[{ a: 'y', b: 5 }, false], | ||
[{ a: 'y', b: 6 }, true], | ||
[{ a: 'z', b: 5 }, true], | ||
[{ a: 'z', b: 6 }, true] | ||
]); | ||
done(); | ||
}); | ||
it('validates only then', function (done) { | ||
var schema = { | ||
a: Joi.alternatives().when('b', { is: 5, then: 'x' }) | ||
.try('z'), | ||
b: Joi.any() | ||
}; | ||
Validate(schema, [ | ||
[{ a: 'x', b: 5 }, true], | ||
[{ a: 'x', b: 6 }, false], | ||
[{ a: 'y', b: 5 }, false], | ||
[{ a: 'y', b: 6 }, false], | ||
[{ a: 'z', b: 5 }, true], | ||
[{ a: 'z', b: 6 }, true] | ||
]); | ||
done(); | ||
}); | ||
it('validates only otherwise', function (done) { | ||
var schema = { | ||
a: Joi.alternatives().when('b', { is: 5, otherwise: 'y' }) | ||
.try('z'), | ||
b: Joi.any() | ||
}; | ||
Validate(schema, [ | ||
[{ a: 'x', b: 5 }, false], | ||
[{ a: 'x', b: 6 }, false], | ||
[{ a: 'y', b: 5 }, false], | ||
[{ a: 'y', b: 6 }, true], | ||
[{ a: 'z', b: 5 }, true], | ||
[{ a: 'z', b: 6 }, true] | ||
]); | ||
done(); | ||
}); | ||
it('validates when is has ref', function (done) { | ||
var schema = { | ||
a: Joi.alternatives().when('b', { is: Joi.ref('c'), then: 'x' }), | ||
b: Joi.any(), | ||
c: Joi.number() | ||
}; | ||
Validate(schema, [ | ||
[{ a: 'x', b: 5, c: '5' }, true], | ||
[{ a: 'x', b: 5, c: '1' }, false], | ||
[{ a: 'x', b: '5', c: '5' }, false], | ||
[{ a: 'y', b: 5, c: 5 }, false], | ||
[{ a: 'y' }, false] | ||
]); | ||
done(); | ||
}); | ||
it('validates when then has ref', function (done) { | ||
var schema = { | ||
a: Joi.alternatives().when('b', { is: 5, then: Joi.ref('c') }), | ||
b: Joi.any(), | ||
c: Joi.number() | ||
}; | ||
Validate(schema, [ | ||
[{ a: 'x', b: 5, c: '1' }, false], | ||
[{ a: 1, b: 5, c: '1' }, true], | ||
[{ a: '1', b: 5, c: '1' }, false] | ||
]); | ||
done(); | ||
}); | ||
it('validates when otherwise has ref', function (done) { | ||
var schema = { | ||
a: Joi.alternatives().when('b', { is: 6, otherwise: Joi.ref('c') }), | ||
b: Joi.any(), | ||
c: Joi.number() | ||
}; | ||
Validate(schema, [ | ||
[{ a: 'x', b: 5, c: '1' }, false], | ||
[{ a: 1, b: 5, c: '1' }, true], | ||
[{ a: '1', b: 5, c: '1' }, false] | ||
]); | ||
done(); | ||
}); | ||
}); | ||
describe('#describe', function () { | ||
it('describes when', function (done) { | ||
var schema = { | ||
a: Joi.alternatives().when('b', { is: 5, then: 'x', otherwise: 'y' }) | ||
.try('z'), | ||
b: Joi.any() | ||
}; | ||
var outcome = { | ||
type: 'object', | ||
valids: [undefined], | ||
invalids: [null], | ||
children: { | ||
b: { | ||
type: 'any', | ||
valids: [undefined], | ||
invalids: [null] | ||
}, | ||
a: [ | ||
{ | ||
ref: 'b', | ||
is: { | ||
type: 'number', | ||
flags: { | ||
allowOnly: true | ||
}, | ||
valids: [undefined, 5 | ||
], | ||
invalids: [null] | ||
}, | ||
then: { | ||
type: 'string', | ||
flags: { | ||
allowOnly: true | ||
}, | ||
valids: [undefined, 'x'], | ||
invalids: [null, ''] | ||
}, | ||
otherwise: { | ||
type: 'string', | ||
flags: { | ||
allowOnly: true | ||
}, | ||
valids: [undefined, 'y'], | ||
invalids: [null, ''] | ||
} | ||
}, | ||
{ | ||
type: 'string', | ||
flags: { | ||
allowOnly: true | ||
}, | ||
valids: [undefined, 'z'], | ||
invalids: [null, ''] | ||
} | ||
] | ||
} | ||
}; | ||
expect(Joi.describe(schema)).to.deep.equal(outcome); | ||
done(); | ||
}); | ||
it('describes when (only then)', function (done) { | ||
var schema = { | ||
a: Joi.alternatives().when('b', { is: 5, then: 'x' }) | ||
.try('z'), | ||
b: Joi.any() | ||
}; | ||
var outcome = { | ||
type: 'object', | ||
valids: [undefined], | ||
invalids: [null], | ||
children: { | ||
b: { | ||
type: 'any', | ||
valids: [undefined], | ||
invalids: [null] | ||
}, | ||
a: [ | ||
{ | ||
ref: 'b', | ||
is: { | ||
type: 'number', | ||
flags: { | ||
allowOnly: true | ||
}, | ||
valids: [undefined, 5 | ||
], | ||
invalids: [null] | ||
}, | ||
then: { | ||
type: 'string', | ||
flags: { | ||
allowOnly: true | ||
}, | ||
valids: [undefined, 'x'], | ||
invalids: [null, ''] | ||
} | ||
}, | ||
{ | ||
type: 'string', | ||
flags: { | ||
allowOnly: true | ||
}, | ||
valids: [undefined, 'z'], | ||
invalids: [null, ''] | ||
} | ||
] | ||
} | ||
}; | ||
expect(Joi.describe(schema)).to.deep.equal(outcome); | ||
done(); | ||
}); | ||
it('describes when (only otherwise)', function (done) { | ||
var schema = { | ||
a: Joi.alternatives().when('b', { is: 5, otherwise: 'y' }) | ||
.try('z'), | ||
b: Joi.any() | ||
}; | ||
var outcome = { | ||
type: 'object', | ||
valids: [undefined], | ||
invalids: [null], | ||
children: { | ||
b: { | ||
type: 'any', | ||
valids: [undefined], | ||
invalids: [null] | ||
}, | ||
a: [ | ||
{ | ||
ref: 'b', | ||
is: { | ||
type: 'number', | ||
flags: { | ||
allowOnly: true | ||
}, | ||
valids: [undefined, 5 | ||
], | ||
invalids: [null] | ||
}, | ||
otherwise: { | ||
type: 'string', | ||
flags: { | ||
allowOnly: true | ||
}, | ||
valids: [undefined, 'y'], | ||
invalids: [null, ''] | ||
} | ||
}, | ||
{ | ||
type: 'string', | ||
flags: { | ||
allowOnly: true | ||
}, | ||
valids: [undefined, 'z'], | ||
invalids: [null, ''] | ||
} | ||
] | ||
} | ||
}; | ||
expect(Joi.describe(schema)).to.deep.equal(outcome); | ||
done(); | ||
}); | ||
}); | ||
}); |
662
test/any.js
@@ -22,404 +22,525 @@ // Load modules | ||
describe('Joi', function () { | ||
describe('any', function () { | ||
describe('any', function () { | ||
describe('#equal', function () { | ||
it('validates both valid() and with()', function (done) { | ||
it('validates valid values', function (done) { | ||
var b = Joi.object({ | ||
first: Joi.any().valid('value').with('second'), | ||
second: Joi.any() | ||
}); | ||
Validate(b, [[{ first: 'value' }, false]]); | ||
Validate(Joi.equal(4), [ | ||
[4, true], | ||
[5, false] | ||
]); | ||
done(); | ||
}); | ||
}); | ||
describe('#strict', function () { | ||
describe('#not', function () { | ||
it('validates without converting', function (done) { | ||
it('validates invalid values', function (done) { | ||
var schema = Joi.object({ | ||
array: Joi.array().includes(Joi.string().min(5), Joi.number().min(3)) | ||
}).strict(); | ||
Validate(Joi.not(5), [ | ||
[4, true], | ||
[5, false] | ||
]); | ||
Validate(schema, [ | ||
[{ array: ['12345'] }, true], | ||
[{ array: ['1'] }, false], | ||
[{ array: [3] }, true], | ||
[{ array: ['12345', 3] }, true] | ||
]); done(); | ||
}); | ||
done(); | ||
}); | ||
}); | ||
describe('#with', function () { | ||
describe('#strict', function () { | ||
it('fails when with set on root', function (done) { | ||
it('validates without converting', function (done) { | ||
var b = Joi.any(); | ||
var result = b.with('test'); | ||
var schema = Joi.object({ | ||
array: Joi.array().includes(Joi.string().min(5), Joi.number().min(3)) | ||
}).strict(); | ||
expect(result.validate('test')).to.exist; | ||
done(); | ||
}); | ||
Validate(schema, [ | ||
[{ array: ['12345'] }, true], | ||
[{ array: ['1'] }, false], | ||
[{ array: [3] }, true], | ||
[{ array: ['12345', 3] }, true] | ||
]); done(); | ||
}); | ||
}); | ||
it('returns error when related type not found', function (done) { | ||
describe('#options', function () { | ||
Validate(Joi.any().with('test'), [['test', false]]) | ||
it('adds to existing options', function (done) { | ||
var schema = Joi.object({ b: Joi.number().strict().options({ convert: true }) }); | ||
var input = { b: '2' }; | ||
schema.validate(input, function (err, value) { | ||
expect(err).to.not.exist; | ||
expect(value.b).to.equal(2); | ||
done(); | ||
}); | ||
}); | ||
}); | ||
it('should throw an error when a parameter is not a string', function (done) { | ||
describe('#strict', function () { | ||
try { | ||
Joi.any().with({}); | ||
var error = false; | ||
} catch (e) { | ||
error = true; | ||
} | ||
expect(error).to.equal(true); | ||
it('adds to existing options', function (done) { | ||
try { | ||
Joi.any().with(123); | ||
error = false; | ||
} catch (e) { | ||
error = true; | ||
} | ||
expect(error).to.equal(true); | ||
var schema = Joi.object({ b: Joi.number().options({ convert: true }).strict() }); | ||
var input = { b: '2' }; | ||
schema.validate(input, function (err, value) { | ||
expect(err).to.exist; | ||
expect(value.b).to.equal('2'); | ||
done(); | ||
}); | ||
}); | ||
}); | ||
describe('#without', function () { | ||
describe('#default', function () { | ||
it('fails when without set on root', function (done) { | ||
it('sets the value', function (done) { | ||
var b = Joi.any(); | ||
var result = b.without('test'); | ||
var schema = Joi.object({ foo: Joi.string().default('test') }); | ||
var input = {}; | ||
expect(result.validate('test')).to.exist; | ||
schema.validate(input, function (err, value) { | ||
expect(err).to.not.exist; | ||
expect(value.foo).to.equal('test'); | ||
done(); | ||
}); | ||
}); | ||
it('should throw an error when a parameter is not a string', function (done) { | ||
it('should not overide a value when value is given', function (done) { | ||
try { | ||
b.without({}); | ||
var error = false; | ||
} catch (e) { | ||
error = true; | ||
} | ||
expect(error).to.equal(true); | ||
var schema = Joi.object({ foo: Joi.string().default('bar') }); | ||
var input = { foo: 'test' }; | ||
try { | ||
b.without(123); | ||
error = false; | ||
} catch (e) { | ||
error = true; | ||
} | ||
expect(error).to.equal(true); | ||
schema.validate(input, function (err, value) { | ||
expect(err).to.not.exist; | ||
expect(value.foo).to.equal('test'); | ||
done(); | ||
}); | ||
}); | ||
}); | ||
describe('#xor', function () { | ||
describe('#description', function () { | ||
it('fails when without set on root', function (done) { | ||
it('sets the description', function (done) { | ||
var b = Joi.any(); | ||
var result = b.xor('test'); | ||
var b = Joi.description('my description'); | ||
expect(b._description).to.equal('my description'); | ||
expect(result.validate('test')).to.exist; | ||
done(); | ||
}); | ||
done(); | ||
}); | ||
it('should throw an error when a parameter is not a string', function (done) { | ||
it('throws when description is missing', function (done) { | ||
try { | ||
b.xor({}); | ||
var error = false; | ||
} catch (e) { | ||
error = true; | ||
} | ||
expect(error).to.equal(true); | ||
expect(function () { | ||
try { | ||
b.xor(123); | ||
error = false; | ||
} catch (e) { | ||
error = true; | ||
} | ||
expect(error).to.equal(true); | ||
done(); | ||
}); | ||
Joi.description(); | ||
}).to.throw('Description must be a non-empty string'); | ||
done(); | ||
}); | ||
}); | ||
describe('#or', function () { | ||
describe('#notes', function () { | ||
it('fails when without set on root', function (done) { | ||
it('sets the notes', function (done) { | ||
var b = Joi.any(); | ||
var result = b.or('test'); | ||
var b = Joi.notes(['a']).notes('my notes'); | ||
expect(b._notes).to.deep.equal(['a', 'my notes']); | ||
expect(result.validate('test')).to.exist; | ||
done(); | ||
}); | ||
done(); | ||
}); | ||
it('should throw an error when a parameter is not a string', function (done) { | ||
it('throws when notes are missing', function (done) { | ||
try { | ||
b.or({}); | ||
var error = false; | ||
} catch (e) { | ||
error = true; | ||
} | ||
expect(error).to.equal(true); | ||
expect(function () { | ||
try { | ||
b.or(123); | ||
error = false; | ||
} catch (e) { | ||
error = true; | ||
} | ||
expect(error).to.equal(true); | ||
done(); | ||
}); | ||
Joi.notes(); | ||
}).to.throw('Notes must be a non-empty string or array'); | ||
done(); | ||
}); | ||
describe('#rename', function () { | ||
it('throws when notes are invalid', function (done) { | ||
it('fails when no parent object is provided', function (done) { | ||
expect(function () { | ||
var schema = Joi.any().rename('test'); | ||
expect(schema.validate('test')).to.exist; | ||
done(); | ||
}); | ||
Joi.notes(5); | ||
}).to.throw('Notes must be a non-empty string or array'); | ||
done(); | ||
}); | ||
}); | ||
it('allows renaming multiple times with multiple enabled', function (done) { | ||
describe('#tags', function () { | ||
var schema = { | ||
test1: Joi.string().rename('test'), | ||
test2: Joi.string().rename('test', { multiple: true }) | ||
}; | ||
it('sets the tags', function (done) { | ||
var err = Joi.validate({ test1: 'a', test2: 'b' }, schema); | ||
expect(err).to.not.exist; | ||
done(); | ||
}); | ||
var b = Joi.tags(['tag1', 'tag2']).tags('tag3'); | ||
expect(b._tags).to.include('tag1'); | ||
expect(b._tags).to.include('tag2'); | ||
expect(b._tags).to.include('tag3'); | ||
it('errors renaming multiple times with multiple disabled', function (done) { | ||
done(); | ||
}); | ||
var schema = { | ||
test1: Joi.string().rename('test'), | ||
test2: Joi.string().rename('test') | ||
}; | ||
it('throws when tags are missing', function (done) { | ||
var err = Joi.validate({ test1: 'a', test2: 'b' }, schema); | ||
expect(function () { | ||
Joi.tags(); | ||
}).to.throw('Tags must be a non-empty string or array'); | ||
done(); | ||
}); | ||
it('throws when tags are invalid', function (done) { | ||
expect(function () { | ||
Joi.tags(5); | ||
}).to.throw('Tags must be a non-empty string or array'); | ||
done(); | ||
}); | ||
}); | ||
describe('#example', function () { | ||
it('sets an example', function (done) { | ||
var schema = Joi.valid(5, 6, 7).example(5); | ||
expect(schema._examples).to.include(5); | ||
expect(schema.describe().examples).to.deep.equal([5]); | ||
done(); | ||
}); | ||
it('throws when tags are missing', function (done) { | ||
expect(function () { | ||
Joi.example(); | ||
}).to.throw('Missing example'); | ||
done(); | ||
}); | ||
it('throws when example fails own rules', function (done) { | ||
expect(function () { | ||
var schema = Joi.valid(5, 6, 7).example(4); | ||
}).to.throw('Bad example: value must be one of 5, 6, 7'); | ||
done(); | ||
}); | ||
}); | ||
describe('#unit', function () { | ||
it('sets the unit', function (done) { | ||
var b = Joi.unit('milliseconds'); | ||
expect(b._unit).to.equal('milliseconds'); | ||
expect(b.describe().unit).to.equal('milliseconds'); | ||
done(); | ||
}); | ||
it('throws when unit is missing', function (done) { | ||
expect(function () { | ||
Joi.unit(); | ||
}).to.throw('Unit name must be a non-empty string'); | ||
done(); | ||
}); | ||
}); | ||
describe('#_validate', function () { | ||
it('checks value after conversion', function (done) { | ||
var schema = Joi.number().invalid(2); | ||
Joi.validate('2', schema, { abortEarly: false }, function (err, value) { | ||
expect(err).to.exist; | ||
done(); | ||
}); | ||
}); | ||
}); | ||
it('with override disabled should not allow overwriting existing value', function (done) { | ||
describe('#concat', function () { | ||
var schema = { | ||
test: Joi.string().rename('test1') | ||
}; | ||
it('throws when schema is not any', function (done) { | ||
expect(Joi.validate({ test: 'b', test1: 'a' }, schema)).to.exist; | ||
done(); | ||
}); | ||
expect(function () { | ||
it('with override enabled should allow overwriting existing value', function (done) { | ||
Joi.string().concat(Joi.number()); | ||
}).to.throw('Cannot merge with another type: number'); | ||
done(); | ||
}); | ||
var schema = { | ||
test: Joi.string().rename('test1', { override: true }), | ||
test1: Joi.any() | ||
}; | ||
it('throws when schema is missing', function (done) { | ||
var err = Joi.validate({ test: 'b', test1: 'a' }, schema); | ||
expect(err).to.not.exist; | ||
done(); | ||
}); | ||
expect(function () { | ||
it('renames when data is nested in an array via includes', function (done) { | ||
Joi.string().concat(); | ||
}).to.throw('Invalid schema object'); | ||
done(); | ||
}); | ||
var schema = { | ||
arr: Joi.array().includes(Joi.object({ | ||
uno: Joi.string().rename('one'), | ||
dos: Joi.string().rename('two') | ||
})) | ||
}; | ||
it('throws when schema is invalid', function (done) { | ||
var data = { arr: [{ uno: '1', dos: '2' }] }; | ||
var err = Joi.validate(data, schema); | ||
expect(function () { | ||
expect(err).to.not.exist; | ||
expect(data.arr[0].one).to.equal('1'); | ||
expect(data.arr[0].two).to.equal('2'); | ||
done(); | ||
}); | ||
Joi.string().concat(1); | ||
}).to.throw('Invalid schema object'); | ||
done(); | ||
}); | ||
describe('#options', function () { | ||
it('merges two schemas (settings)', function (done) { | ||
it('adds to existing options', function (done) { | ||
var a = Joi.number().options({ convert: true }); | ||
var b = Joi.options({ convert: false }); | ||
var a = { b: Joi.number().strict().options({ convert: true, modify: true }) }; | ||
var c = { b: '2' }; | ||
expect(Joi.validate(c, a)).to.not.exist; | ||
expect(c.b).to.equal(2); | ||
done(); | ||
}); | ||
Validate(a, [[1, true], ['1', true]]); | ||
Validate(a.concat(b), [[1, true], ['1', false]]); | ||
done(); | ||
}); | ||
describe('#strict', function () { | ||
it('merges two schemas (invalid)', function (done) { | ||
it('adds to existing options', function (done) { | ||
var a = Joi.string().valid('a'); | ||
var b = Joi.string().valid('b'); | ||
var a = { b: Joi.number().options({ convert: true }).strict() }; | ||
var c = { b: '2' }; | ||
expect(Joi.validate(c, a)).to.exist; | ||
expect(c.b).to.equal('2'); | ||
done(); | ||
}); | ||
Validate(a, [['a', true], ['b', false]]); | ||
Validate(b, [['b', true], ['a', false]]); | ||
Validate(a.concat(b), [['a', true], ['b', true]]); | ||
done(); | ||
}); | ||
describe('#default', function () { | ||
it('merges two schemas (invalid)', function (done) { | ||
it('sets the value', function (done) { | ||
var a = Joi.string().invalid('a'); | ||
var b = Joi.invalid('b'); | ||
var schema = { foo: Joi.string().default('test') }; | ||
var input = {}; | ||
Validate(a, [['b', true], ['a', false]]); | ||
Validate(b, [['a', true], ['b', false]]); | ||
Validate(a.concat(b), [['a', false], ['b', false]]); | ||
done(); | ||
}); | ||
expect(Joi.validate(input, schema)).to.not.exist; | ||
expect(input.foo).to.equal('test'); | ||
it('merges two schemas (tests)', function (done) { | ||
done(); | ||
}); | ||
var a = Joi.number().min(5); | ||
var b = Joi.number().max(10); | ||
it('sets the value after key is renamed', function (done) { | ||
Validate(a, [[4, false], [11, true]]); | ||
Validate(b, [[6, true], [11, false]]); | ||
Validate(a.concat(b), [[4, false], [6, true], [11, false]]); | ||
done(); | ||
}); | ||
var schema = { foo: Joi.string().rename('foo2').default('test') }; | ||
var input = {}; | ||
it('merges two schemas (flags)', function (done) { | ||
expect(Joi.validate(input, schema)).to.not.exist; | ||
expect(input.foo2).to.equal('test'); | ||
var a = Joi.string().valid('a'); | ||
var b = Joi.string().insensitive(); | ||
done(); | ||
Validate(a, [['a', true], ['A', false], ['b', false]]); | ||
Validate(a.concat(b), [['a', true], ['A', true], ['b', false]]); | ||
done(); | ||
}); | ||
it('overrides and append information', function (done) { | ||
var a = Joi.description('a').unit('a').tags('a').example('a'); | ||
var b = Joi.description('b').unit('b').tags('b').example('b'); | ||
var desc = a.concat(b).describe(); | ||
expect(desc).to.deep.equal({ | ||
type: 'any', | ||
description: 'b', | ||
tags: ['a', 'b'], | ||
examples: ['a', 'b'], | ||
unit: 'b', | ||
valids: [undefined], | ||
invalids: [null] | ||
}); | ||
done(); | ||
}); | ||
it('sets the value after key is renamed. Old key should not exist', function (done) { | ||
it('merges two objects (any key + specific key)', function (done) { | ||
var schema = { foo: Joi.string().rename('foo2', { move: true }).default('test') }; | ||
var input = {}; | ||
var a = Joi.object(); | ||
var b = Joi.object({ b: 1 }); | ||
expect(Joi.validate(input, schema)).to.not.exist; | ||
expect(input.foo2).to.equal('test'); | ||
expect(input.foo).to.not.exist; | ||
Validate(a, [[{ b: 1 }, true], [{ b: 2 }, true]]); | ||
Validate(b, [[{ b: 1 }, true], [{ b: 2 }, false]]); | ||
Validate(a.concat(b), [[{ b: 1 }, true], [{ b: 2 }, false]]); | ||
Validate(b.concat(a), [[{ b: 1 }, true], [{ b: 2 }, false]]); | ||
done(); | ||
}); | ||
done(); | ||
}); | ||
it('merges two objects (no key + any key)', function (done) { | ||
it('should not overide a value when value is given', function (done) { | ||
var a = Joi.object({}); | ||
var b = Joi.object(); | ||
var schema = { foo: Joi.string().default('bar') }; | ||
var input = { foo: 'test' }; | ||
Validate(a, [[{}, true], [{ b: 2 }, false]]); | ||
Validate(b, [[{}, true], [{ b: 2 }, true]]); | ||
Validate(a.concat(b), [[{}, true], [{ b: 2 }, false]]); | ||
Validate(b.concat(a), [[{}, true], [{ b: 2 }, false]]); | ||
done(); | ||
}); | ||
expect(Joi.validate(input, schema)).to.not.exist; | ||
expect(input.foo).to.equal('test'); | ||
it('merges two objects (key + key)', function (done) { | ||
done(); | ||
}); | ||
var a = Joi.object({ a: 1 }); | ||
var b = Joi.object({ b: 2 }); | ||
Validate(a, [[{ a: 1 }, true], [{ b: 2 }, false]]); | ||
Validate(b, [[{ a: 1 }, false], [{ b: 2 }, true]]); | ||
Validate(a.concat(b), [[{ a: 1 }, true], [{ b: 2 }, true]]); | ||
Validate(b.concat(a), [[{ a: 1 }, true], [{ b: 2 }, true]]); | ||
done(); | ||
}); | ||
describe('#validateCallback', function () { | ||
it('merges two objects (renames)', function (done) { | ||
it('validates using callback interface', function (done) { | ||
var a = Joi.object({ a: 1 }).rename('c', 'a'); | ||
var b = Joi.object({ b: 2 }).rename('d', 'b'); | ||
var schema = Joi.number(); | ||
schema.validateCallback(4, {}, function (err) { | ||
a.concat(b).validate({ c: 1, d: 2 }, function (err, value) { | ||
expect(err).to.not.exist; | ||
done(); | ||
}); | ||
expect(err).to.not.exist; | ||
expect(value).to.deep.equal({ a: 1, b: 2 }); | ||
}); | ||
done(); | ||
}); | ||
describe('#description', function () { | ||
it('merges two objects (deps)', function (done) { | ||
it('sets the description', function (done) { | ||
var a = Joi.object({ a: 1 }); | ||
var b = Joi.object({ b: 2 }).and('b', 'a'); | ||
var b = Joi.any().description('my description'); | ||
expect(b._description).to.equal('my description'); | ||
a.concat(b).validate({ a: 1, b: 2 }, function (err, value) { | ||
done(); | ||
expect(err).to.not.exist; | ||
}); | ||
done(); | ||
}); | ||
it('throws when description is missing', function (done) { | ||
it('merges two alternatives with references', function (done) { | ||
expect(function () { | ||
var schema = { | ||
a: { c: Joi.number() }, | ||
b: Joi.alternatives(Joi.ref('a.c')).concat(Joi.alternatives(Joi.ref('c'))), | ||
c: Joi.number() | ||
}; | ||
Joi.any().description(); | ||
}).to.throw('Description must be a non-empty string'); | ||
done(); | ||
}); | ||
Validate(schema, [ | ||
[{ a: {} }, true], | ||
[{ a: { c: '5' }, b: 5 }, true], | ||
[{ a: { c: '5' }, b: 6, c: '6' }, true], | ||
[{ a: { c: '5' }, b: 7, c: '6' }, false] | ||
]); | ||
done(); | ||
}); | ||
}); | ||
describe('#notes', function () { | ||
describe('#when', function () { | ||
it('sets the notes', function (done) { | ||
it('throws when options are invalid', function (done) { | ||
var b = Joi.any().notes(['a']).notes('my notes'); | ||
expect(b._notes).to.deep.equal(['a', 'my notes']); | ||
expect(function () { | ||
done(); | ||
}); | ||
Joi.when('a'); | ||
}).to.throw('Invalid options'); | ||
it('throws when notes are missing', function (done) { | ||
done(); | ||
}); | ||
expect(function () { | ||
it('forks type into alternatives', function (done) { | ||
Joi.any().notes(); | ||
}).to.throw('Notes must be a non-empty string or array'); | ||
done(); | ||
}); | ||
var schema = { | ||
a: Joi.any(), | ||
b: Joi.string().valid('x').when('a', { is: 5, then: Joi.valid('y'), otherwise: Joi.valid('z') }) | ||
}; | ||
it('throws when notes are invalid', function (done) { | ||
Validate(schema, [ | ||
[{ a: 5, b: 'x' }, true], | ||
[{ a: 5, b: 'y' }, true], | ||
[{ a: 5, b: 'z' }, false], | ||
[{ a: 1, b: 'x' }, true], | ||
[{ a: 1, b: 'y' }, false], | ||
[{ a: 1, b: 'z' }, true], | ||
[{ a: 5, b: 'a' }, false], | ||
[{ b: 'a' }, false] | ||
]); | ||
expect(function () { | ||
done(); | ||
}); | ||
Joi.any().notes(5); | ||
}).to.throw('Notes must be a non-empty string or array'); | ||
done(); | ||
}); | ||
it('forks type into alternatives (only then)', function (done) { | ||
var schema = { | ||
a: Joi.any(), | ||
b: Joi.string().valid('x').when('a', { is: 5, then: Joi.valid('y') }) | ||
}; | ||
Validate(schema, [ | ||
[{ a: 5, b: 'x' }, true], | ||
[{ a: 5, b: 'y' }, true], | ||
[{ a: 5, b: 'z' }, false], | ||
[{ a: 1, b: 'x' }, true], | ||
[{ a: 1, b: 'y' }, false], | ||
[{ a: 1, b: 'z' }, false], | ||
[{ a: 5, b: 'a' }, false], | ||
[{ b: 'a' }, false] | ||
]); | ||
done(); | ||
}); | ||
describe('#tags', function () { | ||
it('forks type into alternatives (only otherwise)', function (done) { | ||
it('sets the tags', function (done) { | ||
var schema = { | ||
a: Joi.any(), | ||
b: Joi.string().valid('x').when('a', { is: 5, otherwise: Joi.valid('z') }) | ||
}; | ||
var b = Joi.any().tags(['tag1', 'tag2']).tags('tag3'); | ||
expect(b._tags).to.include('tag1'); | ||
expect(b._tags).to.include('tag2'); | ||
expect(b._tags).to.include('tag3'); | ||
Validate(schema, [ | ||
[{ a: 5, b: 'x' }, true], | ||
[{ a: 5, b: 'y' }, false], | ||
[{ a: 5, b: 'z' }, false], | ||
[{ a: 1, b: 'x' }, true], | ||
[{ a: 1, b: 'y' }, false], | ||
[{ a: 1, b: 'z' }, true], | ||
[{ a: 5, b: 'a' }, false], | ||
[{ b: 'a' }, false] | ||
]); | ||
done(); | ||
}); | ||
done(); | ||
}); | ||
}); | ||
it('throws when tags are missing', function (done) { | ||
describe('Set', function () { | ||
describe('#add', function () { | ||
it('throws when adding a non ref function', function (done) { | ||
expect(function () { | ||
Joi.any().tags(); | ||
}).to.throw('Tags must be a non-empty string or array'); | ||
Joi.valid(function () { }); | ||
}).to.throw('Value cannot be an object or function'); | ||
done(); | ||
}); | ||
it('throws when tags are invalid', function (done) { | ||
it('throws when adding an object function', function (done) { | ||
expect(function () { | ||
Joi.any().tags(5); | ||
}).to.throw('Tags must be a non-empty string or array'); | ||
Joi.valid({}); | ||
}).to.throw('Value cannot be an object or function'); | ||
done(); | ||
@@ -429,8 +550,12 @@ }); | ||
describe('#_validate', function () { | ||
describe('#values', function () { | ||
it('checks value after conversion', function (done) { | ||
it('returns array', function (done) { | ||
var a = Joi.number().invalid(2); | ||
expect(Joi.validate('2', a, { abortEarly: false })).to.exist; | ||
var a = Joi.any(); | ||
var b = a.required(); | ||
expect(a._valids.values().length).to.equal(1); | ||
expect(b._valids.values().length).to.equal(0); | ||
expect(a._invalids.values().length).to.equal(1); | ||
expect(b._invalids.values().length).to.equal(2); | ||
done(); | ||
@@ -440,12 +565,9 @@ }); | ||
describe('Set', function () { | ||
describe('#toString', function () { | ||
describe('#toString', function () { | ||
it('includes undefined', function (done) { | ||
it('includes undefined', function (done) { | ||
var b = Joi.any(); | ||
expect(b._valids.toString(true)).to.equal('undefined'); | ||
done(); | ||
}); | ||
var b = Joi.any(); | ||
expect(b._valids.toString(true)).to.equal('undefined'); | ||
done(); | ||
}); | ||
@@ -452,0 +574,0 @@ }); |
@@ -22,303 +22,320 @@ // Load modules | ||
describe('Types', function () { | ||
describe('array', function () { | ||
describe('Array', function () { | ||
it('converts a string to an array', function (done) { | ||
describe('#_convert', function () { | ||
Joi.array().validate('[1,2,3]', function (err, value) { | ||
it('should convert a string to an array', function (done) { | ||
expect(err).to.not.exist; | ||
expect(value.length).to.equal(3); | ||
done(); | ||
}); | ||
}); | ||
var result = Joi.array()._convert('[1,2,3]'); | ||
expect(result.length).to.equal(3); | ||
done(); | ||
}); | ||
it('errors on non-array string', function (done) { | ||
it('should convert a non-array string to an array', function (done) { | ||
Joi.array().validate('{ "something": false }', function (err, value) { | ||
var result = Joi.array()._convert('{ "something": false }'); | ||
expect(result.length).to.equal(1); | ||
done(); | ||
}); | ||
expect(err).to.exist; | ||
expect(err.message).to.equal('value must be an array'); | ||
done(); | ||
}); | ||
}); | ||
it('should return a non array', function (done) { | ||
it('errors on number', function (done) { | ||
var result = Joi.array()._convert(3); | ||
expect(result).to.equal(3); | ||
done(); | ||
}); | ||
Joi.array().validate(3, function (err, value) { | ||
it('should convert a non-array string with number type', function (done) { | ||
expect(err).to.exist; | ||
expect(value).to.equal(3); | ||
done(); | ||
}); | ||
}); | ||
var result = Joi.array()._convert('3'); | ||
expect(result.length).to.equal(1); | ||
expect(result[0]).to.equal('3'); | ||
done(); | ||
}); | ||
it('converts a non-array string with number type', function (done) { | ||
it('should convert a non-array string', function (done) { | ||
Joi.array().validate('3', function (err, value) { | ||
var result = Joi.array()._convert('asdf'); | ||
expect(result).to.equal('asdf'); | ||
done(); | ||
}); | ||
expect(err).to.exist; | ||
expect(value).to.equal('3'); | ||
done(); | ||
}); | ||
}); | ||
describe('#includes', function () { | ||
it('errors on a non-array string', function (done) { | ||
it('converts members', function (done) { | ||
Joi.array().validate('asdf', function (err, value) { | ||
var array = ['1', '2', '3']; | ||
var schema = Joi.array().includes(Joi.number()); | ||
var err = Joi.validate(array, schema, { modify: true }); | ||
expect(err).to.exist; | ||
expect(value).to.equal('asdf'); | ||
done(); | ||
}); | ||
}); | ||
describe('#includes', function () { | ||
it('converts members', function (done) { | ||
var schema = Joi.array().includes(Joi.number()); | ||
var input = ['1', '2', '3']; | ||
schema.validate(input, function (err, value) { | ||
expect(err).to.not.exist; | ||
expect(array).to.deep.equal([1, 2, 3]); | ||
expect(value).to.deep.equal([1, 2, 3]); | ||
done(); | ||
}); | ||
}); | ||
it('allows zero size', function (done) { | ||
it('allows zero size', function (done) { | ||
var data = { test: [] }; | ||
var schema = { | ||
test: Joi.array().includes(Joi.object({ | ||
foo: Joi.string().required() | ||
})) | ||
}; | ||
var schema = Joi.object({ | ||
test: Joi.array().includes(Joi.object({ | ||
foo: Joi.string().required() | ||
})) | ||
}); | ||
var input = { test: [] }; | ||
var err = Joi.validate(data, schema); | ||
schema.validate(input, function (err, value) { | ||
expect(err).to.not.exist; | ||
done(); | ||
}); | ||
}); | ||
it('returns the first error when only one inclusion', function (done) { | ||
it('returns the first error when only one inclusion', function (done) { | ||
var data = { test: [{ foo: 'a' }, { bar: 2 }] }; | ||
var schema = { | ||
test: Joi.array().includes(Joi.object({ | ||
foo: Joi.string().required() | ||
})) | ||
}; | ||
var schema = Joi.object({ | ||
test: Joi.array().includes(Joi.object({ | ||
foo: Joi.string().required() | ||
})) | ||
}); | ||
var input = { test: [{ foo: 'a' }, { bar: 2 }] }; | ||
var err = Joi.validate(data, schema); | ||
expect(err.message).to.equal('the test array value in position 1 fails because the value of foo is not allowed to be undefined'); | ||
schema.validate(input, function (err, value) { | ||
expect(err.message).to.equal('test position 1 fails because foo is required'); | ||
done(); | ||
}); | ||
}); | ||
}); | ||
describe('#min', function () { | ||
describe('#min', function () { | ||
it('validates array size', function (done) { | ||
it('validates array size', function (done) { | ||
var schema = Joi.array().min(2); | ||
Validate(schema, [ | ||
[[1, 2], true], | ||
[[1], false] | ||
]); | ||
done(); | ||
}); | ||
var schema = Joi.array().min(2); | ||
Validate(schema, [ | ||
[[1, 2], true], | ||
[[1], false] | ||
]); | ||
done(); | ||
}); | ||
it('throws when limit is not a number', function (done) { | ||
it('throws when limit is not a number', function (done) { | ||
expect(function () { | ||
expect(function () { | ||
Joi.array().min('a'); | ||
}).to.throw('limit must be a positive integer'); | ||
done(); | ||
}); | ||
Joi.array().min('a'); | ||
}).to.throw('limit must be a positive integer'); | ||
done(); | ||
}); | ||
it('throws when limit is not an integer', function (done) { | ||
it('throws when limit is not an integer', function (done) { | ||
expect(function () { | ||
expect(function () { | ||
Joi.array().min(1.2); | ||
}).to.throw('limit must be a positive integer'); | ||
done(); | ||
}); | ||
Joi.array().min(1.2); | ||
}).to.throw('limit must be a positive integer'); | ||
done(); | ||
}); | ||
}); | ||
describe('#max', function () { | ||
describe('#max', function () { | ||
it('validates array size', function (done) { | ||
it('validates array size', function (done) { | ||
var schema = Joi.array().max(1); | ||
Validate(schema, [ | ||
[[1, 2], false], | ||
[[1], true] | ||
]); | ||
done(); | ||
}); | ||
var schema = Joi.array().max(1); | ||
Validate(schema, [ | ||
[[1, 2], false], | ||
[[1], true] | ||
]); | ||
done(); | ||
}); | ||
it('throws when limit is not a number', function (done) { | ||
it('throws when limit is not a number', function (done) { | ||
expect(function () { | ||
expect(function () { | ||
Joi.array().max('a'); | ||
}).to.throw('limit must be a positive integer'); | ||
done(); | ||
}); | ||
Joi.array().max('a'); | ||
}).to.throw('limit must be a positive integer'); | ||
done(); | ||
}); | ||
it('throws when limit is not an integer', function (done) { | ||
it('throws when limit is not an integer', function (done) { | ||
expect(function () { | ||
expect(function () { | ||
Joi.array().max(1.2); | ||
}).to.throw('limit must be a positive integer'); | ||
done(); | ||
}); | ||
Joi.array().max(1.2); | ||
}).to.throw('limit must be a positive integer'); | ||
done(); | ||
}); | ||
}); | ||
describe('#length', function () { | ||
describe('#length', function () { | ||
it('validates array size', function (done) { | ||
it('validates array size', function (done) { | ||
var schema = Joi.array().length(2); | ||
Validate(schema, [ | ||
[[1, 2], true], | ||
[[1], false] | ||
]); | ||
done(); | ||
}); | ||
var schema = Joi.array().length(2); | ||
Validate(schema, [ | ||
[[1, 2], true], | ||
[[1], false] | ||
]); | ||
done(); | ||
}); | ||
it('throws when limit is not a number', function (done) { | ||
it('throws when limit is not a number', function (done) { | ||
expect(function () { | ||
expect(function () { | ||
Joi.array().length('a'); | ||
}).to.throw('limit must be a positive integer'); | ||
done(); | ||
}); | ||
Joi.array().length('a'); | ||
}).to.throw('limit must be a positive integer'); | ||
done(); | ||
}); | ||
it('throws when limit is not an integer', function (done) { | ||
it('throws when limit is not an integer', function (done) { | ||
expect(function () { | ||
expect(function () { | ||
Joi.array().length(1.2); | ||
}).to.throw('limit must be a positive integer'); | ||
done(); | ||
}); | ||
Joi.array().length(1.2); | ||
}).to.throw('limit must be a positive integer'); | ||
done(); | ||
}); | ||
}); | ||
describe('#validate', function () { | ||
describe('#validate', function () { | ||
it('should, by default, allow undefined, allow empty array', function (done) { | ||
it('should, by default, allow undefined, allow empty array', function (done) { | ||
Validate(Joi.array(), [ | ||
[undefined, true], | ||
[[], true] | ||
]); | ||
done(); | ||
}); | ||
Validate(Joi.array(), [ | ||
[undefined, true], | ||
[[], true] | ||
]); | ||
done(); | ||
}); | ||
it('should, when .required(), deny undefined', function (done) { | ||
it('should, when .required(), deny undefined', function (done) { | ||
Validate(Joi.array().required(), [ | ||
[undefined, false] | ||
]); | ||
done(); | ||
}); | ||
Validate(Joi.array().required(), [ | ||
[undefined, false] | ||
]); | ||
done(); | ||
}); | ||
it('allows empty arrays', function (done) { | ||
it('allows empty arrays', function (done) { | ||
Validate(Joi.array(), [ | ||
[undefined, true], | ||
[[], true] | ||
]); | ||
done(); | ||
}); | ||
Validate(Joi.array(), [ | ||
[undefined, true], | ||
[[], true] | ||
]); | ||
done(); | ||
}); | ||
it('should exclude values when excludes is called', function (done) { | ||
it('should exclude values when excludes is called', function (done) { | ||
Validate(Joi.array().excludes(Joi.string()), [ | ||
[['2', '1'], false], | ||
[['1'], false], | ||
[[2], true] | ||
]); | ||
done(); | ||
}); | ||
Validate(Joi.array().excludes(Joi.string()), [ | ||
[['2', '1'], false], | ||
[['1'], false], | ||
[[2], true] | ||
]); | ||
done(); | ||
}); | ||
it('should allow types to be excluded', function (done) { | ||
it('should allow types to be excluded', function (done) { | ||
var schema = Joi.array().excludes(Joi.number()); | ||
var schema = Joi.array().excludes(Joi.number()); | ||
var n = [1, 2, 'hippo']; | ||
var result = schema.validate(n); | ||
var n = [1, 2, 'hippo']; | ||
schema.validate(n, function (err, value) { | ||
expect(result).to.exist; | ||
expect(err).to.exist; | ||
var m = ['x', 'y', 'z']; | ||
var result2 = schema.validate(m); | ||
schema.validate(m, function (err2, value) { | ||
expect(result2).to.not.exist; | ||
done(); | ||
expect(err2).to.not.exist; | ||
done(); | ||
}); | ||
}); | ||
}); | ||
it('should validate array of Numbers', function (done) { | ||
it('should validate array of Numbers', function (done) { | ||
Validate(Joi.array().includes(Joi.number()), [ | ||
[[1, 2, 3], true], | ||
[[50, 100, 1000], true], | ||
[['a', 1, 2], false] | ||
]); | ||
done(); | ||
}); | ||
Validate(Joi.array().includes(Joi.number()), [ | ||
[[1, 2, 3], true], | ||
[[50, 100, 1000], true], | ||
[['a', 1, 2], false] | ||
]); | ||
done(); | ||
}); | ||
it('should validate array of mixed Numbers & Strings', function (done) { | ||
it('should validate array of mixed Numbers & Strings', function (done) { | ||
Validate(Joi.array().includes(Joi.number(), Joi.string()), [ | ||
[[1, 2, 3], true], | ||
[[50, 100, 1000], true], | ||
[[1, 'a', 5, 10], true], | ||
[['joi', 'everydaylowprices', 5000], true] | ||
]); | ||
done(); | ||
}); | ||
Validate(Joi.array().includes(Joi.number(), Joi.string()), [ | ||
[[1, 2, 3], true], | ||
[[50, 100, 1000], true], | ||
[[1, 'a', 5, 10], true], | ||
[['joi', 'everydaylowprices', 5000], true] | ||
]); | ||
done(); | ||
}); | ||
it('should validate array of objects with schema', function (done) { | ||
it('should validate array of objects with schema', function (done) { | ||
Validate(Joi.array().includes(Joi.object({ h1: Joi.number().required() })), [ | ||
[[{ h1: 1 }, { h1: 2 }, { h1: 3 }], true], | ||
[[{ h2: 1, h3: 'somestring' }, { h1: 2 }, { h1: 3 }], false], | ||
[[1, 2, [1]], false] | ||
]); | ||
done(); | ||
}); | ||
Validate(Joi.array().includes(Joi.object({ h1: Joi.number().required() })), [ | ||
[[{ h1: 1 }, { h1: 2 }, { h1: 3 }], true], | ||
[[{ h2: 1, h3: 'somestring' }, { h1: 2 }, { h1: 3 }], false], | ||
[[1, 2, [1]], false] | ||
]); | ||
done(); | ||
}); | ||
it('should not validate array of unallowed mixed types (Array)', function (done) { | ||
it('should not validate array of unallowed mixed types (Array)', function (done) { | ||
Validate(Joi.array().includes(Joi.number()), [ | ||
[[1, 2, 3], true], | ||
[[1, 2, [1]], false] | ||
]); | ||
done(); | ||
}); | ||
Validate(Joi.array().includes(Joi.number()), [ | ||
[[1, 2, 3], true], | ||
[[1, 2, [1]], false] | ||
]); | ||
done(); | ||
}); | ||
it('errors on invalid number rule using includes', function (done) { | ||
it('errors on invalid number rule using includes', function (done) { | ||
var schema = { | ||
arr: Joi.array().includes(Joi.number().integer()) | ||
}; | ||
var schema = Joi.object({ | ||
arr: Joi.array().includes(Joi.number().integer()) | ||
}); | ||
var input = { arr: [1, 2, 2.1] }; | ||
var err = Joi.validate(input, schema); | ||
var input = { arr: [1, 2, 2.1] }; | ||
schema.validate(input, function (err, value) { | ||
expect(err).to.exist; | ||
expect(err.message).to.equal('the arr array value in position 2 fails because the value of 2 must be an integer'); | ||
expect(err.message).to.equal('arr position 2 fails because 2 must be an integer'); | ||
done(); | ||
}); | ||
}); | ||
it('validates an array within an object', function (done) { | ||
it('validates an array within an object', function (done) { | ||
var schema = Joi.object({ | ||
array: Joi.array().includes(Joi.string().min(5), Joi.number().min(3)) | ||
}).options({ convert: false }); | ||
var schema = Joi.object({ | ||
array: Joi.array().includes(Joi.string().min(5), Joi.number().min(3)) | ||
}).options({ convert: false }); | ||
Validate(schema, [ | ||
[{ array: ['12345'] }, true], | ||
[{ array: ['1'] }, false], | ||
[{ array: [3] }, true], | ||
[{ array: ['12345', 3] }, true] | ||
]); | ||
done(); | ||
}); | ||
Validate(schema, [ | ||
[{ array: ['12345'] }, true], | ||
[{ array: ['1'] }, false], | ||
[{ array: [3] }, true], | ||
[{ array: ['12345', 3] }, true] | ||
]); | ||
done(); | ||
}); | ||
}); | ||
}); |
@@ -22,107 +22,107 @@ // Load modules | ||
describe('Types', function () { | ||
describe('boolean', function () { | ||
describe('Boolean', function () { | ||
it('converts a string to a boolean', function (done) { | ||
describe('#convert', function () { | ||
Joi.boolean().validate('true', function (err, value) { | ||
it('should convert a string to a boolean', function (done) { | ||
expect(err).to.not.exist; | ||
expect(value).to.equal(true); | ||
done(); | ||
}); | ||
}); | ||
var result = Joi.boolean()._convert('true'); | ||
expect(result).to.equal(true); | ||
done(); | ||
}); | ||
it('errors on a number', function (done) { | ||
it('should not convert a number to a boolean', function (done) { | ||
Joi.boolean().validate(1, function (err, value) { | ||
var result = Joi.boolean()._convert(1); | ||
expect(result).to.equal(1); | ||
done(); | ||
}); | ||
expect(err).to.exist; | ||
expect(value).to.equal(1); | ||
done(); | ||
}); | ||
}); | ||
describe('#validate', function () { | ||
describe('#validate', function () { | ||
it('converts string values and validates', function (done) { | ||
it('converts string values and validates', function (done) { | ||
var rule = Joi.boolean(); | ||
Validate(rule, [ | ||
['1234', false], | ||
[false, true], | ||
[true, true], | ||
[null, false], | ||
['on', true], | ||
['off', true], | ||
['true', true], | ||
['false', true], | ||
['yes', true], | ||
['no', true] | ||
]); done(); | ||
}); | ||
var rule = Joi.boolean(); | ||
Validate(rule, [ | ||
['1234', false], | ||
[false, true], | ||
[true, true], | ||
[null, false], | ||
['on', true], | ||
['off', true], | ||
['true', true], | ||
['false', true], | ||
['yes', true], | ||
['no', true] | ||
]); done(); | ||
}); | ||
it('should handle work with required', function (done) { | ||
it('should handle work with required', function (done) { | ||
var rule = Joi.boolean().required(); | ||
Validate(rule, [ | ||
['1234', false], | ||
['true', true], | ||
[false, true], | ||
[true, true], | ||
[null, false] | ||
]); done(); | ||
}); | ||
var rule = Joi.boolean().required(); | ||
Validate(rule, [ | ||
['1234', false], | ||
['true', true], | ||
[false, true], | ||
[true, true], | ||
[null, false] | ||
]); done(); | ||
}); | ||
it('should handle work with allow', function (done) { | ||
it('should handle work with allow', function (done) { | ||
var rule = Joi.boolean().allow(false); | ||
Validate(rule, [ | ||
['1234', false], | ||
[false, true], | ||
[null, false] | ||
]); done(); | ||
}); | ||
var rule = Joi.boolean().allow(false); | ||
Validate(rule, [ | ||
['1234', false], | ||
[false, true], | ||
[null, false] | ||
]); done(); | ||
}); | ||
it('should handle work with invalid', function (done) { | ||
it('should handle work with invalid', function (done) { | ||
var rule = Joi.boolean().invalid(false); | ||
Validate(rule, [ | ||
['1234', false], | ||
[false, false], | ||
[true, true], | ||
[null, false] | ||
]); done(); | ||
}); | ||
var rule = Joi.boolean().invalid(false); | ||
Validate(rule, [ | ||
['1234', false], | ||
[false, false], | ||
[true, true], | ||
[null, false] | ||
]); done(); | ||
}); | ||
it('should handle work with invalid and null allowed', function (done) { | ||
it('should handle work with invalid and null allowed', function (done) { | ||
var rule = Joi.boolean().invalid(false).allow(null); | ||
Validate(rule, [ | ||
['1234', false], | ||
[false, false], | ||
[true, true], | ||
[null, true] | ||
]); done(); | ||
}); | ||
var rule = Joi.boolean().invalid(false).allow(null); | ||
Validate(rule, [ | ||
['1234', false], | ||
[false, false], | ||
[true, true], | ||
[null, true] | ||
]); done(); | ||
}); | ||
it('should handle work with allow and invalid', function (done) { | ||
it('should handle work with allow and invalid', function (done) { | ||
var rule = Joi.boolean().invalid(true).allow(false); | ||
Validate(rule, [ | ||
['1234', false], | ||
[false, true], | ||
[true, false], | ||
[null, false] | ||
]); done(); | ||
}); | ||
var rule = Joi.boolean().invalid(true).allow(false); | ||
Validate(rule, [ | ||
['1234', false], | ||
[false, true], | ||
[true, false], | ||
[null, false] | ||
]); done(); | ||
}); | ||
it('should handle work with allow, invalid, and null allowed', function (done) { | ||
it('should handle work with allow, invalid, and null allowed', function (done) { | ||
var rule = Joi.boolean().invalid(true).allow(false).allow(null); | ||
Validate(rule, [ | ||
['1234', false], | ||
[false, true], | ||
[true, false], | ||
[null, true] | ||
]); done(); | ||
}); | ||
var rule = Joi.boolean().invalid(true).allow(false).allow(null); | ||
Validate(rule, [ | ||
['1234', false], | ||
[false, true], | ||
[true, false], | ||
[null, true] | ||
]); done(); | ||
}); | ||
@@ -129,0 +129,0 @@ }); |
@@ -24,9 +24,42 @@ // Load modules | ||
it('fails on boolean', function (done) { | ||
var schema = Joi.date(); | ||
Validate(schema, [ | ||
[true, false], | ||
[false, false] | ||
]); | ||
done(); | ||
}); | ||
it('matches specific date', function (done) { | ||
var now = Date.now(); | ||
expect(Joi.validate(new Date(now), Joi.date().valid(new Date(now)))).to.not.exist; | ||
done(); | ||
Joi.date().valid(new Date(now)).validate(new Date(now), function (err, value) { | ||
expect(err).to.not.exist; | ||
done(); | ||
}); | ||
}); | ||
it('errors on invalid input and convert disabled', function (done) { | ||
Joi.date().options({ convert: false }).validate('1-1-2013', function (err, value) { | ||
expect(err).to.exist; | ||
expect(err.message).to.equal('value must be a number of milliseconds or valid date string'); | ||
done(); | ||
}); | ||
}); | ||
it('validates date', function (done) { | ||
Joi.date().validate(new Date(), function (err, value) { | ||
expect(err).to.not.exist; | ||
done(); | ||
}); | ||
}); | ||
describe('#validate', function () { | ||
@@ -33,0 +66,0 @@ |
@@ -22,16 +22,13 @@ // Load modules | ||
describe('Types', function () { | ||
describe('func', function () { | ||
describe('Function', function () { | ||
it('should validate a function', function (done) { | ||
it('should validate a function', function (done) { | ||
Validate(Joi.func().required(), [ | ||
[function(){ }, true], | ||
['', false] | ||
]); | ||
done(); | ||
}); | ||
Validate(Joi.func().required(), [ | ||
[function () { }, true], | ||
['', false] | ||
]); | ||
done(); | ||
}); | ||
}); | ||
@@ -23,16 +23,18 @@ // Load modules | ||
var compiled = Joi.compile(schema); | ||
for (var i in config) { | ||
var result = Joi.validate(config[i][0], schema); | ||
compiled.validate(config[i][0], function (err, value) { | ||
if (result !== null && config[i][1]) { | ||
console.log(result); | ||
} | ||
if (err !== null && config[i][1]) { | ||
console.log(err); | ||
} | ||
if (result === null && !config[i][1]) { | ||
console.log(config[i][0]); | ||
} | ||
if (err === null && !config[i][1]) { | ||
console.log(config[i][0]); | ||
} | ||
expect(result === null).to.equal(config[i][1]); | ||
expect(err === null).to.equal(config[i][1]); | ||
}); | ||
} | ||
}; | ||
1153
test/index.js
@@ -27,7 +27,7 @@ // Load modules | ||
var schema = { | ||
a: Joi.number().min(0).max(3).without('none'), | ||
var schema = Joi.object({ | ||
a: Joi.number().min(0).max(3), | ||
b: Joi.string().valid('a', 'b', 'c'), | ||
c: Joi.string().email().optional() | ||
}; | ||
}).without('a', 'none'); | ||
@@ -40,23 +40,4 @@ var obj = { | ||
var err = Joi.validate(obj, schema); | ||
expect(err).to.not.exist; | ||
done(); | ||
}); | ||
schema.validate(obj, function (err, value) { | ||
it('validates object with callback interface', function (done) { | ||
var schema = { | ||
a: Joi.number().min(0).max(3).without('none'), | ||
b: Joi.string().valid('a', 'b', 'c'), | ||
c: Joi.string().email().optional() | ||
}; | ||
var obj = { | ||
a: 1, | ||
b: 'a', | ||
c: 'joe@example.com' | ||
}; | ||
Joi.validateCallback(obj, schema, null, function (err) { | ||
expect(err).to.not.exist; | ||
@@ -89,7 +70,8 @@ done(); | ||
var err = Joi.validate(null, Joi.string()); | ||
expect(err).to.exist; | ||
err.annotated(); | ||
expect(err.message).to.equal('{\n \u001b[41m\"<root>\"\u001b[0m\u001b[31m [1]: -- missing --\u001b[0m\n}\n\u001b[31m\n[1] the value of <root> is not allowed to be null\u001b[0m'); | ||
done(); | ||
Joi.string().validate(null, function (err, value) { | ||
expect(err).to.exist; | ||
expect(err.annotate()).to.equal('{\n \u001b[41m\"value\"\u001b[0m\u001b[31m [1]: -- missing --\u001b[0m\n}\n\u001b[31m\n[1] value contains an invalid value\u001b[0m'); | ||
done(); | ||
}); | ||
}); | ||
@@ -99,4 +81,6 @@ | ||
expect(Joi.validate(null, null)).to.not.exist; | ||
expect(Joi.validate('a', null)).to.exist; | ||
Validate(null, [ | ||
['a', false], | ||
[null, true] | ||
]); | ||
done(); | ||
@@ -107,4 +91,6 @@ }); | ||
expect(Joi.validate(5, 5)).to.not.exist; | ||
expect(Joi.validate(6, 5)).to.exist; | ||
Validate(5, [ | ||
[6, false], | ||
[5, true] | ||
]); | ||
done(); | ||
@@ -115,4 +101,6 @@ }); | ||
expect(Joi.validate('5', '5')).to.not.exist; | ||
expect(Joi.validate('6', '5')).to.exist; | ||
Validate('5', [ | ||
['6', false], | ||
['5', true] | ||
]); | ||
done(); | ||
@@ -123,4 +111,6 @@ }); | ||
expect(Joi.validate(true, true)).to.not.exist; | ||
expect(Joi.validate(false, true)).to.exist; | ||
Validate(true, [ | ||
[false, false], | ||
[true, true] | ||
]); | ||
done(); | ||
@@ -132,5 +122,7 @@ }); | ||
var now = Date.now(); | ||
expect(Joi.validate(new Date(now), new Date(now))).to.not.exist; | ||
expect(Joi.validate(now, new Date(now))).to.not.exist; | ||
expect(Joi.validate(now * 2, new Date(now))).to.exist; | ||
Validate(new Date(now), [ | ||
[new Date(now), true], | ||
[now, true], | ||
[now * 2, false] | ||
]); | ||
done(); | ||
@@ -175,5 +167,11 @@ }); | ||
expect(Joi.validate('5', /^5$/)).to.not.exist; | ||
expect(Joi.validate('6', /.{2}/)).to.exist; | ||
done(); | ||
Joi.compile(/^5$/).validate('5', function (err, value) { | ||
expect(err).to.not.exist; | ||
Joi.compile(/.{2}/).validate('6', function (err, value) { | ||
expect(err).to.exist; | ||
done(); | ||
}); | ||
}); | ||
}); | ||
@@ -184,16 +182,21 @@ | ||
var schema = Joi.object({ | ||
txt: Joi.string().with('upc'), | ||
txt: Joi.string(), | ||
upc: Joi.string() | ||
}); | ||
}).with('txt', 'upc'); | ||
Validate(schema, [ | ||
[{ upc: 'test' }, true], | ||
[{ txt: 'test' }, false], | ||
[{ txt: 'test', upc: null }, false], | ||
[{ txt: 'test', upc: '' }, false], | ||
[{ txt: 'test', upc: undefined }, false], | ||
[{ txt: 'test', upc: 'test' }, true] | ||
]); | ||
Joi.validate({ txt: 'a' }, schema, { abortEarly: false }, function (err, value) { | ||
done(); | ||
expect(err.message).to.equal('txt missing required peer upc'); | ||
Validate(schema, [ | ||
[{ upc: 'test' }, true], | ||
[{ txt: 'test' }, false], | ||
[{ txt: 'test', upc: null }, false], | ||
[{ txt: 'test', upc: '' }, false], | ||
[{ txt: 'test', upc: undefined }, false], | ||
[{ txt: 'test', upc: 'test' }, true] | ||
]); | ||
done(); | ||
}); | ||
}); | ||
@@ -204,16 +207,21 @@ | ||
var schema = Joi.object({ | ||
txt: Joi.string().without('upc'), | ||
txt: Joi.string(), | ||
upc: Joi.string() | ||
}); | ||
}).without('txt', 'upc'); | ||
Validate(schema, [ | ||
[{ upc: 'test' }, true], | ||
[{ txt: 'test' }, true], | ||
[{ txt: 'test', upc: null }, false], | ||
[{ txt: 'test', upc: '' }, false], | ||
[{ txt: 'test', upc: undefined }, true], | ||
[{ txt: 'test', upc: 'test' }, false] | ||
]); | ||
Joi.validate({ txt: 'a', upc: 'b' }, schema, { abortEarly: false }, function (err, value) { | ||
done(); | ||
expect(err.message).to.equal('txt conflict with forbidden peer upc'); | ||
Validate(schema, [ | ||
[{ upc: 'test' }, true], | ||
[{ txt: 'test' }, true], | ||
[{ txt: 'test', upc: null }, false], | ||
[{ txt: 'test', upc: '' }, false], | ||
[{ txt: 'test', upc: undefined }, true], | ||
[{ txt: 'test', upc: 'test' }, false] | ||
]); | ||
done(); | ||
}); | ||
}); | ||
@@ -224,28 +232,30 @@ | ||
var schema = Joi.object({ | ||
txt: Joi.string().xor('upc'), | ||
upc: Joi.string().xor('txt') | ||
}); | ||
txt: Joi.string(), | ||
upc: Joi.string() | ||
}).xor('txt', 'upc'); | ||
var err = Joi.validate({}, schema, { abortEarly: false }); | ||
expect(err.message).to.equal('at least one of txt upc is required. at least one of upc txt is required'); | ||
Joi.validate({}, schema, { abortEarly: false }, function (err, value) { | ||
Validate(schema, [ | ||
[{ upc: null }, false], | ||
[{ upc: 'test' }, true], | ||
[{ txt: null }, false], | ||
[{ txt: 'test' }, true], | ||
[{ txt: 'test', upc: null }, false], | ||
[{ txt: 'test', upc: '' }, false], | ||
[{ txt: '', upc: 'test' }, false], | ||
[{ txt: null, upc: 'test' }, false], | ||
[{ txt: undefined, upc: 'test' }, true], | ||
[{ txt: 'test', upc: undefined }, true], | ||
[{ txt: 'test', upc: '' }, false], | ||
[{ txt: 'test', upc: null }, false], | ||
[{ txt: '', upc: undefined }, false], | ||
[{ txt: '', upc: '' }, false], | ||
[{ txt: 'test', upc: 'test' }, false] | ||
]); | ||
expect(err.message).to.equal('value must contain at least one of txt, upc'); | ||
done(); | ||
Validate(schema, [ | ||
[{ upc: null }, false], | ||
[{ upc: 'test' }, true], | ||
[{ txt: null }, false], | ||
[{ txt: 'test' }, true], | ||
[{ txt: 'test', upc: null }, false], | ||
[{ txt: 'test', upc: '' }, false], | ||
[{ txt: '', upc: 'test' }, false], | ||
[{ txt: null, upc: 'test' }, false], | ||
[{ txt: undefined, upc: 'test' }, true], | ||
[{ txt: 'test', upc: undefined }, true], | ||
[{ txt: 'test', upc: '' }, false], | ||
[{ txt: 'test', upc: null }, false], | ||
[{ txt: '', upc: undefined }, false], | ||
[{ txt: '', upc: '' }, false], | ||
[{ txt: 'test', upc: 'test' }, false] | ||
]); | ||
done(); | ||
}); | ||
}); | ||
@@ -256,6 +266,6 @@ | ||
var schema = Joi.object({ | ||
txt: Joi.string().xor('upc', 'code'), | ||
txt: Joi.string(), | ||
upc: Joi.string(), | ||
code: Joi.string() | ||
}); | ||
}).xor('txt', 'upc', 'code'); | ||
@@ -274,5 +284,5 @@ Validate(schema, [ | ||
var schema = Joi.object({ | ||
code: Joi.number().xor('upc'), | ||
code: Joi.number(), | ||
upc: Joi.number() | ||
}); | ||
}).xor('code', 'upc'); | ||
@@ -292,5 +302,5 @@ Validate(schema, [ | ||
var schema = Joi.object({ | ||
code: Joi.string().xor('upc'), | ||
code: Joi.string(), | ||
upc: Joi.string().allow('') | ||
}); | ||
}).xor('code', 'upc'); | ||
@@ -311,35 +321,76 @@ Validate(schema, [ | ||
var schema = Joi.object({ | ||
txt: Joi.string().or('upc', 'code'), | ||
txt: Joi.string(), | ||
upc: Joi.string().allow(null, ''), | ||
code: Joi.number() | ||
}).or('txt', 'upc', 'code'); | ||
Joi.validate({}, schema, { abortEarly: false }, function (err, value) { | ||
expect(err.message).to.equal('value must contain at least one of txt, upc, code'); | ||
Validate(schema, [ | ||
[{ upc: null }, true], | ||
[{ upc: 'test' }, true], | ||
[{ txt: null }, false], | ||
[{ txt: 'test' }, true], | ||
[{ code: null }, false], | ||
[{ code: 123 }, true], | ||
[{ txt: 'test', upc: null }, true], | ||
[{ txt: 'test', upc: '' }, true], | ||
[{ txt: '', upc: 'test' }, false], | ||
[{ txt: null, upc: 'test' }, false], | ||
[{ txt: undefined, upc: 'test' }, true], | ||
[{ txt: 'test', upc: undefined }, true], | ||
[{ txt: 'test', upc: '' }, true], | ||
[{ txt: 'test', upc: null }, true], | ||
[{ txt: '', upc: undefined }, false], | ||
[{ txt: '', upc: undefined, code: 999 }, false], | ||
[{ txt: '', upc: undefined, code: undefined }, false], | ||
[{ txt: '', upc: '' }, false], | ||
[{ txt: 'test', upc: 'test' }, true], | ||
[{ txt: 'test', upc: 'test', code: 322 }, true] | ||
]); | ||
done(); | ||
}); | ||
}); | ||
var err = Joi.validate({}, schema, { abortEarly: false }); | ||
expect(err.message).to.equal('missing alternative peers upc,code'); | ||
it('validates and', function (done) { | ||
Validate(schema, [ | ||
[{ upc: null }, true], | ||
[{ upc: 'test' }, true], | ||
[{ txt: null }, false], | ||
[{ txt: 'test' }, true], | ||
[{ code: null }, false], | ||
[{ code: 123 }, true], | ||
[{ txt: 'test', upc: null }, true], | ||
[{ txt: 'test', upc: '' }, true], | ||
[{ txt: '', upc: 'test' }, false], | ||
[{ txt: null, upc: 'test' }, false], | ||
[{ txt: undefined, upc: 'test' }, true], | ||
[{ txt: 'test', upc: undefined }, true], | ||
[{ txt: 'test', upc: '' }, true], | ||
[{ txt: 'test', upc: null }, true], | ||
[{ txt: '', upc: undefined }, false], | ||
[{ txt: '', upc: undefined, code: 999 }, false], | ||
[{ txt: '', upc: undefined, code: undefined }, false], | ||
[{ txt: '', upc: '' }, false], | ||
[{ txt: 'test', upc: 'test' }, true], | ||
[{ txt: 'test', upc: 'test', code: 322 }, true] | ||
var schema = Joi.object({ | ||
txt: Joi.string(), | ||
upc: Joi.string().allow(null, ''), | ||
code: Joi.number() | ||
}).and('txt', 'upc', 'code'); | ||
]); | ||
Joi.validate({ txt: 'x' }, schema, { abortEarly: false }, function (err, value) { | ||
done(); | ||
expect(err.message).to.equal('value contains txt without its required peers upc, code'); | ||
Validate(schema, [ | ||
[{ upc: null }, false], | ||
[{ upc: 'test' }, false], | ||
[{ txt: null }, false], | ||
[{ txt: 'test' }, false], | ||
[{ code: null }, false], | ||
[{ code: 123 }, false], | ||
[{ txt: 'test', upc: null }, false], | ||
[{ txt: 'test', upc: '' }, false], | ||
[{ txt: '', upc: 'test' }, false], | ||
[{ txt: null, upc: 'test' }, false], | ||
[{ txt: undefined, upc: 'test' }, false], | ||
[{ txt: 'test', upc: undefined }, false], | ||
[{ txt: 'test', upc: '' }, false], | ||
[{ txt: 'test', upc: null }, false], | ||
[{ txt: '', upc: undefined }, false], | ||
[{ txt: '', upc: undefined, code: 999 }, false], | ||
[{ txt: '', upc: undefined, code: undefined }, false], | ||
[{ txt: '', upc: '' }, false], | ||
[{ txt: 'test', upc: 'test' }, false], | ||
[{ txt: 'test', upc: 'test', code: 322 }, true], | ||
[{ txt: 'test', upc: null, code: 322 }, true] | ||
]); | ||
done(); | ||
}); | ||
}); | ||
@@ -349,3 +400,3 @@ | ||
var schema = { | ||
var schema = Joi.object({ | ||
auth: [ | ||
@@ -358,20 +409,22 @@ Joi.object({ | ||
] | ||
}; | ||
}); | ||
var err = Joi.validate({ auth: { mode: 'none' } }, schema); | ||
expect(err).to.exist; | ||
expect(err.message).to.equal('the value of mode must be one of required, optional, try, null. the value of auth must be a string. the value of auth must be a boolean'); | ||
schema.validate({ auth: { mode: 'none' } }, function (err, value) { | ||
Validate(schema, [ | ||
[{ auth: { mode: 'try' } }, true], | ||
[{ something: undefined }, false], | ||
[{ auth: { something: undefined } }, false], | ||
[{ auth: null }, true], | ||
[{ auth: undefined }, true], | ||
[{}, true], | ||
[{ auth: true }, true], | ||
[{ auth: 123 }, false] | ||
]); | ||
expect(err).to.exist; | ||
expect(err.message).to.equal('mode must be one of required, optional, try, null. auth must be a string. auth must be a boolean'); | ||
done(); | ||
Validate(schema, [ | ||
[{ auth: { mode: 'try' } }, true], | ||
[{ something: undefined }, false], | ||
[{ auth: { something: undefined } }, false], | ||
[{ auth: null }, true], | ||
[{ auth: undefined }, true], | ||
[{}, true], | ||
[{ auth: true }, true], | ||
[{ auth: 123 }, false] | ||
]); | ||
done(); | ||
}); | ||
}); | ||
@@ -381,3 +434,3 @@ | ||
var schema = { | ||
var schema = Joi.object({ | ||
auth: Joi.alternatives( | ||
@@ -390,17 +443,65 @@ Joi.object({ | ||
) | ||
}); | ||
schema.validate({ auth: { mode: 'none' } }, function (err, value) { | ||
expect(err).to.exist; | ||
expect(err.message).to.equal('mode must be one of required, optional, try, null. auth must be a string. auth must be a boolean'); | ||
Validate(schema, [ | ||
[{ auth: { mode: 'try' } }, true], | ||
[{ something: undefined }, false], | ||
[{ auth: { something: undefined } }, false], | ||
[{ auth: null }, true], | ||
[{ auth: undefined }, true], | ||
[{}, true], | ||
[{ auth: true }, true], | ||
[{ auth: 123 }, false] | ||
]); | ||
done(); | ||
}); | ||
}); | ||
it('validates required alternatives', function (done) { | ||
var schema = { | ||
a: Joi.alternatives( | ||
Joi.string().required(), | ||
Joi.boolean().required() | ||
) | ||
}; | ||
var err = Joi.validate({ auth: { mode: 'none' } }, schema); | ||
expect(err).to.exist; | ||
expect(err.message).to.equal('the value of mode must be one of required, optional, try, null. the value of auth must be a string. the value of auth must be a boolean'); | ||
Validate(schema, [ | ||
[{ a: null }, false], | ||
[{ a: undefined }, true], | ||
[{}, true], | ||
[{ a: true }, true], | ||
[{ a: 'true' }, true], | ||
[{ a: 123 }, false], | ||
[{ a: { c: 1 } }, false], | ||
[{ b: undefined }, false] | ||
]); | ||
done(); | ||
}); | ||
it('validates required [] alternatives', function (done) { | ||
var schema = { | ||
a: [ | ||
Joi.string().required(), | ||
Joi.boolean().required() | ||
] | ||
}; | ||
Validate(schema, [ | ||
[{ auth: { mode: 'try' } }, true], | ||
[{ something: undefined }, false], | ||
[{ auth: { something: undefined } }, false], | ||
[{ auth: null }, true], | ||
[{ auth: undefined }, true], | ||
[{ a: null }, false], | ||
[{ a: undefined }, true], | ||
[{}, true], | ||
[{ auth: true }, true], | ||
[{ auth: 123 }, false] | ||
[{ a: true }, true], | ||
[{ a: 'true' }, true], | ||
[{ a: 123 }, false], | ||
[{ a: { c: 1 } }, false], | ||
[{ b: undefined }, false] | ||
]); | ||
@@ -417,4 +518,6 @@ | ||
expect(Joi.validate({ brand: ['amex'] }, schema)).to.not.exist; | ||
expect(Joi.validate({ brand: ['visa', 'mc'] }, schema)).to.exist; | ||
Validate(schema, [ | ||
[{ brand: ['amex'] }, true], | ||
[{ brand: ['visa', 'mc'] }, false] | ||
]); | ||
done(); | ||
@@ -436,5 +539,5 @@ }); | ||
var schema = { | ||
var schema = Joi.object({ | ||
a: Joi.number().valid(2) | ||
}; | ||
}); | ||
@@ -445,5 +548,8 @@ var obj = { | ||
expect(Joi.validate(obj, schema, { modify: true })).to.exist; | ||
expect(obj.a).to.equal('5'); | ||
done(); | ||
schema.validate(obj, function (err, value) { | ||
expect(err).to.exist; | ||
expect(value.a).to.equal('5'); | ||
done(); | ||
}); | ||
}); | ||
@@ -453,12 +559,14 @@ | ||
var schema = { | ||
var schema = Joi.object({ | ||
a: Joi.number() | ||
}; | ||
}); | ||
var obj = {}; | ||
var err = Joi.validate(obj, schema, { modify: true }); | ||
expect(err).to.not.exist; | ||
expect(obj.hasOwnProperty('a')).to.equal(false); | ||
done(); | ||
schema.validate(obj, function (err, value) { | ||
expect(err).to.not.exist; | ||
expect(value.hasOwnProperty('a')).to.equal(false); | ||
done(); | ||
}); | ||
}); | ||
@@ -479,10 +587,12 @@ | ||
var schema = { | ||
username: Joi.string().with('password'), | ||
password: Joi.string().without('access_token') | ||
}; | ||
var schema = Joi.object({ | ||
username: Joi.string(), | ||
password: Joi.string() | ||
}).with('username', 'password').without('password', 'access_token'); | ||
var err = Joi.validate({ username: 'bob' }, schema); | ||
expect(err).to.exist; | ||
done(); | ||
schema.validate({ username: 'bob' }, function (err, value) { | ||
expect(err).to.exist; | ||
done(); | ||
}); | ||
}); | ||
@@ -492,90 +602,78 @@ | ||
expect(Joi.validate(true, Joi.boolean().allow(null))).to.be.null; | ||
expect(Joi.validate({ auth: { mode: 'try' } }, Joi.object())).to.be.null; | ||
Joi.boolean().allow(null).validate(true, function (err, value) { | ||
var err = Joi.validate(true, Joi.object()); | ||
expect(err.message).to.contain('the value of <root> must be an object'); | ||
expect(err).to.be.null; | ||
Joi.object().validate({ auth: { mode: 'try' } }, function (err, value) { | ||
err = Joi.validate(true, Joi.string()); | ||
expect(err.message).to.contain('the value of <root> must be a string'); | ||
expect(err).to.be.null; | ||
expect(Joi.validate('test@test.com', Joi.string().email())).to.be.null; | ||
expect(Joi.validate({ param: 'item' }, Joi.object({ param: Joi.string().required() }))).to.be.null; | ||
done(); | ||
}); | ||
Joi.object().validate(true, function (err, value) { | ||
it('validates config where the root item is a joi Object and modify setting is enabled', function (done) { | ||
expect(err.message).to.contain('value must be an object'); | ||
var config = Joi.object({ | ||
a: Joi.string() | ||
}); | ||
Joi.string().validate(true, function (err, value) { | ||
expect(Joi.validate({ a: 'okay' }, config, { modify: true })).to.be.null; | ||
done(); | ||
}); | ||
expect(err.message).to.contain('value must be a string'); | ||
it('converts string to number in a schema', function (done) { | ||
Joi.string().email().validate('test@test.com', function (err, value) { | ||
var config = { | ||
a: Joi.number() | ||
}; | ||
expect(err).to.be.null; | ||
Joi.object({ param: Joi.string().required() }).validate({ param: 'item' }, function (err, value) { | ||
var original = { a: '5' }; | ||
var validated = { a: 5 }; | ||
expect(Joi.validate(original, config, { modify: true })).to.be.null; | ||
expect(validated).to.deep.equal(original); | ||
done(); | ||
expect(err).to.be.null; | ||
done(); | ||
}); | ||
}); | ||
}); | ||
}); | ||
}); | ||
}); | ||
}); | ||
it('moves a key', function (done) { | ||
it('converts string to number', function (done) { | ||
var schema = { | ||
a: Joi.number().rename('b', { move: true }) | ||
}; | ||
var schema = Joi.object({ | ||
a: Joi.number() | ||
}); | ||
var obj = { a: 10 }; | ||
var input = { a: '5' }; | ||
schema.validate(input, function (err, value) { | ||
var err = Joi.validate(obj, schema); | ||
expect(err).to.not.exist; | ||
expect(obj.a).to.not.exist; | ||
expect(obj.b).to.equal(10); | ||
done(); | ||
expect(err).to.be.null; | ||
expect(value.a).to.equal(5); | ||
expect(input.a).to.equal('5'); | ||
done(); | ||
}); | ||
}); | ||
it('does not alter valid top level objects when modify setting is enabled', function (done) { | ||
it('allows unknown keys in objects if no schema was given', function (done) { | ||
var config = Joi.object({ | ||
a: Joi.string() | ||
Joi.object().validate({ foo: 'bar' }, function (err, value) { | ||
expect(err).to.not.exist; | ||
done(); | ||
}); | ||
}); | ||
var original = { a: 'okay' }; | ||
var validated = { a: 'okay' }; | ||
it('fails on unknown keys in objects if a schema was given', function (done) { | ||
expect(Joi.validate(validated, config, { modify: true })).to.be.null; | ||
expect(validated).to.deep.equal(original); | ||
done(); | ||
}); | ||
Joi.object({}).validate({ foo: 'bar' }, function (err, value) { | ||
it('allows unknown keys in objects if no schema was given', function (done) { | ||
expect(err).to.exist; | ||
expect(err.message).to.equal('foo is not allowed'); | ||
expect(Joi.validate({ foo: 'bar' }, Joi.object())).to.not.exist; | ||
done(); | ||
}); | ||
Joi.compile({}).validate({ foo: 'bar' }, function (err, value) { | ||
it('fails on unknown keys in objects if a schema was given', function (done) { | ||
expect(err).to.exist; | ||
expect(err.message).to.equal('foo is not allowed'); | ||
var err = Joi.validate({ foo: 'bar' }, Joi.object({})); | ||
expect(err).to.exist; | ||
expect(err.message).to.equal('the key foo is not allowed'); | ||
Joi.compile({ other: Joi.number() }).validate({ foo: 'bar' }, function (err, value) { | ||
err = Joi.validate({ foo: 'bar' }, {}); | ||
expect(err).to.exist; | ||
expect(err.message).to.equal('the key foo is not allowed'); | ||
expect(err).to.exist; | ||
expect(err.message).to.equal('foo is not allowed'); | ||
err = Joi.validate({ foo: 'bar' }, { other: Joi.number() }); | ||
expect(err).to.exist; | ||
expect(err.message).to.equal('the key foo is not allowed'); | ||
done(); | ||
done(); | ||
}); | ||
}); | ||
}); | ||
}); | ||
@@ -591,11 +689,15 @@ | ||
var err = Joi.validate({ auth: { unknown: true } }, config); | ||
expect(err).to.not.be.null; | ||
expect(err.message).to.contain('the key unknown is not allowed'); | ||
Joi.compile(config).validate({ auth: { unknown: true } }, function (err, value) { | ||
err = Joi.validate({ something: false }, config); | ||
expect(err).to.not.be.null; | ||
expect(err.message).to.contain('the key something is not allowed'); | ||
expect(err).to.not.be.null; | ||
expect(err.message).to.contain('unknown is not allowed'); | ||
done(); | ||
Joi.compile(config).validate({ something: false }, function (err, value) { | ||
expect(err).to.not.be.null; | ||
expect(err.message).to.contain('something is not allowed'); | ||
done(); | ||
}); | ||
}); | ||
}); | ||
@@ -606,25 +708,34 @@ | ||
var config = { | ||
module: [ | ||
module: Joi.alternatives([ | ||
Joi.object({ | ||
compile: Joi.func().required(), | ||
execute: Joi.func() | ||
}).required(), | ||
Joi.string().required() | ||
] | ||
}), | ||
Joi.string() | ||
]).required() | ||
}; | ||
var err = Joi.validate({}, config); | ||
expect(err).to.exist; | ||
expect(err.message).to.contain('the value of module is not allowed to be undefined'); | ||
Joi.compile(config).validate({}, function (err, value) { | ||
expect(Joi.validate({ module: 'test' }, config)).to.be.null; | ||
expect(err).to.exist; | ||
expect(err.message).to.contain('module is required'); | ||
err = Joi.validate({ module: {} }, config); | ||
expect(err).to.not.be.null; | ||
expect(err.message).to.contain('the value of compile is not allowed to be undefined'); | ||
expect(err.message).to.contain('the value of module must be a string'); | ||
Joi.compile(config).validate({ module: 'test' }, function (err, value) { | ||
expect(Joi.validate({ module: { compile: function () { } } }, config)).to.be.null; | ||
expect(err).to.be.null; | ||
done(); | ||
Joi.compile(config).validate({ module: {} }, function (err, value) { | ||
expect(err).to.not.be.null; | ||
expect(err.message).to.contain('compile is required'); | ||
expect(err.message).to.contain('module must be a string'); | ||
Joi.compile(config).validate({ module: { compile: function () { } } }, function (err, value) { | ||
expect(err).to.be.null; | ||
done(); | ||
}); | ||
}); | ||
}); | ||
}); | ||
}); | ||
@@ -635,3 +746,3 @@ | ||
var config = { | ||
module: Joi.alt( | ||
module: Joi.alt().try( | ||
Joi.object({ | ||
@@ -645,5 +756,7 @@ compile: Joi.func().required(), | ||
var err = Joi.validate({}, config); | ||
expect(err).to.not.exist; | ||
done(); | ||
Joi.compile(config).validate({}, function (err, value) { | ||
expect(err).to.not.exist; | ||
done(); | ||
}); | ||
}); | ||
@@ -654,3 +767,3 @@ | ||
var config = { | ||
module: Joi.alt( | ||
module: Joi.alt().try( | ||
Joi.object({ | ||
@@ -664,6 +777,8 @@ compile: Joi.func().required(), | ||
var err = Joi.validate({}, config); | ||
expect(err).to.exist; | ||
expect(err.message).to.contain('the value of module is not allowed to be undefined'); | ||
done(); | ||
Joi.compile(config).validate({}, function (err, value) { | ||
expect(err).to.exist; | ||
expect(err.message).to.contain('module is required'); | ||
done(); | ||
}); | ||
}); | ||
@@ -678,6 +793,12 @@ | ||
expect(Joi.validate({ suggestion: 'something' }, config)).to.be.null; | ||
expect(Joi.validate({ position: 1 }, config)).to.be.null; | ||
Joi.compile(config).validate({ suggestion: 'something' }, function (err, value) { | ||
done(); | ||
expect(err).to.be.null; | ||
Joi.compile(config).validate({ position: 1 }, function (err, value) { | ||
expect(err).to.be.null; | ||
done(); | ||
}) | ||
}); | ||
}); | ||
@@ -692,6 +813,12 @@ | ||
expect(Joi.validate({ suggestion: {} }, config)).to.be.null; | ||
expect(Joi.validate({ position: 1 }, config)).to.be.null; | ||
Joi.compile(config).validate({ suggestion: {} }, function (err, value) { | ||
done(); | ||
expect(err).to.be.null; | ||
Joi.compile(config).validate({ position: 1 }, function (err, value) { | ||
expect(err).to.be.null; | ||
done(); | ||
}); | ||
}); | ||
}); | ||
@@ -710,6 +837,8 @@ | ||
}; | ||
var err = Joi.validate(obj, schema); | ||
expect(err).to.not.exist; | ||
done(); | ||
Joi.compile(schema).validate(obj, function (err, value) { | ||
expect(err).to.not.exist; | ||
done(); | ||
}); | ||
}); | ||
@@ -729,6 +858,8 @@ | ||
}; | ||
var err = Joi.validate(obj, schema); | ||
expect(err).to.not.exist; | ||
done(); | ||
Joi.compile(schema).validate(obj, function (err, value) { | ||
expect(err).to.not.exist; | ||
done(); | ||
}); | ||
}); | ||
@@ -749,6 +880,8 @@ | ||
}; | ||
var err = Joi.validate(obj, schema); | ||
expect(err).to.exist; | ||
done(); | ||
Joi.compile(schema).validate(obj, function (err, value) { | ||
expect(err).to.exist; | ||
done(); | ||
}); | ||
}); | ||
@@ -769,6 +902,8 @@ | ||
}; | ||
var err = Joi.validate(obj, schema); | ||
expect(err).to.exist; | ||
done(); | ||
Joi.compile(schema).validate(obj, function (err, value) { | ||
expect(err).to.exist; | ||
done(); | ||
}); | ||
}); | ||
@@ -781,6 +916,8 @@ | ||
}; | ||
var err = Joi.validate(obj, { a: Joi.string().required() }); | ||
expect(err).to.exist; | ||
done(); | ||
Joi.compile({ a: Joi.string().required() }).validate(obj, function (err, value) { | ||
expect(err).to.exist; | ||
done(); | ||
}); | ||
}); | ||
@@ -793,6 +930,8 @@ | ||
}; | ||
var err = Joi.validate(obj, { a: Joi.object({ b: Joi.string().required() }) }); | ||
expect(err).to.exist; | ||
done(); | ||
Joi.compile({ a: Joi.object({ b: Joi.string().required() }) }).validate(obj, function (err, value) { | ||
expect(err).to.exist; | ||
done(); | ||
}); | ||
}); | ||
@@ -805,5 +944,8 @@ | ||
}; | ||
var err = Joi.validate(obj, { a: Joi.object({ b: Joi.string().required() }) }); | ||
expect(err).to.exist; | ||
done(); | ||
Joi.compile({ a: Joi.object({ b: Joi.string().required() }) }).validate(obj, function (err, value) { | ||
expect(err).to.exist; | ||
done(); | ||
}); | ||
}); | ||
@@ -813,8 +955,19 @@ | ||
var obj = { | ||
var schema = { | ||
a: Joi.object({ | ||
b: Joi.string().required() | ||
}) | ||
}; | ||
var input = { | ||
a: '{"b":"string"}' | ||
}; | ||
var err = Joi.validate(obj, { a: Joi.object({ b: Joi.string().required() }) }); | ||
expect(err).to.be.null; | ||
done(); | ||
Joi.validate(input, schema, function (err, value) { | ||
expect(err).to.not.exist; | ||
expect(input.a).to.equal('{"b":"string"}'); | ||
expect(value.a.b).to.equal('string'); | ||
done(); | ||
}); | ||
}); | ||
@@ -827,5 +980,8 @@ | ||
}; | ||
var err = Joi.validate(obj, { a: Joi.object({ b: Joi.string().required() }) }); | ||
expect(err).to.exist; | ||
done(); | ||
Joi.object({ a: Joi.object({ b: Joi.string().required() }) }).validate(obj, function (err, value) { | ||
expect(err).to.exist; | ||
done(); | ||
}); | ||
}); | ||
@@ -838,5 +994,8 @@ | ||
}; | ||
var err = Joi.validate(obj, { a: Joi.array() }); | ||
expect(err).to.exist; | ||
done(); | ||
Joi.object({ a: Joi.array() }).validate(obj, function (err, value) { | ||
expect(err).to.exist; | ||
done(); | ||
}); | ||
}); | ||
@@ -849,5 +1008,8 @@ | ||
}; | ||
var err = Joi.validate(obj, { a: Joi.array() }); | ||
expect(err).to.be.null; | ||
done(); | ||
Joi.object({ a: Joi.array() }).validate(obj, function (err, value) { | ||
expect(err).to.be.null; | ||
done(); | ||
}); | ||
}); | ||
@@ -860,5 +1022,8 @@ | ||
}; | ||
var err = Joi.validate(obj, { a: Joi.object({ b: Joi.string().required() }) }); | ||
expect(err).to.exist; | ||
done(); | ||
Joi.object({ a: Joi.object({ b: Joi.string().required() }) }).validate(obj, function (err, value) { | ||
expect(err).to.exist; | ||
done(); | ||
}); | ||
}); | ||
@@ -877,6 +1042,8 @@ | ||
}; | ||
var err = Joi.validate(obj, schema); | ||
expect(err).to.exist; | ||
done(); | ||
Joi.compile(schema).validate(obj, function (err, value) { | ||
expect(err).to.exist; | ||
done(); | ||
}); | ||
}); | ||
@@ -895,6 +1062,8 @@ | ||
}; | ||
var err = Joi.validate(obj, schema); | ||
expect(err).to.exist; | ||
done(); | ||
Joi.compile(schema).validate(obj, function (err, value) { | ||
expect(err).to.exist; | ||
done(); | ||
}); | ||
}); | ||
@@ -912,6 +1081,8 @@ | ||
}; | ||
var err = Joi.validate(obj, schema); | ||
expect(err).to.exist; | ||
done(); | ||
Joi.compile(schema).validate(obj, function (err, value) { | ||
expect(err).to.exist; | ||
done(); | ||
}); | ||
}); | ||
@@ -925,5 +1096,7 @@ | ||
var err = Joi.validate({}, schema); | ||
expect(err).to.not.exist; | ||
done(); | ||
Joi.compile(schema).validate({}, function (err, value) { | ||
expect(err).to.not.exist; | ||
done(); | ||
}); | ||
}); | ||
@@ -944,7 +1117,9 @@ | ||
}; | ||
var err = Joi.validate(obj, schema, { stripUnknown: true, allowUnknown: true }); | ||
expect(err).to.be.null; | ||
expect(obj).to.deep.equal({ a: 1, b: 'a' }); | ||
done(); | ||
Joi.validate(obj, schema, { stripUnknown: true, allowUnknown: true }, function (err, value) { | ||
expect(err).to.be.null; | ||
expect(value).to.deep.equal({ a: 1, b: 'a' }); | ||
done(); | ||
}); | ||
}); | ||
@@ -965,6 +1140,8 @@ | ||
}; | ||
var err = Joi.validate(obj, schema, { stripUnknown: true, abortEarly: false }); | ||
expect(err).to.exist; | ||
done(); | ||
Joi.validate(obj, schema, { stripUnknown: true, abortEarly: false }, function (err, value) { | ||
expect(err).to.exist; | ||
done(); | ||
}); | ||
}); | ||
@@ -985,7 +1162,9 @@ | ||
}; | ||
var err = Joi.validate(obj, schema, { allowUnknown: true }); | ||
expect(err).to.be.null; | ||
expect(obj).to.deep.equal({ a: 1, b: 'a', d: 'c' }); | ||
done(); | ||
Joi.validate(obj, schema, { allowUnknown: true }, function (err, value) { | ||
expect(err).to.be.null; | ||
expect(value).to.deep.equal({ a: 1, b: 'a', d: 'c' }); | ||
done(); | ||
}); | ||
}); | ||
@@ -995,6 +1174,6 @@ | ||
var localConfig = { | ||
var localConfig = Joi.object({ | ||
a: Joi.number().min(0).max(3), | ||
b: Joi.string().valid('a', 'b', 'c'), | ||
}; | ||
}).options({ allowUnknown: true }); | ||
@@ -1006,12 +1185,15 @@ var obj = { | ||
}; | ||
var err = Joi.validate(obj, localConfig, { allowUnknown: true }); | ||
expect(err).to.be.null; | ||
expect(obj).to.deep.equal({ a: 1, b: 'a', d: 'c' }); | ||
localConfig.validate(obj, function (err, value) { | ||
err = Joi.validate(obj, localConfig, { allowUnknown: true }); | ||
expect(err).to.be.null; | ||
expect(value).to.deep.equal({ a: 1, b: 'a', d: 'c' }); | ||
expect(err).to.be.null; | ||
expect(obj).to.deep.equal({ a: 1, b: 'a', d: 'c' }); | ||
done(); | ||
localConfig.validate(value, function (err, value) { | ||
expect(err).to.be.null; | ||
expect(value).to.deep.equal({ a: 1, b: 'a', d: 'c' }); | ||
done(); | ||
}); | ||
}); | ||
}); | ||
@@ -1022,6 +1204,6 @@ | ||
var localConfig = { | ||
var localConfig = Joi.object({ | ||
a: Joi.number().min(0).max(3), | ||
b: Joi.string().valid('a', 'b', 'c') | ||
}; | ||
}).options({ stripUnknown: true, allowUnknown: true }); | ||
@@ -1033,13 +1215,16 @@ var obj = { | ||
}; | ||
var err = Joi.validate(obj, localConfig, { stripUnknown: true, allowUnknown: true }); | ||
expect(err).to.be.null; | ||
expect(obj).to.deep.equal({ a: 1, b: 'a' }); | ||
localConfig.validate(obj, function (err, value) { | ||
err = Joi.validate(obj, localConfig, { stripUnknown: true, allowUnknown: true }); | ||
expect(err).to.be.null; | ||
expect(value).to.deep.equal({ a: 1, b: 'a' }); | ||
expect(err).to.be.null; | ||
expect(obj).to.deep.equal({ a: 1, b: 'a' }); | ||
localConfig.validate(value, function (err, value) { | ||
done(); | ||
expect(err).to.be.null; | ||
expect(value).to.deep.equal({ a: 1, b: 'a' }); | ||
done(); | ||
}); | ||
}); | ||
}); | ||
@@ -1051,6 +1236,7 @@ | ||
var input = { username: 'test', func: function () { } }; | ||
var err = Joi.validate(input, schema); | ||
Joi.validate(input, schema, function (err, value) { | ||
expect(err).to.not.exist; | ||
done(); | ||
expect(err).to.not.exist; | ||
done(); | ||
}); | ||
}); | ||
@@ -1062,31 +1248,11 @@ | ||
var input = { username: 'test', func: function () { } }; | ||
var err = Joi.validate(input, schema, { skipFunctions: false }); | ||
expect(err).to.exist; | ||
expect(err.message).to.contain('the key func is not allowed'); | ||
done(); | ||
}); | ||
Joi.validate(input, schema, { skipFunctions: false }, function (err, value) { | ||
it('should work when the modify setting is enabled', function (done) { | ||
var schema = { item: Joi.number() }; | ||
var input = { item: '1' }; | ||
var err = Joi.validate(input, schema, { modify: true }); | ||
expect(err).to.not.exist; | ||
expect(input.item).to.equal(1); | ||
done(); | ||
expect(err).to.exist; | ||
expect(err.message).to.contain('func is not allowed'); | ||
done(); | ||
}); | ||
}); | ||
it('should work when the modify setting is disabled', function (done) { | ||
var schema = { item: Joi.number() }; | ||
var input = { item: '1' }; | ||
var err = Joi.validate(input, schema, { modify: false }); | ||
expect(err).to.not.exist; | ||
expect(input.item).to.equal('1'); | ||
done(); | ||
}); | ||
it('should not convert values when convert is false', function (done) { | ||
@@ -1099,6 +1265,7 @@ | ||
var input = { arr: 'foo' }; | ||
var err = Joi.validate(input, schema, { convert: false }); | ||
Joi.validate(input, schema, { convert: false }, function (err, value) { | ||
expect(err).to.exist; | ||
done(); | ||
expect(err).to.exist; | ||
done(); | ||
}); | ||
}); | ||
@@ -1115,123 +1282,27 @@ | ||
var errOne = Joi.validate(input, schema); | ||
var errFull = Joi.validate(input, schema, { abortEarly: false }); | ||
Joi.validate(input, schema, function (errOne, value) { | ||
expect(errOne).to.exist | ||
expect(errFull).to.exist | ||
expect(errFull.details.length).to.be.greaterThan(errOne.details.length); | ||
done(); | ||
}); | ||
Joi.validate(input, schema, { abortEarly: false }, function (errFull, value) { | ||
it('supports custom errors when validating types', function (done) { | ||
var schema = { | ||
email: Joi.string().email(), | ||
date: Joi.date(), | ||
alphanum: Joi.string().alphanum(), | ||
min: Joi.string().min(3), | ||
max: Joi.string().max(3), | ||
required: Joi.string().required().without('xor'), | ||
xor: Joi.string().without('required'), | ||
renamed: Joi.string().rename('required').valid('456'), | ||
notEmpty: Joi.string().required() | ||
}; | ||
var input = { | ||
email: 'invalid-email', | ||
date: 'invalid-date', | ||
alphanum: '\b\n\f\r\t', | ||
min: 'ab', | ||
max: 'abcd', | ||
required: 'hello', | ||
xor: '123', | ||
renamed: '456', | ||
notEmpty: '' | ||
}; | ||
var lang = { | ||
any: { | ||
empty: '3', | ||
without: { | ||
peer: '7' | ||
}, | ||
rename: { | ||
override: '11' | ||
} | ||
}, | ||
date: { | ||
base: '18' | ||
}, | ||
string: { | ||
base: '13', | ||
min: '14', | ||
max: '15', | ||
alphanum: '16', | ||
email: '19' | ||
} | ||
}; | ||
var err = Joi.validate(input, schema, { abortEarly: false, language: lang }); | ||
expect(err).to.exist; | ||
expect(err.message).to.equal('19. 18. 16. 14. 15. 7. 7. 11. 3. 13'); | ||
done(); | ||
expect(errOne).to.exist | ||
expect(errFull).to.exist | ||
expect(errFull.details.length).to.be.greaterThan(errOne.details.length); | ||
done(); | ||
}); | ||
}); | ||
}); | ||
it('returns error type in validation error', function (done) { | ||
it('validates using the root any object', function (done) { | ||
var input = { | ||
notNumber: '', | ||
notString: true, | ||
notBoolean: 9 | ||
}; | ||
var any = Joi; | ||
any.validate('abc', function (err, value) { | ||
var schema = { | ||
notNumber: Joi.number().required(), | ||
notString: Joi.string().required(), | ||
notBoolean: Joi.boolean().required() | ||
} | ||
var err = Joi.validate(input, schema, { abortEarly: false }); | ||
expect(err).to.exist; | ||
expect(err.details).to.have.length(3); | ||
expect(err.details[0].type).to.equal('number.base'); | ||
expect(err.details[1].type).to.equal('string.base'); | ||
expect(err.details[2].type).to.equal('boolean.base'); | ||
done(); | ||
expect(err).to.not.exist; | ||
done(); | ||
}); | ||
}); | ||
it('annotates error', function (done) { | ||
var object = { | ||
a: 'm', | ||
y: { | ||
b: { | ||
c: 10 | ||
} | ||
} | ||
}; | ||
var schema = { | ||
a: Joi.string().valid('a', 'b', 'c', 'd'), | ||
y: Joi.object({ | ||
u: Joi.string().valid(['e', 'f', 'g', 'h']).required(), | ||
b: Joi.string().valid('i', 'j').allow(false), | ||
d: Joi.object({ | ||
x: Joi.string().valid('k', 'l').required(), | ||
c: Joi.number() | ||
}) | ||
}) | ||
}; | ||
var err = Joi.validate(object, schema, { abortEarly: false }); | ||
expect(err).to.exist; | ||
err.annotated(); | ||
expect(err.message).to.equal('{\n \"y\": {\n \"b\" \u001b[31m[6]\u001b[0m: {\n \"c\": 10\n },\n \u001b[41m\"b\"\u001b[0m\u001b[31m [5]: -- missing --\u001b[0m,\n \u001b[41m\"u\"\u001b[0m\u001b[31m [4]: -- missing --\u001b[0m,\n \u001b[41m\"u\"\u001b[0m\u001b[31m [3]: -- missing --\u001b[0m,\n \u001b[41m\"u\"\u001b[0m\u001b[31m [2]: -- missing --\u001b[0m\n },\n \"a\" \u001b[31m[1]\u001b[0m: \"m\"\n}\n\u001b[31m\n[1] the value of a must be one of a, b, c, d\n[2] the value of u is not allowed to be undefined\n[3] the value of u must be one of e, f, g, h\n[4] the value of u must be a string\n[5] the value of b must be one of i, j, false\n[6] the value of b must be a string\u001b[0m'); | ||
done(); | ||
}); | ||
describe('#describe', function () { | ||
var schema = { | ||
var schema = Joi.object({ | ||
sub: { | ||
@@ -1246,15 +1317,10 @@ email: Joi.string().email(), | ||
max: Joi.string().max(3), | ||
required: Joi.string().required().without('xor'), | ||
xor: Joi.string().without('required'), | ||
renamed: Joi.string().rename('required').valid('456'), | ||
required: Joi.string().required(), | ||
xor: Joi.string(), | ||
renamed: Joi.string().valid('456'), | ||
notEmpty: Joi.string().required().description('a').notes('b').tags('c') | ||
}; | ||
}).rename('renamed', 'required').without('required', 'xor').without('xor', 'required'); | ||
var result = { | ||
type: 'object', | ||
flags: { | ||
insensitive: false, | ||
allowOnly: false, | ||
default: undefined | ||
}, | ||
valids: [undefined], | ||
@@ -1265,7 +1331,2 @@ invalids: [null], | ||
type: 'object', | ||
flags: { | ||
insensitive: false, | ||
allowOnly: false, | ||
default: undefined | ||
}, | ||
valids: [undefined], | ||
@@ -1276,7 +1337,2 @@ invalids: [null], | ||
type: 'string', | ||
flags: { | ||
insensitive: false, | ||
allowOnly: false, | ||
default: undefined | ||
}, | ||
valids: [undefined], | ||
@@ -1288,7 +1344,2 @@ invalids: [null, ''], | ||
type: 'date', | ||
flags: { | ||
insensitive: false, | ||
allowOnly: false, | ||
default: undefined | ||
}, | ||
valids: [undefined], | ||
@@ -1299,7 +1350,2 @@ invalids: [null] | ||
type: 'object', | ||
flags: { | ||
insensitive: false, | ||
allowOnly: false, | ||
default: undefined | ||
}, | ||
valids: [undefined], | ||
@@ -1310,7 +1356,2 @@ invalids: [null], | ||
type: 'string', | ||
flags: { | ||
insensitive: false, | ||
allowOnly: false, | ||
default: undefined | ||
}, | ||
valids: [undefined], | ||
@@ -1327,7 +1368,2 @@ invalids: [null, ''], | ||
type: 'number', | ||
flags: { | ||
insensitive: false, | ||
allowOnly: false, | ||
default: undefined | ||
}, | ||
valids: [undefined], | ||
@@ -1338,7 +1374,2 @@ invalids: [null] | ||
type: 'string', | ||
flags: { | ||
insensitive: false, | ||
allowOnly: false, | ||
default: undefined | ||
}, | ||
valids: [undefined], | ||
@@ -1351,7 +1382,2 @@ invalids: [null, ''], | ||
type: 'string', | ||
flags: { | ||
insensitive: false, | ||
allowOnly: false, | ||
default: undefined | ||
}, | ||
valids: [undefined], | ||
@@ -1363,20 +1389,8 @@ invalids: [null, ''], | ||
type: 'string', | ||
flags: { | ||
insensitive: false, | ||
allowOnly: false, | ||
default: undefined | ||
}, | ||
invalids: [null, '', undefined], | ||
rules: [{ name: 'without', arg: ['xor'] }] | ||
invalids: [null, '', undefined] | ||
}, | ||
xor: { | ||
type: 'string', | ||
flags: { | ||
insensitive: false, | ||
allowOnly: false, | ||
default: undefined | ||
}, | ||
valids: [undefined], | ||
invalids: [null, ''], | ||
rules: [{ name: 'without', arg: ['required'] }] | ||
invalids: [null, ''] | ||
}, | ||
@@ -1386,5 +1400,3 @@ renamed: { | ||
flags: { | ||
insensitive: false, | ||
allowOnly: true, | ||
default: undefined | ||
allowOnly: true | ||
}, | ||
@@ -1396,7 +1408,2 @@ valids: [undefined, '456'], | ||
type: 'string', | ||
flags: { | ||
insensitive: false, | ||
allowOnly: false, | ||
default: undefined | ||
}, | ||
description: 'a', | ||
@@ -1407,8 +1414,20 @@ notes: ['b'], | ||
} | ||
} | ||
}, | ||
dependencies: [ | ||
{ | ||
type: 'without', | ||
key: 'required', | ||
peers: ['xor'] | ||
}, | ||
{ | ||
type: 'without', | ||
key: 'xor', | ||
peers: ['required'] | ||
} | ||
] | ||
}; | ||
it('describes schema', function (done) { | ||
it('describes schema (direct)', function (done) { | ||
var description = Joi.describe(schema); | ||
var description = schema.describe(); | ||
expect(description).to.deep.equal(result); | ||
@@ -1418,5 +1437,5 @@ done(); | ||
it('describes schema with object', function (done) { | ||
it('describes schema (root)', function (done) { | ||
var description = Joi.describe(Joi.object(schema)); | ||
var description = Joi.describe(schema); | ||
expect(description).to.deep.equal(result); | ||
@@ -1426,5 +1445,17 @@ done(); | ||
it('describes schema (any)', function (done) { | ||
var any = Joi; | ||
var description = any.describe(); | ||
expect(description).to.deep.equal({ | ||
type: 'any', | ||
valids: [undefined], | ||
invalids: [null] | ||
}); | ||
done(); | ||
}); | ||
it('describes schema without invalids', function (done) { | ||
var description = Joi.describe(Joi.any().allow(null)); | ||
var description = Joi.allow(null).describe(); | ||
expect(description.invalids).to.not.exist; | ||
@@ -1431,0 +1462,0 @@ done(); |
@@ -22,4 +22,15 @@ // Load modules | ||
describe('Joi.number', function () { | ||
describe('number', function () { | ||
it('fails on boolean', function (done) { | ||
var schema = Joi.number(); | ||
Validate(schema, [ | ||
[true, false], | ||
[false, false] | ||
]); | ||
done(); | ||
}); | ||
describe('#validate', function () { | ||
@@ -46,5 +57,7 @@ | ||
var text = Joi.number().invalid(50); | ||
var result = text.validate(50); | ||
expect(result).to.exist; | ||
done(); | ||
text.validate(50, function (err, value) { | ||
expect(err).to.exist; | ||
done(); | ||
}); | ||
}); | ||
@@ -98,20 +111,28 @@ | ||
var error = Joi.validate(obj, config, { modify: true }); | ||
expect(error).to.not.exist; | ||
expect(obj.a).to.equal(123); | ||
done(); | ||
Joi.compile(config).validate(obj, function (err, value) { | ||
expect(err).to.not.exist; | ||
expect(value.a).to.equal(123); | ||
done(); | ||
}); | ||
}); | ||
it('convert will convert a string to a number', function (done) { | ||
it('converts a string to a number', function (done) { | ||
var t = Joi.number()._convert('1'); | ||
expect(t).to.equal(1); | ||
done(); | ||
Joi.number().validate('1', function (err, value) { | ||
expect(err).to.not.exist; | ||
expect(value).to.equal(1); | ||
done(); | ||
}); | ||
}); | ||
it('convert will not convert a null', function (done) { | ||
it('errors on null', function (done) { | ||
var t = Joi.number()._convert(null); | ||
expect(t).to.not.exist; | ||
done(); | ||
Joi.number().validate(null, function (err, value) { | ||
expect(err).to.exist; | ||
expect(value).to.equal(null); | ||
done(); | ||
}); | ||
}); | ||
@@ -405,5 +426,7 @@ | ||
var t = Joi.number().integer(); | ||
var result = Joi.validate('1.1', t); | ||
expect(result.message).to.contain('integer'); | ||
done(); | ||
Joi.compile(t).validate('1.1', function (err, value) { | ||
expect(err.message).to.contain('integer'); | ||
done(); | ||
}); | ||
}); | ||
@@ -422,2 +445,15 @@ }); | ||
}); | ||
it('supports 64bit numbers', function (done) { | ||
var schema = Joi.number().min(1394035612500); | ||
var input = 1394035612552 | ||
schema.validate(input, function (err, value) { | ||
expect(err).to.not.exist; | ||
expect(value).to.equal(input); | ||
done(); | ||
}); | ||
}); | ||
}); | ||
@@ -424,0 +460,0 @@ |
@@ -22,280 +22,664 @@ // Load modules | ||
describe('Types', function () { | ||
describe('object', function () { | ||
describe('Object', function () { | ||
it('converts a json string to an object', function (done) { | ||
it('can convert a json string to an object', function (done) { | ||
Joi.object().validate('{"hi":true}', function (err, value) { | ||
var result = Joi.object()._convert('{"hi":true}'); | ||
expect(result.hi).to.be.true; | ||
expect(err).to.not.exist; | ||
expect(value.hi).to.equal(true); | ||
done(); | ||
}); | ||
}); | ||
it('should convert a non-json string as a string', function (done) { | ||
it('errors on non-object string', function (done) { | ||
var result = Joi.object()._convert('a string'); | ||
expect(result).to.be.equal('a string'); | ||
Joi.object().validate('a string', function (err, value) { | ||
expect(err).to.exist; | ||
expect(value).to.equal('a string'); | ||
done(); | ||
}); | ||
}); | ||
it('should validate an object', function (done) { | ||
it('should validate an object', function (done) { | ||
var schema = Joi.object().required(); | ||
Validate(schema, [ | ||
[{ }, true], | ||
[{ hi: true }, true], | ||
['', false] | ||
]); | ||
done(); | ||
var schema = Joi.object().required(); | ||
Validate(schema, [ | ||
[{}, true], | ||
[{ hi: true }, true], | ||
['', false] | ||
]); | ||
done(); | ||
}); | ||
it('allows any key when schema is undefined', function (done) { | ||
Joi.object().validate({ a: 4 }, function (err, value) { | ||
expect(err).to.not.exist; | ||
Joi.object(undefined).validate({ a: 4 }, function (err, value) { | ||
expect(err).to.not.exist; | ||
done(); | ||
}); | ||
}); | ||
}); | ||
it('allows any key when schema is undefined', function (done) { | ||
it('allows any key when schema is null', function (done) { | ||
expect(Joi.validate({ a: 4 }, Joi.object())).to.not.exist; | ||
expect(Joi.validate({ a: 4 }, Joi.object(undefined))).to.not.exist; | ||
Joi.object(null).validate({ a: 4 }, function (err, value) { | ||
expect(err).to.not.exist; | ||
done(); | ||
}); | ||
}); | ||
it('allows any key when schema is null', function (done) { | ||
it('throws on invalid object schema', function (done) { | ||
expect(Joi.validate({ a: 4 }, Joi.object(null))).to.not.exist; | ||
expect(function () { | ||
Joi.object(4); | ||
}).to.throw('Object schema must be a valid object'); | ||
done(); | ||
}); | ||
it('throws on joi object schema', function (done) { | ||
expect(function () { | ||
Joi.object(Joi.object()); | ||
}).to.throw('Object schema cannot be a joi schema'); | ||
done(); | ||
}); | ||
it('skips conversion when value is undefined', function (done) { | ||
Joi.object({ a: Joi.object() }).validate(undefined, function (err, value) { | ||
expect(err).to.not.exist; | ||
expect(value).to.not.exist; | ||
done(); | ||
}); | ||
}); | ||
it('throws on invalid object schema', function (done) { | ||
it('errors on array', function (done) { | ||
expect(function () { | ||
Joi.object().validate([1, 2, 3], function (err, value) { | ||
Joi.object(4); | ||
}).to.throw('Object schema must be a valid object and cannot be a joi schema'); | ||
expect(err).to.exist; | ||
done(); | ||
}); | ||
}); | ||
it('throws on joi object schema', function (done) { | ||
it('should prevent extra keys from existing by default', function (done) { | ||
expect(function () { | ||
var schema = Joi.object({ item: Joi.string().required() }).required(); | ||
Validate(schema, [ | ||
[{ item: 'something' }, true], | ||
[{ item: 'something', item2: 'something else' }, false], | ||
['', false] | ||
]); | ||
done(); | ||
}); | ||
Joi.object(Joi.object()); | ||
}).to.throw('Object schema must be a valid object and cannot be a joi schema'); | ||
done(); | ||
it('should validate count when min is set', function (done) { | ||
var schema = Joi.object().min(3); | ||
Validate(schema, [ | ||
[{ item: 'something' }, false], | ||
[{ item: 'something', item2: 'something else' }, false], | ||
[{ item: 'something', item2: 'something else', item3: 'something something else' }, true], | ||
['', false] | ||
]); | ||
done(); | ||
}); | ||
it('should validate count when max is set', function (done) { | ||
var schema = Joi.object().max(2); | ||
Validate(schema, [ | ||
[{ item: 'something' }, true], | ||
[{ item: 'something', item2: 'something else' }, true], | ||
[{ item: 'something', item2: 'something else', item3: 'something something else' }, false], | ||
['', false] | ||
]); | ||
done(); | ||
}); | ||
it('should validate count when min and max is set', function (done) { | ||
var schema = Joi.object().max(3).min(2); | ||
Validate(schema, [ | ||
[{ item: 'something' }, false], | ||
[{ item: 'something', item2: 'something else' }, true], | ||
[{ item: 'something', item2: 'something else', item3: 'something something else' }, true], | ||
[{ item: 'something', item2: 'something else', item3: 'something something else', item4: 'item4' }, false], | ||
['', false] | ||
]); | ||
done(); | ||
}); | ||
it('should validate count when length is set', function (done) { | ||
var schema = Joi.object().length(2); | ||
Validate(schema, [ | ||
[{ item: 'something' }, false], | ||
[{ item: 'something', item2: 'something else' }, true], | ||
[{ item: 'something', item2: 'something else', item3: 'something something else' }, false], | ||
['', false] | ||
]); | ||
done(); | ||
}); | ||
it('should traverse an object and validate all properties in the top level', function (done) { | ||
var schema = Joi.object({ | ||
num: Joi.number() | ||
}); | ||
it('errors on array', function (done) { | ||
Validate(schema, [ | ||
[{ num: 1 }, true], | ||
[{ num: [1, 2, 3] }, false] | ||
]); | ||
done(); | ||
}); | ||
expect(Joi.validate([1, 2, 3], Joi.object())).to.exist; | ||
done(); | ||
it('should traverse an object and child objects and validate all properties', function (done) { | ||
var schema = Joi.object({ | ||
num: Joi.number(), | ||
obj: Joi.object({ | ||
item: Joi.string() | ||
}) | ||
}); | ||
it('should prevent extra keys from existing by default', function (done) { | ||
Validate(schema, [ | ||
[{ num: 1 }, true], | ||
[{ num: [1, 2, 3] }, false], | ||
[{ num: 1, obj: { item: 'something' } }, true], | ||
[{ num: 1, obj: { item: 123 } }, false] | ||
]); | ||
done(); | ||
}); | ||
var schema = Joi.object({ item: Joi.string().required() }).required(); | ||
Validate(schema, [ | ||
[{ item: 'something' }, true], | ||
[{ item: 'something', item2: 'something else' }, false], | ||
['', false] | ||
]); | ||
done(); | ||
it('should traverse an object several levels', function (done) { | ||
var schema = Joi.object({ | ||
obj: Joi.object({ | ||
obj: Joi.object({ | ||
obj: Joi.object({ | ||
item: Joi.boolean() | ||
}) | ||
}) | ||
}) | ||
}); | ||
it('should validate the key count when min is set', function (done) { | ||
Validate(schema, [ | ||
[{ num: 1 }, false], | ||
[{ obj: {} }, true], | ||
[{ obj: { obj: {} } }, true], | ||
[{ obj: { obj: { obj: {} } } }, true], | ||
[{ obj: { obj: { obj: { item: true } } } }, true], | ||
[{ obj: { obj: { obj: { item: 10 } } } }, false] | ||
]); | ||
done(); | ||
}); | ||
var schema = Joi.object().min(3); | ||
Validate(schema, [ | ||
[{ item: 'something' }, false], | ||
[{ item: 'something', item2: 'something else' }, false], | ||
[{ item: 'something', item2: 'something else', item3: 'something something else' }, true], | ||
['', false] | ||
]); | ||
done(); | ||
it('should traverse an object several levels with required levels', function (done) { | ||
var schema = Joi.object({ | ||
obj: Joi.object({ | ||
obj: Joi.object({ | ||
obj: Joi.object({ | ||
item: Joi.boolean() | ||
}) | ||
}).required() | ||
}) | ||
}); | ||
it('should validate the key count when max is set', function (done) { | ||
Validate(schema, [ | ||
[null, false], | ||
[undefined, true], | ||
[{}, true], | ||
[{ obj: {} }, false], | ||
[{ obj: { obj: {} } }, true], | ||
[{ obj: { obj: { obj: {} } } }, true], | ||
[{ obj: { obj: { obj: { item: true } } } }, true], | ||
[{ obj: { obj: { obj: { item: 10 } } } }, false] | ||
]); | ||
done(); | ||
}); | ||
var schema = Joi.object().max(2); | ||
Validate(schema, [ | ||
[{ item: 'something' }, true], | ||
[{ item: 'something', item2: 'something else' }, true], | ||
[{ item: 'something', item2: 'something else', item3: 'something something else' }, false], | ||
['', false] | ||
]); | ||
it('should traverse an object several levels with required levels (without Joi.obj())', function (done) { | ||
var schema = { | ||
obj: { | ||
obj: { | ||
obj: { | ||
item: Joi.boolean().required() | ||
} | ||
} | ||
} | ||
}; | ||
Validate(schema, [ | ||
[null, false], | ||
[undefined, true], | ||
[{}, true], | ||
[{ obj: {} }, true], | ||
[{ obj: { obj: {} } }, true], | ||
[{ obj: { obj: { obj: {} } } }, false], | ||
[{ obj: { obj: { obj: { item: true } } } }, true], | ||
[{ obj: { obj: { obj: { item: 10 } } } }, false] | ||
]); | ||
done(); | ||
}); | ||
it('errors on unknown keys when functions allows', function (done) { | ||
var schema = Joi.object({ a: Joi.number() }).options({ skipFunctions: true }); | ||
var obj = { a: 5, b: 'value' }; | ||
schema.validate(obj, function (err, value) { | ||
expect(err).to.exist; | ||
done(); | ||
}); | ||
}); | ||
it('should validate the key count when min and max is set', function (done) { | ||
it('validates both valid() and with()', function (done) { | ||
var schema = Joi.object().max(3).min(2); | ||
Validate(schema, [ | ||
[{ item: 'something' }, false], | ||
[{ item: 'something', item2: 'something else' }, true], | ||
[{ item: 'something', item2: 'something else', item3: 'something something else' }, true], | ||
[{ item: 'something', item2: 'something else', item3: 'something something else', item4: 'item4' }, false], | ||
['', false] | ||
]); | ||
done(); | ||
var schema = Joi.object({ | ||
first: Joi.valid('value'), | ||
second: Joi.any() | ||
}).with('first', 'second'); | ||
Validate(schema, [[{ first: 'value' }, false]]); | ||
done(); | ||
}); | ||
describe('#keys', function () { | ||
it('allows any key', function (done) { | ||
var a = Joi.object({ a: 4 }); | ||
var b = a.keys(); | ||
a.validate({ b: 3 }, function (err, value) { | ||
expect(err).to.exist; | ||
b.validate({ b: 3 }, function (err, value) { | ||
expect(err).to.not.exist; | ||
done(); | ||
}); | ||
}); | ||
}); | ||
it('should validate the key count when length is set', function (done) { | ||
it('forbids all keys', function (done) { | ||
var schema = Joi.object().length(2); | ||
Validate(schema, [ | ||
[{ item: 'something' }, false], | ||
[{ item: 'something', item2: 'something else' }, true], | ||
[{ item: 'something', item2: 'something else', item3: 'something something else' }, false], | ||
['', false] | ||
]); | ||
done(); | ||
var a = Joi.object(); | ||
var b = a.keys({}); | ||
a.validate({ b: 3 }, function (err, value) { | ||
expect(err).to.not.exist; | ||
b.validate({ b: 3 }, function (err, value) { | ||
expect(err).to.exist; | ||
done(); | ||
}); | ||
}); | ||
}); | ||
it('should traverse an object and validate all properties in the top level', function (done) { | ||
it('adds to existing keys', function (done) { | ||
var a = Joi.object({ a: 1 }); | ||
var b = a.keys({ b: 2 }); | ||
a.validate({ a: 1, b: 2 }, function (err, value) { | ||
expect(err).to.exist; | ||
b.validate({ a: 1, b: 2 }, function (err, value) { | ||
expect(err).to.not.exist; | ||
done(); | ||
}); | ||
}); | ||
}); | ||
}); | ||
describe('#rename', function () { | ||
it('allows renaming multiple times with multiple enabled', function (done) { | ||
var schema = Joi.object({ | ||
num: Joi.number() | ||
test: Joi.string() | ||
}).rename('test1', 'test').rename('test2', 'test', { multiple: true }); | ||
Joi.compile(schema).validate({ test1: 'a', test2: 'b' }, function (err, value) { | ||
expect(err).to.not.exist; | ||
done(); | ||
}); | ||
}); | ||
Validate(schema, [ | ||
[{ num: 1 }, true], | ||
[{ num: [1,2,3] }, false] | ||
]); | ||
done(); | ||
it('errors renaming multiple times with multiple disabled', function (done) { | ||
var schema = Joi.object({ | ||
test: Joi.string() | ||
}).rename('test1', 'test').rename('test2', 'test'); | ||
Joi.compile(schema).validate({ test1: 'a', test2: 'b' }, function (err, value) { | ||
expect(err.message).to.equal('value cannot rename child test2 because multiple renames are disabled and another key was already renamed to test'); | ||
done(); | ||
}); | ||
}); | ||
it('should traverse an object and child objects and validate all properties', function (done) { | ||
it('errors multiple times when abortEarly is false', function (done) { | ||
Joi.object().rename('a', 'b').rename('c', 'b').rename('d', 'b').options({ abortEarly: false }).validate({ a: 1, c: 1, d: 1 }, function (err, value) { | ||
expect(err).to.exist; | ||
expect(err.message).to.equal('value cannot rename child c because multiple renames are disabled and another key was already renamed to b. value cannot rename child d because multiple renames are disabled and another key was already renamed to b'); | ||
done(); | ||
}); | ||
}); | ||
it('aliases a key', function (done) { | ||
var schema = Joi.object({ | ||
num: Joi.number(), | ||
obj: Joi.object({ | ||
item: Joi.string() | ||
}) | ||
a: Joi.number(), | ||
b: Joi.number() | ||
}).rename('a', 'b', { alias: true }); | ||
var obj = { a: 10 }; | ||
Joi.compile(schema).validate(obj, function (err, value) { | ||
expect(err).to.not.exist; | ||
expect(value.a).to.equal(10); | ||
expect(value.b).to.equal(10); | ||
done(); | ||
}); | ||
}); | ||
Validate(schema, [ | ||
[{ num: 1 }, true], | ||
[{ num: [1,2,3] }, false], | ||
[{ num: 1, obj: { item: 'something' }}, true], | ||
[{ num: 1, obj: { item: 123 }}, false] | ||
]); | ||
done(); | ||
it('with override disabled should not allow overwriting existing value', function (done) { | ||
var schema = Joi.object({ | ||
test1: Joi.string() | ||
}).rename('test', 'test1'); | ||
schema.validate({ test: 'b', test1: 'a' }, function (err, value) { | ||
expect(err.message).to.equal('value cannot rename child test because override is disabled and target test1 exists'); | ||
done(); | ||
}); | ||
}); | ||
it('should traverse an object several levels', function (done) { | ||
it('with override enabled should allow overwriting existing value', function (done) { | ||
var schema = Joi.object({ | ||
obj: Joi.object({ | ||
obj: Joi.object({ | ||
obj: Joi.object({ | ||
item: Joi.boolean() | ||
}) | ||
}) | ||
}) | ||
test1: Joi.string() | ||
}).rename('test', 'test1', { override: true }); | ||
schema.validate({ test: 'b', test1: 'a' }, function (err, value) { | ||
expect(err).to.not.exist; | ||
done(); | ||
}); | ||
}); | ||
Validate(schema, [ | ||
[{ num: 1 }, false], | ||
[{ obj: {} }, true], | ||
[{ obj: { obj: { }}}, true], | ||
[{ obj: { obj: { obj: { } }}}, true], | ||
[{ obj: { obj: { obj: { item: true } }}}, true], | ||
[{ obj: { obj: { obj: { item: 10 } }}}, false] | ||
]); | ||
done(); | ||
it('renames when data is nested in an array via includes', function (done) { | ||
var schema = { | ||
arr: Joi.array().includes(Joi.object({ | ||
one: Joi.string(), | ||
two: Joi.string() | ||
}).rename('uno', 'one').rename('dos', 'two')) | ||
}; | ||
var data = { arr: [{ uno: '1', dos: '2' }] }; | ||
Joi.object(schema).validate(data, function (err, value) { | ||
expect(err).to.not.exist; | ||
expect(value.arr[0].one).to.equal('1'); | ||
expect(value.arr[0].two).to.equal('2'); | ||
done(); | ||
}); | ||
}); | ||
it('should traverse an object several levels with required levels', function (done) { | ||
it('applies rename and validation in the correct order regardless of key order', function (done) { | ||
var schema1 = Joi.object({ | ||
a: Joi.number() | ||
}).rename('b', 'a'); | ||
var input1 = { b: '5' }; | ||
schema1.validate(input1, function (err1, value1) { | ||
expect(err1).to.not.exist; | ||
expect(value1.b).to.not.exist; | ||
expect(value1.a).to.equal(5); | ||
var schema2 = Joi.object({ a: Joi.number(), b: Joi.any() }).rename('b', 'a'); | ||
var input2 = { b: '5' }; | ||
schema2.validate(input2, function (err2, value2) { | ||
expect(err2).to.not.exist; | ||
expect(value2.b).to.not.exist; | ||
expect(value2.a).to.equal(5); | ||
done(); | ||
}); | ||
}); | ||
}); | ||
it('sets the default value after key is renamed', function (done) { | ||
var schema = Joi.object({ | ||
obj: Joi.object({ | ||
obj: Joi.object({ | ||
obj: Joi.object({ | ||
item: Joi.boolean() | ||
}) | ||
}).required() | ||
}) | ||
foo2: Joi.string().default('test') | ||
}).rename('foo', 'foo2'); | ||
var input = {}; | ||
Joi.validate(input, schema, function (err, value) { | ||
expect(err).to.not.exist; | ||
expect(value.foo2).to.equal('test'); | ||
done(); | ||
}); | ||
}); | ||
}); | ||
Validate(schema, [ | ||
[null, false], | ||
[undefined, true], | ||
[{}, true], | ||
[{ obj: {} }, false], | ||
[{ obj: { obj: {} } }, true], | ||
[{ obj: { obj: { obj: {} } } }, true], | ||
[{ obj: { obj: { obj: { item: true } } } }, true], | ||
[{ obj: { obj: { obj: { item: 10 } } } }, false] | ||
]); | ||
describe('#describe', function () { | ||
it('return empty description when no schema defined', function (done) { | ||
var schema = Joi.object(); | ||
var desc = schema.describe(); | ||
expect(desc).to.deep.equal({ | ||
type: 'object', | ||
valids: [undefined], | ||
invalids: [null] | ||
}); | ||
done(); | ||
}); | ||
}); | ||
it('should traverse an object several levels with required levels (without Joi.obj())', function (done) { | ||
describe('#length', function () { | ||
var schema = { | ||
obj: { | ||
obj: { | ||
obj: { | ||
item: Joi.boolean().required() | ||
} | ||
} | ||
} | ||
}; | ||
it('throws when length is not a number', function (done) { | ||
Validate(schema, [ | ||
[null, false], | ||
[undefined, true], | ||
[{}, true], | ||
[{ obj: {} }, true], | ||
[{ obj: { obj: {} } }, true], | ||
[{ obj: { obj: { obj: {} } } }, false], | ||
[{ obj: { obj: { obj: { item: true } } } }, true], | ||
[{ obj: { obj: { obj: { item: 10 } } } }, false] | ||
]); | ||
expect(function () { | ||
Joi.object().length('a'); | ||
}).to.throw('limit must be a positive integer'); | ||
done(); | ||
}); | ||
}); | ||
it('errors on unknown keys when functions allows', function (done) { | ||
describe('#min', function () { | ||
var schema = { a: Joi.number() }; | ||
var obj = { a: 5, b: 'value' }; | ||
expect(Joi.validate(obj, schema, { skipFunctions: true })).to.exist; | ||
it('throws when limit is not a number', function (done) { | ||
expect(function () { | ||
Joi.object().min('a'); | ||
}).to.throw('limit must be a positive integer'); | ||
done(); | ||
}); | ||
}); | ||
describe('#describe', function () { | ||
describe('#max', function () { | ||
it('return empty description when no schema defined', function (done) { | ||
it('throws when limit is not a number', function (done) { | ||
var schema = Joi.object(); | ||
var desc = schema.describe(); | ||
expect(desc).to.deep.equal({ | ||
type: 'object', | ||
flags: { | ||
insensitive: false, | ||
allowOnly: false, | ||
default: undefined | ||
}, | ||
valids: [undefined], | ||
invalids: [null] | ||
}); | ||
done(); | ||
}); | ||
expect(function () { | ||
Joi.object().max('a'); | ||
}).to.throw('limit must be a positive integer'); | ||
done(); | ||
}); | ||
}); | ||
describe('#length', function () { | ||
describe('#with', function () { | ||
it('throws when length is not a number', function (done) { | ||
it('should throw an error when a parameter is not a string', function (done) { | ||
expect(function () { | ||
try { | ||
Joi.object().with({}); | ||
var error = false; | ||
} | ||
catch (e) { | ||
error = true; | ||
} | ||
expect(error).to.equal(true); | ||
Joi.object().length('a'); | ||
}).to.throw('limit must be an integer'); | ||
try { | ||
Joi.object().with(123); | ||
error = false; | ||
} | ||
catch (e) { | ||
error = true; | ||
} | ||
expect(error).to.equal(true); | ||
done(); | ||
}); | ||
}); | ||
describe('#without', function () { | ||
it('should throw an error when a parameter is not a string', function (done) { | ||
try { | ||
Joi.object().without({}); | ||
var error = false; | ||
} | ||
catch (e) { | ||
error = true; | ||
} | ||
expect(error).to.equal(true); | ||
try { | ||
Joi.object().without(123); | ||
error = false; | ||
} | ||
catch (e) { | ||
error = true; | ||
} | ||
expect(error).to.equal(true); | ||
done(); | ||
}); | ||
}); | ||
describe('#xor', function () { | ||
it('should throw an error when a parameter is not a string', function (done) { | ||
try { | ||
Joi.object().xor({}); | ||
var error = false; | ||
} | ||
catch (e) { | ||
error = true; | ||
} | ||
expect(error).to.equal(true); | ||
try { | ||
Joi.object().xor(123); | ||
error = false; | ||
} | ||
catch (e) { | ||
error = true; | ||
} | ||
expect(error).to.equal(true); | ||
done(); | ||
}); | ||
}); | ||
describe('#or', function () { | ||
it('should throw an error when a parameter is not a string', function (done) { | ||
try { | ||
Joi.object().or({}); | ||
var error = false; | ||
} | ||
catch (e) { | ||
error = true; | ||
} | ||
expect(error).to.equal(true); | ||
try { | ||
Joi.object().or(123); | ||
error = false; | ||
} | ||
catch (e) { | ||
error = true; | ||
} | ||
expect(error).to.equal(true); | ||
done(); | ||
}); | ||
it('errors multiple levels deep', function (done) { | ||
Joi.object({ | ||
a: { | ||
b: Joi.object().or('x', 'y') | ||
} | ||
}).validate({ a: { b: { c: 1 } } }, function (err, value) { | ||
expect(err).to.exist; | ||
expect(err.message).to.equal('value must contain at least one of x, y'); | ||
done(); | ||
}); | ||
}); | ||
}); | ||
describe('#min', function () { | ||
describe('#assert', function () { | ||
it('throws when limit is not a number', function (done) { | ||
it('validates upwards reference', function (done) { | ||
expect(function () { | ||
var schema = Joi.object({ | ||
a: { | ||
b: Joi.string(), | ||
c: Joi.number() | ||
}, | ||
d: { | ||
e: Joi.any() | ||
} | ||
}).assert(Joi.ref('d/e', { separator: '/' }), Joi.ref('a.c'), 'equal to a.c'); | ||
Joi.object().min('a'); | ||
}).to.throw('limit must be an integer'); | ||
schema.validate({ a: { b: 'x', c: 5 }, d: { e: 6 } }, function (err, value) { | ||
expect(err).to.exist; | ||
expect(err.message).to.equal('value validation failed because d.e failed to equal to a.c'); | ||
Validate(schema, [ | ||
[{ a: { b: 'x', c: 5 }, d: { e: 5 } }, true] | ||
]); | ||
done(); | ||
@@ -305,15 +689,45 @@ }); | ||
describe('#max', function () { | ||
it('validates upwards reference with implicit context', function (done) { | ||
it('throws when limit is not a number', function (done) { | ||
var schema = Joi.object({ | ||
a: { | ||
b: Joi.string(), | ||
c: Joi.number() | ||
}, | ||
d: { | ||
e: Joi.any() | ||
} | ||
}).assert('d.e', Joi.ref('a.c'), 'equal to a.c'); | ||
expect(function () { | ||
schema.validate({ a: { b: 'x', c: 5 }, d: { e: 6 } }, function (err, value) { | ||
Joi.object().max('a'); | ||
}).to.throw('limit must be an integer'); | ||
expect(err).to.exist; | ||
expect(err.message).to.equal('value validation failed because d.e failed to equal to a.c'); | ||
Validate(schema, [ | ||
[{ a: { b: 'x', c: 5 }, d: { e: 5 } }, true] | ||
]); | ||
done(); | ||
}); | ||
}); | ||
it('throws when context is at root level', function (done) { | ||
expect(function () { | ||
var schema = Joi.object({ | ||
a: { | ||
b: Joi.string(), | ||
c: Joi.number() | ||
}, | ||
d: { | ||
e: Joi.any() | ||
} | ||
}).assert('a', Joi.ref('d.e'), 'equal to d.e'); | ||
}).to.throw('Cannot use assertions for root level references - use direct key rules instead'); | ||
done(); | ||
}); | ||
}); | ||
}); | ||
@@ -22,4 +22,15 @@ // Load modules | ||
describe('Joi.string', function () { | ||
describe('string', function () { | ||
it('fails on boolean', function (done) { | ||
var schema = Joi.string(); | ||
Validate(schema, [ | ||
[true, false], | ||
[false, false] | ||
]); | ||
done(); | ||
}); | ||
describe('#valid', function () { | ||
@@ -62,6 +73,20 @@ | ||
['A', true], | ||
['B', true] | ||
['B', true], | ||
[4, false] | ||
]); | ||
done(); | ||
}); | ||
it('validates case insensitive values with non-strings', function (done) { | ||
Validate(Joi.string().valid('a', 'b', 5).insensitive(), [ | ||
['a', true], | ||
['b', true], | ||
['A', true], | ||
['B', true], | ||
[4, false], | ||
[5, true] | ||
]); | ||
done(); | ||
}); | ||
}); | ||
@@ -131,2 +156,13 @@ | ||
}); | ||
it('enforces a limit using byte count', function (done) { | ||
var schema = Joi.string().min(2, 'utf8'); | ||
Validate(schema, [ | ||
['\u00bd', true], | ||
['a', false] | ||
]); | ||
done(); | ||
}); | ||
}); | ||
@@ -153,2 +189,13 @@ | ||
}); | ||
it('enforces a limit using byte count', function (done) { | ||
var schema = Joi.string().max(1, 'utf8'); | ||
Validate(schema, [ | ||
['\u00bd', false], | ||
['a', true] | ||
]); | ||
done(); | ||
}); | ||
}); | ||
@@ -175,16 +222,40 @@ | ||
}); | ||
it('enforces a limit using byte count', function (done) { | ||
var schema = Joi.string().length(2, 'utf8'); | ||
Validate(schema, [ | ||
['\u00bd', true], | ||
['a', false] | ||
]); | ||
done(); | ||
}); | ||
}); | ||
describe('#validate', function () { | ||
describe('#hostname', function () { | ||
it('should work', function (done) { | ||
it('validates hostnames', function (done) { | ||
expect(function () { | ||
var schema = Joi.string().hostname(); | ||
Validate(schema, [ | ||
['www.example.com', true], | ||
['domain.local', true], | ||
['3domain.local', true], | ||
['hostname', true], | ||
['host:name', false], | ||
['-', false], | ||
['2387628', true], | ||
['01234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789', false], | ||
['::1', true], | ||
['0:0:0:0:0:0:0:1', true], | ||
['0:?:0:0:0:0:0:1', false] | ||
]); | ||
var text = Joi.string(); | ||
var result = text.validate('joi'); | ||
}).to.not.throw; | ||
done(); | ||
}); | ||
}); | ||
describe('#validate', function () { | ||
it('should, by default, allow undefined, deny empty string', function (done) { | ||
@@ -211,6 +282,7 @@ | ||
var schema = Joi.string().required(); | ||
var result = Joi.validate('', schema); | ||
Joi.compile(schema).validate('', function (err, value) { | ||
expect(result.message).to.contain('be empty'); | ||
done(); | ||
expect(err.message).to.contain('be empty'); | ||
done(); | ||
}); | ||
}); | ||
@@ -379,6 +451,7 @@ | ||
var schema = { item: Joi.string().email() }; | ||
var err = Joi.validate({ item: 'something' }, schema); | ||
Joi.compile(schema).validate({ item: 'something' }, function (err, value) { | ||
expect(err.message).to.contain('must be a valid email'); | ||
done(); | ||
expect(err.message).to.contain('must be a valid email'); | ||
done(); | ||
}); | ||
}); | ||
@@ -389,5 +462,7 @@ | ||
var text = Joi.string().invalid('joi'); | ||
var result = text.validate('joi'); | ||
expect(result).to.exist; | ||
done(); | ||
text.validate('joi', function (err, value) { | ||
expect(err).to.exist; | ||
done(); | ||
}); | ||
}); | ||
@@ -398,5 +473,7 @@ | ||
var text = Joi.string().allow('hapi'); | ||
var result = text.validate('result'); | ||
expect(result).to.not.exist; | ||
done(); | ||
text.validate('result', function (err, value) { | ||
expect(err).to.not.exist; | ||
done(); | ||
}); | ||
}); | ||
@@ -407,5 +484,7 @@ | ||
var text = Joi.string().min(3); | ||
var result = text.validate('joi'); | ||
expect(result).to.not.exist; | ||
done(); | ||
text.validate('joi', function (err, value) { | ||
expect(err).to.not.exist; | ||
done(); | ||
}); | ||
}); | ||
@@ -416,9 +495,12 @@ | ||
var text = Joi.string().min(3).required(); | ||
var result = text.validate('joi'); | ||
expect(result).to.not.exist; | ||
text.validate('joi', function (err, value) { | ||
var result2 = text.validate(); | ||
expect(result2).to.exist; | ||
expect(err).to.not.exist; | ||
done(); | ||
text.validate('', function (err, value) { | ||
expect(err).to.exist; | ||
done(); | ||
}); | ||
}); | ||
}); | ||
@@ -911,6 +993,7 @@ | ||
var schema = { item: Joi.string().isoDate() }; | ||
var err = Joi.validate({ item: 'something' }, schema); | ||
Joi.compile(schema).validate({ item: 'something' }, function (err, value) { | ||
expect(err.message).to.contain('must be a valid ISO 8601 date'); | ||
done(); | ||
expect(err.message).to.contain('must be a valid ISO 8601 date'); | ||
done(); | ||
}); | ||
}); | ||
@@ -1233,6 +1316,7 @@ | ||
var schema = { item: Joi.string().guid() }; | ||
var err = Joi.validate({ item: 'something' }, schema); | ||
Joi.compile(schema).validate({ item: 'something' }, function (err, value) { | ||
expect(err.message).to.contain('must be a valid GUID'); | ||
done(); | ||
expect(err.message).to.contain('must be a valid GUID'); | ||
done(); | ||
}); | ||
}); | ||
@@ -1239,0 +1323,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
Network access
Supply chain riskThis module accesses the network.
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
427068
40
6584
966
2
1
+ Addedtopo@1.x.x
+ Addedtopo@1.1.0(transitive)
Updatedhoek@^2.1.x