Socket
Socket
Sign inDemoInstall

joi

Package Overview
Dependencies
Maintainers
4
Versions
238
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

joi - npm Package Compare versions

Comparing version 3.1.0 to 4.0.0

lib/binary.js

126

lib/alternatives.js

@@ -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();
// 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();
// 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 @@ {

@@ -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();
});
});
});

@@ -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]);
});
}
};

@@ -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 @@

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc