tlivings / enjoi Goto Github PK
View Code? Open in Web Editor NEWConverts a JSON schema to a Joi schema.
License: Apache License 2.0
Converts a JSON schema to a Joi schema.
License: Apache License 2.0
Thanks for your great library!
I am wondering if it would be possible to define conditional validation rules in a json schema, since I used this feature a lot with Joi.
Taken from their examples page:
const schema = {
a: Joi.valid('a', 'b', 'other'),
other: Joi.string()
.when('a', { is: 'other', then: Joi.required() }),
};
Can such conditional validation rules be expressed via a static schema with enjoi?
Thanks!
By default, the schemas created by enjoi allow empty strings. For example:
const schema = Enjoi({
type: 'object',
properties: {
firstName: {
description: 'First name.',
type: 'string'
},
lastName: {
description: 'Last name.',
type: 'string'
},
age: {
description: 'Age in years',
type: 'integer',
minimum: 1
}
},
'required': ['firstName', 'lastName']
});
schema.validate({
firstName: '',
lastName: 'Smith'
}); // Does not return an error
Is there a way of disabling this so that the above validation would fail?
Current behavior:
The date
and date-time
string formats validate values according to Joi
's built in date
type using Joi.date()
Expected behavior:
The validation should be done with different regex
es for each of the formats. I.e. using Joi.string().regex()
Explanation:
TLDR: The Joi.date()
type validates against ISO8601, which is less restrictive than JSON Schema's RFC3339 date format, rendering it inappropriate.
Details: The JSON Schema specification defines the date string formats to be: date
, time
or date-time
, referring to RFC3339's date and time definitions. RFC3339 in turn refers to ISO8601, but uses only a specified restricted subset of it. Joi.date()
validates against all ISO8601 valid formats, which means it will pass more than it has to. Joi.date()
even accepts JavaScript Date objects and timestamp numbers.
The proper way to validate is using a Joi string schema with the appropriate regex.
All types are incorrectly reporting undefined
as valid. Array is the exception because of #33
Enjoi({ type: 'string' }).validate(undefined, console.log.bind(console, 'string'));
Enjoi({ type: 'array' }).validate(undefined, console.log.bind(console, 'array'));
Enjoi({ type: 'boolean' }).validate(undefined, console.log.bind(console, 'boolean'));
Enjoi({ type: 'number' }).validate(undefined, console.log.bind(console, 'number'));
Enjoi({ type: 'integer' }).validate(undefined, console.log.bind(console, 'integer'));
Enjoi({ type: 'object' }).validate(undefined, console.log.bind(console, 'object'));
Enjoi({ type: 'string' }).validate(undefined, console.log.bind(console, 'string'));
No ValidationError for any of those and the value returned is undefined
.
(Sorry for the deluge!)
Joi had breaking changes.
The current version is: 6.4.3
Hey,
I have a problem where empty string is blocked by Joi (this is the default behavior unless allow('')
is stated),
Any ideas where we can configure whether empty strings are allowed or not? maybe a general option such allowEmptyStrings: true
?
Thanks.
joi and hoek have been deprecated appear to have migrated to @hapi/joi and @hapi/hoek. Didn't see anything about this on the board so I'm posting it here. Did a quick swap out of the import statements and ran npm test which came back clean. Let me know if this has been addressed or you don't want to support the latest joi and hoek. Our security scanning software flagged hoek as having known vulnerabilities already, I'm not sure how valid that is, the software is not great, but we have to move away from enjoi if this isn't changed regardless. Have a nice day. PR here: #75
https://www.npmjs.com/package/joi
https://www.npmjs.com/package/hoek
ps I opened an issue for this earlier and it seems to have dissappeared so there might be a duplicate floating around
Hi there,
I'm trying to do a schema with nested objects and I want to make some fields inside the object required.
As an example,
type: 'object',
person:{
type: 'object',
description:{
firstName:{
type: 'string',
length: 2
}
}
}
How can i make 'firstName' required?
cumps
const Joi = require('joi');
const Enjoi = require('enjoi');
const schema = Enjoi({
type: 'object',
properties: {
firstName: {
description: 'First name.',
type: 'string'
},
lastName: {
description: 'Last name.',
type: 'string'
},
mail: {type: "email"},
effectiveDate: {type: 'date', minimum: '12-21-2012'},
age: {
description: 'Age in years',
type: 'integer',
minimum: 1
}
}
}, {
types: {
email: Joi.string().email(),
date: Joi.date()
}
});
const {error} = Joi.validate({firstName: 'John', lastName: 'Doe', age: 9, mail: "[email protected]", effectiveDate: "2010-08-09"}, schema);
console.log(error);
i just need to add minimum and maximum to effectiveDate
attribute, i know that i can add it in types to be like that date: Joi.date().min('12-21-2012')
but this is a bad soln as maybe i have more than one attribute with type of date ?!!
The example from the README is throwing an error. It is saying message: '"name" is required',.
const Joi = require('joi');
const Enjoi = require('enjoi');
const schema = Enjoi.schema({
type: 'object',
properties: {
firstName: {
description: 'First name.',
type: 'string'
},
lastName: {
description: 'Last name.',
type: 'string'
},
age: {
description: 'Age in years',
type: 'integer',
minimum: 1
}
},
'required': ['firstName', 'lastName']
});
const { error, value } = schema.validate({firstName: 'John', lastName: 'Doe', age: 45});
Logs
/tmp/node_modules/@hapi/joi/lib/index.js:183
throw error;
^
Error [ValidationError]: {
"type": "object",
"base": {
"isJoi": true,
"_type": "object",
"_settings": null,
"_valids": {
"_set": {},
"_hasRef": false
},
"_invalids": {
"_set": {},
"_hasRef": false
},
"_tests": [],
"_refs": [],
"_flags": {},
"_description": null,
"_unit": null,
"_notes": [],
"_tags": [],
"_examples": [],
"_meta": [],
"_inner": {
"children": null,
"renames": [],
"dependencies": [],
"patterns": []
},
"_currentJoi": {
"isJoi": true,
"_currentJoi": "[Circular ~.base._currentJoi]",
"_type": "any",
"_settings": null,
"_valids": {
"_set": {},
"_hasRef": false
},
"_invalids": {
"_set": {},
"_hasRef": false
},
"_tests": [],
"_refs": [],
"_flags": {},
"_description": null,
"_unit": null,
"_notes": [],
"_tags": [],
"_examples": [],
"_meta": [],
"_inner": {},
"_binds": {},
"any": function (...args) {\n\n Hoek.assert(args.length === 0, 'Joi.any() does not allow arguments.');\n\n return internals.callWithDefaults.call(this, any, args);\n },
"alt": function (...args) {\n\n return internals.callWithDefaults.call(this, internals.alternatives, args);\n },
"alternatives": function (...args) {\n\n return internals.callWithDefaults.call(this, internals.alternatives, args);\n },
"array": function (...args) {\n\n Hoek.assert(args.length === 0, 'Joi.array() does not allow arguments.');\n\n return internals.callWithDefaults.call(this, internals.array, args);\n },
"bool": function (...args) {\n\n Hoek.assert(args.length === 0, 'Joi.boolean() does not allow arguments.');\n\n return internals.callWithDefaults.call(this, internals.boolean, args);\n },
"boolean": function (...args) {\n\n Hoek.assert(args.length === 0, 'Joi.boolean() does not allow arguments.');\n\n return internals.callWithDefaults.call(this, internals.boolean, args);\n },
"binary": function (...args) {\n\n Hoek.assert(args.length === 0, 'Joi.binary() does not allow arguments.');\n\n return internals.callWithDefaults.call(this, internals.binary, args);\n },
"date": function (...args) {\n\n Hoek.assert(args.length === 0, 'Joi.date() does not allow arguments.');\n\n return internals.callWithDefaults.call(this, internals.date, args);\n },
"func": function (...args) {\n\n Hoek.assert(args.length === 0, 'Joi.func() does not allow arguments.');\n\n return internals.callWithDefaults.call(this, internals.func, args);\n },
"number": function (...args) {\n\n Hoek.assert(args.length === 0, 'Joi.number() does not allow arguments.');\n\n return internals.callWithDefaults.call(this, internals.number, args);\n },
"object": function (...args) {\n\n return internals.callWithDefaults.call(this, internals.object, args);\n },
"string": function (...args) {\n\n Hoek.assert(args.length === 0, 'Joi.string() does not allow arguments.');\n\n return internals.callWithDefaults.call(this, internals.string, args);\n },
"symbol": function (...args) {\n\n Hoek.assert(args.length === 0, 'Joi.symbol() does not allow arguments.');\n\n return internals.callWithDefaults.call(this, internals.symbol, args);\n },
"ref": function (...args) {\n\n return Ref.create(...args);\n },
"isRef": function (ref) {\n\n return Ref.isRef(ref);\n },
"validate": function (value, ...args /*, [schema], [options], callback */) {\n\n const last = args[args.length - 1];\n const callback = typeof last === 'function' ? last : null;\n\n const count = args.length - (callback ? 1 : 0);\n if (count === 0) {\n return any.validate(value, callback);\n }\n\n const options = count === 2 ? args[1] : undefined;\n const schema = this.compile(args[0]);\n\n return schema._validateWithOptions(value, options, callback);\n },
"describe": function (...args) {\n\n const schema = args.length ? this.compile(args[0]) : any;\n return schema.describe();\n },
"compile": function (schema) {\n\n try {\n return Cast.schema(this, schema);\n }\n catch (err) {\n if (err.hasOwnProperty('path')) {\n err.message = err.message + '(' + err.path + ')';\n }\n\n throw err;\n }\n },
"assert": function (value, schema, message) {\n\n this.attempt(value, schema, message);\n },
"attempt": function (value, schema, message) {\n\n const result = this.validate(value, schema);\n const error = result.error;\n if (error) {\n if (!message) {\n if (typeof error.annotate === 'function') {\n error.message = error.annotate();\n }\n\n throw error;\n }\n\n if (!(message instanceof Error)) {\n if (typeof error.annotate === 'function') {\n error.message = `${message} ${error.annotate()}`;\n }\n\n throw error;\n }\n\n throw message;\n }\n\n return result.value;\n },
"reach": function (schema, path) {\n\n Hoek.assert(schema && schema instanceof Any, 'you must provide a joi schema');\n Hoek.assert(Array.isArray(path) || typeof path === 'string', 'path must be a string or an array of strings');\n\n const reach = (sourceSchema, schemaPath) => {\n\n if (!schemaPath.length) {\n return sourceSchema;\n }\n\n const children = sourceSchema._inner.children;\n if (!children) {\n return;\n }\n\n const key = schemaPath.shift();\n for (let i = 0; i < children.length; ++i) {\n const child = children[i];\n if (child.key === key) {\n return reach(child.schema, schemaPath);\n }\n }\n };\n\n const schemaPath = typeof path === 'string' ? (path ? path.split('.') : []) : path.slice();\n\n return reach(schema, schemaPath);\n },
"lazy": function (...args) {\n\n return internals.callWithDefaults.call(this, Lazy, args);\n },
"defaults": function (fn) {\n\n Hoek.assert(typeof fn === 'function', 'Defaults must be a function');\n\n let joi = Object.create(this.any());\n joi = fn(joi);\n\n Hoek.assert(joi && joi instanceof this.constructor, 'defaults() must return a schema');\n\n Object.assign(joi, this, joi.clone()); // Re-add the types from `this` but also keep the settings from joi's potential new defaults\n\n joi._defaults = (schema) => {\n\n if (this._defaults) {\n schema = this._defaults(schema);\n Hoek.assert(schema instanceof this.constructor, 'defaults() must return a schema');\n }\n\n schema = fn(schema);\n Hoek.assert(schema instanceof this.constructor, 'defaults() must return a schema');\n return schema;\n };\n\n return joi;\n },
"bind": function () {\n\n const joi = Object.create(this);\n\n joi._binds.forEach((bind) => {\n\n joi[bind] = joi[bind].bind(joi);\n });\n\n return joi;\n },
"extend": function (...args) {\n\n const extensions = Hoek.flatten(args);\n Hoek.assert(extensions.length > 0, 'You need to provide at least one extension');\n\n this.assert(extensions, root.extensionsSchema);\n\n const joi = Object.create(this.any());\n Object.assign(joi, this);\n joi._currentJoi = joi;\n joi._binds = new Set(joi._binds);\n\n for (let i = 0; i < extensions.length; ++i) {\n let extension = extensions[i];\n\n if (typeof extension === 'function') {\n extension = extension(joi);\n }\n\n this.assert(extension, root.extensionSchema);\n\n const base = (extension.base || this.any()).clone(); // Cloning because we're going to override language afterwards\n const ctor = base.constructor;\n const type = class extends ctor { // eslint-disable-line no-loop-func\n\n constructor() {\n\n super();\n if (extension.base) {\n Object.assign(this, base);\n }\n\n this._type = extension.name;\n }\n\n };\n\n if (extension.language) {\n const lang = {\n [extension.name]: extension.language\n };\n type.prototype._language = Hoek.applyToDefaults(type.prototype._language || (base._settings && base._settings.language) || {}, lang);\n }\n\n\n if (extension.coerce) {\n type.prototype._coerce = function (value, state, options) {\n\n if (ctor.prototype._coerce) {\n const baseRet = ctor.prototype._coerce.call(this, value, state, options);\n\n if (baseRet.errors) {\n return baseRet;\n }\n\n value = baseRet.value;\n }\n\n const ret = extension.coerce.call(this, value, state, options);\n if (ret instanceof Errors.Err) {\n return { value, errors: ret };\n }\n\n return { value: ret };\n };\n }\n\n if (extension.pre) {\n type.prototype._base = function (value, state, options) {\n\n if (ctor.prototype._base) {\n const baseRet = ctor.prototype._base.call(this, value, state, options);\n\n if (baseRet.errors) {\n return baseRet;\n }\n\n value = baseRet.value;\n }\n\n const ret = extension.pre.call(this, value, state, options);\n if (ret instanceof Errors.Err) {\n return { value, errors: ret };\n }\n\n return { value: ret };\n };\n }\n\n if (extension.rules) {\n for (let j = 0; j < extension.rules.length; ++j) {\n const rule = extension.rules[j];\n const ruleArgs = rule.params ?\n (rule.params instanceof Any ? rule.params._inner.children.map((k) => k.key) : Object.keys(rule.params)) :\n [];\n const validateArgs = rule.params ? Cast.schema(this, rule.params) : null;\n\n type.prototype[rule.name] = function (...rArgs) { // eslint-disable-line no-loop-func\n\n if (rArgs.length > ruleArgs.length) {\n throw new Error('Unexpected number of arguments');\n }\n\n let hasRef = false;\n let arg = {};\n\n for (let k = 0; k < ruleArgs.length; ++k) {\n arg[ruleArgs[k]] = rArgs[k];\n if (!hasRef && Ref.isRef(rArgs[k])) {\n hasRef = true;\n }\n }\n\n if (validateArgs) {\n arg = joi.attempt(arg, validateArgs);\n }\n\n let schema;\n if (rule.validate && !rule.setup) {\n const validate = function (value, state, options) {\n\n return rule.validate.call(this, arg, value, state, options);\n };\n\n schema = this._test(rule.name, arg, validate, {\n description: rule.description,\n hasRef\n });\n }\n else {\n schema = this.clone();\n }\n\n if (rule.setup) {\n const newSchema = rule.setup.call(schema, arg);\n if (newSchema !== undefined) {\n Hoek.assert(newSchema instanceof Any, `Setup of extension Joi.${this._type}().${rule.name}() must return undefined or a Joi object`);\n schema = newSchema;\n }\n\n if (rule.validate) {\n const validate = function (value, state, options) {\n\n return rule.validate.call(this, arg, value, state, options);\n };\n\n schema = schema._test(rule.name, arg, validate, {\n description: rule.description,\n hasRef\n });\n }\n }\n\n return schema;\n };\n }\n }\n\n if (extension.describe) {\n type.prototype.describe = function () {\n\n const description = ctor.prototype.describe.call(this);\n return extension.describe.call(this, description);\n };\n }\n\n const instance = new type();\n joi[extension.name] = function (...extArgs) {\n\n return internals.callWithDefaults.call(this, instance, extArgs);\n };\n\n joi._binds.add(extension.name);\n }\n\n return joi;\n },
"extensionSchema": {
"isJoi": true,
"_currentJoi": "[Circular ~.base._currentJoi]",
"_type": "object",
"_settings": {
"convert": false
},
"_valids": {
"_set": {},
"_hasRef": false
},
"_invalids": {
"_set": {},
"_hasRef": false
},
"_tests": [],
"_refs": [],
"_flags": {},
"_description": null,
"_unit": null,
"_notes": [],
"_tags": [],
"_examples": [],
"_meta": [],
"_inner": {
"children": [
{
"key": "base",
"schema": {
"isJoi": true,
"_currentJoi": "[Circular ~.base._currentJoi]",
"_type": "object",
"_settings": null,
"_valids": {
"_set": {},
"_hasRef": false
},
"_invalids": {
"_set": {},
"_hasRef": false
},
"_tests": [
{
"func": function (value, state, options) {\n\n if (value instanceof constructor) {\n return value;\n }\n\n return this.createError('object.type', { type: typeData.name, value }, state, options);\n },
"name": "type",
"arg": {
"name": "Joi object",
"ctor": "[class {\n\n constructor() {\n\n this.isJoi = true;\n this._type = 'any';\n this._settings = null;\n this._valids = new internals.Set();\n this._invalids = new internals.Set();\n this._tests = [];\n this._refs = [];\n this._flags = {\n /*\n presence: 'optional', // optional, required, forbidden, ignore\n allowOnly: false,\n allowUnknown: undefined,\n default: undefined,\n forbidden: false,\n encoding: undefined,\n insensitive: false,\n trim: false,\n normalize: undefined, // NFC, NFD, NFKC, NFKD\n case: undefined, // upper, lower\n empty: undefined,\n func: false,\n raw: false\n */\n };\n\n this._description = null;\n this._unit = null;\n this._notes = [];\n this._tags = [];\n this._examples = [];\n this._meta = [];\n\n this._inner = {}; // Hash of arrays of immutable objects\n }\n\n _init() {\n\n return this;\n }\n\n get schemaType() {\n\n return this._type;\n }\n\n createError(type, context, state, options, flags = this._flags) {\n\n return Errors.create(type, context, state, options, flags);\n }\n\n createOverrideError(type, context, state, options, message, template) {\n\n return Errors.create(type, context, state, options, this._flags, message, template);\n }\n\n checkOptions(options) {\n\n Schemas = Schemas || require('../../schemas');\n\n const result = Schemas.options.validate(options);\n\n if (result.error) {\n throw new Error(result.error.details[0].message);\n }\n }\n\n clone() {\n\n const obj = Object.create(Object.getPrototypeOf(this));\n\n obj.isJoi = true;\n obj._currentJoi = this._currentJoi;\n obj._type = this._type;\n obj._settings = this._settings;\n obj._baseType = this._baseType;\n obj._valids = this._valids.slice();\n obj._invalids = this._invalids.slice();\n obj._tests = this._tests.slice();\n obj._refs = this._refs.slice();\n obj._flags = Hoek.clone(this._flags);\n\n obj._description = this._description;\n obj._unit = this._unit;\n obj._notes = this._notes.slice();\n obj._tags = this._tags.slice();\n obj._examples = this._examples.slice();\n obj._meta = this._meta.slice();\n\n obj._inner = {};\n const inners = Object.keys(this._inner);\n for (let i = 0; i < inners.length; ++i) {\n const key = inners[i];\n obj._inner[key] = this._inner[key] ? this._inner[key].slice() : null;\n }\n\n return obj;\n }\n\n concat(schema) {\n\n Hoek.assert(schema instanceof internals.Any, 'Invalid schema object');\n Hoek.assert(this._type === 'any' || schema._type === 'any' || schema._type === this._type, 'Cannot merge type', this._type, 'with another type:', schema._type);\n\n let obj = this.clone();\n\n if (this._type === 'any' && schema._type !== 'any') {\n\n // Reset values as if we were \"this\"\n const tmpObj = schema.clone();\n const keysToRestore = ['_settings', '_valids', '_invalids', '_tests', '_refs', '_flags', '_description', '_unit',\n '_notes', '_tags', '_examples', '_meta', '_inner'];\n\n for (let i = 0; i < keysToRestore.length; ++i) {\n tmpObj[keysToRestore[i]] = obj[keysToRestore[i]];\n }\n\n obj = tmpObj;\n }\n\n obj._settings = obj._settings ? Settings.concat(obj._settings, schema._settings) : schema._settings;\n obj._valids.merge(schema._valids, schema._invalids);\n obj._invalids.merge(schema._invalids, schema._valids);\n obj._tests.push(...schema._tests);\n obj._refs.push(...schema._refs);\n if (obj._flags.empty && schema._flags.empty) {\n obj._flags.empty = obj._flags.empty.concat(schema._flags.empty);\n const flags = Object.assign({}, schema._flags);\n delete flags.empty;\n Hoek.merge(obj._flags, flags);\n }\n else if (schema._flags.empty) {\n obj._flags.empty = schema._flags.empty;\n const flags = Object.assign({}, schema._flags);\n delete flags.empty;\n Hoek.merge(obj._flags, flags);\n }\n else {\n Hoek.merge(obj._flags, schema._flags);\n }\n\n obj._description = schema._description || obj._description;\n obj._unit = schema._unit || obj._unit;\n obj._notes.push(...schema._notes);\n obj._tags.push(...schema._tags);\n obj._examples.push(...schema._examples);\n obj._meta.push(...schema._meta);\n\n const inners = Object.keys(schema._inner);\n const isObject = obj._type === 'object';\n for (let i = 0; i < inners.length; ++i) {\n const key = inners[i];\n const source = schema._inner[key];\n if (source) {\n const target = obj._inner[key];\n if (target) {\n if (isObject && key === 'children') {\n const keys = {};\n\n for (let j = 0; j < target.length; ++j) {\n keys[target[j].key] = j;\n }\n\n for (let j = 0; j < source.length; ++j) {\n const sourceKey = source[j].key;\n if (keys[sourceKey] >= 0) {\n target[keys[sourceKey]] = {\n key: sourceKey,\n schema: target[keys[sourceKey]].schema.concat(source[j].schema)\n };\n }\n else {\n target.push(source[j]);\n }\n }\n }\n else {\n obj._inner[key] = obj._inner[key].concat(source);\n }\n }\n else {\n obj._inner[key] = source.slice();\n }\n }\n }\n\n return obj;\n }\n\n _test(name, arg, func, options) {\n\n const obj = this.clone();\n obj._tests.push({ func, name, arg, options });\n return obj;\n }\n\n _testUnique(name, arg, func, options) {\n\n const obj = this.clone();\n obj._tests = obj._tests.filter((test) => test.name !== name);\n obj._tests.push({ func, name, arg, options });\n return obj;\n }\n\n options(options) {\n\n Hoek.assert(!options.context, 'Cannot override context');\n this.checkOptions(options);\n\n const obj = this.clone();\n obj._settings = Settings.concat(obj._settings, options);\n return obj;\n }\n\n strict(isStrict) {\n\n const obj = this.clone();\n\n const convert = isStrict === undefined ? false : !isStrict;\n obj._settings = Settings.concat(obj._settings, { convert });\n return obj;\n }\n\n raw(isRaw) {\n\n const value = isRaw === undefined ? true : isRaw;\n\n if (this._flags.raw === value) {\n return this;\n }\n\n const obj = this.clone();\n obj._flags.raw = value;\n return obj;\n }\n\n error(err, options = { self: false }) {\n\n Hoek.assert(err && (err instanceof Error || typeof err === 'function'), 'Must provide a valid Error object or a function');\n\n const unknownKeys = Object.keys(options).filter((k) => !['self'].includes(k));\n Hoek.assert(unknownKeys.length === 0, `Options ${unknownKeys} are unknown`);\n\n const obj = this.clone();\n obj._flags.error = err;\n\n if (options.self) {\n obj._flags.selfError = true;\n }\n\n return obj;\n }\n\n allow(...values) {\n\n const obj = this.clone();\n values = Hoek.flatten(values);\n for (let i = 0; i < values.length; ++i) {\n const value = values[i];\n\n Hoek.assert(value !== undefined, 'Cannot call allow/valid/invalid with undefined');\n obj._invalids.remove(value);\n obj._valids.add(value, obj._refs);\n }\n\n return obj;\n }\n\n valid(...values) {\n\n const obj = this.allow(...values);\n obj._flags.allowOnly = true;\n return obj;\n }\n\n invalid(...values) {\n\n const obj = this.clone();\n values = Hoek.flatten(values);\n for (let i = 0; i < values.length; ++i) {\n const value = values[i];\n\n Hoek.assert(value !== undefined, 'Cannot call allow/valid/invalid with undefined');\n obj._valids.remove(value);\n obj._invalids.add(value, obj._refs);\n }\n\n return obj;\n }\n\n required() {\n\n if (this._flags.presence === 'required') {\n return this;\n }\n\n const obj = this.clone();\n obj._flags.presence = 'required';\n return obj;\n }\n\n optional() {\n\n if (this._flags.presence === 'optional') {\n return this;\n }\n\n const obj = this.clone();\n obj._flags.presence = 'optional';\n return obj;\n }\n\n\n forbidden() {\n\n if (this._flags.presence === 'forbidden') {\n return this;\n }\n\n const obj = this.clone();\n obj._flags.presence = 'forbidden';\n return obj;\n }\n\n\n strip() {\n\n if (this._flags.strip) {\n return this;\n }\n\n const obj = this.clone();\n obj._flags.strip = true;\n return obj;\n }\n\n applyFunctionToChildren(children, fn, args = [], root) {\n\n children = [].concat(children);\n\n if (children.length !== 1 || children[0] !== '') {\n root = root ? (root + '.') : '';\n\n const extraChildren = (children[0] === '' ? children.slice(1) : children).map((child) => {\n\n return root + child;\n });\n\n throw new Error('unknown key(s) ' + extraChildren.join(', '));\n }\n\n return this[fn](...args);\n }\n\n default(value, description) {\n\n if (typeof value === 'function' &&\n !Ref.isRef(value)) {\n\n if (!value.description &&\n description) {\n\n value.description = description;\n }\n\n if (!this._flags.func) {\n Hoek.assert(typeof value.description === 'string' && value.description.length > 0, 'description must be provided when default value is a function');\n }\n }\n\n const obj = this.clone();\n obj._flags.default = value;\n Ref.push(obj._refs, value);\n return obj;\n }\n\n empty(schema) {\n\n const obj = this.clone();\n if (schema === undefined) {\n delete obj._flags.empty;\n }\n else {\n obj._flags.empty = Cast.schema(this._currentJoi, schema);\n }\n\n return obj;\n }\n\n when(condition, options) {\n\n Hoek.assert(options && typeof options === 'object', 'Invalid options');\n Hoek.assert(options.then !== undefined || options.otherwise !== undefined, 'options must have at least one of \"then\" or \"otherwise\"');\n\n const then = options.hasOwnProperty('then') ? this.concat(Cast.schema(this._currentJoi, options.then)) : undefined;\n const otherwise = options.hasOwnProperty('otherwise') ? this.concat(Cast.schema(this._currentJoi, options.otherwise)) : undefined;\n\n Alternatives = Alternatives || require('../alternatives');\n\n const alternativeOptions = { then, otherwise };\n if (Object.prototype.hasOwnProperty.call(options, 'is')) {\n alternativeOptions.is = options.is;\n }\n\n const obj = Alternatives.when(condition, alternativeOptions);\n obj._flags.presence = 'ignore';\n obj._baseType = this;\n\n return obj;\n }\n\n description(desc) {\n\n Hoek.assert(desc && typeof desc === 'string', 'Description must be a non-empty string');\n\n const obj = this.clone();\n obj._description = desc;\n return obj;\n }\n\n notes(notes) {\n\n Hoek.assert(notes && (typeof notes === 'string' || Array.isArray(notes)), 'Notes must be a non-empty string or array');\n\n const obj = this.clone();\n obj._notes = obj._notes.concat(notes);\n return obj;\n }\n\n tags(tags) {\n\n Hoek.assert(tags && (typeof tags === 'string' || Array.isArray(tags)), 'Tags must be a non-empty string or array');\n\n const obj = this.clone();\n obj._tags = obj._tags.concat(tags);\n return obj;\n }\n\n meta(meta) {\n\n Hoek.assert(meta !== undefined, 'Meta cannot be undefined');\n\n const obj = this.clone();\n obj._meta = obj._meta.concat(meta);\n return obj;\n }\n\n example(...examples) {\n\n Hoek.assert(examples.length > 0, 'Missing examples');\n\n const processed = [];\n for (let i = 0; i < examples.length; ++i) {\n const example = [].concat(examples[i]);\n Hoek.assert(example.length <= 2, `Bad example format at index ${i}`);\n\n const value = example[0];\n let options = example[1];\n if (options !== undefined) {\n Hoek.assert(options && typeof options === 'object', `Options for example at index ${i} must be an object`);\n const unknownOptions = Object.keys(options).filter((option) => !['parent', 'context'].includes(option));\n Hoek.assert(unknownOptions.length === 0, `Unknown example options ${unknownOptions} at index ${i}`);\n }\n else {\n options = {};\n }\n\n const localState = new State('', [], options.parent || null);\n const result = this._validate(value, localState, Settings.concat(internals.defaults, options.context ? { context: options.context } : null));\n Hoek.assert(!result.errors, `Bad example at index ${i}:`, result.errors && Errors.process(result.errors, value));\n\n const ex = { value };\n if (Object.keys(options).length) {\n ex.options = options;\n }\n\n processed.push(ex);\n }\n\n const obj = this.clone();\n obj._examples = processed;\n return obj;\n }\n\n unit(name) {\n\n Hoek.assert(name && typeof name === 'string', 'Unit name must be a non-empty string');\n\n const obj = this.clone();\n obj._unit = name;\n return obj;\n }\n\n _prepareEmptyValue(value) {\n\n if (typeof value === 'string' && this._flags.trim) {\n return value.trim();\n }\n\n return value;\n }\n\n _validate(value, state, options, reference) {\n\n const originalValue = value;\n\n // Setup state and settings\n\n state = state || new State('', [], null, reference);\n\n if (this._settings) {\n const isDefaultOptions = options === internals.defaults;\n if (isDefaultOptions && this._settings[Symbols.settingsCache]) {\n options = this._settings[Symbols.settingsCache];\n }\n else {\n options = Settings.concat(this._language ? Settings.concat({ language: this._language }, options) : options, this._settings);\n if (isDefaultOptions) {\n this._settings[Symbols.settingsCache] = options;\n }\n }\n }\n else if (this._language) {\n options = Settings.concat({ language: this._language }, options);\n }\n\n let errors = [];\n\n if (this._coerce) {\n const coerced = this._coerce(value, state, options);\n if (coerced.errors) {\n value = coerced.value;\n errors = errors.concat(coerced.errors);\n return this._finalizeValue(value, originalValue, errors, state, options); // Coerced error always aborts early\n }\n\n value = coerced.value;\n }\n\n if (this._flags.empty && !this._flags.empty._validate(this._prepareEmptyValue(value), null, internals.defaults).errors) {\n value = undefined;\n }\n\n // Check presence requirements\n\n const presence = this._flags.presence || options.presence;\n if (presence === 'optional') {\n if (value === undefined) {\n const isDeepDefault = this._flags.hasOwnProperty('default') && this._flags.default === undefined;\n if (isDeepDefault && this._type === 'object') {\n value = {};\n }\n else {\n return this._finalizeValue(value, originalValue, errors, state, options);\n }\n }\n }\n else if (presence === 'required' &&\n value === undefined) {\n\n errors.push(this.createError('any.required', null, state, options));\n return this._finalizeValue(value, originalValue, errors, state, options);\n }\n else if (presence === 'forbidden') {\n if (value === undefined) {\n return this._finalizeValue(value, originalValue, errors, state, options);\n }\n\n errors.push(this.createError('any.unknown', null, state, options));\n return this._finalizeValue(value, originalValue, errors, state, options);\n }\n\n // Check allowed and denied values using the original value\n\n let match = this._valids.get(value, state, options, this._flags.insensitive);\n if (match) {\n if (options.convert) {\n value = match.value;\n }\n\n return this._finalizeValue(value, originalValue, errors, state, options);\n }\n\n if (this._invalids.has(value, state, options, this._flags.insensitive)) {\n errors.push(this.createError(value === '' ? 'any.empty' : 'any.invalid', { value, invalids: this._invalids.values({ stripUndefined: true }) }, state, options));\n if (options.abortEarly) {\n\n return this._finalizeValue(value, originalValue, errors, state, options);\n }\n }\n\n // Convert value and validate type\n\n if (this._base) {\n const base = this._base(value, state, options);\n if (base.errors) {\n value = base.value;\n errors = errors.concat(base.errors);\n return this._finalizeValue(value, originalValue, errors, state, options); // Base error always aborts early\n }\n\n if (base.value !== value) {\n value = base.value;\n\n // Check allowed and denied values using the converted value\n\n match = this._valids.get(value, state, options, this._flags.insensitive);\n if (match) {\n value = match.value;\n return this._finalizeValue(value, originalValue, errors, state, options);\n }\n\n if (this._invalids.has(value, state, options, this._flags.insensitive)) {\n errors.push(this.createError(value === '' ? 'any.empty' : 'any.invalid', { value, invalids: this._invalids.values({ stripUndefined: true }) }, state, options));\n if (options.abortEarly) {\n return this._finalizeValue(value, originalValue, errors, state, options);\n }\n }\n }\n }\n\n // Required values did not match\n\n if (this._flags.allowOnly) {\n errors.push(this.createError('any.allowOnly', { value, valids: this._valids.values({ stripUndefined: true }) }, state, options));\n if (options.abortEarly) {\n return this._finalizeValue(value, originalValue, errors, state, options);\n }\n }\n\n // Validate tests\n\n for (let i = 0; i < this._tests.length; ++i) {\n const test = this._tests[i];\n const ret = test.func.call(this, value, state, options);\n if (ret instanceof Errors.Err) {\n errors.push(ret);\n if (options.abortEarly) {\n return this._finalizeValue(value, originalValue, errors, state, options);\n }\n }\n else {\n value = ret;\n }\n }\n\n return this._finalizeValue(value, originalValue, errors, state, options);\n }\n\n _finalizeValue(value, originalValue, errors, state, options) {\n\n let finalValue;\n\n if (value !== undefined) {\n finalValue = this._flags.raw ? originalValue : value;\n }\n else if (options.noDefaults) {\n finalValue = value;\n }\n else if (Ref.isRef(this._flags.default)) {\n finalValue = this._flags.default(state.parent, options);\n }\n else if (typeof this._flags.default === 'function' &&\n !(this._flags.func && !this._flags.default.description)) {\n\n let args;\n\n if (state.parent !== null &&\n this._flags.default.length > 0) {\n\n args = [Hoek.clone(state.parent), options];\n }\n\n const defaultValue = internals._try(this._flags.default, args);\n finalValue = defaultValue.value;\n if (defaultValue.error) {\n errors.push(this.createError('any.default', { error: defaultValue.error }, state, options));\n }\n }\n else {\n finalValue = Hoek.clone(this._flags.default);\n }\n\n if (errors.length &&\n typeof this._flags.error === 'function' &&\n (\n !this._flags.selfError ||\n errors.some((e) => state.path.length === e.path.length)\n )\n ) {\n const change = this._flags.error.call(this, errors);\n\n if (typeof change === 'string') {\n errors = [this.createOverrideError('override', { reason: errors }, state, options, change)];\n }\n else {\n errors = [].concat(change)\n .map((err) => {\n\n return err instanceof Error ?\n err :\n this.createOverrideError(err.type || 'override', err.context, state, options, err.message, err.template);\n });\n }\n }\n\n return {\n value: this._flags.strip ? undefined : finalValue,\n finalValue,\n errors: errors.length ? errors : null\n };\n }\n\n _validateWithOptions(value, options, callback) {\n\n if (options) {\n this.checkOptions(options);\n }\n\n const settings = Settings.concat(internals.defaults, options);\n const result = this._validate(value, null, settings);\n const errors = Errors.process(result.errors, value);\n\n if (callback) {\n return callback(errors, result.value);\n }\n\n return {\n error: errors,\n value: result.value,\n then(resolve, reject) {\n\n if (errors) {\n return Promise.reject(errors).catch(reject);\n }\n\n return Promise.resolve(result.value).then(resolve);\n },\n catch(reject) {\n\n if (errors) {\n return Promise.reject(errors).catch(reject);\n }\n\n return Promise.resolve(result.value);\n }\n };\n }\n\n validate(value, options, callback) {\n\n if (typeof options === 'function') {\n return this._validateWithOptions(value, null, options);\n }\n\n return this._validateWithOptions(value, options, callback);\n }\n\n describe() {\n\n const description = {\n type: this._type\n };\n\n const flags = Object.keys(this._flags);\n if (flags.length) {\n if (['empty', 'default', 'lazy', 'label'].some((flag) => this._flags.hasOwnProperty(flag))) {\n description.flags = {};\n for (let i = 0; i < flags.length; ++i) {\n const flag = flags[i];\n if (flag === 'empty') {\n description.flags[flag] = this._flags[flag].describe();\n }\n else if (flag === 'default') {\n if (Ref.isRef(this._flags[flag])) {\n description.flags[flag] = this._flags[flag].toString();\n }\n else if (typeof this._flags[flag] === 'function') {\n description.flags[flag] = {\n description: this._flags[flag].description,\n function : this._flags[flag]\n };\n }\n else {\n description.flags[flag] = this._flags[flag];\n }\n }\n else if (flag === 'lazy' || flag === 'label') {\n // We don't want it in the description\n }\n else {\n description.flags[flag] = this._flags[flag];\n }\n }\n }\n else {\n description.flags = this._flags;\n }\n }\n\n if (this._settings) {\n description.options = Hoek.clone(this._settings);\n }\n\n if (this._baseType) {\n description.base = this._baseType.describe();\n }\n\n if (this._description) {\n description.description = this._description;\n }\n\n if (this._notes.length) {\n description.notes = this._notes;\n }\n\n if (this._tags.length) {\n description.tags = this._tags;\n }\n\n if (this._meta.length) {\n description.meta = this._meta;\n }\n\n if (this._examples.length) {\n description.examples = this._examples;\n }\n\n if (this._unit) {\n description.unit = this._unit;\n }\n\n const valids = this._valids.values();\n if (valids.length) {\n description.valids = valids.map((v) => {\n\n return Ref.isRef(v) ? v.toString() : v;\n });\n }\n\n const invalids = this._invalids.values();\n if (invalids.length) {\n description.invalids = invalids.map((v) => {\n\n return Ref.isRef(v) ? v.toString() : v;\n });\n }\n\n description.rules = [];\n\n for (let i = 0; i < this._tests.length; ++i) {\n const validator = this._tests[i];\n const item = { name: validator.name };\n\n if (validator.arg !== void 0) {\n item.arg = Ref.isRef(validator.arg) ? validator.arg.toString() : validator.arg;\n }\n\n const options = validator.options;\n if (options) {\n if (options.hasRef) {\n item.arg = {};\n const keys = Object.keys(validator.arg);\n for (let j = 0; j < keys.length; ++j) {\n const key = keys[j];\n const value = validator.arg[key];\n item.arg[key] = Ref.isRef(value) ? value.toString() : value;\n }\n }\n\n if (typeof options.description === 'string') {\n item.description = options.description;\n }\n else if (typeof options.description === 'function') {\n item.description = options.description(item.arg);\n }\n }\n\n description.rules.push(item);\n }\n\n if (!description.rules.length) {\n delete description.rules;\n }\n\n const label = this._getLabel();\n if (label) {\n description.label = label;\n }\n\n return description;\n }\n\n label(name) {\n\n Hoek.assert(name && typeof name === 'string', 'Label name must be a non-empty string');\n\n const obj = this.clone();\n obj._flags.label = name;\n return obj;\n }\n\n _getLabel(def) {\n\n return this._flags.label || def;\n }\n\n}]"
}
}
],
"_refs": [],
"_flags": {},
"_description": null,
"_unit": null,
"_notes": [],
"_tags": [],
"_examples": [],
"_meta": [],
"_inner": {
"children": null,
"renames": [],
"dependencies": [],
"patterns": []
}
}
},
{
"key": "name",
"schema": {
"isJoi": true,
"_currentJoi": "[Circular ~.base._currentJoi]",
"_type": "string",
"_settings": null,
"_valids": {
"_set": {},
"_hasRef": false
},
"_invalids": {
"_set": {},
"_hasRef": 0
},
"_tests": [],
"_refs": [],
"_flags": {
"presence": "required"
},
"_description": null,
"_unit": null,
"_notes": [],
"_tags": [],
"_examples": [],
"_meta": [],
"_inner": {}
}
},
{
"key": "coerce",
"schema": {
"isJoi": true,
"_currentJoi": "[Circular ~.base._currentJoi]",
"_type": "object",
"_settings": null,
"_valids": {
"_set": {},
"_hasRef": false
},
"_invalids": {
"_set": {},
"_hasRef": false
},
"_tests": [
{
"func": function (value, state, options) {\n\n if (value.length === n) {\n return value;\n }\n\n return this.createError('function.arity', { n }, state, options);\n },
"name": "arity",
"arg": 3
}
],
"_refs": [],
"_flags": {
"func": true
},
"_description": null,
"_unit": null,
"_notes": [],
"_tags": [],
"_examples": [],
"_meta": [],
"_inner": {
"children": null,
"renames": [],
"dependencies": [],
"patterns": []
}
}
},
{
"key": "pre",
"schema": {
"isJoi": true,
"_currentJoi": "[Circular ~.base._currentJoi]",
"_type": "object",
"_settings": null,
"_valids": {
"_set": {},
"_hasRef": false
},
"_invalids": {
"_set": {},
"_hasRef": false
},
"_tests": [
{
"func": function (value, state, options) {\n\n if (value.length === n) {\n return value;\n }\n\n return this.createError('function.arity', { n }, state, options);\n },
"name": "arity",
"arg": 3
}
],
"_refs": [],
"_flags": {
"func": true
},
"_description": null,
"_unit": null,
"_notes": [],
"_tags": [],
"_examples": [],
"_meta": [],
"_inner": {
"children": null,
"renames": [],
"dependencies": [],
"patterns": []
}
}
},
{
"key": "language",
"schema": "[Circular ~.base]"
},
{
"key": "describe",
"schema": {
"isJoi": true,
"_currentJoi": "[Circular ~.base._currentJoi]",
"_type": "object",
"_settings": null,
"_valids": {
"_set": {},
"_hasRef": false
},
"_invalids": {
"_set": {},
"_hasRef": false
},
"_tests": [
{
"func": function (value, state, options) {\n\n if (value.length === n) {\n return value;\n }\n\n return this.createError('function.arity', { n }, state, options);\n },
"name": "arity",
"arg": 1
}
],
"_refs": [],
"_flags": {
"func": true
},
"_description": null,
"_unit": null,
"_notes": [],
"_tags": [],
"_examples": [],
"_meta": [],
"_inner": {
"children": null,
"renames": [],
"dependencies": [],
"patterns": []
}
}
},
{
"key": "rules",
"schema": {
"isJoi": true,
"_currentJoi": "[Circular ~.base._currentJoi]",
"_type": "array",
"_settings": null,
"_valids": {
"_set": {},
"_hasRef": false
},
"_invalids": {
"_set": {},
"_hasRef": false
},
"_tests": [],
"_refs": [],
"_flags": {
"sparse": false
},
"_description": null,
"_unit": null,
"_notes": [],
"_tags": [],
"_examples": [],
"_meta": [],
"_inner": {
"items": [
{
"isJoi": true,
"_currentJoi": "[Circular ~.base._currentJoi]",
"_type": "object",
"_settings": null,
"_valids": {
"_set": {},
"_hasRef": false
},
"_invalids": {
"_set": {},
"_hasRef": false
},
"_tests": [],
"_refs": [],
"_flags": {},
"_description": null,
"_unit": null,
"_notes": [],
"_tags": [],
"_examples": [],
"_meta": [],
"_inner": {
"children": [
{
"key": "name",
"schema": {
"isJoi": true,
"_currentJoi": "[Circular ~.base._currentJoi]",
"_type": "string",
"_settings": null,
"_valids": {
"_set": {},
"_hasRef": false
},
"_invalids": {
"_set": {},
"_hasRef": 0
},
"_tests": [],
"_refs": [],
"_flags": {
"presence": "required"
},
"_description": null,
"_unit": null,
"_notes": [],
"_tags": [],
"_examples": [],
"_meta": [],
"_inner": {}
}
},
{
"key": "setup",
"schema": {
"isJoi": true,
"_currentJoi": "[Circular ~.base._currentJoi]",
"_type": "object",
"_settings": null,
"_valids": {
"_set": {},
"_hasRef": false
},
"_invalids": {
"_set": {},
"_hasRef": false
},
"_tests": [
{
"func": function (value, state, options) {\n\n if (value.length === n) {\n return value;\n }\n\n return this.createError('function.arity', { n }, state, options);\n },
"name": "arity",
"arg": 1
}
],
"_refs": [],
"_flags": {
"func": true
},
"_description": null,
"_unit": null,
"_notes": [],
"_tags": [],
"_examples": [],
"_meta": [],
"_inner": {
"children": null,
"renames": [],
"dependencies": [],
"patterns": []
}
}
},
{
"key": "validate",
"schema": {
"isJoi": true,
"_currentJoi": "[Circular ~.base._currentJoi]",
"_type": "object",
"_settings": null,
"_valids": {
"_set": {},
"_hasRef": false
},
"_invalids": {
"_set": {},
"_hasRef": false
},
"_tests": [
{
"func": function (value, state, options) {\n\n if (value.length === n) {\n return value;\n }\n\n return this.createError('function.arity', { n }, state, options);\n },
"name": "arity",
"arg": 4
}
],
"_refs": [],
"_flags": {
"func": true
},
"_description": null,
"_unit": null,
"_notes": [],
"_tags": [],
"_examples": [],
"_meta": [],
"_inner": {
"children": null,
"renames": [],
"dependencies": [],
"patterns": []
}
}
},
{
"key": "params",
"schema": {
"isJoi": true,
"_currentJoi": "[Circular ~.base._currentJoi]",
"_type": "alternatives",
"_settings": null,
"_valids": {
"_set": {},
"_hasRef": false
},
"_invalids": {
"_set": {},
"_hasRef": false
},
"_tests": [],
"_refs": [],
"_flags": {},
"_description": null,
"_unit": null,
"_notes": [],
"_tags": [],
"_examples": [],
"_meta": [],
"_inner": {
"matches": [
{
"schema": {
"isJoi": true,
"_currentJoi": "[Circular ~.base._currentJoi]",
"_type": "object",
"_settings": null,
"_valids": {
"_set": {},
"_hasRef": false
},
"_invalids": {
"_set": {},
"_hasRef": false
},
"_tests": [],
"_refs": [],
"_flags": {},
"_description": null,
"_unit": null,
"_notes": [],
"_tags": [],
"_examples": [],
"_meta": [],
"_inner": {
"children": null,
"renames": [],
"dependencies": [],
"patterns": [
{
"regex": {},
"rule": {
"isJoi": true,
"_currentJoi": "[Circular ~.base._currentJoi]",
"_type": "object",
"_settings": null,
"_valids": {
"_set": {},
"_hasRef": false
},
"_invalids": {
"_set": {},
"_hasRef": false
},
"_tests": [
{
"func": function (value, state, options) {\n\n if (value instanceof constructor) {\n return value;\n }\n\n return this.createError('object.type', { type: typeData.name, value }, state, options);\n },
"name": "type",
"arg": {
"name": "Joi object",
"ctor": "[class {\n\n constructor() {\n\n this.isJoi = true;\n this._type = 'any';\n this._settings = null;\n this._valids = new internals.Set();\n this._invalids = new internals.Set();\n this._tests = [];\n this._refs = [];\n this._flags = {\n /*\n presence: 'optional', // optional, required, forbidden, ignore\n allowOnly: false,\n allowUnknown: undefined,\n default: undefined,\n forbidden: false,\n encoding: undefined,\n insensitive: false,\n trim: false,\n normalize: undefined, // NFC, NFD, NFKC, NFKD\n case: undefined, // upper, lower\n empty: undefined,\n func: false,\n raw: false\n */\n };\n\n this._description = null;\n this._unit = null;\n this._notes = [];\n this._tags = [];\n this._examples = [];\n this._meta = [];\n\n this._inner = {}; // Hash of arrays of immutable objects\n }\n\n _init() {\n\n return this;\n }\n\n get schemaType() {\n\n return this._type;\n }\n\n createError(type, context, state, options, flags = this._flags) {\n\n return Errors.create(type, context, state, options, flags);\n }\n\n createOverrideError(type, context, state, options, message, template) {\n\n return Errors.create(type, context, state, options, this._flags, message, template);\n }\n\n checkOptions(options) {\n\n Schemas = Schemas || require('../../schemas');\n\n const result = Schemas.options.validate(options);\n\n if (result.error) {\n throw new Error(result.error.details[0].message);\n }\n }\n\n clone() {\n\n const obj = Object.create(Object.getPrototypeOf(this));\n\n obj.isJoi = true;\n obj._currentJoi = this._currentJoi;\n obj._type = this._type;\n obj._settings = this._settings;\n obj._baseType = this._baseType;\n obj._valids = this._valids.slice();\n obj._invalids = this._invalids.slice();\n obj._tests = this._tests.slice();\n obj._refs = this._refs.slice();\n obj._flags = Hoek.clone(this._flags);\n\n obj._description = this._description;\n obj._unit = this._unit;\n obj._notes = this._notes.slice();\n obj._tags = this._tags.slice();\n obj._examples = this._examples.slice();\n obj._meta = this._meta.slice();\n\n obj._inner = {};\n const inners = Object.keys(this._inner);\n for (let i = 0; i < inners.length; ++i) {\n const key = inners[i];\n obj._inner[key] = this._inner[key] ? this._inner[key].slice() : null;\n }\n\n return obj;\n }\n\n concat(schema) {\n\n Hoek.assert(schema instanceof internals.Any, 'Invalid schema object');\n Hoek.assert(this._type === 'any' || schema._type === 'any' || schema._type === this._type, 'Cannot merge type', this._type, 'with another type:', schema._type);\n\n let obj = this.clone();\n\n if (this._type === 'any' && schema._type !== 'any') {\n\n // Reset values as if we were \"this\"\n const tmpObj = schema.clone();\n const keysToRestore = ['_settings', '_valids', '_invalids', '_tests', '_refs', '_flags', '_description', '_unit',\n '_notes', '_tags', '_examples', '_meta', '_inner'];\n\n for (let i = 0; i < keysToRestore.length; ++i) {\n tmpObj[keysToRestore[i]] = obj[keysToRestore[i]];\n }\n\n obj = tmpObj;\n }\n\n obj._settings = obj._settings ? Settings.concat(obj._settings, schema._settings) : schema._settings;\n obj._valids.merge(schema._valids, schema._invalids);\n obj._invalids.merge(schema._invalids, schema._valids);\n obj._tests.push(...schema._tests);\n obj._refs.push(...schema._refs);\n if (obj._flags.empty && schema._flags.empty) {\n obj._flags.empty = obj._flags.empty.concat(schema._flags.empty);\n const flags = Object.assign({}, schema._flags);\n delete flags.empty;\n Hoek.merge(obj._flags, flags);\n }\n else if (schema._flags.empty) {\n obj._flags.empty = schema._flags.empty;\n const flags = Object.assign({}, schema._flags);\n delete flags.empty;\n Hoek.merge(obj._flags, flags);\n }\n else {\n Hoek.merge(obj._flags, schema._flags);\n }\n\n obj._description = schema._description || obj._description;\n obj._unit = schema._unit || obj._unit;\n obj._notes.push(...schema._notes);\n obj._tags.push(...schema._tags);\n obj._examples.push(...schema._examples);\n obj._meta.push(...schema._meta);\n\n const inners = Object.keys(schema._inner);\n const isObject = obj._type === 'object';\n for (let i = 0; i < inners.length; ++i) {\n const key = inners[i];\n const source = schema._inner[key];\n if (source) {\n const target = obj._inner[key];\n if (target) {\n if (isObject && key === 'children') {\n const keys = {};\n\n for (let j = 0; j < target.length; ++j) {\n keys[target[j].key] = j;\n }\n\n for (let j = 0; j < source.length; ++j) {\n const sourceKey = source[j].key;\n if (keys[sourceKey] >= 0) {\n target[keys[sourceKey]] = {\n key: sourceKey,\n schema: target[keys[sourceKey]].schema.concat(source[j].schema)\n };\n }\n else {\n target.push(source[j]);\n }\n }\n }\n else {\n obj._inner[key] = obj._inner[key].concat(source);\n }\n }\n else {\n obj._inner[key] = source.slice();\n }\n }\n }\n\n return obj;\n }\n\n _test(name, arg, func, options) {\n\n const obj = this.clone();\n obj._tests.push({ func, name, arg, options });\n return obj;\n }\n\n _testUnique(name, arg, func, options) {\n\n const obj = this.clone();\n obj._tests = obj._tests.filter((test) => test.name !== name);\n obj._tests.push({ func, name, arg, options });\n return obj;\n }\n\n options(options) {\n\n Hoek.assert(!options.context, 'Cannot override context');\n this.checkOptions(options);\n\n const obj = this.clone();\n obj._settings = Settings.concat(obj._settings, options);\n return obj;\n }\n\n strict(isStrict) {\n\n const obj = this.clone();\n\n const convert = isStrict === undefined ? false : !isStrict;\n obj._settings = Settings.concat(obj._settings, { convert });\n return obj;\n }\n\n raw(isRaw) {\n\n const value = isRaw === undefined ? true : isRaw;\n\n if (this._flags.raw === value) {\n return this;\n }\n\n const obj = this.clone();\n obj._flags.raw = value;\n return obj;\n }\n\n error(err, options = { self: false }) {\n\n Hoek.assert(err && (err instanceof Error || typeof err === 'function'), 'Must provide a valid Error object or a function');\n\n const unknownKeys = Object.keys(options).filter((k) => !['self'].includes(k));\n Hoek.assert(unknownKeys.length === 0, `Options ${unknownKeys} are unknown`);\n\n const obj = this.clone();\n obj._flags.error = err;\n\n if (options.self) {\n obj._flags.selfError = true;\n }\n\n return obj;\n }\n\n allow(...values) {\n\n const obj = this.clone();\n values = Hoek.flatten(values);\n for (let i = 0; i < values.length; ++i) {\n const value = values[i];\n\n Hoek.assert(value !== undefined, 'Cannot call allow/valid/invalid with undefined');\n obj._invalids.remove(value);\n obj._valids.add(value, obj._refs);\n }\n\n return obj;\n }\n\n valid(...values) {\n\n const obj = this.allow(...values);\n obj._flags.allowOnly = true;\n return obj;\n }\n\n invalid(...values) {\n\n const obj = this.clone();\n values = Hoek.flatten(values);\n for (let i = 0; i < values.length; ++i) {\n const value = values[i];\n\n Hoek.assert(value !== undefined, 'Cannot call allow/valid/invalid with undefined');\n obj._valids.remove(value);\n obj._invalids.add(value, obj._refs);\n }\n\n return obj;\n }\n\n required() {\n\n if (this._flags.presence === 'required') {\n return this;\n }\n\n const obj = this.clone();\n obj._flags.presence = 'required';\n return obj;\n }\n\n optional() {\n\n if (this._flags.presence === 'optional') {\n return this;\n }\n\n const obj = this.clone();\n obj._flags.presence = 'optional';\n return obj;\n }\n\n\n forbidden() {\n\n if (this._flags.presence === 'forbidden') {\n return this;\n }\n\n const obj = this.clone();\n obj._flags.presence = 'forbidden';\n return obj;\n }\n\n\n strip() {\n\n if (this._flags.strip) {\n return this;\n }\n\n const obj = this.clone();\n obj._flags.strip = true;\n return obj;\n }\n\n applyFunctionToChildren(children, fn, args = [], root) {\n\n children = [].concat(children);\n\n if (children.length !== 1 || children[0] !== '') {\n root = root ? (root + '.') : '';\n\n const extraChildren = (children[0] === '' ? children.slice(1) : children).map((child) => {\n\n return root + child;\n });\n\n throw new Error('unknown key(s) ' + extraChildren.join(', '));\n }\n\n return this[fn](...args);\n }\n\n default(value, description) {\n\n if (typeof value === 'function' &&\n !Ref.isRef(value)) {\n\n if (!value.description &&\n description) {\n\n value.description = description;\n }\n\n if (!this._flags.func) {\n Hoek.assert(typeof value.description === 'string' && value.description.length > 0, 'description must be provided when default value is a function');\n }\n }\n\n const obj = this.clone();\n obj._flags.default = value;\n Ref.push(obj._refs, value);\n return obj;\n }\n\n empty(schema) {\n\n const obj = this.clone();\n if (schema === undefined) {\n delete obj._flags.empty;\n }\n else {\n obj._flags.empty = Cast.schema(this._currentJoi, schema);\n }\n\n return obj;\n }\n\n when(condition, options) {\n\n Hoek.assert(options && typeof options === 'object', 'Invalid options');\n Hoek.assert(options.then !== undefined || options.otherwise !== undefined, 'options must have at least one of \"then\" or \"otherwise\"');\n\n const then = options.hasOwnProperty('then') ? this.concat(Cast.schema(this._currentJoi, options.then)) : undefined;\n const otherwise = options.hasOwnProperty('otherwise') ? this.concat(Cast.schema(this._currentJoi, options.otherwise)) : undefined;\n\n Alternatives = Alternatives || require('../alternatives');\n\n const alternativeOptions = { then, otherwise };\n if (Object.prototype.hasOwnProperty.call(options, 'is')) {\n alternativeOptions.is = options.is;\n }\n\n const obj = Alternatives.when(condition, alternativeOptions);\n obj._flags.presence = 'ignore';\n obj._baseType = this;\n\n return obj;\n }\n\n description(desc) {\n\n Hoek.assert(desc && typeof desc === 'string', 'Description must be a non-empty string');\n\n const obj = this.clone();\n obj._description = desc;\n return obj;\n }\n\n notes(notes) {\n\n Hoek.assert(notes && (typeof notes === 'string' || Array.isArray(notes)), 'Notes must be a non-empty string or array');\n\n const obj = this.clone();\n obj._notes = obj._notes.concat(notes);\n return obj;\n }\n\n tags(tags) {\n\n Hoek.assert(tags && (typeof tags === 'string' || Array.isArray(tags)), 'Tags must be a non-empty string or array');\n\n const obj = this.clone();\n obj._tags = obj._tags.concat(tags);\n return obj;\n }\n\n meta(meta) {\n\n Hoek.assert(meta !== undefined, 'Meta cannot be undefined');\n\n const obj = this.clone();\n obj._meta = obj._meta.concat(meta);\n return obj;\n }\n\n example(...examples) {\n\n Hoek.assert(examples.length > 0, 'Missing examples');\n\n const processed = [];\n for (let i = 0; i < examples.length; ++i) {\n const example = [].concat(examples[i]);\n Hoek.assert(example.length <= 2, `Bad example format at index ${i}`);\n\n const value = example[0];\n let options = example[1];\n if (options !== undefined) {\n Hoek.assert(options && typeof options === 'object', `Options for example at index ${i} must be an object`);\n const unknownOptions = Object.keys(options).filter((option) => !['parent', 'context'].includes(option));\n Hoek.assert(unknownOptions.length === 0, `Unknown example options ${unknownOptions} at index ${i}`);\n }\n else {\n options = {};\n }\n\n const localState = new State('', [], options.parent || null);\n const result = this._validate(value, localState, Settings.concat(internals.defaults, options.context ? { context: options.context } : null));\n Hoek.assert(!result.errors, `Bad example at index ${i}:`, result.errors && Errors.process(result.errors, value));\n\n const ex = { value };\n if (Object.keys(options).length) {\n ex.options = options;\n }\n\n processed.push(ex);\n }\n\n const obj = this.clone();\n obj._examples = processed;\n return obj;\n }\n\n unit(name) {\n\n Hoek.assert(name && typeof name === 'string', 'Unit name must be a non-empty string');\n\n const obj = this.clone();\n obj._unit = name;\n return obj;\n }\n\n _prepareEmptyValue(value) {\n\n if (typeof value === 'string' && this._flags.trim) {\n return value.trim();\n }\n\n return value;\n }\n\n _validate(value, state, options, reference) {\n\n const originalValue = value;\n\n // Setup state and settings\n\n state = state || new State('', [], null, reference);\n\n if (this._settings) {\n const isDefaultOptions = options === internals.defaults;\n if (isDefaultOptions && this._settings[Symbols.settingsCache]) {\n options = this._settings[Symbols.settingsCache];\n }\n else {\n options = Settings.concat(this._language ? Settings.concat({ language: this._language }, options) : options, this._settings);\n if (isDefaultOptions) {\n this._settings[Symbols.settingsCache] = options;\n }\n }\n }\n else if (this._language) {\n options = Settings.concat({ language: this._language }, options);\n }\n\n let errors = [];\n\n if (this._coerce) {\n const coerced = this._coerce(value, state, options);\n if (coerced.errors) {\n value = coerced.value;\n errors = errors.concat(coerced.errors);\n return this._finalizeValue(value, originalValue, errors, state, options); // Coerced error always aborts early\n }\n\n value = coerced.value;\n }\n\n if (this._flags.empty && !this._flags.empty._validate(this._prepareEmptyValue(value), null, internals.defaults).errors) {\n value = undefined;\n }\n\n // Check presence requirements\n\n const presence = this._flags.presence || options.presence;\n if (presence === 'optional') {\n if (value === undefined) {\n const isDeepDefault = this._flags.hasOwnProperty('default') && this._flags.default === undefined;\n if (isDeepDefault && this._type === 'object') {\n value = {};\n }\n else {\n return this._finalizeValue(value, originalValue, errors, state, options);\n }\n }\n }\n else if (presence === 'required' &&\n value === undefined) {\n\n errors.push(this.createError('any.required', null, state, options));\n return this._finalizeValue(value, originalValue, errors, state, options);\n }\n else if (presence === 'forbidden') {\n if (value === undefined) {\n return this._finalizeValue(value, originalValue, errors, state, options);\n }\n\n errors.push(this.createError('any.unknown', null, state, options));\n return this._finalizeValue(value, originalValue, errors, state, options);\n }\n\n // Check allowed and denied values using the original value\n\n let match = this._valids.get(value, state, options, this._flags.insensitive);\n if (match) {\n if (options.convert) {\n value = match.value;\n }\n\n return this._finalizeValue(value, originalValue, errors, state, options);\n }\n\n if (this._invalids.has(value, state, options, this._flags.insensitive)) {\n errors.push(this.createError(value === '' ? 'any.empty' : 'any.invalid', { value, invalids: this._invalids.values({ stripUndefined: true }) }, state, options));\n if (options.abortEarly) {\n\n return this._finalizeValue(value, originalValue, errors, state, options);\n }\n }\n\n // Convert value and validate type\n\n if (this._base) {\n const base = this._base(value, state, options);\n if (base.errors) {\n value = base.value;\n errors = errors.concat(base.errors);\n return this._finalizeValue(value, originalValue, errors, state, options); // Base error always aborts early\n }\n\n if (base.value !== value) {\n value = base.value;\n\n // Check allowed and denied values using the converted value\n\n match = this._valids.get(value, state, options, this._flags.insensitive);\n if (match) {\n value = match.value;\n return this._finalizeValue(value, originalValue, errors, state, options);\n }\n\n if (this._invalids.has(value, state, options, this._flags.insensitive)) {\n errors.push(this.createError(value === '' ? 'any.empty' : 'any.invalid', { value, invalids: this._invalids.values({ stripUndefined: true }) }, state, options));\n if (options.abortEarly) {\n return this._finalizeValue(value, originalValue, errors, state, options);\n }\n }\n }\n }\n\n // Required values did not match\n\n if (this._flags.allowOnly) {\n errors.push(this.createError('any.allowOnly', { value, valids: this._valids.values({ stripUndefined: true }) }, state, options));\n if (options.abortEarly) {\n return this._finalizeValue(value, originalValue, errors, state, options);\n }\n }\n\n // Validate tests\n\n for (let i = 0; i < this._tests.length; ++i) {\n const test = this._tests[i];\n const ret = test.func.call(this, value, state, options);\n if (ret instanceof Errors.Err) {\n errors.push(ret);\n if (options.abortEarly) {\n return this._finalizeValue(value, originalValue, errors, state, options);\n }\n }\n else {\n value = ret;\n }\n }\n\n return this._finalizeValue(value, originalValue, errors, state, options);\n }\n\n _finalizeValue(value, originalValue, errors, state, options) {\n\n let finalValue;\n\n if (value !== undefined) {\n finalValue = this._flags.raw ? originalValue : value;\n }\n else if (options.noDefaults) {\n finalValue = value;\n }\n else if (Ref.isRef(this._flags.default)) {\n finalValue = this._flags.default(state.parent, options);\n }\n else if (typeof this._flags.default === 'function' &&\n !(this._flags.func && !this._flags.default.description)) {\n\n let args;\n\n if (state.parent !== null &&\n this._flags.default.length > 0) {\n\n args = [Hoek.clone(state.parent), options];\n }\n\n const defaultValue = internals._try(this._flags.default, args);\n finalValue = defaultValue.value;\n if (defaultValue.error) {\n errors.push(this.createError('any.default', { error: defaultValue.error }, state, options));\n }\n }\n else {\n finalValue = Hoek.clone(this._flags.default);\n }\n\n if (errors.length &&\n typeof this._flags.error === 'function' &&\n (\n !this._flags.selfError ||\n errors.some((e) => state.path.length === e.path.length)\n )\n ) {\n const change = this._flags.error.call(this, errors);\n\n if (typeof change === 'string') {\n errors = [this.createOverrideError('override', { reason: errors }, state, options, change)];\n }\n else {\n errors = [].concat(change)\n .map((err) => {\n\n return err instanceof Error ?\n err :\n this.createOverrideError(err.type || 'override', err.context, state, options, err.message, err.template);\n });\n }\n }\n\n return {\n value: this._flags.strip ? undefined : finalValue,\n finalValue,\n errors: errors.length ? errors : null\n };\n }\n\n _validateWithOptions(value, options, callback) {\n\n if (options) {\n this.checkOptions(options);\n }\n\n const settings = Settings.concat(internals.defaults, options);\n const result = this._validate(value, null, settings);\n const errors = Errors.process(result.errors, value);\n\n if (callback) {\n return callback(errors, result.value);\n }\n\n return {\n error: errors,\n value: result.value,\n then(resolve, reject) {\n\n if (errors) {\n return Promise.reject(errors).catch(reject);\n }\n\n return Promise.resolve(result.value).then(resolve);\n },\n catch(reject) {\n\n if (errors) {\n return Promise.reject(errors).catch(reject);\n }\n\n return Promise.resolve(result.value);\n }\n };\n }\n\n validate(value, options, callback) {\n\n if (typeof options === 'function') {\n return this._validateWithOptions(value, null, options);\n }\n\n return this._validateWithOptions(value, options, callback);\n }\n\n describe() {\n\n const description = {\n type: this._type\n };\n\n const flags = Object.keys(this._flags);\n if (flags.length) {\n if (['empty', 'default', 'lazy', 'label'].some((flag) => this._flags.hasOwnProperty(flag))) {\n description.flags = {};\n for (let i = 0; i < flags.length; ++i) {\n const flag = flags[i];\n if (flag === 'empty') {\n description.flags[flag] = this._flags[flag].describe();\n }\n else if (flag === 'default') {\n if (Ref.isRef(this._flags[flag])) {\n description.flags[flag] = this._flags[flag].toString();\n }\n else if (typeof this._flags[flag] === 'function') {\n description.flags[flag] = {\n description: this._flags[flag].description,\n function : this._flags[flag]\n };\n }\n else {\n description.flags[flag] = this._flags[flag];\n }\n }\n else if (flag === 'lazy' || flag === 'label') {\n // We don't want it in the description\n }\n else {\n description.flags[flag] = this._flags[flag];\n }\n }\n }\n else {\n description.flags = this._flags;\n }\n }\n\n if (this._settings) {\n description.options = Hoek.clone(this._settings);\n }\n\n if (this._baseType) {\n description.base = this._baseType.describe();\n }\n\n if (this._description) {\n description.description = this._description;\n }\n\n if (this._notes.length) {\n description.notes = this._notes;\n }\n\n if (this._tags.length) {\n description.tags = this._tags;\n }\n\n if (this._meta.length) {\n description.meta = this._meta;\n }\n\n if (this._examples.length) {\n description.examples = this._examples;\n }\n\n if (this._unit) {\n description.unit = this._unit;\n }\n\n const valids = this._valids.values();\n if (valids.length) {\n description.valids = valids.map((v) => {\n\n return Ref.isRef(v) ? v.toString() : v;\n });\n }\n\n const invalids = this._invalids.values();\n if (invalids.length) {\n description.invalids = invalids.map((v) => {\n\n return Ref.isRef(v) ? v.toString() : v;\n });\n }\n\n description.rules = [];\n\n for (let i = 0; i < this._tests.length; ++i) {\n const validator = this._tests[i];\n const item = { name: validator.name };\n\n if (validator.arg !== void 0) {\n item.arg = Ref.isRef(validator.arg) ? validator.arg.toString() : validator.arg;\n }\n\n const options = validator.options;\n if (options) {\n if (options.hasRef) {\n item.arg = {};\n const keys = Object.keys(validator.arg);\n for (let j = 0; j < keys.length; ++j) {\n const key = keys[j];\n const value = validator.arg[key];\n item.arg[key] = Ref.isRef(value) ? value.toString() : value;\n }\n }\n\n if (typeof options.description === 'string') {\n item.description = options.description;\n }\n else if (typeof options.description === 'function') {\n item.description = options.description(item.arg);\n }\n }\n\n description.rules.push(item);\n }\n\n if (!description.rules.length) {\n delete description.rules;\n }\n\n const label = this._getLabel();\n if (label) {\n description.label = label;\n }\n\n return description;\n }\n\n label(name) {\n\n Hoek.assert(name && typeof name === 'string', 'Label name must be a non-empty string');\n\n const obj = this.clone();\n obj._flags.label = name;\n return obj;\n }\n\n _getLabel(def) {\n\n return this._flags.label || def;\n }\n\n}]"
}
}
],
"_refs": [],
"_flags": {},
"_description": null,
"_unit": null,
"_notes": [],
"_tags": [],
"_examples": [],
"_meta": [],
"_inner": {
"children": null,
"renames": [],
"dependencies": [],
"patterns": []
}
}
}
]
}
}
},
{
"schema": {
"isJoi": true,
"_currentJoi": "[Circular ~.base._currentJoi]",
"_type": "object",
"_settings": null,
"_valids": {
"_set": {},
"_hasRef": false
},
"_invalids": {
"_set": {},
"_hasRef": false
},
"_tests": [
{
"func": function (value, state, options) {\n\n if (value instanceof constructor) {\n return value;\n }\n\n return this.createError('object.type', { type: typeData.name, value }, state, options);\n },
"name": "type",
"arg": {
"name": "Joi object",
"ctor": "[class extends Any {\n\n constructor() {\n\n super();\n this._type = 'object';\n this._inner.children = null;\n this._inner.renames = [];\n this._inner.dependencies = [];\n this._inner.patterns = [];\n }\n\n _init(...args) {\n\n return args.length ? this.keys(...args) : this;\n }\n\n _base(value, state, options) {\n\n let target = value;\n const errors = [];\n const finish = () => {\n\n return {\n value: target,\n errors: errors.length ? errors : null\n };\n };\n\n if (typeof value === 'string' &&\n options.convert) {\n\n if (value.length > 1 &&\n (value[0] === '{' || /^\\s*\\{/.test(value))) {\n\n try {\n value = Bourne.parse(value);\n }\n catch (e) { }\n }\n }\n\n const type = this._flags.func ? 'function' : 'object';\n if (!value ||\n typeof value !== type ||\n Array.isArray(value)) {\n\n errors.push(this.createError(type + '.base', { value }, state, options));\n return finish();\n }\n\n // Skip if there are no other rules to test\n\n if (!this._inner.renames.length &&\n !this._inner.dependencies.length &&\n !this._inner.children && // null allows any keys\n !this._inner.patterns.length) {\n\n target = value;\n return finish();\n }\n\n // Ensure target is a local copy (parsed) or shallow copy\n\n if (target === value) {\n if (type === 'object') {\n target = Object.create(Object.getPrototypeOf(value));\n }\n else {\n target = function (...args) {\n\n return value.apply(this, args);\n };\n\n target.prototype = Hoek.clone(value.prototype);\n }\n\n const valueKeys = Object.keys(value);\n for (let i = 0; i < valueKeys.length; ++i) {\n target[valueKeys[i]] = value[valueKeys[i]];\n }\n }\n else {\n target = value;\n }\n\n // Rename keys\n\n const renamed = {};\n for (let i = 0; i < this._inner.renames.length; ++i) {\n const rename = this._inner.renames[i];\n\n if (rename.isRegExp) {\n const targetKeys = Object.keys(target);\n const matchedTargetKeys = [];\n\n for (let j = 0; j < targetKeys.length; ++j) {\n if (rename.from.test(targetKeys[j])) {\n matchedTargetKeys.push(targetKeys[j]);\n }\n }\n\n const allUndefined = matchedTargetKeys.every((key) => target[key] === undefined);\n if (rename.options.ignoreUndefined && allUndefined) {\n continue;\n }\n\n if (!rename.options.multiple &&\n renamed[rename.to]) {\n\n errors.push(this.createError('object.rename.regex.multiple', { from: matchedTargetKeys, to: rename.to }, state, options));\n if (options.abortEarly) {\n return finish();\n }\n }\n\n if (Object.prototype.hasOwnProperty.call(target, rename.to) &&\n !rename.options.override &&\n !renamed[rename.to]) {\n\n errors.push(this.createError('object.rename.regex.override', { from: matchedTargetKeys, to: rename.to }, state, options));\n if (options.abortEarly) {\n return finish();\n }\n }\n\n if (allUndefined) {\n delete target[rename.to];\n }\n else {\n target[rename.to] = target[matchedTargetKeys[matchedTargetKeys.length - 1]];\n }\n\n renamed[rename.to] = true;\n\n if (!rename.options.alias) {\n for (let j = 0; j < matchedTargetKeys.length; ++j) {\n delete target[matchedTargetKeys[j]];\n }\n }\n }\n else {\n if (rename.options.ignoreUndefined && target[rename.from] === undefined) {\n continue;\n }\n\n if (!rename.options.multiple &&\n renamed[rename.to]) {\n\n errors.push(this.createError('object.rename.multiple', { from: rename.from, to: rename.to }, state, options));\n if (options.abortEarly) {\n return finish();\n }\n }\n\n if (Object.prototype.hasOwnProperty.call(target, rename.to) &&\n !rename.options.override &&\n !renamed[rename.to]) {\n\n errors.push(this.createError('object.rename.override', { from: rename.from, to: rename.to }, state, options));\n if (options.abortEarly) {\n return finish();\n }\n }\n\n if (target[rename.from] === undefined) {\n delete target[rename.to];\n }\n else {\n target[rename.to] = target[rename.from];\n }\n\n renamed[rename.to] = true;\n\n if (!rename.options.alias) {\n delete target[rename.from];\n }\n }\n }\n\n // Validate schema\n\n if (!this._inner.children && // null allows any keys\n !this._inner.patterns.length &&\n !this._inner.dependencies.length) {\n\n return finish();\n }\n\n const unprocessed = new Set(Object.keys(target));\n\n if (this._inner.children) {\n const stripProps = [];\n\n for (let i = 0; i < this._inner.children.length; ++i) {\n const child = this._inner.children[i];\n const key = child.key;\n const item = target[key];\n\n unprocessed.delete(key);\n\n const localState = new State(key, [...state.path, key], target, state.reference);\n const result = child.schema._validate(item, localState, options);\n if (result.errors) {\n errors.push(this.createError('object.child', { key, child: child.schema._getLabel(key), reason: result.errors }, localState, options));\n\n if (options.abortEarly) {\n return finish();\n }\n }\n else {\n if (child.schema._flags.strip || (result.value === undefined && result.value !== item)) {\n stripProps.push(key);\n target[key] = result.finalValue;\n }\n else if (result.value !== undefined) {\n target[key] = result.value;\n }\n }\n }\n\n for (let i = 0; i < stripProps.length; ++i) {\n delete target[stripProps[i]];\n }\n }\n\n // Unknown keys\n\n if (unprocessed.size && this._inner.patterns.length) {\n\n for (const key of unprocessed) {\n const localState = new State(key, [...state.path, key], target, state.reference);\n const item = target[key];\n\n for (let i = 0; i < this._inner.patterns.length; ++i) {\n const pattern = this._inner.patterns[i];\n\n if (pattern.regex ?\n pattern.regex.test(key) :\n !pattern.schema._validate(key, state, { ...options, abortEarly:true }).errors) {\n\n unprocessed.delete(key);\n\n const result = pattern.rule._validate(item, localState, options);\n if (result.errors) {\n errors.push(this.createError('object.child', {\n key,\n child: pattern.rule._getLabel(key),\n reason: result.errors\n }, localState, options));\n\n if (options.abortEarly) {\n return finish();\n }\n }\n\n target[key] = result.value;\n }\n }\n }\n }\n\n if (unprocessed.size && (this._inner.children || this._inner.patterns.length)) {\n if ((options.stripUnknown && this._flags.allowUnknown !== true) ||\n options.skipFunctions) {\n\n const stripUnknown = options.stripUnknown\n ? (options.stripUnknown === true ? true : !!options.stripUnknown.objects)\n : false;\n\n\n for (const key of unprocessed) {\n if (stripUnknown) {\n delete target[key];\n unprocessed.delete(key);\n }\n else if (typeof target[key] === 'function') {\n unprocessed.delete(key);\n }\n }\n }\n\n if ((this._flags.allowUnknown !== undefined ? !this._flags.allowUnknown : !options.allowUnknown)) {\n\n for (const unprocessedKey of unprocessed) {\n errors.push(this.createError('object.allowUnknown', { child: unprocessedKey, value: target[unprocessedKey] }, {\n key: unprocessedKey,\n path: [...state.path, unprocessedKey]\n }, options, {}));\n }\n }\n }\n\n // Validate dependencies\n\n for (let i = 0; i < this._inner.dependencies.length; ++i) {\n const dep = this._inner.dependencies[i];\n const hasKey = dep.key !== null;\n const splitKey = hasKey && dep.key.split('.');\n const localState = hasKey ? new State(splitKey[splitKey.length - 1], [...state.path, ...splitKey]) : new State(null, state.path);\n const err = internals[dep.type].call(this, dep.key, hasKey && Hoek.reach(target, dep.key, { functions: true }), dep.peers, target, localState, options);\n if (err instanceof Errors.Err) {\n errors.push(err);\n if (options.abortEarly) {\n return finish();\n }\n }\n }\n\n return finish();\n }\n\n keys(schema) {\n\n Hoek.assert(schema === null || schema === undefined || typeof schema === 'object', 'Object schema must be a valid object');\n Hoek.assert(!schema || !(schema instanceof Any), 'Object schema cannot be a joi schema');\n\n const obj = this.clone();\n\n if (!schema) {\n obj._inner.children = null;\n return obj;\n }\n\n const children = Object.keys(schema);\n\n if (!children.length) {\n obj._inner.children = [];\n return obj;\n }\n\n const topo = new Topo();\n if (obj._inner.children) {\n for (let i = 0; i < obj._inner.children.length; ++i) {\n const child = obj._inner.children[i];\n\n // Only add the key if we are not going to replace it later\n if (!children.includes(child.key)) {\n topo.add(child, { after: child._refs, group: child.key });\n }\n }\n }\n\n for (let i = 0; i < children.length; ++i) {\n const key = children[i];\n const child = schema[key];\n try {\n const cast = Cast.schema(this._currentJoi, child);\n topo.add({ key, schema: cast }, { after: cast._refs, group: key });\n }\n catch (castErr) {\n if (castErr.hasOwnProperty('path')) {\n castErr.path = key + '.' + castErr.path;\n }\n else {\n castErr.path = key;\n }\n\n throw castErr;\n }\n }\n\n obj._inner.children = topo.nodes;\n\n return obj;\n }\n\n append(schema) {\n // Skip any changes\n if (schema === null || schema === undefined || Object.keys(schema).length === 0) {\n return this;\n }\n\n return this.keys(schema);\n }\n\n unknown(allow) {\n\n const value = allow !== false;\n\n if (this._flags.allowUnknown === value) {\n return this;\n }\n\n const obj = this.clone();\n obj._flags.allowUnknown = value;\n return obj;\n }\n\n length(limit) {\n\n Hoek.assert(Number.isSafeInteger(limit) && limit >= 0, 'limit must be a positive integer');\n\n return this._test('length', limit, function (value, state, options) {\n\n if (Object.keys(value).length === limit) {\n return value;\n }\n\n return this.createError('object.length', { limit, value }, state, options);\n });\n }\n\n min(limit) {\n\n Hoek.assert(Number.isSafeInteger(limit) && limit >= 0, 'limit must be a positive integer');\n\n return this._test('min', limit, function (value, state, options) {\n\n if (Object.keys(value).length >= limit) {\n return value;\n }\n\n return this.createError('object.min', { limit, value }, state, options);\n });\n }\n\n max(limit) {\n\n Hoek.assert(Number.isSafeInteger(limit) && limit >= 0, 'limit must be a positive integer');\n\n return this._test('max', limit, function (value, state, options) {\n\n if (Object.keys(value).length <= limit) {\n return value;\n }\n\n return this.createError('object.max', { limit, value }, state, options);\n });\n }\n\n pattern(pattern, schema) {\n\n const isRegExp = pattern instanceof RegExp;\n Hoek.assert(isRegExp || pattern instanceof Any, 'pattern must be a regex or schema');\n Hoek.assert(schema !== undefined, 'Invalid rule');\n\n if (isRegExp) {\n Hoek.assert(!pattern.flags.includes('g') && !pattern.flags.includes('y'), 'pattern should not use global or sticky mode');\n }\n\n try {\n schema = Cast.schema(this._currentJoi, schema);\n }\n catch (castErr) {\n if (castErr.hasOwnProperty('path')) {\n castErr.message = `${castErr.message}(${castErr.path})`;\n }\n\n throw castErr;\n }\n\n const obj = this.clone();\n if (isRegExp) {\n obj._inner.patterns.push({ regex: pattern, rule: schema });\n }\n else {\n obj._inner.patterns.push({ schema: pattern, rule: schema });\n }\n\n return obj;\n }\n\n schema() {\n\n return this._test('schema', null, function (value, state, options) {\n\n if (value instanceof Any) {\n return value;\n }\n\n return this.createError('object.schema', null, state, options);\n });\n }\n\n with(key, peers) {\n\n Hoek.assert(arguments.length === 2, 'Invalid number of arguments, expected 2.');\n\n return this._dependency('with', key, peers);\n }\n\n without(key, peers) {\n\n Hoek.assert(arguments.length === 2, 'Invalid number of arguments, expected 2.');\n\n return this._dependency('without', key, peers);\n }\n\n xor(...peers) {\n\n peers = Hoek.flatten(peers);\n return this._dependency('xor', null, peers);\n }\n\n oxor(...peers) {\n\n return this._dependency('oxor', null, peers);\n }\n\n or(...peers) {\n\n peers = Hoek.flatten(peers);\n return this._dependency('or', null, peers);\n }\n\n and(...peers) {\n\n peers = Hoek.flatten(peers);\n return this._dependency('and', null, peers);\n }\n\n nand(...peers) {\n\n peers = Hoek.flatten(peers);\n return this._dependency('nand', null, peers);\n }\n\n requiredKeys(...children) {\n\n children = Hoek.flatten(children);\n return this.applyFunctionToChildren(children, 'required');\n }\n\n optionalKeys(...children) {\n\n children = Hoek.flatten(children);\n return this.applyFunctionToChildren(children, 'optional');\n }\n\n forbiddenKeys(...children) {\n\n children = Hoek.flatten(children);\n return this.applyFunctionToChildren(children, 'forbidden');\n }\n\n rename(from, to, options) {\n\n Hoek.assert(typeof from === 'string' || from instanceof RegExp, 'Rename missing the from argument');\n Hoek.assert(typeof to === 'string', 'Rename missing the to argument');\n Hoek.assert(to !== from, 'Cannot rename key to same name:', from);\n\n for (let i = 0; i < this._inner.renames.length; ++i) {\n Hoek.assert(this._inner.renames[i].from !== from, 'Cannot rename the same key multiple times');\n }\n\n const obj = this.clone();\n\n obj._inner.renames.push({\n from,\n to,\n options: Hoek.applyToDefaults(internals.renameDefaults, options || {}),\n isRegExp: from instanceof RegExp\n });\n\n return obj;\n }\n\n applyFunctionToChildren(children, fn, args = [], root) {\n\n children = [].concat(children);\n Hoek.assert(children.length > 0, 'expected at least one children');\n\n const groupedChildren = internals.groupChildren(children);\n let obj;\n\n if ('' in groupedChildren) {\n obj = this[fn](...args);\n delete groupedChildren[''];\n }\n else {\n obj = this.clone();\n }\n\n if (obj._inner.children) {\n root = root ? (root + '.') : '';\n\n for (let i = 0; i < obj._inner.children.length; ++i) {\n const child = obj._inner.children[i];\n const group = groupedChildren[child.key];\n\n if (group) {\n obj._inner.children[i] = {\n key: child.key,\n _refs: child._refs,\n schema: child.schema.applyFunctionToChildren(group, fn, args, root + child.key)\n };\n\n delete groupedChildren[child.key];\n }\n }\n }\n\n const remaining = Object.keys(groupedChildren);\n Hoek.assert(remaining.length === 0, 'unknown key(s)', remaining.join(', '));\n\n return obj;\n }\n\n _dependency(type, key, peers) {\n\n peers = [].concat(peers);\n for (let i = 0; i < peers.length; ++i) {\n Hoek.assert(typeof peers[i] === 'string', type, 'peers must be a string or array of strings');\n }\n\n const obj = this.clone();\n obj._inner.dependencies.push({ type, key, peers });\n return obj;\n }\n\n describe(shallow) {\n\n const description = super.describe();\n\n if (description.rules) {\n for (let i = 0; i < description.rules.length; ++i) {\n const rule = description.rules[i];\n // Coverage off for future-proof descriptions, only object().assert() is use right now\n if (/* $lab:coverage:off$ */rule.arg &&\n typeof rule.arg === 'object' &&\n rule.arg.schema &&\n rule.arg.ref /* $lab:coverage:on$ */) {\n rule.arg = {\n schema: rule.arg.schema.describe(),\n ref: rule.arg.ref.toString()\n };\n }\n }\n }\n\n if (this._inner.children &&\n !shallow) {\n\n description.children = {};\n for (let i = 0; i < this._inner.children.length; ++i) {\n const child = this._inner.children[i];\n description.children[child.key] = child.schema.describe();\n }\n }\n\n if (this._inner.dependencies.length) {\n description.dependencies = Hoek.clone(this._inner.dependencies);\n }\n\n if (this._inner.patterns.length) {\n description.patterns = [];\n\n for (let i = 0; i < this._inner.patterns.length; ++i) {\n const pattern = this._inner.patterns[i];\n if (pattern.regex) {\n description.patterns.push({ regex: pattern.regex.toString(), rule: pattern.rule.describe() });\n }\n else {\n description.patterns.push({ schema: pattern.schema.describe(), rule: pattern.rule.describe() });\n }\n }\n }\n\n if (this._inner.renames.length > 0) {\n description.renames = Hoek.clone(this._inner.renames);\n }\n\n return description;\n }\n\n assert(ref, schema, message) {\n\n ref = Cast.ref(ref);\n Hoek.assert(ref.isContext || ref.depth > 1, 'Cannot use assertions for root level references - use direct key rules instead');\n message = message || 'pass the assertion test';\n Hoek.assert(typeof message === 'string', 'Message must be a string');\n\n try {\n schema = Cast.schema(this._currentJoi, schema);\n }\n catch (castErr) {\n if (castErr.hasOwnProperty('path')) {\n castErr.message = `${castErr.message}(${castErr.path})`;\n }\n\n throw castErr;\n }\n\n const key = ref.path[ref.path.length - 1];\n const path = ref.path.join('.');\n\n return this._test('assert', { schema, ref }, function (value, state, options) {\n\n const result = schema._validate(ref(value), null, options, value);\n if (!result.errors) {\n return value;\n }\n\n const localState = new State(key, ref.path, state.parent, state.reference);\n return this.createError('object.assert', { ref: path, message }, localState, options);\n });\n }\n\n type(constructor, name = constructor.name) {\n\n Hoek.assert(typeof constructor === 'function', 'type must be a constructor function');\n const typeData = {\n name,\n ctor: constructor\n };\n\n return this._test('type', typeData, function (value, state, options) {\n\n if (value instanceof constructor) {\n return value;\n }\n\n return this.createError('object.type', { type: typeData.name, value }, state, options);\n });\n }\n}]"
}
}
],
"_refs": [],
"_flags": {},
"_description": null,
"_unit": null,
"_notes": [],
"_tags": [],
"_examples": [],
"_meta": [],
"_inner": {
"children": null,
"renames": [],
"dependencies": [],
"patterns": []
}
}
}
]
}
}
},
{
"key": "description",
"schema": {
"isJoi": true,
"_currentJoi": "[Circular ~.base._currentJoi]",
"_type": "alternatives",
"_settings": null,
"_valids": {
"_set": {},
"_hasRef": false
},
"_invalids": {
"_set": {},
"_hasRef": false
},
"_tests": [],
"_refs": [],
"_flags": {},
"_description": null,
"_unit": null,
"_notes": [],
"_tags": [],
"_examples": [],
"_meta": [],
"_inner": {
"matches": [
{
"schema": {
"isJoi": true,
"_type": "string",
"_settings": null,
"_valids": {
"_set": {},
"_hasRef": false
},
"_invalids": {
"_set": {},
"_hasRef": 0
},
"_tests": [],
"_refs": [],
"_flags": {},
"_description": null,
"_unit": null,
"_notes": [],
"_tags": [],
"_examples": [],
"_meta": [],
"_inner": {},
"_currentJoi": "[Circular ~.base._currentJoi]"
}
},
{
"schema": {
"isJoi": true,
"_currentJoi": "[Circular ~.base._currentJoi]",
"_type": "object",
"_settings": null,
"_valids": {
"_set": {},
"_hasRef": false
},
"_invalids": {
"_set": {},
"_hasRef": false
},
"_tests": [
{
"func": function (value, state, options) {\n\n if (value.length === n) {\n return value;\n }\n\n return this.createError('function.arity', { n }, state, options);\n },
"name": "arity",
"arg": 1
}
],
"_refs": [],
"_flags": {
"func": true
},
"_description": null,
"_unit": null,
"_notes": [],
"_tags": [],
"_examples": [],
"_meta": [],
"_inner": {
"children": null,
"renames": [],
"dependencies": [],
"patterns": []
}
}
}
]
}
}
}
],
"renames": [],
"dependencies": [
{
"type": "or",
"key": null,
"peers": [
"setup",
"validate"
]
}
],
"patterns": []
}
}
],
"ordereds": [],
"inclusions": [
{
"isJoi": true,
"_currentJoi": "[Circular ~.base._currentJoi]",
"_type": "object",
"_settings": null,
"_valids": {
"_set": {},
"_hasRef": false
},
"_invalids": {
"_set": {},
"_hasRef": false
},
"_tests": [],
"_refs": [],
"_flags": {},
"_description": null,
"_unit": null,
"_notes": [],
"_tags": [],
"_examples": [],
"_meta": [],
"_inner": {
"children": [
{
"key": "name",
"schema": {
"isJoi": true,
"_currentJoi": "[Circular ~.base._currentJoi]",
"_type": "string",
"_settings": null,
"_valids": {
"_set": {},
"_hasRef": false
},
"_invalids": {
"_set": {},
"_hasRef": 0
},
"_tests": [],
"_refs": [],
"_flags": {
"presence": "required"
},
"_description": null,
"_unit": null,
"_notes": [],
"_tags": [],
"_examples": [],
"_meta": [],
"_inner": {}
}
},
{
"key": "setup",
"schema": {
"isJoi": true,
"_currentJoi": "[Circular ~.base._currentJoi]",
"_type": "object",
"_settings": null,
"_valids": {
"_set": {},
"_hasRef": false
},
"_invalids": {
"_set": {},
"_hasRef": false
},
"_tests": [
{
"func": function (value, state, options) {\n\n if (value.length === n) {\n return value;\n }\n\n return this.createError('function.arity', { n }, state, options);\n },
"name": "arity",
"arg": 1
}
],
"_refs": [],
"_flags": {
"func": true
},
"_description": null,
"_unit": null,
"_notes": [],
"_tags": [],
"_examples": [],
"_meta": [],
"_inner": {
"children": null,
"renames": [],
"dependencies": [],
"patterns": []
}
}
},
{
"key": "validate",
"schema": {
"isJoi": true,
"_currentJoi": "[Circular ~.base._currentJoi]",
"_type": "object",
"_settings": null,
"_valids": {
"_set": {},
"_hasRef": false
},
"_invalids": {
"_set": {},
"_hasRef": false
},
"_tests": [
{
"func": function (value, state, options) {\n\n if (value.length === n) {\n return value;\n }\n\n return this.createError('function.arity', { n }, state, options);\n },
"name": "arity",
"arg": 4
}
],
"_refs": [],
"_flags": {
"func": true
},
"_description": null,
"_unit": null,
"_notes": [],
"_tags": [],
"_examples": [],
"_meta": [],
"_inner": {
"children": null,
"renames": [],
"dependencies": [],
"patterns": []
}
}
},
{
"key": "params",
"schema": {
"isJoi": true,
"_currentJoi": "[Circular ~.base._currentJoi]",
"_type": "alternatives",
"_settings": null,
"_valids": {
"_set": {},
"_hasRef": false
},
"_invalids": {
"_set": {},
"_hasRef": false
},
"_tests": [],
"_refs": [],
"_flags": {},
"_description": null,
"_unit": null,
"_notes": [],
"_tags": [],
"_examples": [],
"_meta": [],
"_inner": {
"matches": [
{
"schema": {
"isJoi": true,
"_currentJoi": "[Circular ~.base._currentJoi]",
"_type": "object",
"_settings": null,
"_valids": {
"_set": {},
"_hasRef": false
},
"_invalids": {
"_set": {},
"_hasRef": false
},
"_tests": [],
"_refs": [],
"_flags": {},
"_description": null,
"_unit": null,
"_notes": [],
"_tags": [],
"_examples": [],
"_meta": [],
"_inner": {
"children": null,
"renames": [],
"dependencies": [],
"patterns": [
{
"regex": {},
"rule": {
"isJoi": true,
"_currentJoi": "[Circular ~.base._currentJoi]",
"_type": "object",
"_settings": null,
"_valids": {
"_set": {},
"_hasRef": false
},
"_invalids": {
"_set": {},
"_hasRef": false
},
"_tests": [
{
"func": function (value, state, options) {\n\n if (value instanceof constructor) {\n return value;\n }\n\n return this.createError('object.type', { type: typeData.name, value }, state, options);\n },
"name": "type",
"arg": {
"name": "Joi object",
"ctor": "[class {\n\n constructor() {\n\n this.isJoi = true;\n this._type = 'any';\n this._settings = null;\n this._valids = new internals.Set();\n this._invalids = new internals.Set();\n this._tests = [];\n this._refs = [];\n this._flags = {\n /*\n presence: 'optional', // optional, required, forbidden, ignore\n allowOnly: false,\n allowUnknown: undefined,\n default: undefined,\n forbidden: false,\n encoding: undefined,\n insensitive: false,\n trim: false,\n normalize: undefined, // NFC, NFD, NFKC, NFKD\n case: undefined, // upper, lower\n empty: undefined,\n func: false,\n raw: false\n */\n };\n\n this._description = null;\n this._unit = null;\n this._notes = [];\n this._tags = [];\n this._examples = [];\n this._meta = [];\n\n this._inner = {}; // Hash of arrays of immutable objects\n }\n\n _init() {\n\n return this;\n }\n\n get schemaType() {\n\n return this._type;\n }\n\n createError(type, context, state, options, flags = this._flags) {\n\n return Errors.create(type, context, state, options, flags);\n }\n\n createOverrideError(type, context, state, options, message, template) {\n\n return Errors.create(type, context, state, options, this._flags, message, template);\n }\n\n checkOptions(options) {\n\n Schemas = Schemas || require('../../schemas');\n\n const result = Schemas.options.validate(options);\n\n if (result.error) {\n throw new Error(result.error.details[0].message);\n }\n }\n\n clone() {\n\n const obj = Object.create(Object.getPrototypeOf(this));\n\n obj.isJoi = true;\n obj._currentJoi = this._currentJoi;\n obj._type = this._type;\n obj._settings = this._settings;\n obj._baseType = this._baseType;\n obj._valids = this._valids.slice();\n obj._invalids = this._invalids.slice();\n obj._tests = this._tests.slice();\n obj._refs = this._refs.slice();\n obj._flags = Hoek.clone(this._flags);\n\n obj._description = this._description;\n obj._unit = this._unit;\n obj._notes = this._notes.slice();\n obj._tags = this._tags.slice();\n obj._examples = this._examples.slice();\n obj._meta = this._meta.slice();\n\n obj._inner = {};\n const inners = Object.keys(this._inner);\n for (let i = 0; i < inners.length; ++i) {\n const key = inners[i];\n obj._inner[key] = this._inner[key] ? this._inner[key].slice() : null;\n }\n\n return obj;\n }\n\n concat(schema) {\n\n Hoek.assert(schema instanceof internals.Any, 'Invalid schema object');\n Hoek.assert(this._type === 'any' || schema._type === 'any' || schema._type === this._type, 'Cannot merge type', this._type, 'with another type:', schema._type);\n\n let obj = this.clone();\n\n if (this._type === 'any' && schema._type !== 'any') {\n\n // Reset values as if we were \"this\"\n const tmpObj = schema.clone();\n const keysToRestore = ['_settings', '_valids', '_invalids', '_tests', '_refs', '_flags', '_description', '_unit',\n '_notes', '_tags', '_examples', '_meta', '_inner'];\n\n for (let i = 0; i < keysToRestore.length; ++i) {\n tmpObj[keysToRestore[i]] = obj[keysToRestore[i]];\n }\n\n obj = tmpObj;\n }\n\n obj._settings = obj._settings ? Settings.concat(obj._settings, schema._settings) : schema._settings;\n obj._valids.merge(schema._valids, schema._invalids);\n obj._invalids.merge(schema._invalids, schema._valids);\n obj._tests.push(...schema._tests);\n obj._refs.push(...schema._refs);\n if (obj._flags.empty && schema._flags.empty) {\n obj._flags.empty = obj._flags.empty.concat(schema._flags.empty);\n const flags = Object.assign({}, schema._flags);\n delete flags.empty;\n Hoek.merge(obj._flags, flags);\n }\n else if (schema._flags.empty) {\n obj._flags.empty = schema._flags.empty;\n const flags = Object.assign({}, schema._flags);\n delete flags.empty;\n Hoek.merge(obj._flags, flags);\n }\n else {\n Hoek.merge(obj._flags, schema._flags);\n }\n\n obj._description = schema._description || obj._description;\n obj._unit = schema._unit || obj._unit;\n obj._notes.push(...schema._notes);\n obj._tags.push(...schema._tags);\n obj._examples.push(...schema._examples);\n obj._meta.push(...schema._meta);\n\n const inners = Object.keys(schema._inner);\n const isObject = obj._type === 'object';\n for (let i = 0; i < inners.length; ++i) {\n const key = inners[i];\n const source = schema._inner[key];\n if (source) {\n const target = obj._inner[key];\n if (target) {\n if (isObject && key === 'children') {\n const keys = {};\n\n for (let j = 0; j < target.length; ++j) {\n keys[target[j].key] = j;\n }\n\n for (let j = 0; j < source.length; ++j) {\n const sourceKey = source[j].key;\n if (keys[sourceKey] >= 0) {\n target[keys[sourceKey]] = {\n key: sourceKey,\n schema: target[keys[sourceKey]].schema.concat(source[j].schema)\n };\n }\n else {\n target.push(source[j]);\n }\n }\n }\n else {\n obj._inner[key] = obj._inner[key].concat(source);\n }\n }\n else {\n obj._inner[key] = source.slice();\n }\n }\n }\n\n return obj;\n }\n\n _test(name, arg, func, options) {\n\n const obj = this.clone();\n obj._tests.push({ func, name, arg, options });\n return obj;\n }\n\n _testUnique(name, arg, func, options) {\n\n const obj = this.clone();\n obj._tests = obj._tests.filter((test) => test.name !== name);\n obj._tests.push({ func, name, arg, options });\n return obj;\n }\n\n options(options) {\n\n Hoek.assert(!options.context, 'Cannot override context');\n this.checkOptions(options);\n\n const obj = this.clone();\n obj._settings = Settings.concat(obj._settings, options);\n return obj;\n }\n\n strict(isStrict) {\n\n const obj = this.clone();\n\n const convert = isStrict === undefined ? false : !isStrict;\n obj._settings = Settings.concat(obj._settings, { convert });\n return obj;\n }\n\n raw(isRaw) {\n\n const value = isRaw === undefined ? true : isRaw;\n\n if (this._flags.raw === value) {\n return this;\n }\n\n const obj = this.clone();\n obj._flags.raw = value;\n return obj;\n }\n\n error(err, options = { self: false }) {\n\n Hoek.assert(err && (err instanceof Error || typeof err === 'function'), 'Must provide a valid Error object or a function');\n\n const unknownKeys = Object.keys(options).filter((k) => !['self'].includes(k));\n Hoek.assert(unknownKeys.length === 0, `Options ${unknownKeys} are unknown`);\n\n const obj = this.clone();\n obj._flags.error = err;\n\n if (options.self) {\n obj._flags.selfError = true;\n }\n\n return obj;\n }\n\n allow(...values) {\n\n const obj = this.clone();\n values = Hoek.flatten(values);\n for (let i = 0; i < values.length; ++i) {\n const value = values[i];\n\n Hoek.assert(value !== undefined, 'Cannot call allow/valid/invalid with undefined');\n obj._invalids.remove(value);\n obj._valids.add(value, obj._refs);\n }\n\n return obj;\n }\n\n valid(...values) {\n\n const obj = this.allow(...values);\n obj._flags.allowOnly = true;\n return obj;\n }\n\n invalid(...values) {\n\n const obj = this.clone();\n values = Hoek.flatten(values);\n for (let i = 0; i < values.length; ++i) {\n const value = values[i];\n\n Hoek.assert(value !== undefined, 'Cannot call allow/valid/invalid with undefined');\n obj._valids.remove(value);\n obj._invalids.add(value, obj._refs);\n }\n\n return obj;\n }\n\n required() {\n\n if (this._flags.presence === 'required') {\n return this;\n }\n\n const obj = this.clone();\n obj._flags.presence = 'required';\n return obj;\n }\n\n optional() {\n\n if (this._flags.presence === 'optional') {\n return this;\n }\n\n const obj = this.clone();\n obj._flags.presence = 'optional';\n return obj;\n }\n\n\n forbidden() {\n\n if (this._flags.presence === 'forbidden') {\n return this;\n }\n\n const obj = this.clone();\n obj._flags.presence = 'forbidden';\n return obj;\n }\n\n\n strip() {\n\n if (this._flags.strip) {\n return this;\n }\n\n const obj = this.clone();\n obj._flags.strip = true;\n return obj;\n }\n\n applyFunctionToChildren(children, fn, args = [], root) {\n\n children = [].concat(children);\n\n if (children.length !== 1 || children[0] !== '') {\n root = root ? (root + '.') : '';\n\n const extraChildren = (children[0] === '' ? children.slice(1) : children).map((child) => {\n\n return root + child;\n });\n\n throw new Error('unknown key(s) ' + extraChildren.join(', '));\n }\n\n return this[fn](...args);\n }\n\n default(value, description) {\n\n if (typeof value === 'function' &&\n !Ref.isRef(value)) {\n\n if (!value.description &&\n description) {\n\n value.description = description;\n }\n\n if (!this._flags.func) {\n Hoek.assert(typeof value.description === 'string' && value.description.length > 0, 'description must be provided when default value is a function');\n }\n }\n\n const obj = this.clone();\n obj._flags.default = value;\n Ref.push(obj._refs, value);\n return obj;\n }\n\n empty(schema) {\n\n const obj = this.clone();\n if (schema === undefined) {\n delete obj._flags.empty;\n }\n else {\n obj._flags.empty = Cast.schema(this._currentJoi, schema);\n }\n\n return obj;\n }\n\n when(condition, options) {\n\n Hoek.assert(options && typeof options === 'object', 'Invalid options');\n Hoek.assert(options.then !== undefined || options.otherwise !== undefined, 'options must have at least one of \"then\" or \"otherwise\"');\n\n const then = options.hasOwnProperty('then') ? this.concat(Cast.schema(this._currentJoi, options.then)) : undefined;\n const otherwise = options.hasOwnProperty('otherwise') ? this.concat(Cast.schema(this._currentJoi, options.otherwise)) : undefined;\n\n Alternatives = Alternatives || require('../alternatives');\n\n const alternativeOptions = { then, otherwise };\n if (Object.prototype.hasOwnProperty.call(options, 'is')) {\n alternativeOptions.is = options.is;\n }\n\n const obj = Alternatives.when(condition, alternativeOptions);\n obj._flags.presence = 'ignore';\n obj._baseType = this;\n\n return obj;\n }\n\n description(desc) {\n\n Hoek.assert(desc && typeof desc === 'string', 'Description must be a non-empty string');\n\n const obj = this.clone();\n obj._description = desc;\n return obj;\n }\n\n notes(notes) {\n\n Hoek.assert(notes && (typeof notes === 'string' || Array.isArray(notes)), 'Notes must be a non-empty string or array');\n\n const obj = this.clone();\n obj._notes = obj._notes.concat(notes);\n return obj;\n }\n\n tags(tags) {\n\n Hoek.assert(tags && (typeof tags === 'string' || Array.isArray(tags)), 'Tags must be a non-empty string or array');\n\n const obj = this.clone();\n obj._tags = obj._tags.concat(tags);\n return obj;\n }\n\n meta(meta) {\n\n Hoek.assert(meta !== undefined, 'Meta cannot be undefined');\n\n const obj = this.clone();\n obj._meta = obj._meta.concat(meta);\n return obj;\n }\n\n example(...examples) {\n\n Hoek.assert(examples.length > 0, 'Missing examples');\n\n const processed = [];\n for (let i = 0; i < examples.length; ++i) {\n const example = [].concat(examples[i]);\n Hoek.assert(example.length <= 2, `Bad example format at index ${i}`);\n\n const value = example[0];\n let options = example[1];\n if (options !== undefined) {\n Hoek.assert(options && typeof options === 'object', `Options for example at index ${i} must be an object`);\n const unknownOptions = Object.keys(options).filter((option) => !['parent', 'context'].includes(option));\n Hoek.assert(unknownOptions.length === 0, `Unknown example options ${unknownOptions} at index ${i}`);\n }\n else {\n options = {};\n }\n\n const localState = new State('', [], options.parent || null);\n const result = this._validate(value, localState, Settings.concat(internals.defaults, options.context ? { context: options.context } : null));\n Hoek.assert(!result.errors, `Bad example at index ${i}:`, result.errors && Errors.process(result.errors, value));\n\n const ex = { value };\n if (Object.keys(options).length) {\n ex.options = options;\n }\n\n processed.push(ex);\n }\n\n const obj = this.clone();\n obj._examples = processed;\n return obj;\n }\n\n unit(name) {\n\n Hoek.assert(name && typeof name === 'string', 'Unit name must be a non-empty string');\n\n const obj = this.clone();\n obj._unit = name;\n return obj;\n }\n\n _prepareEmptyValue(value) {\n\n if (typeof value === 'string' && this._flags.trim) {\n return value.trim();\n }\n\n return value;\n }\n\n _validate(value, state, options, reference) {\n\n const originalValue = value;\n\n // Setup state and settings\n\n state = state || new State('', [], null, reference);\n\n if (this._settings) {\n const isDefaultOptions = options === internals.defaults;\n if (isDefaultOptions && this._settings[Symbols.settingsCache]) {\n options = this._settings[Symbols.settingsCache];\n }\n else {\n options = Settings.concat(this._language ? Settings.concat({ language: this._language }, options) : options, this._settings);\n if (isDefaultOptions) {\n this._settings[Symbols.settingsCache] = options;\n }\n }\n }\n else if (this._language) {\n options = Settings.concat({ language: this._language }, options);\n }\n\n let errors = [];\n\n if (this._coerce) {\n const coerced = this._coerce(value, state, options);\n if (coerced.errors) {\n value = coerced.value;\n errors = errors.concat(coerced.errors);\n return this._finalizeValue(value, originalValue, errors, state, options); // Coerced error always aborts early\n }\n\n value = coerced.value;\n }\n\n if (this._flags.empty && !this._flags.empty._validate(this._prepareEmptyValue(value), null, internals.defaults).errors) {\n value = undefined;\n }\n\n // Check presence requirements\n\n const presence = this._flags.presence || options.presence;\n if (presence === 'optional') {\n if (value === undefined) {\n const isDeepDefault = this._flags.hasOwnProperty('default') && this._flags.default === undefined;\n if (isDeepDefault && this._type === 'object') {\n value = {};\n }\n else {\n return this._finalizeValue(value, originalValue, errors, state, options);\n }\n }\n }\n else if (presence === 'required' &&\n value === undefined) {\n\n errors.push(this.createError('any.required', null, state, options));\n return this._finalizeValue(value, originalValue, errors, state, options);\n }\n else if (presence === 'forbidden') {\n if (value === undefined) {\n return this._finalizeValue(value, originalValue, errors, state, options);\n }\n\n errors.push(this.createError('any.unknown', null, state, options));\n return this._finalizeValue(value, originalValue, errors, state, options);\n }\n\n // Check allowed and denied values using the original value\n\n let match = this._valids.get(value, state, options, this._flags.insensitive);\n if (match) {\n if (options.convert) {\n value = match.value;\n }\n\n return this._finalizeValue(value, originalValue, errors, state, options);\n }\n\n if (this._invalids.has(value, state, options, this._flags.insensitive)) {\n errors.push(this.createError(value === '' ? 'any.empty' : 'any.invalid', { value, invalids: this._invalids.values({ stripUndefined: true }) }, state, options));\n if (options.abortEarly) {\n\n return this._finalizeValue(value, originalValue, errors, state, options);\n }\n }\n\n // Convert value and validate type\n\n if (this._base) {\n const base = this._base(value, state, options);\n if (base.errors) {\n value = base.value;\n errors = errors.concat(base.errors);\n return this._finalizeValue(value, originalValue, errors, state, options); // Base error always aborts early\n }\n\n if (base.value !== value) {\n value = base.value;\n\n // Check allowed and denied values using the converted value\n\n match = this._valids.get(value, state, options, this._flags.insensitive);\n if (match) {\n value = match.value;\n return this._finalizeValue(value, originalValue, errors, state, options);\n }\n\n if (this._invalids.has(value, state, options, this._flags.insensitive)) {\n errors.push(this.createError(value === '' ? 'any.empty' : 'any.invalid', { value, invalids: this._invalids.values({ stripUndefined: true }) }, state, options));\n if (options.abortEarly) {\n return this._finalizeValue(value, originalValue, errors, state, options);\n }\n }\n }\n }\n\n // Required values did not match\n\n if (this._flags.allowOnly) {\n errors.push(this.createError('any.allowOnly', { value, valids: this._valids.values({ stripUndefined: true }) }, state, options));\n if (options.abortEarly) {\n return this._finalizeValue(value, originalValue, errors, state, options);\n }\n }\n\n // Validate tests\n\n for (let i = 0; i < this._tests.length; ++i) {\n const test = this._tests[i];\n const ret = test.func.call(this, value, state, options);\n if (ret instanceof Errors.Err) {\n errors.push(ret);\n if (options.abortEarly) {\n return this._finalizeValue(value, originalValue, errors, state, options);\n }\n }\n else {\n value = ret;\n }\n }\n\n return this._finalizeValue(value, originalValue, errors, state, options);\n }\n\n _finalizeValue(value, originalValue, errors, state, options) {\n\n let finalValue;\n\n if (value !== undefined) {\n finalValue = this._flags.raw ? originalValue : value;\n }\n else if (options.noDefaults) {\n finalValue = value;\n }\n else if (Ref.isRef(this._flags.default)) {\n finalValue = this._flags.default(state.parent, options);\n }\n else if (typeof this._flags.default === 'function' &&\n !(this._flags.func && !this._flags.default.description)) {\n\n let args;\n\n if (state.parent !== null &&\n this._flags.default.length > 0) {\n\n args = [Hoek.clone(state.parent), options];\n }\n\n const defaultValue = internals._try(this._flags.default, args);\n finalValue = defaultValue.value;\n if (defaultValue.error) {\n errors.push(this.createError('any.default', { error: defaultValue.error }, state, options));\n }\n }\n else {\n finalValue = Hoek.clone(this._flags.default);\n }\n\n if (errors.length &&\n typeof this._flags.error === 'function' &&\n (\n !this._flags.selfError ||\n errors.some((e) => state.path.length === e.path.length)\n )\n ) {\n const change = this._flags.error.call(this, errors);\n\n if (typeof change === 'string') {\n errors = [this.createOverrideError('override', { reason: errors }, state, options, change)];\n }\n else {\n errors = [].concat(change)\n .map((err) => {\n\n return err instanceof Error ?\n err :\n this.createOverrideError(err.type || 'override', err.context, state, options, err.message, err.template);\n });\n }\n }\n\n return {\n value: this._flags.strip ? undefined : finalValue,\n finalValue,\n errors: errors.length ? errors : null\n };\n }\n\n _validateWithOptions(value, options, callback) {\n\n if (options) {\n this.checkOptions(options);\n }\n\n const settings = Settings.concat(internals.defaults, options);\n const result = this._validate(value, null, settings);\n const errors = Errors.process(result.errors, value);\n\n if (callback) {\n return callback(errors, result.value);\n }\n\n return {\n error: errors,\n value: result.value,\n then(resolve, reject) {\n\n if (errors) {\n return Promise.reject(errors).catch(reject);\n }\n\n return Promise.resolve(result.value).then(resolve);\n },\n catch(reject) {\n\n if (errors) {\n return Promise.reject(errors).catch(reject);\n }\n\n return Promise.resolve(result.value);\n }\n };\n }\n\n validate(value, options, callback) {\n\n if (typeof options === 'function') {\n return this._validateWithOptions(value, null, options);\n }\n\n return this._validateWithOptions(value, options, callback);\n }\n\n describe() {\n\n const description = {\n type: this._type\n };\n\n const flags = Object.keys(this._flags);\n if (flags.length) {\n if (['empty', 'default', 'lazy', 'label'].some((flag) => this._flags.hasOwnProperty(flag))) {\n description.flags = {};\n for (let i = 0; i < flags.length; ++i) {\n const flag = flags[i];\n if (flag === 'empty') {\n description.flags[flag] = this._flags[flag].describe();\n }\n else if (flag === 'default') {\n if (Ref.isRef(this._flags[flag])) {\n description.flags[flag] = this._flags[flag].toString();\n }\n else if (typeof this._flags[flag] === 'function') {\n description.flags[flag] = {\n description: this._flags[flag].description,\n function : this._flags[flag]\n };\n }\n else {\n description.flags[flag] = this._flags[flag];\n }\n }\n else if (flag === 'lazy' || flag === 'label') {\n // We don't want it in the description\n }\n else {\n description.flags[flag] = this._flags[flag];\n }\n }\n }\n else {\n description.flags = this._flags;\n }\n }\n\n if (this._settings) {\n description.options = Hoek.clone(this._settings);\n }\n\n if (this._baseType) {\n description.base = this._baseType.describe();\n }\n\n if (this._description) {\n description.description = this._description;\n }\n\n if (this._notes.length) {\n description.notes = this._notes;\n }\n\n if (this._tags.length) {\n description.tags = this._tags;\n }\n\n if (this._meta.length) {\n description.meta = this._meta;\n }\n\n if (this._examples.length) {\n description.examples = this._examples;\n }\n\n if (this._unit) {\n description.unit = this._unit;\n }\n\n const valids = this._valids.values();\n if (valids.length) {\n description.valids = valids.map((v) => {\n\n return Ref.isRef(v) ? v.toString() : v;\n });\n }\n\n const invalids = this._invalids.values();\n if (invalids.length) {\n description.invalids = invalids.map((v) => {\n\n return Ref.isRef(v) ? v.toString() : v;\n });\n }\n\n description.rules = [];\n\n for (let i = 0; i < this._tests.length; ++i) {\n const validator = this._tests[i];\n const item = { name: validator.name };\n\n if (validator.arg !== void 0) {\n item.arg = Ref.isRef(validator.arg) ? validator.arg.toString() : validator.arg;\n }\n\n const options = validator.options;\n if (options) {\n if (options.hasRef) {\n item.arg = {};\n const keys = Object.keys(validator.arg);\n for (let j = 0; j < keys.length; ++j) {\n const key = keys[j];\n const value = validator.arg[key];\n item.arg[key] = Ref.isRef(value) ? value.toString() : value;\n }\n }\n\n if (typeof options.description === 'string') {\n item.description = options.description;\n }\n else if (typeof options.description === 'function') {\n item.description = options.description(item.arg);\n }\n }\n\n description.rules.push(item);\n }\n\n if (!description.rules.length) {\n delete description.rules;\n }\n\n const label = this._getLabel();\n if (label) {\n description.label = label;\n }\n\n return description;\n }\n\n label(name) {\n\n Hoek.assert(name && typeof name === 'string', 'Label name must be a non-empty string');\n\n const obj = this.clone();\n obj._flags.label = name;\n return obj;\n }\n\n _getLabel(def) {\n\n return this._flags.label || def;\n }\n\n}]"
}
}
],
"_refs": [],
"_flags": {},
"_description": null,
"_unit": null,
"_notes": [],
"_tags": [],
"_examples": [],
"_meta": [],
"_inner": {
"children": null,
"renames": [],
"dependencies": [],
"patterns": []
}
}
}
]
}
}
},
{
"schema": {
"isJoi": true,
"_currentJoi": "[Circular ~.base._currentJoi]",
"_type": "object",
"_settings": null,
"_valids": {
"_set": {},
"_hasRef": false
},
"_invalids": {
"_set": {},
"_hasRef": false
},
"_tests": [
{
"func": function (value, state, options) {\n\n if (value instanceof constructor) {\n return value;\n }\n\n return this.createError('object.type', { type: typeData.name, value }, state, options);\n },
"name": "type",
"arg": {
"name": "Joi object",
"ctor": "[class extends Any {\n\n constructor() {\n\n super();\n this._type = 'object';\n this._inner.children = null;\n this._inner.renames = [];\n this._inner.dependencies = [];\n this._inner.patterns = [];\n }\n\n _init(...args) {\n\n return args.length ? this.keys(...args) : this;\n }\n\n _base(value, state, options) {\n\n let target = value;\n const errors = [];\n const finish = () => {\n\n return {\n value: target,\n errors: errors.length ? errors : null\n };\n };\n\n if (typeof value === 'string' &&\n options.convert) {\n\n if (value.length > 1 &&\n (value[0] === '{' || /^\\s*\\{/.test(value))) {\n\n try {\n value = Bourne.parse(value);\n }\n catch (e) { }\n }\n }\n\n const type = this._flags.func ? 'function' : 'object';\n if (!value ||\n typeof value !== type ||\n Array.isArray(value)) {\n\n errors.push(this.createError(type + '.base', { value }, state, options));\n return finish();\n }\n\n // Skip if there are no other rules to test\n\n if (!this._inner.renames.length &&\n !this._inner.dependencies.length &&\n !this._inner.children && // null allows any keys\n !this._inner.patterns.length) {\n\n target = value;\n return finish();\n }\n\n // Ensure target is a local copy (parsed) or shallow copy\n\n if (target === value) {\n if (type === 'object') {\n target = Object.create(Object.getPrototypeOf(value));\n }\n else {\n target = function (...args) {\n\n return value.apply(this, args);\n };\n\n target.prototype = Hoek.clone(value.prototype);\n }\n\n const valueKeys = Object.keys(value);\n for (let i = 0; i < valueKeys.length; ++i) {\n target[valueKeys[i]] = value[valueKeys[i]];\n }\n }\n else {\n target = value;\n }\n\n // Rename keys\n\n const renamed = {};\n for (let i = 0; i < this._inner.renames.length; ++i) {\n const rename = this._inner.renames[i];\n\n if (rename.isRegExp) {\n const targetKeys = Object.keys(target);\n const matchedTargetKeys = [];\n\n for (let j = 0; j < targetKeys.length; ++j) {\n if (rename.from.test(targetKeys[j])) {\n matchedTargetKeys.push(targetKeys[j]);\n }\n }\n\n const allUndefined = matchedTargetKeys.every((key) => target[key] === undefined);\n if (rename.options.ignoreUndefined && allUndefined) {\n continue;\n }\n\n if (!rename.options.multiple &&\n renamed[rename.to]) {\n\n errors.push(this.createError('object.rename.regex.multiple', { from: matchedTargetKeys, to: rename.to }, state, options));\n if (options.abortEarly) {\n return finish();\n }\n }\n\n if (Object.prototype.hasOwnProperty.call(target, rename.to) &&\n !rename.options.override &&\n !renamed[rename.to]) {\n\n errors.push(this.createError('object.rename.regex.override', { from: matchedTargetKeys, to: rename.to }, state, options));\n if (options.abortEarly) {\n return finish();\n }\n }\n\n if (allUndefined) {\n delete target[rename.to];\n }\n else {\n target[rename.to] = target[matchedTargetKeys[matchedTargetKeys.length - 1]];\n }\n\n renamed[rename.to] = true;\n\n if (!rename.options.alias) {\n for (let j = 0; j < matchedTargetKeys.length; ++j) {\n delete target[matchedTargetKeys[j]];\n }\n }\n }\n else {\n if (rename.options.ignoreUndefined && target[rename.from] === undefined) {\n continue;\n }\n\n if (!rename.options.multiple &&\n renamed[rename.to]) {\n\n errors.push(this.createError('object.rename.multiple', { from: rename.from, to: rename.to }, state, options));\n if (options.abortEarly) {\n return finish();\n }\n }\n\n if (Object.prototype.hasOwnProperty.call(target, rename.to) &&\n !rename.options.override &&\n !renamed[rename.to]) {\n\n errors.push(this.createError('object.rename.override', { from: rename.from, to: rename.to }, state, options));\n if (options.abortEarly) {\n return finish();\n }\n }\n\n if (target[rename.from] === undefined) {\n delete target[rename.to];\n }\n else {\n target[rename.to] = target[rename.from];\n }\n\n renamed[rename.to] = true;\n\n if (!rename.options.alias) {\n delete target[rename.from];\n }\n }\n }\n\n // Validate schema\n\n if (!this._inner.children && // null allows any keys\n !this._inner.patterns.length &&\n !this._inner.dependencies.length) {\n\n return finish();\n }\n\n const unprocessed = new Set(Object.keys(target));\n\n if (this._inner.children) {\n const stripProps = [];\n\n for (let i = 0; i < this._inner.children.length; ++i) {\n const child = this._inner.children[i];\n const key = child.key;\n const item = target[key];\n\n unprocessed.delete(key);\n\n const localState = new State(key, [...state.path, key], target, state.reference);\n const result = child.schema._validate(item, localState, options);\n if (result.errors) {\n errors.push(this.createError('object.child', { key, child: child.schema._getLabel(key), reason: result.errors }, localState, options));\n\n if (options.abortEarly) {\n return finish();\n }\n }\n else {\n if (child.schema._flags.strip || (result.value === undefined && result.value !== item)) {\n stripProps.push(key);\n target[key] = result.finalValue;\n }\n else if (result.value !== undefined) {\n target[key] = result.value;\n }\n }\n }\n\n for (let i = 0; i < stripProps.length; ++i) {\n delete target[stripProps[i]];\n }\n }\n\n // Unknown keys\n\n if (unprocessed.size && this._inner.patterns.length) {\n\n for (const key of unprocessed) {\n const localState = new State(key, [...state.path, key], target, state.reference);\n const item = target[key];\n\n for (let i = 0; i < this._inner.patterns.length; ++i) {\n const pattern = this._inner.patterns[i];\n\n if (pattern.regex ?\n pattern.regex.test(key) :\n !pattern.schema._validate(key, state, { ...options, abortEarly:true }).errors) {\n\n unprocessed.delete(key);\n\n const result = pattern.rule._validate(item, localState, options);\n if (result.errors) {\n errors.push(this.createError('object.child', {\n key,\n child: pattern.rule._getLabel(key),\n reason: result.errors\n }, localState, options));\n\n if (options.abortEarly) {\n return finish();\n }\n }\n\n target[key] = result.value;\n }\n }\n }\n }\n\n if (unprocessed.size && (this._inner.children || this._inner.patterns.length)) {\n if ((options.stripUnknown && this._flags.allowUnknown !== true) ||\n options.skipFunctions) {\n\n const stripUnknown = options.stripUnknown\n ? (options.stripUnknown === true ? true : !!options.stripUnknown.objects)\n : false;\n\n\n for (const key of unprocessed) {\n if (stripUnknown) {\n delete target[key];\n unprocessed.delete(key);\n }\n else if (typeof target[key] === 'function') {\n unprocessed.delete(key);\n }\n }\n }\n\n if ((this._flags.allowUnknown !== undefined ? !this._flags.allowUnknown : !options.allowUnknown)) {\n\n for (const unprocessedKey of unprocessed) {\n errors.push(this.createError('object.allowUnknown', { child: unprocessedKey, value: target[unprocessedKey] }, {\n key: unprocessedKey,\n path: [...state.path, unprocessedKey]\n }, options, {}));\n }\n }\n }\n\n // Validate dependencies\n\n for (let i = 0; i < this._inner.dependencies.length; ++i) {\n const dep = this._inner.dependencies[i];\n const hasKey = dep.key !== null;\n const splitKey = hasKey && dep.key.split('.');\n const localState = hasKey ? new State(splitKey[splitKey.length - 1], [...state.path, ...splitKey]) : new State(null, state.path);\n const err = internals[dep.type].call(this, dep.key, hasKey && Hoek.reach(target, dep.key, { functions: true }), dep.peers, target, localState, options);\n if (err instanceof Errors.Err) {\n errors.push(err);\n if (options.abortEarly) {\n return finish();\n }\n }\n }\n\n return finish();\n }\n\n keys(schema) {\n\n Hoek.assert(schema === null || schema === undefined || typeof schema === 'object', 'Object schema must be a valid object');\n Hoek.assert(!schema || !(schema instanceof Any), 'Object schema cannot be a joi schema');\n\n const obj = this.clone();\n\n if (!schema) {\n obj._inner.children = null;\n return obj;\n }\n\n const children = Object.keys(schema);\n\n if (!children.length) {\n obj._inner.children = [];\n return obj;\n }\n\n const topo = new Topo();\n if (obj._inner.children) {\n for (let i = 0; i < obj._inner.children.length; ++i) {\n const child = obj._inner.children[i];\n\n // Only add the key if we are not going to replace it later\n if (!children.includes(child.key)) {\n topo.add(child, { after: child._refs, group: child.key });\n }\n }\n }\n\n for (let i = 0; i < children.length; ++i) {\n const key = children[i];\n const child = schema[key];\n try {\n const cast = Cast.schema(this._currentJoi, child);\n topo.add({ key, schema: cast }, { after: cast._refs, group: key });\n }\n catch (castErr) {\n if (castErr.hasOwnProperty('path')) {\n castErr.path = key + '.' + castErr.path;\n }\n else {\n castErr.path = key;\n }\n\n throw castErr;\n }\n }\n\n obj._inner.children = topo.nodes;\n\n return obj;\n }\n\n append(schema) {\n // Skip any changes\n if (schema === null || schema === undefined || Object.keys(schema).length === 0) {\n return this;\n }\n\n return this.keys(schema);\n }\n\n unknown(allow) {\n\n const value = allow !== false;\n\n if (this._flags.allowUnknown === value) {\n return this;\n }\n\n const obj = this.clone();\n obj._flags.allowUnknown = value;\n return obj;\n }\n\n length(limit) {\n\n Hoek.assert(Number.isSafeInteger(limit) && limit >= 0, 'limit must be a positive integer');\n\n return this._test('length', limit, function (value, state, options) {\n\n if (Object.keys(value).length === limit) {\n return value;\n }\n\n return this.createError('object.length', { limit, value }, state, options);\n });\n }\n\n min(limit) {\n\n Hoek.assert(Number.isSafeInteger(limit) && limit >= 0, 'limit must be a positive integer');\n\n return this._test('min', limit, function (value, state, options) {\n\n if (Object.keys(value).length >= limit) {\n return value;\n }\n\n return this.createError('object.min', { limit, value }, state, options);\n });\n }\n\n max(limit) {\n\n Hoek.assert(Number.isSafeInteger(limit) && limit >= 0, 'limit must be a positive integer');\n\n return this._test('max', limit, function (value, state, options) {\n\n if (Object.keys(value).length <= limit) {\n return value;\n }\n\n return this.createError('object.max', { limit, value }, state, options);\n });\n }\n\n pattern(pattern, schema) {\n\n const isRegExp = pattern instanceof RegExp;\n Hoek.assert(isRegExp || pattern instanceof Any, 'pattern must be a regex or schema');\n Hoek.assert(schema !== undefined, 'Invalid rule');\n\n if (isRegExp) {\n Hoek.assert(!pattern.flags.includes('g') && !pattern.flags.includes('y'), 'pattern should not use global or sticky mode');\n }\n\n try {\n schema = Cast.schema(this._currentJoi, schema);\n }\n catch (castErr) {\n if (castErr.hasOwnProperty('path')) {\n castErr.message = `${castErr.message}(${castErr.path})`;\n }\n\n throw castErr;\n }\n\n const obj = this.clone();\n if (isRegExp) {\n obj._inner.patterns.push({ regex: pattern, rule: schema });\n }\n else {\n obj._inner.patterns.push({ schema: pattern, rule: schema });\n }\n\n return obj;\n }\n\n schema() {\n\n return this._test('schema', null, function (value, state, options) {\n\n if (value instanceof Any) {\n return value;\n }\n\n return this.createError('object.schema', null, state, options);\n });\n }\n\n with(key, peers) {\n\n Hoek.assert(arguments.length === 2, 'Invalid number of arguments, expected 2.');\n\n return this._dependency('with', key, peers);\n }\n\n without(key, peers) {\n\n Hoek.assert(arguments.length === 2, 'Invalid number of arguments, expected 2.');\n\n return this._dependency('without', key, peers);\n }\n\n xor(...peers) {\n\n peers = Hoek.flatten(peers);\n return this._dependency('xor', null, peers);\n }\n\n oxor(...peers) {\n\n return this._dependency('oxor', null, peers);\n }\n\n or(...peers) {\n\n peers = Hoek.flatten(peers);\n return this._dependency('or', null, peers);\n }\n\n and(...peers) {\n\n peers = Hoek.flatten(peers);\n return this._dependency('and', null, peers);\n }\n\n nand(...peers) {\n\n peers = Hoek.flatten(peers);\n return this._dependency('nand', null, peers);\n }\n\n requiredKeys(...children) {\n\n children = Hoek.flatten(children);\n return this.applyFunctionToChildren(children, 'required');\n }\n\n optionalKeys(...children) {\n\n children = Hoek.flatten(children);\n return this.applyFunctionToChildren(children, 'optional');\n }\n\n forbiddenKeys(...children) {\n\n children = Hoek.flatten(children);\n return this.applyFunctionToChildren(children, 'forbidden');\n }\n\n rename(from, to, options) {\n\n Hoek.assert(typeof from === 'string' || from instanceof RegExp, 'Rename missing the from argument');\n Hoek.assert(typeof to === 'string', 'Rename missing the to argument');\n Hoek.assert(to !== from, 'Cannot rename key to same name:', from);\n\n for (let i = 0; i < this._inner.renames.length; ++i) {\n Hoek.assert(this._inner.renames[i].from !== from, 'Cannot rename the same key multiple times');\n }\n\n const obj = this.clone();\n\n obj._inner.renames.push({\n from,\n to,\n options: Hoek.applyToDefaults(internals.renameDefaults, options || {}),\n isRegExp: from instanceof RegExp\n });\n\n return obj;\n }\n\n applyFunctionToChildren(children, fn, args = [], root) {\n\n children = [].concat(children);\n Hoek.assert(children.length > 0, 'expected at least one children');\n\n const groupedChildren = internals.groupChildren(children);\n let obj;\n\n if ('' in groupedChildren) {\n obj = this[fn](...args);\n delete groupedChildren[''];\n }\n else {\n obj = this.clone();\n }\n\n if (obj._inner.children) {\n root = root ? (root + '.') : '';\n\n for (let i = 0; i < obj._inner.children.length; ++i) {\n const child = obj._inner.children[i];\n const group = groupedChildren[child.key];\n\n if (group) {\n obj._inner.children[i] = {\n key: child.key,\n _refs: child._refs,\n schema: child.schema.applyFunctionToChildren(group, fn, args, root + child.key)\n };\n\n delete groupedChildren[child.key];\n }\n }\n }\n\n const remaining = Object.keys(groupedChildren);\n Hoek.assert(remaining.length === 0, 'unknown key(s)', remaining.join(', '));\n\n return obj;\n }\n\n _dependency(type, key, peers) {\n\n peers = [].concat(peers);\n for (let i = 0; i < peers.length; ++i) {\n Hoek.assert(typeof peers[i] === 'string', type, 'peers must be a string or array of strings');\n }\n\n const obj = this.clone();\n obj._inner.dependencies.push({ type, key, peers });\n return obj;\n }\n\n describe(shallow) {\n\n const description = super.describe();\n\n if (description.rules) {\n for (let i = 0; i < description.rules.length; ++i) {\n const rule = description.rules[i];\n // Coverage off for future-proof descriptions, only object().assert() is use right now\n if (/* $lab:coverage:off$ */rule.arg &&\n typeof rule.arg === 'object' &&\n rule.arg.schema &&\n rule.arg.ref /* $lab:coverage:on$ */) {\n rule.arg = {\n schema: rule.arg.schema.describe(),\n ref: rule.arg.ref.toString()\n };\n }\n }\n }\n\n if (this._inner.children &&\n !shallow) {\n\n description.children = {};\n for (let i = 0; i < this._inner.children.length; ++i) {\n const child = this._inner.children[i];\n description.children[child.key] = child.schema.describe();\n }\n }\n\n if (this._inner.dependencies.length) {\n description.dependencies = Hoek.clone(this._inner.dependencies);\n }\n\n if (this._inner.patterns.length) {\n description.patterns = [];\n\n for (let i = 0; i < this._inner.patterns.length; ++i) {\n const pattern = this._inner.patterns[i];\n if (pattern.regex) {\n description.patterns.push({ regex: pattern.regex.toString(), rule: pattern.rule.describe() });\n }\n else {\n description.patterns.push({ schema: pattern.schema.describe(), rule: pattern.rule.describe() });\n }\n }\n }\n\n if (this._inner.renames.length > 0) {\n description.renames = Hoek.clone(this._inner.renames);\n }\n\n return description;\n }\n\n assert(ref, schema, message) {\n\n ref = Cast.ref(ref);\n Hoek.assert(ref.isContext || ref.depth > 1, 'Cannot use assertions for root level references - use direct key rules instead');\n message = message || 'pass the assertion test';\n Hoek.assert(typeof message === 'string', 'Message must be a string');\n\n try {\n schema = Cast.schema(this._currentJoi, schema);\n }\n catch (castErr) {\n if (castErr.hasOwnProperty('path')) {\n castErr.message = `${castErr.message}(${castErr.path})`;\n }\n\n throw castErr;\n }\n\n const key = ref.path[ref.path.length - 1];\n const path = ref.path.join('.');\n\n return this._test('assert', { schema, ref }, function (value, state, options) {\n\n const result = schema._validate(ref(value), null, options, value);\n if (!result.errors) {\n return value;\n }\n\n const localState = new State(key, ref.path, state.parent, state.reference);\n return this.createError('object.assert', { ref: path, message }, localState, options);\n });\n }\n\n type(constructor, name = constructor.name) {\n\n Hoek.assert(typeof constructor === 'function', 'type must be a constructor function');\n const typeData = {\n name,\n ctor: constructor\n };\n\n return this._test('type', typeData, function (value, state, options) {\n\n if (value instanceof constructor) {\n return value;\n }\n\n return this.createError('object.type', { type: typeData.name, value }, state, options);\n });\n }\n}]"
}
}
],
"_refs": [],
"_flags": {},
"_description": null,
"_unit": null,
"_notes": [],
"_tags": [],
"_examples": [],
"_meta": [],
"_inner": {
"children": null,
"renames": [],
"dependencies": [],
"patterns": []
}
}
}
]
}
}
},
{
"key": "description",
"schema": {
"isJoi": true,
"_currentJoi": "[Circular ~.base._currentJoi]",
"_type": "alternatives",
"_settings": null,
"_valids": {
"_set": {},
"_hasRef": false
},
"_invalids": {
"_set": {},
"_hasRef": false
},
"_tests": [],
"_refs": [],
"_flags": {},
"_description": null,
"_unit": null,
"_notes": [],
"_tags": [],
"_examples": [],
"_meta": [],
"_inner": {
"matches": [
{
"schema": {
"isJoi": true,
"_type": "string",
"_settings": null,
"_valids": {
"_set": {},
"_hasRef": false
},
"_invalids": {
"_set": {},
"_hasRef": 0
},
"_tests": [],
"_refs": [],
"_flags": {},
"_description": null,
"_unit": null,
"_notes": [],
"_tags": [],
"_examples": [],
"_meta": [],
"_inner": {},
"_currentJoi": "[Circular ~.base._currentJoi]"
}
},
{
"schema": {
"isJoi": true,
"_currentJoi": "[Circular ~.base._currentJoi]",
"_type": "object",
"_settings": null,
"_valids": {
"_set": {},
"_hasRef": false
},
"_invalids": {
"_set": {},
"_hasRef": false
},
"_tests": [
{
"func": function (value, state, options) {\n\n if (value.length === n) {\n return value;\n }\n\n return this.createError('function.arity', { n }, state, options);\n },
"name": "arity",
"arg": 1
}
],
"_refs": [],
"_flags": {
"func": true
},
"_description": null,
"_unit": null,
"_notes": [],
"_tags": [],
"_examples": [],
"_meta": [],
"_inner": {
"children": null,
"renames": [],
"dependencies": [],
"patterns": []
}
}
}
]
}
}
}
],
"renames": [],
"dependencies": [
{
"type": "or",
"key": null,
"peers": [
"setup",
"validate"
]
}
],
"patterns": []
}
}
],
"exclusions": [],
"requireds": []
}
}
}
],
"renames": [],
"dependencies": [],
"patterns": []
}
},
"extensionsSchema": {
"isJoi": true,
"_currentJoi": "[Circular ~.base._currentJoi]",
"_type": "array",
"_settings": {
"convert": false
},
"_valids": {
"_set": {},
"_hasRef": false
},
"_invalids": {
"_set": {},
"_hasRef": false
},
"_tests": [],
"_refs": [],
"_flags": {
"sparse": false
},
"_description": null,
"_unit": null,
"_notes": [],
"_tags": [],
"_examples": [],
"_meta": [],
"_inner": {
"items": [
"[Circular ~.base]",
{
"isJoi": true,
"_currentJoi": "[Circular ~.base._currentJoi]",
"_type": "object",
"_settings": null,
"_valids": {
"_set": {},
"_hasRef": false
},
"_invalids": {
"_set": {},
"_hasRef": false
},
"_tests": [
{
"func": function (value, state, options) {\n\n if (value.length === n) {\n return value;\n }\n\n return this.createError('function.arity', { n }, state, options);\n },
"name": "arity",
"arg": 1
}
],
"_refs": [],
"_flags": {
"func": true
},
"_description": null,
"_unit": null,
"_notes": [],
"_tags": [],
"_examples": [],
"_meta": [],
"_inner": {
"children": null,
"renames": [],
"dependencies": [],
"patterns": []
}
}
],
"ordereds": [],
"inclusions": [
"[Circular ~.base]",
{
"isJoi": true,
"_currentJoi": "[Circular ~.base._currentJoi]",
"_type": "object",
"_settings": null,
"_valids": {
"_set": {},
"_hasRef": false
},
"_invalids": {
"_set": {},
"_hasRef": false
},
"_tests": [
{
"func": function (value, state, options) {\n\n if (value.length === n) {\n return value;\n }\n\n return this.createError('function.arity', { n }, state, options);\n },
"name": "arity",
"arg": 1
}
],
"_refs": [],
"_flags": {
"func": true
},
"_description": null,
"_unit": null,
"_notes": [],
"_tags": [],
"_examples": [],
"_meta": [],
"_inner": {
"children": null,
"renames": [],
"dependencies": [],
"patterns": []
}
}
],
"exclusions": [],
"requireds": []
}
},
"version": "15.1.1"
}
},
"coerce": {
"from": "string",
"method": "[method(value) {\n if (typeof value !== 'string' || (value[0] !== '{' && !/^\\s*\\{/.test(value))) {\n return;\n }\n\n try {\n return { value: Bourne.parse(value) };\n } catch (ignoreErr) { }\n }]"
},
�[41m"name"�[0m�[31m [1]: -- missing --�[0m
}
�[31m
[1] "name" is required�[0m
at Object.exports.process (/tmp/node_modules/@hapi/joi/lib/errors.js:202:19)
at internals.Object._validateWithOptions (/tmp/node_modules/@hapi/joi/lib/types/any/index.js:763:31)
at module.exports.internals.Any.root.validate (/tmp/node_modules/@hapi/joi/lib/index.js:145:23)
at module.exports.internals.Any.root.attempt (/tmp/node_modules/@hapi/joi/lib/index.js:175:29)
at module.exports.internals.Any.root.assert (/tmp/node_modules/@hapi/joi/lib/index.js:170:14)
at module.exports.internals.Any.root.extend (/tmp/node_modules/@hapi/joi/lib/index.js:292:18)
at new SchemaResolver (/tmp/node_modules/enjoi/lib/resolver.js:13:24)
at Object.exports.schema (/tmp/node_modules/enjoi/index.js:47:12)
at Object.<anonymous> (/tmp/main.js:3:20)
at Module._compile (internal/modules/cjs/loader.js:1123:30) {
isJoi: true,
details: [
{
message: '"name" is required',
path: [Array],
type: 'any.required',
context: [Object]
}
],
_object: {
type: 'object',
base: {
isJoi: true,
_type: 'object',
_settings: null,
_valids: [InternalSet],
_invalids: [InternalSet],
_tests: [],
_refs: [],
_flags: {},
_description: null,
_unit: null,
_notes: [],
_tags: [],
_examples: [],
_meta: [],
_inner: [Object],
_currentJoi: [Object]
},
coerce: { from: 'string', method: [Function: method] }
},
annotate: [Function (anonymous)]
}
JSONSchemas without the type
keyword are still valid, eg:
{ format: 'date-time' }
is a valid JSONSchema that matches against any non-string value, or any string value that is date-time formatted.
Additionally, type
can be an array of types. In this way a single JSONSchema can do complex validations of multiple types of value without using combination keywords (allOf, etc.). As an example:
{
type: ['string', 'integer']
minLength: 5
maximum: 10
}
The above would successfully match a string with length 5 or greater, or a integer less than 10.
Since these are valid JSONSchema cases they should ideally be supported by Enjoi, but it would require notable changes to how JSONSchemas are translated to Joi. Application of validation keywords is currently tied to the presence of an expected type
value.
For cases where there is no explicit type Joi conditional validations would need to be used, something like:
Joi.any().when({ is: Joi.string(), then: Joi.date(), otherwise: Joi.any() })
Enjoi doesn't attempt to create Joi validators for valid schemas like:
{
type: "object",
required: ['foo'],
properties: {
"foo": { type: "string" }
},
oneOf: [
{
type: "object",
required: ['bar'],
properties: {
"bar": { type: "string" }
}
},
{
type: "object",
properties: {
"baz": { type: "string" }
}
}
]
}
It will only create the validator for the base object including the foo
property. It ignores the additional mutually exclusive properties expressed in the oneOf
.
Invalid data like { foo: 'a', bar: 'b', baz: 'c' }
passes the validator returned.
enjoi 5.0.2
Edit: Solved. Changed the lib.
#25 added support for array of types but array throws:
Enjoi({ type: [ 'array', 'null' ] })
TypeError: Cannot read property 'type' of undefined
at resolve (..../node_modules/enjoi/lib/enjoi.js:21:20)
I have converted object using enjoi. How do I store it as file now?
const Enjoi = require('enjoi')
const { convert } = require('json-to-json-schema')
const jsonSchema = convert({ name:'Bob' })
const joiSchema = Enjoi(jsonSchema)
// I want to store 'joiSchema' as file so that it would have content similar to this:
//
// const SCHEMA = Joi.object().keys({
// name : Joi.string().required()
// })
When used with allOf
, abortEarly
does not work. I suppose it is related to Joi extension for allOf
.
I pasted a minimal sample code below:
const Joi = require("joi");
const Enjoi = require("enjoi");
const jsonSchema = {
allOf: [
{
allOf: [
{
type: "object",
properties: {
a: { type: "string" },
b: { type: "string" },
},
required: ["a", "b"],
},
{
type: "object",
properties: {
c: { type: "string" },
d: { type: "string" },
},
required: ["d"],
},
],
},
],
};
const jsonSchema2 = {
type: "object",
properties: {
a: { type: "string" },
b: { type: "string" },
},
required: ["a", "b"],
};
const result = Joi.validate({}, Enjoi.schema(jsonSchema), { abortEarly: false });
const result2 = Joi.validate({}, Enjoi.schema(jsonSchema2), { abortEarly: false });
console.log(result.error);
console.log(result2.error);
result.error
has single error.
result2.error
has multiple errors.
result.error
has multiple error.
result2.error
has multiple errors.
Kind Regards,
The time-secfrac
part of the time validation regex was added with #66. Currently it is a 3 digit fraction. But as stated in java-json-tools/json-schema-validator#143 it actually needs to be a one or more number of digits. I couldn't find an exact explanation for the 1*DIGIT
format stated in RFC3339, but it seems that's the case. It would be great to hear what @nevezide thinks about this.
See #11 .
Enjoi({ type: 'boolean' }).validate(1, console.log) // valid; value === true
Enjoi({ type: 'boolean' }).validate('1', console.log) // valid; value === true
These should both be invalid or maybe add strict mode to options to enable it?
Hi All,
Can you please let me know when do you plan to publish version 6.0.2 to the NPM?
I upgrade to latest version 7.0.0 But I still have conflicts that node_modules/enjoi/index.js
links to @hapi/joi
also the npm website still specifies @hapi/joi as a dev dependency
Am I doing something wrong? I already removed node modules and deleted yarn cache.
Or was there an older version released as 7.0.0 on npm?
@hapi/joi is being used in the main code file index.js
and therefor should be listed as a dependency instead of a devDependency.
The issue particularly affects you when you are already using the latest version of @hapi/joi in a project. The latest version has breaking changes to the version enjoi needs. Enjoi has no problem using the latest version you may have installed, instead of it's required version ^14. When enjoi tries to run Joi.validate
a TypeError is thrown as that function has been removed.
@tlivings thanks again for enjoi
!
On a project right now and we have a need for additionalProperties
. What would be your preferred way to see this implemented?
General:
Also:
Hello all,
Can you please let me know where I can get the source code for version 6.0.0?
Current behavior:
The oneOf
type makes a field required.
Proper behavior:
The oneOf
type should make no changes to the required state of the field.
Explanation:
The oneOf
type is a variation of subschema boolean logic. It's difference to anyOf
which works properly in enjoi
is that an instance should match exactly one of the subschemas. If it matches two or more - validation fails. But this is only if the instance value is present. That is, if the field is oneOf
, but is not included as a required
field it should pass validation.
This looks like a cool project but I'm unable to use it due to the missing license. Under which license is the code published?
To validate UUID (GUID), It is only add the case in the switch of current.format
case 'uuid':
joischema = Joi.string().guid();
break;
This is not supported today.
Hi,
Is there a way to transform a Joi object into a JSON-schema? I would like to be able to save a Joi instance into a Database and retrieve it afterwards.
If it is not possible, can you point me towards a library that does it?
Cheers,
lapwat
@tlivings I'm not sure how... but if you yarn add enjoi
which pulls the latest (3.2.3) it is not the same envoi.js
as on master...
Hapi and Joi have had a massive refactor of their libraries hapijs/joi#2037 hapi-openapi relies on this library to validate a swagger. The enjoi library needs an update to use these latest libraries so that hapi-openapi can use the latest version of this
It would be extremely nice to have this library to support parsing to Yup schema as well. I've a case where I use Joi in the backend and I want to share schemas with frontend. Right now I am using Joi on both sides but Yup is much smaller and works really well with Formik. If I could describe schemas in JSON format and then parse it separately on backend and frontend It would be extremely helpful.
Hey maintainers, reviewing the history of this project I can see that when additionalProperties
support was introduced, it was written so that the default value was false. This was done to maintain consistency with the preceding versions of enjoi
which had previously always generated validators that errored on additional properties.
However, the JSONSchema specification indicates that additionalProperties
default value should be true. As a result, new enjoi users who have large amounts of JSONSchemas they wish to integrate with joi will have this undocumented inconsistency waiting for them. When they discover it, the only way to resolve it is add additionalProperties: true
to all objects schemas.
In a large organization, with many schemas belonging to a multitude of teams, updating all object schemas to explicitly state additionalProperties: true
in order to use enjoi is a big ask.
Would you consider a PR for a new major version that would change this behavior to be consistent with the JSONSchema spec? We may be able to provide a toggle so that users could still opt into the old non-standard behavior.
Thanks for your work on this library and for considering this request
Hi,
enjoi 3.2.4
I got following error when using allOf
:
ssertionError [ERR_ASSERTION]: Expected allOf item to be an array or object.
at new AssertionError (internal/assert.js:268:11)
at Object.exports.assert (.../node_modules/hoek/lib/index.js:736:11)
at .../node_modules/enjoi/lib/enjoi.js:187:18
at Array.map (<anonymous>)
at resolveAllOf (.../node_modules/enjoi/lib/enjoi.js:185:23)
at resolve (.../node_modules/enjoi/lib/enjoi.js:38:20)
at enjoi (.../node_modules/enjoi/lib/enjoi.js:410:12)
at Object.<anonymous> (.../deneme.js:3:16)
at Module._compile (internal/modules/cjs/loader.js:689:30)
at Object.Module._extensions..js (internal/modules/cjs/loader.js:700:10)
Here is a minimal sample code: (I tested JsonSchema with a Json Validator and it works as expected)
const Enjoi = require("enjoi");
const schema = Enjoi({
title: "Organization Input",
allOf: [
{
title: "Organization Common",
allOf: [
{
type: "object",
properties: {
name: { type: "string", maxLength: 40 },
billingAddress: { type: "string", maxLength: 100 }
},
required: ["name"]
},
{
type: "object",
title: "Phone Number",
properties: { phoneCountryCode: { type: "string", minLength: 1 } },
required: ["phoneCountryCode"]
}
]
}
]
});
Kind regards,
Hi,
Joi supports abortEarly flag to return only first error that occured when validating the schema. This is by default set to true. This can be passed to joi passed via:
Joi.validate({firstName: 'John', lastName: 'Doe', age: 45}, schema, { abortEarly: false } , function (error, value) {
error && console.log(error);
});
When using schema.validate() we lose the option to pass in options down to Joi:
schema.validate({firstName: 'John', lastName: 'Doe', age: 45}, function (error, value) {
error && console.log(error);
});
Without having to add Joi as dependency, is there a away for enjoi to pass options down to joi?
JSON schema seems to support nullable fields through an array of types. e.g. some_property: { type: 'string' }
nullable would be some_property: { type: [ 'string', 'null' ] }
.
The array is auto joined to 'string,null' and since no type exists this fails.
Doing Enjoiy(..., { types: { 'string,null': Joi.string().allow(null) } })
seems to be a workaround.
Hi,
When additionalProperties
is used with allOf
, it does not work.
const Joi = require("joi");
const Enjoi = require("enjoi");
const jsonSchema = {
allOf: [
{
allOf: [
{
type: "object",
additionalProperties: false,
properties: {
a: { type: "string" },
b: { type: "string" },
},
},
{
type: "object",
properties: {
c: { type: "string" },
d: { type: "string" },
},
},
],
},
],
};
const jsonSchema2 = {
type: "object",
properties: {
a: { type: "string" },
b: { type: "string" },
},
};
const result = Joi.validate({ z: "3" }, Enjoi.schema(jsonSchema), { abortEarly: false });
const result2 = Joi.validate({ z: "4" }, Enjoi.schema(jsonSchema2), { abortEarly: false });
console.log(result.error);
console.log(result2.error);
null
{ ValidationError: "z" is not allowed ...}
{ ValidationError: "z" is not allowed ...}
{ ValidationError: "z" is not allowed ...}
Kind Regards,
Having an issue with running a swagger application. The api.json has an object which within itself has an array of the same objects. For example :
"Box": {
"id" : "boxid",
"boxes": {
"type" : "array",
"items" : {
$ref : "BOX";
}
}
}
This is causing the error
enjoi/node_modules/joi/lib/any.js:65
var obj = Object.create(Object.getPrototypeOf(this));
^
RangeError: Maximum call stack size exceeded
Currently, the not
resolution assumes that it is an array
Lines 195 to 201 in abdd705
However, according to OAS, not
is an object, not an array. (https://swagger.io/docs/specification/data-models/oneof-anyof-allof-not/)
Hi,
If name
property is not present in schema, field name in thrown error (by Joi) is undefined
.
It may not be a bug (name property is already missing), but during conversion Enjoi
may use property key as field name if it is not present.
const Joi = require("joi");
const Enjoi = require("enjoi");
const jsonSchema = {
title: "User Detailed Output",
allOf: [
{
title: "User Basic Output",
allOf: [
{
type: "object",
properties: {
id: { type: "string", format: "uuid", description: "Kaydın ID'si." },
},
required: ["id"],
},
{
title: "User Common",
type: "object",
properties: {
businessUnitId: {
type: "string",
format: "uuid",
},
},
},
],
},
{ type: "object" },
],
$schema: "http://json-schema.org/draft-04/schema#",
};
Joi.assert({}, Enjoi.schema(jsonSchema));
ValidationError: {
"id" [1]: -- missing --
}
[1] "undefined" is required
ValidationError: {
"id" [1]: -- missing --
}
[1] "id" is required
items
If this property is an array, then it must contain schemas.
This is not supported today, only a single schema.
My bad if I'm missing something, but I used code similar to the example and my description is not being set:
Enjoi({
type: 'integer',
minimum: 0,
default: 1,
description: 'a query param',
})
The above gives me something like this when logged out:
{ isJoi: true,
_type: 'number',
_settings: null,
_valids: { _set: [] },
_invalids: { _set: [ Infinity, -Infinity ] },
_tests:
[ { func: [Function],
name: 'integer',
arg: undefined,
options: undefined },
{ func: [Function], name: 'min', arg: 0, options: undefined } ],
_refs: [],
_flags: {},
_description: null,
_unit: null,
_notes: [],
_tags: [],
_examples: [],
_meta: [],
_inner: {} }
But if I use Joi
directly, I can see the description:
Joi.number().integer().min(0).default(1).description('a query param');
{ isJoi: true,
_type: 'number',
_settings: null,
_valids: Set { _set: [] },
_invalids: Set { _set: [ Infinity, -Infinity ] },
_tests:
[ { func: [Function],
name: 'integer',
arg: undefined,
options: undefined },
{ func: [Function], name: 'min', arg: 0, options: undefined } ],
_refs: [],
_flags: { default: 1 },
_description: 'a query param',
_unit: null,
_notes: [],
_tags: [],
_examples: [],
_meta: [],
_inner: {} }
Am I perhaps doing something wrong, or might this be a bug?
Thank you!
The regularString
method makes, by default, an empty string a valid value for almost all string formats. This is a bug, since an empty string is not a valid value for e.g. an ipv4
string (which by the JSON Schema spec should be in "dotted quad" format as per RFC 2673).
Seems like the original reasoning behind this is that by default joi
marks an empty string as an invalid value for any string type schema. But as I pointed out this breaks the logic for most string formats.
Hi,
JSON schema simplified below throws warning and does not create Joi object.
I'm not sure if this is a bug, or whether object
type is default, but I validated schema using several validators and they worked as expected. ('type', '$ref', 'enum' seems not to be mandatory)
const Joi = require("joi");
const Enjoi = require("enjoi");
const jsonSchema = {
"$schema": "http://json-schema.org/draft-04/schema#",
"properties": {
"page": { "type": "integer" },
"limit": { "type": "integer" }
}
}
Joi.assert({ limit: "abc" }, Enjoi.schema(jsonSchema));
schema missing a 'type' or '$ref' or 'enum'
Joi assertion error
Thanks,
Hey there,
thanks for this useful lib, we hope to use it in our system very soon, however we ran into this bug:
Given the following schema:
{
"type": "object",
"properties": {
"one": { "type": "number", "minimum": 0, "maximum": 10 },
"two": { "type": "number", "minimum": 0, "maximum": 10 }
},
"anyOf": [
{ "required": ["one"] },
{ "required": ["two"] }
],
"additionalProperties": false
}
When I run this code:
const enjoiSchema = Enjoi.schema(jsonSchema)
console.log(Joi.validate({}, enjoiSchema).error === null)
I'd expect this to be false, however it returns true. If I take the same schema and run it through ajv or jsonschema, I get the expected results, so I don't think there's anything wrong with the schema.
If I write the same schema directly in Joi, like this:
Joi.object()
.keys({
one: Joi.number().positive().min(0).max(10),
two: Joi.number().positive().min(0).max(10)
})
.or('one', 'two')
it also works as expected and does not see the empty object as valid.
Thanks again for building Enjoi, we hope to enjoy it very soon ;-)
While trying to better understand JSON Schema I ended up with this Q/A thread json-schema-org/json-schema-spec#733 which explained a lot in how JSON Schema validation should work.
Taking that into consideration I think enjoi
is due for a major refactor. At the moment enjoy validates against e.g. required
and properties
only if "type": "object"
. And all validation keywords should be independently taken into consideration and validated against.
I am willing to work on this if you would consider a later PR. Also since it will be a major refactor would you consider me changing the testing library with jest
. I've been using it for some time now and it's proven very flexible and versatile.
Hi,
I found one issue with @hapi/joi. Options.extensions could not be an array.
The location of require('joi/lib/alternatives')
has been moved.
The reference should be updated or package.json updated to supported joi versions.
Description:
Would you consider a PR for the ability to add additional Joi validation to the converted schemas?
Joi supports this using schema concatenation. So it will be something like:
resolve(schema = this.root, joiSchema = this.additionalValidation) {
if (schema.type) {
return joiSchema ?
this.resolveType(schema).concat(joiSchema) :
this.resolveType(schema);
}
...
This will give great flexibility to Enjoi
by enabling the use of the full potential of Joi
.
Background:
In our project we need to have more precise validation than what JSON Schema provides. E.g. for dates we want not only to match the YYYY-MM-DD
format from RFC3339 Section 5.6, but also apply the restrictions from Section 5.7 - forbid the 31st day for Feb, Apr, etc.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.