Spaces:
Sleeping
Sleeping
; | |
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(); | |