|
|
'use strict';
const Assert = require('@hapi/hoek/lib/assert'); const Clone = require('@hapi/hoek/lib/clone');
const Cache = require('./cache'); const Common = require('./common'); const Compile = require('./compile'); const Errors = require('./errors'); const Extend = require('./extend'); const Manifest = require('./manifest'); const Ref = require('./ref'); const Template = require('./template'); const Trace = require('./trace');
let Schemas;
const internals = { types: { alternatives: require('./types/alternatives'), any: require('./types/any'), array: require('./types/array'), boolean: require('./types/boolean'), date: require('./types/date'), function: require('./types/function'), link: require('./types/link'), number: require('./types/number'), object: require('./types/object'), string: require('./types/string'), symbol: require('./types/symbol') }, aliases: { alt: 'alternatives', bool: 'boolean', func: 'function' } };
if (Buffer) { // $lab:coverage:ignore$
internals.types.binary = require('./types/binary'); }
internals.root = function () {
const root = { _types: new Set(Object.keys(internals.types)) };
// Types
for (const type of root._types) { root[type] = function (...args) {
Assert(!args.length || ['alternatives', 'link', 'object'].includes(type), 'The', type, 'type does not allow arguments'); return internals.generate(this, internals.types[type], args); }; }
// Shortcuts
for (const method of ['allow', 'custom', 'disallow', 'equal', 'exist', 'forbidden', 'invalid', 'not', 'only', 'optional', 'options', 'prefs', 'preferences', 'required', 'strip', 'valid', 'when']) { root[method] = function (...args) {
return this.any()[method](...args); }; }
// Methods
Object.assign(root, internals.methods);
// Aliases
for (const alias in internals.aliases) { const target = internals.aliases[alias]; root[alias] = root[target]; }
root.x = root.expression;
// Trace
if (Trace.setup) { // $lab:coverage:ignore$
Trace.setup(root); }
return root; };
internals.methods = {
ValidationError: Errors.ValidationError, version: Common.version, cache: Cache.provider,
assert(value, schema, ...args /* [message], [options] */) {
internals.assert(value, schema, true, args); },
attempt(value, schema, ...args /* [message], [options] */) {
return internals.assert(value, schema, false, args); },
build(desc) {
Assert(typeof Manifest.build === 'function', 'Manifest functionality disabled'); return Manifest.build(this, desc); },
checkPreferences(prefs) {
Common.checkPreferences(prefs); },
compile(schema, options) {
return Compile.compile(this, schema, options); },
defaults(modifier) {
Assert(typeof modifier === 'function', 'modifier must be a function');
const joi = Object.assign({}, this); for (const type of joi._types) { const schema = modifier(joi[type]()); Assert(Common.isSchema(schema), 'modifier must return a valid schema object');
joi[type] = function (...args) {
return internals.generate(this, schema, args); }; }
return joi; },
expression(...args) {
return new Template(...args); },
extend(...extensions) {
Common.verifyFlat(extensions, 'extend');
Schemas = Schemas || require('./schemas');
Assert(extensions.length, 'You need to provide at least one extension'); this.assert(extensions, Schemas.extensions);
const joi = Object.assign({}, this); joi._types = new Set(joi._types);
for (let extension of extensions) { if (typeof extension === 'function') { extension = extension(joi); }
this.assert(extension, Schemas.extension);
const expanded = internals.expandExtension(extension, joi); for (const item of expanded) { Assert(joi[item.type] === undefined || joi._types.has(item.type), 'Cannot override name', item.type);
const base = item.base || this.any(); const schema = Extend.type(base, item);
joi._types.add(item.type); joi[item.type] = function (...args) {
return internals.generate(this, schema, args); }; } }
return joi; },
isError: Errors.ValidationError.isError, isExpression: Template.isTemplate, isRef: Ref.isRef, isSchema: Common.isSchema,
in(...args) {
return Ref.in(...args); },
override: Common.symbols.override,
ref(...args) {
return Ref.create(...args); },
types() {
const types = {}; for (const type of this._types) { types[type] = this[type](); }
for (const target in internals.aliases) { types[target] = this[target](); }
return types; } };
// Helpers
internals.assert = function (value, schema, annotate, args /* [message], [options] */) {
const message = args[0] instanceof Error || typeof args[0] === 'string' ? args[0] : null; const options = message !== null ? args[1] : args[0]; const result = schema.validate(value, Common.preferences({ errors: { stack: true } }, options || {}));
let error = result.error; if (!error) { return result.value; }
if (message instanceof Error) { throw message; }
const display = annotate && typeof error.annotate === 'function' ? error.annotate() : error.message;
if (error instanceof Errors.ValidationError === false) { error = Clone(error); }
error.message = message ? `${message} ${display}` : display; throw error; };
internals.generate = function (root, schema, args) {
Assert(root, 'Must be invoked on a Joi instance.');
schema.$_root = root;
if (!schema._definition.args || !args.length) {
return schema; }
return schema._definition.args(schema, ...args); };
internals.expandExtension = function (extension, joi) {
if (typeof extension.type === 'string') { return [extension]; }
const extended = []; for (const type of joi._types) { if (extension.type.test(type)) { const item = Object.assign({}, extension); item.type = type; item.base = joi[type](); extended.push(item); } }
return extended; };
module.exports = internals.root();
|