391 lines
12 KiB
JavaScript
391 lines
12 KiB
JavaScript
|
'use strict';
|
||
|
|
||
|
// Load modules
|
||
|
|
||
|
const Hoek = require('hoek');
|
||
|
const Any = require('./types/any');
|
||
|
const Cast = require('./cast');
|
||
|
const Errors = require('./errors');
|
||
|
const Lazy = require('./types/lazy');
|
||
|
const Ref = require('./ref');
|
||
|
|
||
|
|
||
|
// Declare internals
|
||
|
|
||
|
const internals = {
|
||
|
alternatives: require('./types/alternatives'),
|
||
|
array: require('./types/array'),
|
||
|
boolean: require('./types/boolean'),
|
||
|
binary: require('./types/binary'),
|
||
|
date: require('./types/date'),
|
||
|
number: require('./types/number'),
|
||
|
object: require('./types/object'),
|
||
|
string: require('./types/string')
|
||
|
};
|
||
|
|
||
|
|
||
|
internals.root = function () {
|
||
|
|
||
|
const any = new Any();
|
||
|
|
||
|
const root = any.clone();
|
||
|
root.any = function () {
|
||
|
|
||
|
Hoek.assert(arguments.length === 0, 'Joi.any() does not allow arguments.');
|
||
|
|
||
|
return any;
|
||
|
};
|
||
|
|
||
|
root.alternatives = root.alt = function () {
|
||
|
|
||
|
return arguments.length ? internals.alternatives.try.apply(internals.alternatives, arguments) : internals.alternatives;
|
||
|
};
|
||
|
|
||
|
root.array = function () {
|
||
|
|
||
|
Hoek.assert(arguments.length === 0, 'Joi.array() does not allow arguments.');
|
||
|
|
||
|
return internals.array;
|
||
|
};
|
||
|
|
||
|
root.boolean = root.bool = function () {
|
||
|
|
||
|
Hoek.assert(arguments.length === 0, 'Joi.boolean() does not allow arguments.');
|
||
|
|
||
|
return internals.boolean;
|
||
|
};
|
||
|
|
||
|
root.binary = function () {
|
||
|
|
||
|
Hoek.assert(arguments.length === 0, 'Joi.binary() does not allow arguments.');
|
||
|
|
||
|
return internals.binary;
|
||
|
};
|
||
|
|
||
|
root.date = function () {
|
||
|
|
||
|
Hoek.assert(arguments.length === 0, 'Joi.date() does not allow arguments.');
|
||
|
|
||
|
return internals.date;
|
||
|
};
|
||
|
|
||
|
root.func = function () {
|
||
|
|
||
|
Hoek.assert(arguments.length === 0, 'Joi.func() does not allow arguments.');
|
||
|
|
||
|
return internals.object._func();
|
||
|
};
|
||
|
|
||
|
root.number = function () {
|
||
|
|
||
|
Hoek.assert(arguments.length === 0, 'Joi.number() does not allow arguments.');
|
||
|
|
||
|
return internals.number;
|
||
|
};
|
||
|
|
||
|
root.object = function () {
|
||
|
|
||
|
return arguments.length ? internals.object.keys.apply(internals.object, arguments) : internals.object;
|
||
|
};
|
||
|
|
||
|
root.string = function () {
|
||
|
|
||
|
Hoek.assert(arguments.length === 0, 'Joi.string() does not allow arguments.');
|
||
|
|
||
|
return internals.string;
|
||
|
};
|
||
|
|
||
|
root.ref = function () {
|
||
|
|
||
|
return Ref.create.apply(null, arguments);
|
||
|
};
|
||
|
|
||
|
root.isRef = function (ref) {
|
||
|
|
||
|
return Ref.isRef(ref);
|
||
|
};
|
||
|
|
||
|
root.validate = function (value /*, [schema], [options], callback */) {
|
||
|
|
||
|
const last = arguments[arguments.length - 1];
|
||
|
const callback = typeof last === 'function' ? last : null;
|
||
|
|
||
|
const count = arguments.length - (callback ? 1 : 0);
|
||
|
if (count === 1) {
|
||
|
return any.validate(value, callback);
|
||
|
}
|
||
|
|
||
|
const options = count === 3 ? arguments[2] : {};
|
||
|
const schema = root.compile(arguments[1]);
|
||
|
|
||
|
return schema._validateWithOptions(value, options, callback);
|
||
|
};
|
||
|
|
||
|
root.describe = function () {
|
||
|
|
||
|
const schema = arguments.length ? root.compile(arguments[0]) : any;
|
||
|
return schema.describe();
|
||
|
};
|
||
|
|
||
|
root.compile = function (schema) {
|
||
|
|
||
|
try {
|
||
|
return Cast.schema(schema);
|
||
|
}
|
||
|
catch (err) {
|
||
|
if (err.hasOwnProperty('path')) {
|
||
|
err.message = err.message + '(' + err.path + ')';
|
||
|
}
|
||
|
throw err;
|
||
|
}
|
||
|
};
|
||
|
|
||
|
root.assert = function (value, schema, message) {
|
||
|
|
||
|
root.attempt(value, schema, message);
|
||
|
};
|
||
|
|
||
|
root.attempt = function (value, schema, message) {
|
||
|
|
||
|
const result = root.validate(value, schema);
|
||
|
const error = result.error;
|
||
|
if (error) {
|
||
|
if (!message) {
|
||
|
if (typeof error.annotate === 'function') {
|
||
|
error.message = error.annotate();
|
||
|
}
|
||
|
throw error;
|
||
|
}
|
||
|
|
||
|
if (!(message instanceof Error)) {
|
||
|
if (typeof error.annotate === 'function') {
|
||
|
error.message = `${message} ${error.annotate()}`;
|
||
|
}
|
||
|
throw error;
|
||
|
}
|
||
|
|
||
|
throw message;
|
||
|
}
|
||
|
|
||
|
return result.value;
|
||
|
};
|
||
|
|
||
|
root.reach = function (schema, path) {
|
||
|
|
||
|
Hoek.assert(schema && schema instanceof Any, 'you must provide a joi schema');
|
||
|
Hoek.assert(typeof path === 'string', 'path must be a string');
|
||
|
|
||
|
if (path === '') {
|
||
|
return schema;
|
||
|
}
|
||
|
|
||
|
const parts = path.split('.');
|
||
|
const children = schema._inner.children;
|
||
|
if (!children) {
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
const key = parts[0];
|
||
|
for (let i = 0; i < children.length; ++i) {
|
||
|
const child = children[i];
|
||
|
if (child.key === key) {
|
||
|
return this.reach(child.schema, path.substr(key.length + 1));
|
||
|
}
|
||
|
}
|
||
|
};
|
||
|
|
||
|
root.lazy = function (fn) {
|
||
|
|
||
|
return Lazy.set(fn);
|
||
|
};
|
||
|
|
||
|
root.extend = function () {
|
||
|
|
||
|
const extensions = Hoek.flatten(Array.prototype.slice.call(arguments));
|
||
|
Hoek.assert(extensions.length > 0, 'You need to provide at least one extension');
|
||
|
|
||
|
this.assert(extensions, root.extensionsSchema);
|
||
|
|
||
|
const joi = Object.create(this.any());
|
||
|
Object.assign(joi, this);
|
||
|
|
||
|
for (let i = 0; i < extensions.length; ++i) {
|
||
|
let extension = extensions[i];
|
||
|
|
||
|
if (typeof extension === 'function') {
|
||
|
extension = extension(joi);
|
||
|
}
|
||
|
|
||
|
this.assert(extension, root.extensionSchema);
|
||
|
|
||
|
const base = (extension.base || this.any()).clone(); // Cloning because we're going to override language afterwards
|
||
|
const ctor = base.constructor;
|
||
|
const type = class extends ctor { // eslint-disable-line no-loop-func
|
||
|
|
||
|
constructor() {
|
||
|
|
||
|
super();
|
||
|
if (extension.base) {
|
||
|
Object.assign(this, base);
|
||
|
}
|
||
|
|
||
|
this._type = extension.name;
|
||
|
|
||
|
if (extension.language) {
|
||
|
this._settings = this._settings || { language: {} };
|
||
|
this._settings.language = Hoek.applyToDefaults(this._settings.language, {
|
||
|
[extension.name]: extension.language
|
||
|
});
|
||
|
}
|
||
|
}
|
||
|
|
||
|
};
|
||
|
|
||
|
if (extension.coerce) {
|
||
|
type.prototype._coerce = function (value, state, options) {
|
||
|
|
||
|
if (ctor.prototype._coerce) {
|
||
|
const baseRet = ctor.prototype._coerce.call(this, value, state, options);
|
||
|
|
||
|
if (baseRet.errors) {
|
||
|
return baseRet;
|
||
|
}
|
||
|
|
||
|
value = baseRet.value;
|
||
|
}
|
||
|
|
||
|
const ret = extension.coerce.call(this, value, state, options);
|
||
|
if (ret instanceof Errors.Err) {
|
||
|
return { value, errors: ret };
|
||
|
}
|
||
|
|
||
|
return { value: ret };
|
||
|
};
|
||
|
}
|
||
|
if (extension.pre) {
|
||
|
type.prototype._base = function (value, state, options) {
|
||
|
|
||
|
if (ctor.prototype._base) {
|
||
|
const baseRet = ctor.prototype._base.call(this, value, state, options);
|
||
|
|
||
|
if (baseRet.errors) {
|
||
|
return baseRet;
|
||
|
}
|
||
|
|
||
|
value = baseRet.value;
|
||
|
}
|
||
|
|
||
|
const ret = extension.pre.call(this, value, state, options);
|
||
|
if (ret instanceof Errors.Err) {
|
||
|
return { value, errors: ret };
|
||
|
}
|
||
|
|
||
|
return { value: ret };
|
||
|
};
|
||
|
}
|
||
|
|
||
|
if (extension.rules) {
|
||
|
for (let j = 0; j < extension.rules.length; ++j) {
|
||
|
const rule = extension.rules[j];
|
||
|
const ruleArgs = rule.params ?
|
||
|
(rule.params instanceof Any ? rule.params._inner.children.map((k) => k.key) : Object.keys(rule.params)) :
|
||
|
[];
|
||
|
const validateArgs = rule.params ? Cast.schema(rule.params) : null;
|
||
|
|
||
|
type.prototype[rule.name] = function () { // eslint-disable-line no-loop-func
|
||
|
|
||
|
if (arguments.length > ruleArgs.length) {
|
||
|
throw new Error('Unexpected number of arguments');
|
||
|
}
|
||
|
|
||
|
const args = Array.prototype.slice.call(arguments);
|
||
|
let hasRef = false;
|
||
|
let arg = {};
|
||
|
|
||
|
for (let k = 0; k < ruleArgs.length; ++k) {
|
||
|
arg[ruleArgs[k]] = args[k];
|
||
|
if (!hasRef && Ref.isRef(args[k])) {
|
||
|
hasRef = true;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (validateArgs) {
|
||
|
arg = joi.attempt(arg, validateArgs);
|
||
|
}
|
||
|
|
||
|
let schema;
|
||
|
if (rule.validate) {
|
||
|
const validate = function (value, state, options) {
|
||
|
|
||
|
return rule.validate.call(this, arg, value, state, options);
|
||
|
};
|
||
|
|
||
|
schema = this._test(rule.name, arg, validate, {
|
||
|
description: rule.description,
|
||
|
hasRef
|
||
|
});
|
||
|
}
|
||
|
else {
|
||
|
schema = this.clone();
|
||
|
}
|
||
|
|
||
|
if (rule.setup) {
|
||
|
const newSchema = rule.setup.call(schema, arg);
|
||
|
if (newSchema !== undefined) {
|
||
|
Hoek.assert(newSchema instanceof Any, `Setup of extension Joi.${this._type}().${rule.name}() must return undefined or a Joi object`);
|
||
|
schema = newSchema;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return schema;
|
||
|
};
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (extension.describe) {
|
||
|
type.prototype.describe = function () {
|
||
|
|
||
|
const description = ctor.prototype.describe.call(this);
|
||
|
return extension.describe.call(this, description);
|
||
|
};
|
||
|
}
|
||
|
|
||
|
const instance = new type();
|
||
|
joi[extension.name] = function () {
|
||
|
|
||
|
return instance;
|
||
|
};
|
||
|
}
|
||
|
|
||
|
return joi;
|
||
|
};
|
||
|
|
||
|
root.extensionSchema = internals.object.keys({
|
||
|
base: internals.object.type(Any, 'Joi object'),
|
||
|
name: internals.string.required(),
|
||
|
coerce: internals.object._func().arity(3),
|
||
|
pre: internals.object._func().arity(3),
|
||
|
language: internals.object,
|
||
|
describe: internals.object._func().arity(1),
|
||
|
rules: internals.array.items(internals.object.keys({
|
||
|
name: internals.string.required(),
|
||
|
setup: internals.object._func().arity(1),
|
||
|
validate: internals.object._func().arity(4),
|
||
|
params: [
|
||
|
internals.object.pattern(/.*/, internals.object.type(Any, 'Joi object')),
|
||
|
internals.object.type(internals.object.constructor, 'Joi object')
|
||
|
],
|
||
|
description: [internals.string, internals.object._func().arity(1)]
|
||
|
}).or('setup', 'validate'))
|
||
|
}).strict();
|
||
|
|
||
|
root.extensionsSchema = internals.array.items([internals.object, internals.object._func().arity(1)]).strict();
|
||
|
|
||
|
root.version = require('../package.json').version;
|
||
|
|
||
|
return root;
|
||
|
};
|
||
|
|
||
|
|
||
|
module.exports = internals.root();
|