1
0
Fork 0
arangodb/js/node/node_modules/joi/lib/any.js

658 lines
16 KiB
JavaScript
Executable File

// Load modules
var Path = require('path');
var Hoek = require('hoek');
var Ref = require('./ref');
var Errors = require('./errors');
var Alternatives = null; // Delay-loaded to prevent circular dependencies
var Cast = null;
// Declare internals
var internals = {};
internals.defaults = {
abortEarly: true,
convert: true,
allowUnknown: false,
skipFunctions: false,
stripUnknown: false,
language: {}
// context: null
};
module.exports = internals.Any = function () {
this.isJoi = true;
this._type = 'any';
this._settings = null;
this._valids = new internals.Set();
this._invalids = new internals.Set();
this._tests = [];
this._refs = [];
this._flags = { /*
presence: 'optional', // optional, required, forbidden, ignore
allowOnly: false,
allowUnknown: undefined,
default: undefined,
forbidden: false,
encoding: undefined,
insensitive: false,
trim: false,
case: undefined // upper, lower
*/};
this._description = null;
this._unit = null;
this._notes = [];
this._tags = [];
this._examples = [];
this._meta = [];
this._inner = {}; // Hash of arrays of immutable objects
};
internals.Any.prototype.isImmutable = true; // Prevents Hoek from deep cloning schema objects
internals.Any.prototype.clone = function () {
var obj = {};
obj.__proto__ = Object.getPrototypeOf(this);
obj.isJoi = true;
obj._type = this._type;
obj._settings = internals.concatSettings(this._settings);
obj._valids = Hoek.clone(this._valids);
obj._invalids = Hoek.clone(this._invalids);
obj._tests = this._tests.slice();
obj._refs = this._refs.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._meta = this._meta.slice();
obj._inner = {};
var inners = Object.keys(this._inner);
for (var i = 0, il = inners.length; i < il; ++i) {
var key = inners[i];
obj._inner[key] = this._inner[key] ? this._inner[key].slice() : null;
}
return obj;
};
internals.Any.prototype.concat = function (schema) {
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();
obj._settings = obj._settings ? internals.concatSettings(obj._settings, schema._settings) : schema._settings;
obj._valids.merge(schema._valids, schema._invalids);
obj._invalids.merge(schema._invalids, schema._valids);
obj._tests = obj._tests.concat(schema._tests);
obj._refs = obj._refs.concat(schema._refs);
Hoek.merge(obj._flags, schema._flags);
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._meta = obj._meta.concat(schema._meta);
var inners = Object.keys(schema._inner);
for (var i = 0, il = inners.length; i < il; ++i) {
var key = inners[i];
if (schema._inner[key]) {
obj._inner[key] = (obj._inner[key] ? obj._inner[key].concat(schema._inner[key]) : schema._inner[key].slice());
}
}
return obj;
};
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._tests.push({ func: func, name: name, arg: arg });
return obj;
};
internals.Any.prototype.options = function (options) {
Hoek.assert(!options.context, 'Cannot override context');
var obj = this.clone();
obj._settings = internals.concatSettings(obj._settings, options);
return obj;
};
internals.Any.prototype.strict = function () {
var obj = this.clone();
obj._settings = obj._settings || {};
obj._settings.convert = false;
return obj;
};
internals.Any.prototype._allow = function () {
var values = Hoek.flatten(Array.prototype.slice.call(arguments));
for (var i = 0, il = values.length; i < il; ++i) {
var value = values[i];
this._invalids.remove(value);
this._valids.add(value, this._refs);
}
};
internals.Any.prototype.allow = function () {
var obj = this.clone();
obj._allow.apply(obj, arguments);
return obj;
};
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);
obj._flags.allowOnly = true;
return obj;
};
internals.Any.prototype.invalid = internals.Any.prototype.not = function (value) {
var obj = this.clone();
var values = Hoek.flatten(Array.prototype.slice.call(arguments));
for (var i = 0, il = values.length; i < il; ++i) {
var value = values[i];
obj._valids.remove(value);
obj._invalids.add(value, this._refs);
}
return obj;
};
internals.Any.prototype.required = internals.Any.prototype.exist = function () {
var obj = this.clone();
obj._flags.presence = 'required';
return obj;
};
internals.Any.prototype.optional = function () {
var obj = this.clone();
delete obj._flags.presence; // Defaults to 'optional'
return obj;
};
internals.Any.prototype.default = function (value) {
var obj = this.clone();
obj._flags.default = value;
Ref.push(obj._refs, value);
return obj;
};
internals.Any.prototype.forbidden = function () {
var obj = this.clone();
obj._flags.presence = 'forbidden';
return obj;
};
internals.Any.prototype.when = function (ref, options) {
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"');
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;
Alternatives = Alternatives || require('./alternatives');
var obj = Alternatives.when(ref, { is: options.is, then: then, otherwise: otherwise });
obj._flags.presence = 'ignore';
return obj;
};
internals.Any.prototype.description = function (desc) {
Hoek.assert(desc && typeof desc === 'string', 'Description must be a non-empty string');
var obj = this.clone();
obj._description = desc;
return obj;
};
internals.Any.prototype.notes = function (notes) {
Hoek.assert(notes && (typeof notes === 'string' || Array.isArray(notes)), 'Notes must be a non-empty string or array');
var obj = this.clone();
obj._notes = obj._notes.concat(notes);
return obj;
};
internals.Any.prototype.tags = function (tags) {
Hoek.assert(tags && (typeof tags === 'string' || Array.isArray(tags)), 'Tags must be a non-empty string or array');
var obj = this.clone();
obj._tags = obj._tags.concat(tags);
return obj;
};
internals.Any.prototype.meta = function (meta) {
Hoek.assert(meta !== undefined, 'Meta cannot be undefined');
var obj = this.clone();
obj._meta = obj._meta.concat(meta);
return obj;
};
internals.Any.prototype.example = function (value) {
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._examples = obj._examples.concat(value);
return obj;
};
internals.Any.prototype.unit = function (name) {
Hoek.assert(name && typeof name === 'string', 'Unit name must be a non-empty string');
var obj = this.clone();
obj._unit = name;
return obj;
};
internals.Any.prototype._validate = function (value, state, options, reference) {
var self = this;
// Setup state and settings
state = state || { key: '', path: '', parent: null, reference: reference };
if (this._settings) {
options = internals.concatSettings(options, this._settings);
}
var errors = [];
var finish = function () {
return {
value: (value !== undefined) ? value : (Ref.isRef(self._flags.default) ? self._flags.default(state.parent, options) : self._flags.default),
errors: errors.length ? errors : null
};
};
// Check presence requirements
if (this._flags.presence) { // 'required', 'forbidden', or 'ignore'
if (this._flags.presence === 'required' &&
value === undefined) {
errors.push(Errors.create('any.required', null, state, options));
return finish();
}
else if (this._flags.presence === 'forbidden') {
if (value === undefined) {
return finish();
}
errors.push(Errors.create('any.unknown', null, state, options));
return finish();
}
}
else { // 'optional'
if (value === undefined) {
return finish();
}
}
// Check allowed and denied values using the original value
if (this._valids.has(value, state, options, this._flags.insensitive)) {
return finish();
}
if (this._invalids.has(value, state, options, this._flags.insensitive)) {
errors.push(Errors.create(value === '' ? 'any.empty' : 'any.invalid', null, state, options));
if (options.abortEarly ||
value === undefined) { // No reason to keep validating missing value
return finish();
}
}
// 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
}
if (base.value !== value) {
value = base.value;
// Check allowed and denied values using the converted value
if (this._valids.has(value, state, options, this._flags.insensitive)) {
return finish();
}
if (this._invalids.has(value, state, options, this._flags.insensitive)) {
errors.push(Errors.create('any.invalid', null, state, options));
if (options.abortEarly) {
return finish();
}
}
}
}
// Required values did not match
if (this._flags.allowOnly) {
errors.push(Errors.create('any.allowOnly', { valids: this._valids.toString(false) }, state, options));
if (options.abortEarly) {
return finish();
}
}
// Helper.validate tests
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();
};
internals.Any.prototype._validateWithOptions = function (value, options, callback) {
var settings = internals.concatSettings(internals.defaults, options);
var result = this._validate(value, null, settings);
var errors = Errors.process(result.errors, value);
if (callback) {
return callback(errors, result.value);
}
return { error: errors, value: result.value };
};
internals.Any.prototype.validate = function (value, callback) {
var result = this._validate(value, null, internals.defaults);
var errors = Errors.process(result.errors, value);
if (callback) {
return callback(errors, result.value);
}
return { error: errors, value: result.value };
};
internals.Any.prototype.describe = function () {
var description = {
type: this._type
};
if (Object.keys(this._flags).length) {
description.flags = this._flags;
}
if (this._description) {
description.description = this._description;
}
if (this._notes.length) {
description.notes = this._notes;
}
if (this._tags.length) {
description.tags = this._tags;
}
if (this._meta.length) {
description.meta = this._meta;
}
if (this._examples.length) {
description.examples = this._examples;
}
if (this._unit) {
description.unit = this._unit;
}
var valids = this._valids.values();
if (valids.length) {
description.valids = valids;
}
var invalids = this._invalids.values();
if (invalids.length) {
description.invalids = invalids;
}
description.rules = [];
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);
}
if (!description.rules.length) {
delete description.rules;
}
return description;
};
// Set
internals.Set = function () {
this._set = [];
};
internals.Set.prototype.add = function (value, refs) {
Hoek.assert(value === null || value === undefined || value instanceof Date || Buffer.isBuffer(value) || Ref.isRef(value) || (typeof value !== 'function' && typeof value !== 'object'), 'Value cannot be an object or function');
if (typeof value !== 'function' &&
this.has(value, null, null, false)) {
return;
}
Ref.push(refs, value);
this._set.push(value);
};
internals.Set.prototype.merge = function (add, remove) {
for (var i = 0, il = add._set.length; i < il; ++i) {
this.add(add._set[i]);
}
for (i = 0, il = remove._set.length; i < il; ++i) {
this.remove(remove._set[i]);
}
};
internals.Set.prototype.remove = function (value) {
this._set = this._set.filter(function (item) {
return value !== item;
});
};
internals.Set.prototype.has = function (value, state, options, insensitive) {
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, options);
}
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()) ||
(Buffer.isBuffer(value) && Buffer.isBuffer(item) && value.length === item.length && value.toString('binary') === item.toString('binary'))) {
return true;
}
}
return false;
};
internals.Set.prototype.values = function () {
return this._set.slice();
};
internals.Set.prototype.toString = function (includeUndefined) {
var list = '';
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);
}
}
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();
};
internals.concatSettings = function (target, source) {
// Used to avoid cloning context
if (!target &&
!source) {
return null;
}
var obj = {};
if (target) {
var tKeys = Object.keys(target);
for (var i = 0, il = tKeys.length; i < il; ++i) {
var key = tKeys[i];
obj[key] = target[key];
}
}
if (source) {
var sKeys = Object.keys(source);
for (var j = 0, jl = sKeys.length; j < jl; ++j) {
var key = sKeys[j];
if (key !== 'language' ||
!obj.hasOwnProperty(key)) {
obj[key] = source[key];
}
else {
obj[key] = Hoek.applyToDefaults(obj[key], source[key]);
}
}
}
return obj;
};