GoScrobble/web/node_modules/@hapi/joi/lib/index.js

479 lines
15 KiB
JavaScript
Executable File

'use strict';
const Hoek = require('@hapi/hoek');
const Any = require('./types/any');
const Cast = require('./cast');
const Errors = require('./errors');
const Lazy = require('./types/lazy');
const Ref = require('./ref');
const internals = {
alternatives: require('./types/alternatives'),
array: require('./types/array'),
boolean: require('./types/boolean'),
binary: require('./types/binary'),
date: require('./types/date'),
func: require('./types/func'),
number: require('./types/number'),
object: require('./types/object'),
string: require('./types/string'),
symbol: require('./types/symbol')
};
internals.callWithDefaults = function (schema, args) {
Hoek.assert(this, 'Must be invoked on a Joi instance.');
if (this._defaults) {
schema = this._defaults(schema);
}
schema._currentJoi = this;
return schema._init(...args);
};
internals.root = function () {
const any = new Any();
const root = any.clone();
Any.prototype._currentJoi = root;
root._currentJoi = root;
root._binds = new Set(['any', 'alternatives', 'alt', 'array', 'bool', 'boolean', 'binary', 'date', 'func', 'number', 'object', 'string', 'symbol', 'validate', 'describe', 'compile', 'assert', 'attempt', 'lazy', 'defaults', 'extend', 'allow', 'valid', 'only', 'equal', 'invalid', 'disallow', 'not', 'required', 'exist', 'optional', 'forbidden', 'strip', 'when', 'empty', 'default']);
root.any = function (...args) {
Hoek.assert(args.length === 0, 'Joi.any() does not allow arguments.');
return internals.callWithDefaults.call(this, any, args);
};
root.alternatives = root.alt = function (...args) {
return internals.callWithDefaults.call(this, internals.alternatives, args);
};
root.array = function (...args) {
Hoek.assert(args.length === 0, 'Joi.array() does not allow arguments.');
return internals.callWithDefaults.call(this, internals.array, args);
};
root.boolean = root.bool = function (...args) {
Hoek.assert(args.length === 0, 'Joi.boolean() does not allow arguments.');
return internals.callWithDefaults.call(this, internals.boolean, args);
};
root.binary = function (...args) {
Hoek.assert(args.length === 0, 'Joi.binary() does not allow arguments.');
return internals.callWithDefaults.call(this, internals.binary, args);
};
root.date = function (...args) {
Hoek.assert(args.length === 0, 'Joi.date() does not allow arguments.');
return internals.callWithDefaults.call(this, internals.date, args);
};
root.func = function (...args) {
Hoek.assert(args.length === 0, 'Joi.func() does not allow arguments.');
return internals.callWithDefaults.call(this, internals.func, args);
};
root.number = function (...args) {
Hoek.assert(args.length === 0, 'Joi.number() does not allow arguments.');
return internals.callWithDefaults.call(this, internals.number, args);
};
root.object = function (...args) {
return internals.callWithDefaults.call(this, internals.object, args);
};
root.string = function (...args) {
Hoek.assert(args.length === 0, 'Joi.string() does not allow arguments.');
return internals.callWithDefaults.call(this, internals.string, args);
};
root.symbol = function (...args) {
Hoek.assert(args.length === 0, 'Joi.symbol() does not allow arguments.');
return internals.callWithDefaults.call(this, internals.symbol, args);
};
root.ref = function (...args) {
return Ref.create(...args);
};
root.isRef = function (ref) {
return Ref.isRef(ref);
};
root.validate = function (value, ...args /*, [schema], [options], callback */) {
const last = args[args.length - 1];
const callback = typeof last === 'function' ? last : null;
const count = args.length - (callback ? 1 : 0);
if (count === 0) {
return any.validate(value, callback);
}
const options = count === 2 ? args[1] : undefined;
const schema = this.compile(args[0]);
return schema._validateWithOptions(value, options, callback);
};
root.describe = function (...args) {
const schema = args.length ? this.compile(args[0]) : any;
return schema.describe();
};
root.compile = function (schema) {
try {
return Cast.schema(this, schema);
}
catch (err) {
if (err.hasOwnProperty('path')) {
err.message = err.message + '(' + err.path + ')';
}
throw err;
}
};
root.assert = function (value, schema, message) {
this.attempt(value, schema, message);
};
root.attempt = function (value, schema, message) {
const result = this.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(Array.isArray(path) || typeof path === 'string', 'path must be a string or an array of strings');
const reach = (sourceSchema, schemaPath) => {
if (!schemaPath.length) {
return sourceSchema;
}
const children = sourceSchema._inner.children;
if (!children) {
return;
}
const key = schemaPath.shift();
for (let i = 0; i < children.length; ++i) {
const child = children[i];
if (child.key === key) {
return reach(child.schema, schemaPath);
}
}
};
const schemaPath = typeof path === 'string' ? (path ? path.split('.') : []) : path.slice();
return reach(schema, schemaPath);
};
root.lazy = function (...args) {
return internals.callWithDefaults.call(this, Lazy, args);
};
root.defaults = function (fn) {
Hoek.assert(typeof fn === 'function', 'Defaults must be a function');
let joi = Object.create(this.any());
joi = fn(joi);
Hoek.assert(joi && joi instanceof this.constructor, 'defaults() must return a schema');
Object.assign(joi, this, joi.clone()); // Re-add the types from `this` but also keep the settings from joi's potential new defaults
joi._defaults = (schema) => {
if (this._defaults) {
schema = this._defaults(schema);
Hoek.assert(schema instanceof this.constructor, 'defaults() must return a schema');
}
schema = fn(schema);
Hoek.assert(schema instanceof this.constructor, 'defaults() must return a schema');
return schema;
};
return joi;
};
root.bind = function () {
const joi = Object.create(this);
joi._binds.forEach((bind) => {
joi[bind] = joi[bind].bind(joi);
});
return joi;
};
root.extend = function (...args) {
const extensions = Hoek.flatten(args);
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);
joi._currentJoi = joi;
joi._binds = new Set(joi._binds);
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) {
const lang = {
[extension.name]: extension.language
};
type.prototype._language = Hoek.applyToDefaults(type.prototype._language || (base._settings && base._settings.language) || {}, lang);
}
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(this, rule.params) : null;
type.prototype[rule.name] = function (...rArgs) { // eslint-disable-line no-loop-func
if (rArgs.length > ruleArgs.length) {
throw new Error('Unexpected number of arguments');
}
let hasRef = false;
let arg = {};
for (let k = 0; k < ruleArgs.length; ++k) {
arg[ruleArgs[k]] = rArgs[k];
if (!hasRef && Ref.isRef(rArgs[k])) {
hasRef = true;
}
}
if (validateArgs) {
arg = joi.attempt(arg, validateArgs);
}
let schema;
if (rule.validate && !rule.setup) {
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;
}
if (rule.validate) {
const validate = function (value, state, options) {
return rule.validate.call(this, arg, value, state, options);
};
schema = schema._test(rule.name, arg, validate, {
description: rule.description,
hasRef
});
}
}
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 (...extArgs) {
return internals.callWithDefaults.call(this, instance, extArgs);
};
joi._binds.add(extension.name);
}
return joi;
};
root.extensionSchema = internals.object.keys({
base: internals.object.type(Any, 'Joi object'),
name: internals.string.required(),
coerce: internals.func.arity(3),
pre: internals.func.arity(3),
language: internals.object,
describe: internals.func.arity(1),
rules: internals.array.items(internals.object.keys({
name: internals.string.required(),
setup: internals.func.arity(1),
validate: internals.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.func.arity(1)]
}).or('setup', 'validate'))
}).strict();
root.extensionsSchema = internals.array.items([internals.object, internals.func.arity(1)]).strict();
root.version = require('../package.json').version;
return root;
};
module.exports = internals.root();