From 26105034da4fcce7ac883c899d781f016559310d Mon Sep 17 00:00:00 2001 From: ruki Date: Thu, 8 Nov 2018 00:38:48 +0800 Subject: switch to vuepress --- node_modules/joi/CHANGELOG.md | 3 + node_modules/joi/LICENSE | 29 + node_modules/joi/README.md | 131 ++++ node_modules/joi/lib/cast.js | 64 ++ node_modules/joi/lib/errors.js | 357 +++++++++ node_modules/joi/lib/index.js | 434 +++++++++++ node_modules/joi/lib/language.js | 156 ++++ node_modules/joi/lib/ref.js | 53 ++ node_modules/joi/lib/schemas.js | 24 + node_modules/joi/lib/set.js | 111 +++ node_modules/joi/lib/types/alternatives/index.js | 188 +++++ node_modules/joi/lib/types/any/index.js | 916 +++++++++++++++++++++++ node_modules/joi/lib/types/array/index.js | 658 ++++++++++++++++ node_modules/joi/lib/types/binary/index.js | 100 +++ node_modules/joi/lib/types/boolean/index.js | 98 +++ node_modules/joi/lib/types/date/index.js | 177 +++++ node_modules/joi/lib/types/func/index.js | 90 +++ node_modules/joi/lib/types/lazy/index.js | 53 ++ node_modules/joi/lib/types/number/index.js | 173 +++++ node_modules/joi/lib/types/object/index.js | 896 ++++++++++++++++++++++ node_modules/joi/lib/types/string/index.js | 632 ++++++++++++++++ node_modules/joi/lib/types/string/ip.js | 54 ++ node_modules/joi/lib/types/string/rfc3986.js | 209 ++++++ node_modules/joi/lib/types/string/uri.js | 46 ++ node_modules/joi/package.json | 33 + 25 files changed, 5685 insertions(+) create mode 100644 node_modules/joi/CHANGELOG.md create mode 100644 node_modules/joi/LICENSE create mode 100644 node_modules/joi/README.md create mode 100644 node_modules/joi/lib/cast.js create mode 100755 node_modules/joi/lib/errors.js create mode 100644 node_modules/joi/lib/index.js create mode 100644 node_modules/joi/lib/language.js create mode 100644 node_modules/joi/lib/ref.js create mode 100644 node_modules/joi/lib/schemas.js create mode 100644 node_modules/joi/lib/set.js create mode 100644 node_modules/joi/lib/types/alternatives/index.js create mode 100644 node_modules/joi/lib/types/any/index.js create mode 100644 node_modules/joi/lib/types/array/index.js create mode 100644 node_modules/joi/lib/types/binary/index.js create mode 100644 node_modules/joi/lib/types/boolean/index.js create mode 100644 node_modules/joi/lib/types/date/index.js create mode 100644 node_modules/joi/lib/types/func/index.js create mode 100644 node_modules/joi/lib/types/lazy/index.js create mode 100644 node_modules/joi/lib/types/number/index.js create mode 100644 node_modules/joi/lib/types/object/index.js create mode 100755 node_modules/joi/lib/types/string/index.js create mode 100644 node_modules/joi/lib/types/string/ip.js create mode 100644 node_modules/joi/lib/types/string/rfc3986.js create mode 100644 node_modules/joi/lib/types/string/uri.js create mode 100644 node_modules/joi/package.json (limited to 'node_modules/joi') diff --git a/node_modules/joi/CHANGELOG.md b/node_modules/joi/CHANGELOG.md new file mode 100644 index 00000000..5771955a --- /dev/null +++ b/node_modules/joi/CHANGELOG.md @@ -0,0 +1,3 @@ +Breaking changes are documented using GitHub issues, see [issues labeled "release notes"](https://github.com/hapijs/joi/issues?q=is%3Aissue+label%3A%22release+notes%22). + +If you want changes of a specific minor or patch release, you can browse the [GitHub milestones](https://github.com/hapijs/joi/milestones?state=closed&direction=asc&sort=due_date). diff --git a/node_modules/joi/LICENSE b/node_modules/joi/LICENSE new file mode 100644 index 00000000..7c9733d9 --- /dev/null +++ b/node_modules/joi/LICENSE @@ -0,0 +1,29 @@ +Copyright (c) 2012-2017, Project contributors +Copyright (c) 2012-2014, Walmart +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * The names of any contributors may not be used to endorse or promote + products derived from this software without specific prior written + permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS AND CONTRIBUTORS BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + * * * + +The complete list of contributors can be found at: https://github.com/hapijs/joi/graphs/contributors diff --git a/node_modules/joi/README.md b/node_modules/joi/README.md new file mode 100644 index 00000000..d175fbdd --- /dev/null +++ b/node_modules/joi/README.md @@ -0,0 +1,131 @@ +![joi Logo](https://raw.github.com/hapijs/joi/master/images/joi.png) + +Object schema description language and validator for JavaScript objects. + +[![npm version](https://badge.fury.io/js/joi.svg)](http://badge.fury.io/js/joi) +[![Build Status](https://secure.travis-ci.org/hapijs/joi.svg?branch=master)](http://travis-ci.org/hapijs/joi) + +[![NSP Status](https://nodesecurity.io/orgs/hapijs/projects/0394bf83-b5bc-410b-878c-e8cf1b92033e/badge)](https://nodesecurity.io/orgs/hapijs/projects/0394bf83-b5bc-410b-878c-e8cf1b92033e) +[![Known Vulnerabilities](https://snyk.io/test/github/hapijs/joi/badge.svg)](https://snyk.io/test/github/hapijs/joi) + +[![Join the chat at https://gitter.im/hapijs/joi](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/hapijs/joi?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) + +Lead Maintainer: [Nicolas Morel](https://github.com/marsup) + +# Introduction + +Imagine you run facebook and you want visitors to sign up on the website with real names and not something like `l337_p@nda` in the first name field. How would you define the limitations of what can be inputted and validate it against the set rules? + +This is joi, joi allows you to create *blueprints* or *schemas* for JavaScript objects (an object that stores information) to ensure *validation* of key information. + +# API +See the detailed [API Reference](https://github.com/hapijs/joi/blob/v11.4.0/API.md). + +# Example + +```javascript +const Joi = require('joi'); + +const schema = Joi.object().keys({ + username: Joi.string().alphanum().min(3).max(30).required(), + password: Joi.string().regex(/^[a-zA-Z0-9]{3,30}$/), + access_token: [Joi.string(), Joi.number()], + birthyear: Joi.number().integer().min(1900).max(2013), + email: Joi.string().email() +}).with('username', 'birthyear').without('password', 'access_token'); + +// Return result. +const result = Joi.validate({ username: 'abc', birthyear: 1994 }, schema); +// result.error === null -> valid + +// You can also pass a callback which will be called synchronously with the validation result. +Joi.validate({ username: 'abc', birthyear: 1994 }, schema, function (err, value) { }); // err === null -> valid + +``` + +The above schema defines the following constraints: +* `username` + * a required string + * must contain only alphanumeric characters + * at least 3 characters long but no more than 30 + * must be accompanied by `birthyear` +* `password` + * an optional string + * must satisfy the custom regex + * cannot appear together with `access_token` +* `access_token` + * an optional, unconstrained string or number +* `birthyear` + * an integer between 1900 and 2013 +* `email` + * a valid email address string + +# Usage + +Usage is a two steps process. First, a schema is constructed using the provided types and constraints: + +```javascript +const schema = { + a: Joi.string() +}; +``` + +Note that **joi** schema objects are immutable which means every additional rule added (e.g. `.min(5)`) will return a +new schema object. + +Then the value is validated against the schema: + +```javascript +const {error, value} = Joi.validate({ a: 'a string' }, schema); + +// or + +Joi.validate({ a: 'a string' }, schema, function (err, value) { }); +``` + +If the input is valid, then the error will be `null`, otherwise it will be an Error object. + +The schema can be a plain JavaScript object where every key is assigned a **joi** type, or it can be a **joi** type directly: + +```javascript +const schema = Joi.string().min(10); +``` + +If the schema is a **joi** type, the `schema.validate(value, callback)` can be called directly on the type. When passing a non-type schema object, +the module converts it internally to an object() type equivalent to: + +```javascript +const schema = Joi.object().keys({ + a: Joi.string() +}); +``` + +When validating a schema: + +* Values (or keys in case of objects) are optional by default. + + ```javascript + Joi.validate(undefined, Joi.string()); // validates fine + ``` + + To disallow this behavior, you can either set the schema as `required()`, or set `presence` to `"required"` when passing `options`: + + ```javascript + Joi.validate(undefined, Joi.string().required()); + // or + Joi.validate(undefined, Joi.string(), /* options */ { presence: "required" }); + ``` + +* Strings are utf-8 encoded by default. +* Rules are defined in an additive fashion and evaluated in order after whitelist and blacklist checks. + +# Browsers + +Joi doesn't directly support browsers, but you could use [joi-browser](https://github.com/jeffbski/joi-browser) for an ES5 build of Joi that works in browsers, or as a source of inspiration for your own builds. diff --git a/node_modules/joi/lib/cast.js b/node_modules/joi/lib/cast.js new file mode 100644 index 00000000..19c4a677 --- /dev/null +++ b/node_modules/joi/lib/cast.js @@ -0,0 +1,64 @@ +'use strict'; + +// Load modules + +const Hoek = require('hoek'); +const Ref = require('./ref'); + +// Type modules are delay-loaded to prevent circular dependencies + + +// Declare internals + +const internals = {}; + + +exports.schema = function (Joi, config) { + + if (config !== undefined && config !== null && typeof config === 'object') { + + if (config.isJoi) { + return config; + } + + if (Array.isArray(config)) { + return Joi.alternatives().try(config); + } + + if (config instanceof RegExp) { + return Joi.string().regex(config); + } + + if (config instanceof Date) { + return Joi.date().valid(config); + } + + return Joi.object().keys(config); + } + + if (typeof config === 'string') { + return Joi.string().valid(config); + } + + if (typeof config === 'number') { + return Joi.number().valid(config); + } + + if (typeof config === 'boolean') { + return Joi.boolean().valid(config); + } + + if (Ref.isRef(config)) { + return Joi.valid(config); + } + + Hoek.assert(config === null, 'Invalid schema content:', config); + + return Joi.valid(null); +}; + + +exports.ref = function (id) { + + return Ref.isRef(id) ? id : Ref.create(id); +}; diff --git a/node_modules/joi/lib/errors.js b/node_modules/joi/lib/errors.js new file mode 100755 index 00000000..f080afb2 --- /dev/null +++ b/node_modules/joi/lib/errors.js @@ -0,0 +1,357 @@ +'use strict'; + +// Load modules + +const Hoek = require('hoek'); +const Language = require('./language'); + + +// Declare internals + +const internals = { + annotations: Symbol('joi-annotations') +}; + +internals.stringify = function (value, wrapArrays) { + + const type = typeof value; + + if (value === null) { + return 'null'; + } + + if (type === 'string') { + return value; + } + + if (value instanceof exports.Err || type === 'function' || type === 'symbol') { + return value.toString(); + } + + if (type === 'object') { + if (Array.isArray(value)) { + let partial = ''; + + for (let i = 0; i < value.length; ++i) { + partial = partial + (partial.length ? ', ' : '') + internals.stringify(value[i], wrapArrays); + } + + return wrapArrays ? '[' + partial + ']' : partial; + } + + return value.toString(); + } + + return JSON.stringify(value); +}; + +exports.Err = class { + + constructor(type, context, state, options, flags, message, template) { + + this.isJoi = true; + this.type = type; + this.context = context || {}; + this.context.key = state.path[state.path.length - 1]; + this.context.label = state.key; + this.path = state.path; + this.options = options; + this.flags = flags; + this.message = message; + this.template = template; + + const localized = this.options.language; + + if (this.flags.label) { + this.context.label = this.flags.label; + } + else if (localized && // language can be null for arrays exclusion check + (this.context.label === '' || + this.context.label === null)) { + this.context.label = localized.root || Language.errors.root; + } + } + + toString() { + + if (this.message) { + return this.message; + } + + let format; + + if (this.template) { + format = this.template; + } + + const localized = this.options.language; + + format = format || Hoek.reach(localized, this.type) || Hoek.reach(Language.errors, this.type); + + if (format === undefined) { + return `Error code "${this.type}" is not defined, your custom type is missing the correct language definition`; + } + + let wrapArrays = Hoek.reach(localized, 'messages.wrapArrays'); + if (typeof wrapArrays !== 'boolean') { + wrapArrays = Language.errors.messages.wrapArrays; + } + + if (format === null) { + const childrenString = internals.stringify(this.context.reason, wrapArrays); + if (wrapArrays) { + return childrenString.slice(1, -1); + } + return childrenString; + } + + const hasKey = /\{\{\!?label\}\}/.test(format); + const skipKey = format.length > 2 && format[0] === '!' && format[1] === '!'; + + if (skipKey) { + format = format.slice(2); + } + + if (!hasKey && !skipKey) { + format = (Hoek.reach(localized, 'key') || Hoek.reach(Language.errors, 'key')) + format; + } + + return format.replace(/\{\{(\!?)([^}]+)\}\}/g, ($0, isSecure, name) => { + + const value = Hoek.reach(this.context, name); + const normalized = internals.stringify(value, wrapArrays); + return (isSecure ? Hoek.escapeHtml(normalized) : normalized); + }); + } + +}; + + +exports.create = function (type, context, state, options, flags, message, template) { + + return new exports.Err(type, context, state, options, flags, message, template); +}; + + +exports.process = function (errors, object) { + + if (!errors || !errors.length) { + return null; + } + + // Construct error + + let message = ''; + const details = []; + + const processErrors = function (localErrors, parent) { + + for (let i = 0; i < localErrors.length; ++i) { + const item = localErrors[i]; + + if (item instanceof Error) { + return item; + } + + if (item.flags.error && typeof item.flags.error !== 'function') { + return item.flags.error; + } + + let itemMessage; + if (parent === undefined) { + itemMessage = item.toString(); + message = message + (message ? '. ' : '') + itemMessage; + } + + // Do not push intermediate errors, we're only interested in leafs + + if (item.context.reason && item.context.reason.length) { + const override = processErrors(item.context.reason, item.path); + if (override) { + return override; + } + } + else { + details.push({ + message: itemMessage || item.toString(), + path: item.path, + type: item.type, + context: item.context + }); + } + } + }; + + const override = processErrors(errors); + if (override) { + return override; + } + + const error = new Error(message); + error.isJoi = true; + error.name = 'ValidationError'; + error.details = details; + error._object = object; + error.annotate = internals.annotate; + return error; +}; + + +// Inspired by json-stringify-safe +internals.safeStringify = function (obj, spaces) { + + return JSON.stringify(obj, internals.serializer(), spaces); +}; + +internals.serializer = function () { + + const keys = []; + const stack = []; + + const cycleReplacer = (key, value) => { + + if (stack[0] === value) { + return '[Circular ~]'; + } + + return '[Circular ~.' + keys.slice(0, stack.indexOf(value)).join('.') + ']'; + }; + + return function (key, value) { + + if (stack.length > 0) { + const thisPos = stack.indexOf(this); + if (~thisPos) { + stack.length = thisPos + 1; + keys.length = thisPos + 1; + keys[thisPos] = key; + } + else { + stack.push(this); + keys.push(key); + } + + if (~stack.indexOf(value)) { + value = cycleReplacer.call(this, key, value); + } + } + else { + stack.push(value); + } + + if (value) { + const annotations = value[internals.annotations]; + if (annotations) { + if (Array.isArray(value)) { + const annotated = []; + + for (let i = 0; i < value.length; ++i) { + if (annotations.errors[i]) { + annotated.push(`_$idx$_${annotations.errors[i].sort().join(', ')}_$end$_`); + } + annotated.push(value[i]); + } + + value = annotated; + } + else { + const errorKeys = Object.keys(annotations.errors); + for (let i = 0; i < errorKeys.length; ++i) { + const errorKey = errorKeys[i]; + value[`${errorKey}_$key$_${annotations.errors[errorKey].sort().join(', ')}_$end$_`] = value[errorKey]; + value[errorKey] = undefined; + } + + const missingKeys = Object.keys(annotations.missing); + for (let i = 0; i < missingKeys.length; ++i) { + const missingKey = missingKeys[i]; + value[`_$miss$_${missingKey}|${annotations.missing[missingKey]}_$end$_`] = '__missing__'; + } + } + + return value; + } + } + + if (value === Infinity || value === -Infinity || Number.isNaN(value) || + typeof value === 'function' || typeof value === 'symbol') { + return '[' + value.toString() + ']'; + } + + return value; + }; +}; + + +internals.annotate = function (stripColorCodes) { + + const redFgEscape = stripColorCodes ? '' : '\u001b[31m'; + const redBgEscape = stripColorCodes ? '' : '\u001b[41m'; + const endColor = stripColorCodes ? '' : '\u001b[0m'; + + if (typeof this._object !== 'object') { + return this.details[0].message; + } + + const obj = Hoek.clone(this._object || {}); + + for (let i = this.details.length - 1; i >= 0; --i) { // Reverse order to process deepest child first + const pos = i + 1; + const error = this.details[i]; + const path = error.path; + let ref = obj; + for (let j = 0; ; ++j) { + const seg = path[j]; + + if (ref.isImmutable) { + ref = ref.clone(); // joi schemas are not cloned by hoek, we have to take this extra step + } + + if (j + 1 < path.length && + ref[seg] && + typeof ref[seg] !== 'string') { + + ref = ref[seg]; + } + else { + const refAnnotations = ref[internals.annotations] = ref[internals.annotations] || { errors: {}, missing: {} }; + const value = ref[seg]; + const cacheKey = seg || error.context.label; + + if (value !== undefined) { + refAnnotations.errors[cacheKey] = refAnnotations.errors[cacheKey] || []; + refAnnotations.errors[cacheKey].push(pos); + } + else { + refAnnotations.missing[cacheKey] = pos; + } + + break; + } + } + } + + const replacers = { + key: /_\$key\$_([, \d]+)_\$end\$_\"/g, + missing: /\"_\$miss\$_([^\|]+)\|(\d+)_\$end\$_\"\: \"__missing__\"/g, + arrayIndex: /\s*\"_\$idx\$_([, \d]+)_\$end\$_\",?\n(.*)/g, + specials: /"\[(NaN|Symbol.*|-?Infinity|function.*|\(.*)\]"/g + }; + + let message = internals.safeStringify(obj, 2) + .replace(replacers.key, ($0, $1) => `" ${redFgEscape}[${$1}]${endColor}`) + .replace(replacers.missing, ($0, $1, $2) => `${redBgEscape}"${$1}"${endColor}${redFgEscape} [${$2}]: -- missing --${endColor}`) + .replace(replacers.arrayIndex, ($0, $1, $2) => `\n${$2} ${redFgEscape}[${$1}]${endColor}`) + .replace(replacers.specials, ($0, $1) => $1); + + message = `${message}\n${redFgEscape}`; + + for (let i = 0; i < this.details.length; ++i) { + const pos = i + 1; + message = `${message}\n[${pos}] ${this.details[i].message}`; + } + + message = message + endColor; + + return message; +}; diff --git a/node_modules/joi/lib/index.js b/node_modules/joi/lib/index.js new file mode 100644 index 00000000..24022fdf --- /dev/null +++ b/node_modules/joi/lib/index.js @@ -0,0 +1,434 @@ +'use strict'; + +// Load modules + +const Hoek = require('hoek'); +const Any = require('./types/any'); +const Cast = require('./cast'); +const Errors = require('./errors'); +const Lazy = require('./types/lazy'); +const Ref = require('./ref'); + + +// Declare internals + +const internals = { + alternatives: require('./types/alternatives'), + array: require('./types/array'), + boolean: require('./types/boolean'), + binary: require('./types/binary'), + date: require('./types/date'), + func: require('./types/func'), + number: require('./types/number'), + object: require('./types/object'), + string: require('./types/string') +}; + +internals.applyDefaults = function (schema) { + + Hoek.assert(this, 'Must be invoked on a Joi instance.'); + + if (this._defaults) { + schema = this._defaults(schema); + } + + schema._currentJoi = this; + + return schema; +}; + +internals.root = function () { + + const any = new Any(); + + const root = any.clone(); + Any.prototype._currentJoi = root; + root._currentJoi = root; + + root.any = function () { + + Hoek.assert(arguments.length === 0, 'Joi.any() does not allow arguments.'); + + return internals.applyDefaults.call(this, any); + }; + + root.alternatives = root.alt = function () { + + const alternatives = internals.applyDefaults.call(this, internals.alternatives); + return arguments.length ? alternatives.try.apply(alternatives, arguments) : alternatives; + }; + + root.array = function () { + + Hoek.assert(arguments.length === 0, 'Joi.array() does not allow arguments.'); + + return internals.applyDefaults.call(this, internals.array); + }; + + root.boolean = root.bool = function () { + + Hoek.assert(arguments.length === 0, 'Joi.boolean() does not allow arguments.'); + + return internals.applyDefaults.call(this, internals.boolean); + }; + + root.binary = function () { + + Hoek.assert(arguments.length === 0, 'Joi.binary() does not allow arguments.'); + + return internals.applyDefaults.call(this, internals.binary); + }; + + root.date = function () { + + Hoek.assert(arguments.length === 0, 'Joi.date() does not allow arguments.'); + + return internals.applyDefaults.call(this, internals.date); + }; + + root.func = function () { + + Hoek.assert(arguments.length === 0, 'Joi.func() does not allow arguments.'); + + return internals.applyDefaults.call(this, internals.func); + }; + + root.number = function () { + + Hoek.assert(arguments.length === 0, 'Joi.number() does not allow arguments.'); + + return internals.applyDefaults.call(this, internals.number); + }; + + root.object = function () { + + const object = internals.applyDefaults.call(this, internals.object); + return arguments.length ? object.keys.apply(object, arguments) : object; + }; + + root.string = function () { + + Hoek.assert(arguments.length === 0, 'Joi.string() does not allow arguments.'); + + return internals.applyDefaults.call(this, internals.string); + }; + + root.ref = function () { + + return Ref.create.apply(null, arguments); + }; + + root.isRef = function (ref) { + + return Ref.isRef(ref); + }; + + root.validate = function (value /*, [schema], [options], callback */) { + + const last = arguments[arguments.length - 1]; + const callback = typeof last === 'function' ? last : null; + + const count = arguments.length - (callback ? 1 : 0); + if (count === 1) { + return any.validate(value, callback); + } + + const options = count === 3 ? arguments[2] : {}; + const schema = root.compile(arguments[1]); + + return schema._validateWithOptions(value, options, callback); + }; + + root.describe = function () { + + const schema = arguments.length ? root.compile(arguments[0]) : any; + return schema.describe(); + }; + + root.compile = function (schema) { + + try { + return Cast.schema(this, schema); + } + catch (err) { + if (err.hasOwnProperty('path')) { + err.message = err.message + '(' + err.path + ')'; + } + throw err; + } + }; + + root.assert = function (value, schema, message) { + + root.attempt(value, schema, message); + }; + + root.attempt = function (value, schema, message) { + + const result = root.validate(value, schema); + const error = result.error; + if (error) { + if (!message) { + if (typeof error.annotate === 'function') { + error.message = error.annotate(); + } + throw error; + } + + if (!(message instanceof Error)) { + if (typeof error.annotate === 'function') { + error.message = `${message} ${error.annotate()}`; + } + throw error; + } + + throw message; + } + + return result.value; + }; + + root.reach = function (schema, path) { + + Hoek.assert(schema && schema instanceof Any, 'you must provide a joi schema'); + Hoek.assert(typeof path === 'string', 'path must be a string'); + + if (path === '') { + return schema; + } + + const parts = path.split('.'); + const children = schema._inner.children; + if (!children) { + return; + } + + const key = parts[0]; + for (let i = 0; i < children.length; ++i) { + const child = children[i]; + if (child.key === key) { + return this.reach(child.schema, path.substr(key.length + 1)); + } + } + }; + + root.lazy = function (fn) { + + return Lazy.set(fn); + }; + + root.defaults = function (fn) { + + Hoek.assert(typeof fn === 'function', 'Defaults must be a function'); + + let joi = Object.create(this.any()); + joi = fn(joi); + + Hoek.assert(joi && joi instanceof this.constructor, 'defaults() must return a schema'); + + Object.assign(joi, this, joi.clone()); // Re-add the types from `this` but also keep the settings from joi's potential new defaults + + joi._defaults = (schema) => { + + if (this._defaults) { + schema = this._defaults(schema); + Hoek.assert(schema instanceof this.constructor, 'defaults() must return a schema'); + } + + schema = fn(schema); + Hoek.assert(schema instanceof this.constructor, 'defaults() must return a schema'); + return schema; + }; + + return joi; + }; + + root.extend = function () { + + const extensions = Hoek.flatten(Array.prototype.slice.call(arguments)); + Hoek.assert(extensions.length > 0, 'You need to provide at least one extension'); + + this.assert(extensions, root.extensionsSchema); + + const joi = Object.create(this.any()); + Object.assign(joi, this); + + for (let i = 0; i < extensions.length; ++i) { + let extension = extensions[i]; + + if (typeof extension === 'function') { + extension = extension(joi); + } + + this.assert(extension, root.extensionSchema); + + const base = (extension.base || this.any()).clone(); // Cloning because we're going to override language afterwards + const ctor = base.constructor; + const type = class extends ctor { // eslint-disable-line no-loop-func + + constructor() { + + super(); + if (extension.base) { + Object.assign(this, base); + } + + this._type = extension.name; + + if (extension.language) { + this._settings = this._settings || { language: {} }; + this._settings.language = Hoek.applyToDefaults(this._settings.language, { + [extension.name]: extension.language + }); + } + } + + }; + + if (extension.coerce) { + type.prototype._coerce = function (value, state, options) { + + if (ctor.prototype._coerce) { + const baseRet = ctor.prototype._coerce.call(this, value, state, options); + + if (baseRet.errors) { + return baseRet; + } + + value = baseRet.value; + } + + const ret = extension.coerce.call(this, value, state, options); + if (ret instanceof Errors.Err) { + return { value, errors: ret }; + } + + return { value: ret }; + }; + } + if (extension.pre) { + type.prototype._base = function (value, state, options) { + + if (ctor.prototype._base) { + const baseRet = ctor.prototype._base.call(this, value, state, options); + + if (baseRet.errors) { + return baseRet; + } + + value = baseRet.value; + } + + const ret = extension.pre.call(this, value, state, options); + if (ret instanceof Errors.Err) { + return { value, errors: ret }; + } + + return { value: ret }; + }; + } + + if (extension.rules) { + for (let j = 0; j < extension.rules.length; ++j) { + const rule = extension.rules[j]; + const ruleArgs = rule.params ? + (rule.params instanceof Any ? rule.params._inner.children.map((k) => k.key) : Object.keys(rule.params)) : + []; + const validateArgs = rule.params ? Cast.schema(this, rule.params) : null; + + type.prototype[rule.name] = function () { // eslint-disable-line no-loop-func + + if (arguments.length > ruleArgs.length) { + throw new Error('Unexpected number of arguments'); + } + + const args = Array.prototype.slice.call(arguments); + let hasRef = false; + let arg = {}; + + for (let k = 0; k < ruleArgs.length; ++k) { + arg[ruleArgs[k]] = args[k]; + if (!hasRef && Ref.isRef(args[k])) { + hasRef = true; + } + } + + if (validateArgs) { + arg = joi.attempt(arg, validateArgs); + } + + let schema; + if (rule.validate) { + const validate = function (value, state, options) { + + return rule.validate.call(this, arg, value, state, options); + }; + + schema = this._test(rule.name, arg, validate, { + description: rule.description, + hasRef + }); + } + else { + schema = this.clone(); + } + + if (rule.setup) { + const newSchema = rule.setup.call(schema, arg); + if (newSchema !== undefined) { + Hoek.assert(newSchema instanceof Any, `Setup of extension Joi.${this._type}().${rule.name}() must return undefined or a Joi object`); + schema = newSchema; + } + } + + return schema; + }; + } + } + + if (extension.describe) { + type.prototype.describe = function () { + + const description = ctor.prototype.describe.call(this); + return extension.describe.call(this, description); + }; + } + + const instance = new type(); + joi[extension.name] = function () { + + return internals.applyDefaults.call(this, instance); + }; + } + + return joi; + }; + + root.extensionSchema = internals.object.keys({ + base: internals.object.type(Any, 'Joi object'), + name: internals.string.required(), + coerce: internals.func.arity(3), + pre: internals.func.arity(3), + language: internals.object, + describe: internals.func.arity(1), + rules: internals.array.items(internals.object.keys({ + name: internals.string.required(), + setup: internals.func.arity(1), + validate: internals.func.arity(4), + params: [ + internals.object.pattern(/.*/, internals.object.type(Any, 'Joi object')), + internals.object.type(internals.object.constructor, 'Joi object') + ], + description: [internals.string, internals.func.arity(1)] + }).or('setup', 'validate')) + }).strict(); + + root.extensionsSchema = internals.array.items([internals.object, internals.func.arity(1)]).strict(); + + root.version = require('../package.json').version; + + return root; +}; + + +module.exports = internals.root(); diff --git a/node_modules/joi/lib/language.js b/node_modules/joi/lib/language.js new file mode 100644 index 00000000..5cf937c7 --- /dev/null +++ b/node_modules/joi/lib/language.js @@ -0,0 +1,156 @@ +'use strict'; + +// Load modules + + +// Declare internals + +const internals = {}; + + +exports.errors = { + root: 'value', + key: '"{{!label}}" ', + messages: { + wrapArrays: true + }, + any: { + unknown: 'is not allowed', + invalid: 'contains an invalid value', + empty: 'is not allowed to be empty', + required: 'is required', + allowOnly: 'must be one of {{valids}}', + default: 'threw an error when running default method' + }, + alternatives: { + base: 'not matching any of the allowed alternatives', + child: null + }, + array: { + base: 'must be an array', + includes: 'at position {{pos}} does not match any of the allowed types', + includesSingle: 'single value of "{{!label}}" does not match any of the allowed types', + includesOne: 'at position {{pos}} fails because {{reason}}', + includesOneSingle: 'single value of "{{!label}}" fails because {{reason}}', + includesRequiredUnknowns: 'does not contain {{unknownMisses}} required value(s)', + includesRequiredKnowns: 'does not contain {{knownMisses}}', + includesRequiredBoth: 'does not contain {{knownMisses}} and {{unknownMisses}} other required value(s)', + excludes: 'at position {{pos}} contains an excluded value', + excludesSingle: 'single value of "{{!label}}" contains an excluded value', + min: 'must contain at least {{limit}} items', + max: 'must contain less than or equal to {{limit}} items', + length: 'must contain {{limit}} items', + ordered: 'at position {{pos}} fails because {{reason}}', + orderedLength: 'at position {{pos}} fails because array must contain at most {{limit}} items', + ref: 'references "{{ref}}" which is not a positive integer', + sparse: 'must not be a sparse array', + unique: 'position {{pos}} contains a duplicate value' + }, + boolean: { + base: 'must be a boolean' + }, + binary: { + base: 'must be a buffer or a string', + min: 'must be at least {{limit}} bytes', + max: 'must be less than or equal to {{limit}} bytes', + length: 'must be {{limit}} bytes' + }, + date: { + base: 'must be a number of milliseconds or valid date string', + format: 'must be a string with one of the following formats {{format}}', + strict: 'must be a valid date', + min: 'must be larger than or equal to "{{limit}}"', + max: 'must be less than or equal to "{{limit}}"', + isoDate: 'must be a valid ISO 8601 date', + timestamp: { + javascript: 'must be a valid timestamp or number of milliseconds', + unix: 'must be a valid timestamp or number of seconds' + }, + ref: 'references "{{ref}}" which is not a date' + }, + function: { + base: 'must be a Function', + arity: 'must have an arity of {{n}}', + minArity: 'must have an arity greater or equal to {{n}}', + maxArity: 'must have an arity lesser or equal to {{n}}', + ref: 'must be a Joi reference', + class: 'must be a class' + }, + lazy: { + base: '!!schema error: lazy schema must be set', + schema: '!!schema error: lazy schema function must return a schema' + }, + object: { + base: 'must be an object', + child: '!!child "{{!child}}" fails because {{reason}}', + min: 'must have at least {{limit}} children', + max: 'must have less than or equal to {{limit}} children', + length: 'must have {{limit}} children', + allowUnknown: '!!"{{!child}}" is not allowed', + with: '!!"{{mainWithLabel}}" missing required peer "{{peerWithLabel}}"', + without: '!!"{{mainWithLabel}}" conflict with forbidden peer "{{peerWithLabel}}"', + missing: 'must contain at least one of {{peersWithLabels}}', + xor: 'contains a conflict between exclusive peers {{peersWithLabels}}', + or: 'must contain at least one of {{peersWithLabels}}', + and: 'contains {{presentWithLabels}} without its required peers {{missingWithLabels}}', + nand: '!!"{{mainWithLabel}}" must not exist simultaneously with {{peersWithLabels}}', + assert: '!!"{{ref}}" validation failed because "{{ref}}" failed to {{message}}', + rename: { + multiple: 'cannot rename child "{{from}}" because multiple renames are disabled and another key was already renamed to "{{to}}"', + override: 'cannot rename child "{{from}}" because override is disabled and target "{{to}}" exists', + regex: { + multiple: 'cannot rename children {{from}} because multiple renames are disabled and another key was already renamed to "{{to}}"', + override: 'cannot rename children {{from}} because override is disabled and target "{{to}}" exists' + } + }, + type: 'must be an instance of "{{type}}"', + schema: 'must be a Joi instance' + }, + number: { + base: 'must be a number', + min: 'must be larger than or equal to {{limit}}', + max: 'must be less than or equal to {{limit}}', + less: 'must be less than {{limit}}', + greater: 'must be greater than {{limit}}', + float: 'must be a float or double', + integer: 'must be an integer', + negative: 'must be a negative number', + positive: 'must be a positive number', + precision: 'must have no more than {{limit}} decimal places', + ref: 'references "{{ref}}" which is not a number', + multiple: 'must be a multiple of {{multiple}}' + }, + string: { + base: 'must be a string', + min: 'length must be at least {{limit}} characters long', + max: 'length must be less than or equal to {{limit}} characters long', + length: 'length must be {{limit}} characters long', + alphanum: 'must only contain alpha-numeric characters', + token: 'must only contain alpha-numeric and underscore characters', + regex: { + base: 'with value "{{!value}}" fails to match the required pattern: {{pattern}}', + name: 'with value "{{!value}}" fails to match the {{name}} pattern', + invert: { + base: 'with value "{{!value}}" matches the inverted pattern: {{pattern}}', + name: 'with value "{{!value}}" matches the inverted {{name}} pattern' + } + }, + email: 'must be a valid email', + uri: 'must be a valid uri', + uriRelativeOnly: 'must be a valid relative uri', + uriCustomScheme: 'must be a valid uri with a scheme matching the {{scheme}} pattern', + isoDate: 'must be a valid ISO 8601 date', + guid: 'must be a valid GUID', + hex: 'must only contain hexadecimal characters', + base64: 'must be a valid base64 string', + hostname: 'must be a valid hostname', + normalize: 'must be unicode normalized in the {{form}} form', + lowercase: 'must only contain lowercase characters', + uppercase: 'must only contain uppercase characters', + trim: 'must not have leading or trailing whitespace', + creditCard: 'must be a credit card', + ref: 'references "{{ref}}" which is not a number', + ip: 'must be a valid ip address with a {{cidr}} CIDR', + ipVersion: 'must be a valid ip address of one of the following versions {{version}} with a {{cidr}} CIDR' + } +}; diff --git a/node_modules/joi/lib/ref.js b/node_modules/joi/lib/ref.js new file mode 100644 index 00000000..f0bcd87e --- /dev/null +++ b/node_modules/joi/lib/ref.js @@ -0,0 +1,53 @@ +'use strict'; + +// Load modules + +const Hoek = require('hoek'); + + +// Declare internals + +const internals = {}; + + +exports.create = function (key, options) { + + Hoek.assert(typeof key === 'string', 'Invalid reference key:', key); + + const settings = Hoek.clone(options); // options can be reused and modified + + const ref = function (value, validationOptions) { + + return Hoek.reach(ref.isContext ? validationOptions.context : value, ref.key, settings); + }; + + ref.isContext = (key[0] === ((settings && settings.contextPrefix) || '$')); + ref.key = (ref.isContext ? key.slice(1) : key); + ref.path = ref.key.split((settings && settings.separator) || '.'); + ref.depth = ref.path.length; + ref.root = ref.path[0]; + ref.isJoi = true; + + ref.toString = function () { + + return (ref.isContext ? 'context:' : 'ref:') + ref.key; + }; + + return ref; +}; + + +exports.isRef = function (ref) { + + return typeof ref === 'function' && ref.isJoi; +}; + + +exports.push = function (array, ref) { + + if (exports.isRef(ref) && + !ref.isContext) { + + array.push(ref.root); + } +}; diff --git a/node_modules/joi/lib/schemas.js b/node_modules/joi/lib/schemas.js new file mode 100644 index 00000000..efbd028f --- /dev/null +++ b/node_modules/joi/lib/schemas.js @@ -0,0 +1,24 @@ +'use strict'; + +// Load modules + +const Joi = require('../'); + + +// Declare internals + +const internals = {}; + +exports.options = Joi.object({ + abortEarly: Joi.boolean(), + convert: Joi.boolean(), + allowUnknown: Joi.boolean(), + skipFunctions: Joi.boolean(), + stripUnknown: [Joi.boolean(), Joi.object({ arrays: Joi.boolean(), objects: Joi.boolean() }).or('arrays', 'objects')], + language: Joi.object(), + presence: Joi.string().only('required', 'optional', 'forbidden', 'ignore'), + raw: Joi.boolean(), + context: Joi.object(), + strip: Joi.boolean(), + noDefaults: Joi.boolean() +}).strict(); diff --git a/node_modules/joi/lib/set.js b/node_modules/joi/lib/set.js new file mode 100644 index 00000000..5a9b6550 --- /dev/null +++ b/node_modules/joi/lib/set.js @@ -0,0 +1,111 @@ +'use strict'; + +const Ref = require('./ref'); + +module.exports = class Set { + + constructor() { + + this._set = []; + } + + add(value, refs) { + + if (!Ref.isRef(value) && this.has(value, null, null, false)) { + + return; + } + + if (refs !== undefined) { // If it's a merge, we don't have any refs + Ref.push(refs, value); + } + + this._set.push(value); + return this; + } + + merge(add, remove) { + + for (let i = 0; i < add._set.length; ++i) { + this.add(add._set[i]); + } + + for (let i = 0; i < remove._set.length; ++i) { + this.remove(remove._set[i]); + } + + return this; + } + + remove(value) { + + this._set = this._set.filter((item) => value !== item); + return this; + } + + has(value, state, options, insensitive) { + + for (let i = 0; i < this._set.length; ++i) { + let items = this._set[i]; + + if (state && Ref.isRef(items)) { // Only resolve references if there is a state, otherwise it's a merge + items = items(state.reference || state.parent, options); + } + + if (!Array.isArray(items)) { + items = [items]; + } + + for (let j = 0; j < items.length; ++j) { + const item = items[j]; + if (typeof value !== typeof item) { + continue; + } + + if (value === item || + (value instanceof Date && item instanceof Date && value.getTime() === item.getTime()) || + (insensitive && typeof value === 'string' && value.toLowerCase() === item.toLowerCase()) || + (Buffer.isBuffer(value) && Buffer.isBuffer(item) && value.length === item.length && value.toString('binary') === item.toString('binary'))) { + + return true; + } + } + } + + return false; + } + + values(options) { + + if (options && options.stripUndefined) { + const values = []; + + for (let i = 0; i < this._set.length; ++i) { + const item = this._set[i]; + if (item !== undefined) { + values.push(item); + } + } + + return values; + } + + return this._set.slice(); + } + + slice() { + + const newSet = new Set(); + newSet._set = this._set.slice(); + + return newSet; + } + + concat(source) { + + const newSet = new Set(); + newSet._set = this._set.concat(source._set); + + return newSet; + } +}; diff --git a/node_modules/joi/lib/types/alternatives/index.js b/node_modules/joi/lib/types/alternatives/index.js new file mode 100644 index 00000000..fcbf1fb9 --- /dev/null +++ b/node_modules/joi/lib/types/alternatives/index.js @@ -0,0 +1,188 @@ +'use strict'; + +// Load modules + +const Hoek = require('hoek'); +const Any = require('../any'); +const Cast = require('../../cast'); +const Ref = require('../../ref'); + + +// Declare internals + +const internals = {}; + + +internals.Alternatives = class extends Any { + + constructor() { + + super(); + this._type = 'alternatives'; + this._invalids.remove(null); + this._inner.matches = []; + } + + _base(value, state, options) { + + let errors = []; + const il = this._inner.matches.length; + const baseType = this._baseType; + + for (let i = 0; i < il; ++i) { + const item = this._inner.matches[i]; + if (!item.schema) { + const schema = item.peek || item.is; + const input = item.is ? item.ref(state.reference || state.parent, options) : value; + const failed = schema._validate(input, null, options, state.parent).errors; + + if (failed) { + if (item.otherwise) { + return item.otherwise._validate(value, state, options); + } + } + else if (item.then) { + return item.then._validate(value, state, options); + } + + if (i === (il - 1) && baseType) { + return baseType._validate(value, state, options); + } + + continue; + } + + const result = item.schema._validate(value, state, options); + if (!result.errors) { // Found a valid match + return result; + } + + errors = errors.concat(result.errors); + } + + if (errors.length) { + return { errors: this.createError('alternatives.child', { reason: errors }, state, options) }; + } + + return { errors: this.createError('alternatives.base', null, state, options) }; + } + + try(/* schemas */) { + + const schemas = Hoek.flatten(Array.prototype.slice.call(arguments)); + Hoek.assert(schemas.length, 'Cannot add other alternatives without at least one schema'); + + const obj = this.clone(); + + for (let i = 0; i < schemas.length; ++i) { + const cast = Cast.schema(this._currentJoi, schemas[i]); + if (cast._refs.length) { + obj._refs = obj._refs.concat(cast._refs); + } + obj._inner.matches.push({ schema: cast }); + } + + return obj; + } + + when(condition, options) { + + let schemaCondition = false; + Hoek.assert(Ref.isRef(condition) || typeof condition === 'string' || (schemaCondition = condition instanceof Any), 'Invalid condition:', condition); + Hoek.assert(options, 'Missing options'); + Hoek.assert(typeof options === 'object', 'Invalid options'); + if (schemaCondition) { + Hoek.assert(!options.hasOwnProperty('is'), '"is" can not be used with a schema condition'); + } + else { + Hoek.assert(options.hasOwnProperty('is'), 'Missing "is" directive'); + } + Hoek.assert(options.then !== undefined || options.otherwise !== undefined, 'options must have at least one of "then" or "otherwise"'); + + const obj = this.clone(); + let is; + if (!schemaCondition) { + is = Cast.schema(this._currentJoi, options.is); + + if (options.is === null || !(Ref.isRef(options.is) || options.is instanceof Any)) { + + // Only apply required if this wasn't already a schema or a ref, we'll suppose people know what they're doing + is = is.required(); + } + } + + const item = { + ref: schemaCondition ? null : Cast.ref(condition), + peek: schemaCondition ? condition : null, + is, + then: options.then !== undefined ? Cast.schema(this._currentJoi, options.then) : undefined, + otherwise: options.otherwise !== undefined ? Cast.schema(this._currentJoi, options.otherwise) : undefined + }; + + if (obj._baseType) { + + item.then = item.then && obj._baseType.concat(item.then); + item.otherwise = item.otherwise && obj._baseType.concat(item.otherwise); + } + + if (!schemaCondition) { + Ref.push(obj._refs, item.ref); + obj._refs = obj._refs.concat(item.is._refs); + } + + if (item.then && item.then._refs) { + obj._refs = obj._refs.concat(item.then._refs); + } + + if (item.otherwise && item.otherwise._refs) { + obj._refs = obj._refs.concat(item.otherwise._refs); + } + + obj._inner.matches.push(item); + + return obj; + } + + describe() { + + const description = Any.prototype.describe.call(this); + const alternatives = []; + for (let i = 0; i < this._inner.matches.length; ++i) { + const item = this._inner.matches[i]; + if (item.schema) { + + // try() + + alternatives.push(item.schema.describe()); + } + else { + + // when() + + const when = item.is ? { + ref: item.ref.toString(), + is: item.is.describe() + } : { + peek: item.peek.describe() + }; + + if (item.then) { + when.then = item.then.describe(); + } + + if (item.otherwise) { + when.otherwise = item.otherwise.describe(); + } + + alternatives.push(when); + } + } + + description.alternatives = alternatives; + return description; + } + +}; + + +module.exports = new internals.Alternatives(); diff --git a/node_modules/joi/lib/types/any/index.js b/node_modules/joi/lib/types/any/index.js new file mode 100644 index 00000000..56afbb15 --- /dev/null +++ b/node_modules/joi/lib/types/any/index.js @@ -0,0 +1,916 @@ +'use strict'; + +// Load modules + +const Hoek = require('hoek'); +const Ref = require('../../ref'); +const Errors = require('../../errors'); +let Alternatives = null; // Delay-loaded to prevent circular dependencies +let Cast = null; + + +// Declare internals + +const internals = { + Set: require('../../set') +}; + + +internals.defaults = { + abortEarly: true, + convert: true, + allowUnknown: false, + skipFunctions: false, + stripUnknown: false, + language: {}, + presence: 'optional', + strip: false, + noDefaults: false + + // context: null +}; + + +module.exports = internals.Any = class { + + constructor() { + + Cast = Cast || require('../../cast'); + + this.isJoi = true; + this._type = 'any'; + this._settings = null; + this._valids = new internals.Set(); + this._invalids = new internals.Set(); + this._tests = []; + this._refs = []; + this._flags = { + /* + presence: 'optional', // optional, required, forbidden, ignore + allowOnly: false, + allowUnknown: undefined, + default: undefined, + forbidden: false, + encoding: undefined, + insensitive: false, + trim: false, + normalize: undefined, // NFC, NFD, NFKC, NFKD + case: undefined, // upper, lower + empty: undefined, + func: false, + raw: false + */ + }; + + this._description = null; + this._unit = null; + this._notes = []; + this._tags = []; + this._examples = []; + this._meta = []; + + this._inner = {}; // Hash of arrays of immutable objects + } + + get schemaType() { + + return this._type; + } + + createError(type, context, state, options, flags) { + + return Errors.create(type, context, state, options, flags || this._flags); + } + + createOverrideError(type, context, state, options, message, template) { + + return Errors.create(type, context, state, options, this._flags, message, template); + } + + checkOptions(options) { + + const Schemas = require('../../schemas'); + const result = Schemas.options.validate(options); + if (result.error) { + throw new Error(result.error.details[0].message); + } + } + + clone() { + + const obj = Object.create(Object.getPrototypeOf(this)); + + obj.isJoi = true; + obj._currentJoi = this._currentJoi; + obj._type = this._type; + obj._settings = internals.concatSettings(this._settings); + obj._baseType = this._baseType; + obj._valids = Hoek.clone(this._valids); + obj._invalids = Hoek.clone(this._invalids); + obj._tests = this._tests.slice(); + obj._refs = this._refs.slice(); + obj._flags = Hoek.clone(this._flags); + + obj._description = this._description; + obj._unit = this._unit; + obj._notes = this._notes.slice(); + obj._tags = this._tags.slice(); + obj._examples = this._examples.slice(); + obj._meta = this._meta.slice(); + + obj._inner = {}; + const inners = Object.keys(this._inner); + for (let i = 0; i < inners.length; ++i) { + const key = inners[i]; + obj._inner[key] = this._inner[key] ? this._inner[key].slice() : null; + } + + return obj; + } + + concat(schema) { + + Hoek.assert(schema instanceof internals.Any, 'Invalid schema object'); + Hoek.assert(this._type === 'any' || schema._type === 'any' || schema._type === this._type, 'Cannot merge type', this._type, 'with another type:', schema._type); + + let obj = this.clone(); + + if (this._type === 'any' && schema._type !== 'any') { + + // Reset values as if we were "this" + const tmpObj = schema.clone(); + const keysToRestore = ['_settings', '_valids', '_invalids', '_tests', '_refs', '_flags', '_description', '_unit', + '_notes', '_tags', '_examples', '_meta', '_inner']; + + for (let i = 0; i < keysToRestore.length; ++i) { + tmpObj[keysToRestore[i]] = obj[keysToRestore[i]]; + } + + obj = tmpObj; + } + + obj._settings = obj._settings ? internals.concatSettings(obj._settings, schema._settings) : schema._settings; + obj._valids.merge(schema._valids, schema._invalids); + obj._invalids.merge(schema._invalids, schema._valids); + obj._tests = obj._tests.concat(schema._tests); + obj._refs = obj._refs.concat(schema._refs); + Hoek.merge(obj._flags, schema._flags); + + obj._description = schema._description || obj._description; + obj._unit = schema._unit || obj._unit; + obj._notes = obj._notes.concat(schema._notes); + obj._tags = obj._tags.concat(schema._tags); + obj._examples = obj._examples.concat(schema._examples); + obj._meta = obj._meta.concat(schema._meta); + + const inners = Object.keys(schema._inner); + const isObject = obj._type === 'object'; + for (let i = 0; i < inners.length; ++i) { + const key = inners[i]; + const source = schema._inner[key]; + if (source) { + const target = obj._inner[key]; + if (target) { + if (isObject && key === 'children') { + const keys = {}; + + for (let j = 0; j < target.length; ++j) { + keys[target[j].key] = j; + } + + for (let j = 0; j < source.length; ++j) { + const sourceKey = source[j].key; + if (keys[sourceKey] >= 0) { + target[keys[sourceKey]] = { + key: sourceKey, + schema: target[keys[sourceKey]].schema.concat(source[j].schema) + }; + } + else { + target.push(source[j]); + } + } + } + else { + obj._inner[key] = obj._inner[key].concat(source); + } + } + else { + obj._inner[key] = source.slice(); + } + } + } + + return obj; + } + + _test(name, arg, func, options) { + + const obj = this.clone(); + obj._tests.push({ func, name, arg, options }); + return obj; + } + + options(options) { + + Hoek.assert(!options.context, 'Cannot override context'); + this.checkOptions(options); + + const obj = this.clone(); + obj._settings = internals.concatSettings(obj._settings, options); + return obj; + } + + strict(isStrict) { + + const obj = this.clone(); + obj._settings = obj._settings || {}; + obj._settings.convert = isStrict === undefined ? false : !isStrict; + return obj; + } + + raw(isRaw) { + + const value = isRaw === undefined ? true : isRaw; + + if (this._flags.raw === value) { + return this; + } + + const obj = this.clone(); + obj._flags.raw = value; + return obj; + } + + error(err) { + + Hoek.assert(err && (err instanceof Error || typeof err === 'function'), 'Must provide a valid Error object or a function'); + + const obj = this.clone(); + obj._flags.error = err; + return obj; + } + + allow() { + + const obj = this.clone(); + const values = Hoek.flatten(Array.prototype.slice.call(arguments)); + for (let i = 0; i < values.length; ++i) { + const value = values[i]; + + Hoek.assert(value !== undefined, 'Cannot call allow/valid/invalid with undefined'); + obj._invalids.remove(value); + obj._valids.add(value, obj._refs); + } + return obj; + } + + valid() { + + const obj = this.allow.apply(this, arguments); + obj._flags.allowOnly = true; + return obj; + } + + invalid(value) { + + const obj = this.clone(); + const values = Hoek.flatten(Array.prototype.slice.call(arguments)); + for (let i = 0; i < values.length; ++i) { + value = values[i]; + + Hoek.assert(value !== undefined, 'Cannot call allow/valid/invalid with undefined'); + obj._valids.remove(value); + obj._invalids.add(value, obj._refs); + } + + return obj; + } + + required() { + + if (this._flags.presence === 'required') { + return this; + } + + const obj = this.clone(); + obj._flags.presence = 'required'; + return obj; + } + + optional() { + + if (this._flags.presence === 'optional') { + return this; + } + + const obj = this.clone(); + obj._flags.presence = 'optional'; + return obj; + } + + + forbidden() { + + if (this._flags.presence === 'forbidden') { + return this; + } + + const obj = this.clone(); + obj._flags.presence = 'forbidden'; + return obj; + } + + + strip() { + + if (this._flags.strip) { + return this; + } + + const obj = this.clone(); + obj._flags.strip = true; + return obj; + } + + applyFunctionToChildren(children, fn, args, root) { + + children = [].concat(children); + + if (children.length !== 1 || children[0] !== '') { + root = root ? (root + '.') : ''; + + const extraChildren = (children[0] === '' ? children.slice(1) : children).map((child) => { + + return root + child; + }); + + throw new Error('unknown key(s) ' + extraChildren.join(', ')); + } + + return this[fn].apply(this, args); + } + + default(value, description) { + + if (typeof value === 'function' && + !Ref.isRef(value)) { + + if (!value.description && + description) { + + value.description = description; + } + + if (!this._flags.func) { + Hoek.assert(typeof value.description === 'string' && value.description.length > 0, 'description must be provided when default value is a function'); + } + } + + const obj = this.clone(); + obj._flags.default = value; + Ref.push(obj._refs, value); + return obj; + } + + empty(schema) { + + const obj = this.clone(); + if (schema === undefined) { + delete obj._flags.empty; + } + else { + obj._flags.empty = Cast.schema(this._currentJoi, schema); + } + return obj; + } + + when(condition, options) { + + Hoek.assert(options && typeof options === 'object', 'Invalid options'); + Hoek.assert(options.then !== undefined || options.otherwise !== undefined, 'options must have at least one of "then" or "otherwise"'); + + const then = options.hasOwnProperty('then') ? this.concat(Cast.schema(this._currentJoi, options.then)) : undefined; + const otherwise = options.hasOwnProperty('otherwise') ? this.concat(Cast.schema(this._currentJoi, options.otherwise)) : undefined; + + Alternatives = Alternatives || require('../alternatives'); + + const alternativeOptions = { then, otherwise }; + if (Object.prototype.hasOwnProperty.call(options, 'is')) { + alternativeOptions.is = options.is; + } + const obj = Alternatives.when(condition, alternativeOptions); + obj._flags.presence = 'ignore'; + obj._baseType = this; + + return obj; + } + + description(desc) { + + Hoek.assert(desc && typeof desc === 'string', 'Description must be a non-empty string'); + + const obj = this.clone(); + obj._description = desc; + return obj; + } + + notes(notes) { + + Hoek.assert(notes && (typeof notes === 'string' || Array.isArray(notes)), 'Notes must be a non-empty string or array'); + + const obj = this.clone(); + obj._notes = obj._notes.concat(notes); + return obj; + } + + tags(tags) { + + Hoek.assert(tags && (typeof tags === 'string' || Array.isArray(tags)), 'Tags must be a non-empty string or array'); + + const obj = this.clone(); + obj._tags = obj._tags.concat(tags); + return obj; + } + + meta(meta) { + + Hoek.assert(meta !== undefined, 'Meta cannot be undefined'); + + const obj = this.clone(); + obj._meta = obj._meta.concat(meta); + return obj; + } + + example(value) { + + Hoek.assert(arguments.length, 'Missing example'); + const result = this._validate(value, null, internals.defaults); + Hoek.assert(!result.errors, 'Bad example:', result.errors && Errors.process(result.errors, value)); + + const obj = this.clone(); + obj._examples.push(value); + return obj; + } + + unit(name) { + + Hoek.assert(name && typeof name === 'string', 'Unit name must be a non-empty string'); + + const obj = this.clone(); + obj._unit = name; + return obj; + } + + _prepareEmptyValue(value) { + + if (typeof value === 'string' && this._flags.trim) { + return value.trim(); + } + + return value; + } + + _validate(value, state, options, reference) { + + const originalValue = value; + + // Setup state and settings + + state = state || { key: '', path: [], parent: null, reference }; + + if (this._settings) { + options = internals.concatSettings(options, this._settings); + } + + let errors = []; + const finish = () => { + + let finalValue; + + if (value !== undefined) { + finalValue = this._flags.raw ? originalValue : value; + } + else if (options.noDefaults) { + finalValue = value; + } + else if (Ref.isRef(this._flags.default)) { + finalValue = this._flags.default(state.parent, options); + } + else if (typeof this._flags.default === 'function' && + !(this._flags.func && !this._flags.default.description)) { + + let args; + + if (state.parent !== null && + this._flags.default.length > 0) { + + args = [Hoek.clone(state.parent), options]; + } + + const defaultValue = internals._try(this._flags.default, args); + finalValue = defaultValue.value; + if (defaultValue.error) { + errors.push(this.createError('any.default', { error: defaultValue.error }, state, options)); + } + } + else { + finalValue = Hoek.clone(this._flags.default); + } + + if (errors.length && typeof this._flags.error === 'function') { + const change = this._flags.error.call(this, errors); + + if (typeof change === 'string') { + errors = [this.createOverrideError('override', { reason: errors }, state, options, change)]; + } + else { + errors = [].concat(change) + .map((err) => { + + return err instanceof Error ? + err : + this.createOverrideError(err.type || 'override', err.context, state, options, err.message, err.template); + }); + } + } + + return { + value: this._flags.strip ? undefined : finalValue, + finalValue, + errors: errors.length ? errors : null + }; + }; + + if (this._coerce) { + const coerced = this._coerce.call(this, value, state, options); + if (coerced.errors) { + value = coerced.value; + errors = errors.concat(coerced.errors); + return finish(); // Coerced error always aborts early + } + + value = coerced.value; + } + + if (this._flags.empty && !this._flags.empty._validate(this._prepareEmptyValue(value), null, internals.defaults).errors) { + value = undefined; + } + + // Check presence requirements + + const presence = this._flags.presence || options.presence; + if (presence === 'optional') { + if (value === undefined) { + const isDeepDefault = this._flags.hasOwnProperty('default') && this._flags.default === undefined; + if (isDeepDefault && this._type === 'object') { + value = {}; + } + else { + return finish(); + } + } + } + else if (presence === 'required' && + value === undefined) { + + errors.push(this.createError('any.required', null, state, options)); + return finish(); + } + else if (presence === 'forbidden') { + if (value === undefined) { + return finish(); + } + + errors.push(this.createError('any.unknown', null, state, options)); + return finish(); + } + + // Check allowed and denied values using the original value + + if (this._valids.has(value, state, options, this._flags.insensitive)) { + return finish(); + } + + if (this._invalids.has(value, state, options, this._flags.insensitive)) { + errors.push(this.createError(value === '' ? 'any.empty' : 'any.invalid', null, state, options)); + if (options.abortEarly || + value === undefined) { // No reason to keep validating missing value + + return finish(); + } + } + + // Convert value and validate type + + if (this._base) { + const base = this._base.call(this, value, state, options); + if (base.errors) { + value = base.value; + errors = errors.concat(base.errors); + return finish(); // Base error always aborts early + } + + if (base.value !== value) { + value = base.value; + + // Check allowed and denied values using the converted value + + if (this._valids.has(value, state, options, this._flags.insensitive)) { + return finish(); + } + + if (this._invalids.has(value, state, options, this._flags.insensitive)) { + errors.push(this.createError(value === '' ? 'any.empty' : 'any.invalid', null, state, options)); + if (options.abortEarly) { + return finish(); + } + } + } + } + + // Required values did not match + + if (this._flags.allowOnly) { + errors.push(this.createError('any.allowOnly', { valids: this._valids.values({ stripUndefined: true }) }, state, options)); + if (options.abortEarly) { + return finish(); + } + } + + // Helper.validate tests + + for (let i = 0; i < this._tests.length; ++i) { + const test = this._tests[i]; + const ret = test.func.call(this, value, state, options); + if (ret instanceof Errors.Err) { + errors.push(ret); + if (options.abortEarly) { + return finish(); + } + } + else { + value = ret; + } + } + + return finish(); + } + + _validateWithOptions(value, options, callback) { + + if (options) { + this.checkOptions(options); + } + + const settings = internals.concatSettings(internals.defaults, options); + const result = this._validate(value, null, settings); + const errors = Errors.process(result.errors, value); + + if (callback) { + return callback(errors, result.value); + } + + return { + error: errors, + value: result.value, + then(resolve, reject) { + + if (errors) { + return Promise.reject(errors).catch(reject); + } + + return Promise.resolve(result.value).then(resolve); + }, + catch(reject) { + + if (errors) { + return Promise.reject(errors).catch(reject); + } + + return Promise.resolve(result.value); + } + }; + } + + validate(value, options, callback) { + + if (typeof options === 'function') { + return this._validateWithOptions(value, null, options); + } + + return this._validateWithOptions(value, options, callback); + } + + describe() { + + const description = { + type: this._type + }; + + const flags = Object.keys(this._flags); + if (flags.length) { + if (['empty', 'default', 'lazy', 'label'].some((flag) => this._flags.hasOwnProperty(flag))) { + description.flags = {}; + for (let i = 0; i < flags.length; ++i) { + const flag = flags[i]; + if (flag === 'empty') { + description.flags[flag] = this._flags[flag].describe(); + } + else if (flag === 'default') { + if (Ref.isRef(this._flags[flag])) { + description.flags[flag] = this._flags[flag].toString(); + } + else if (typeof this._flags[flag] === 'function') { + description.flags[flag] = { + description: this._flags[flag].description, + function : this._flags[flag] + }; + } + else { + description.flags[flag] = this._flags[flag]; + } + } + else if (flag === 'lazy' || flag === 'label') { + // We don't want it in the description + } + else { + description.flags[flag] = this._flags[flag]; + } + } + } + else { + description.flags = this._flags; + } + } + + if (this._settings) { + description.options = Hoek.clone(this._settings); + } + + if (this._baseType) { + description.base = this._baseType.describe(); + } + + if (this._description) { + description.description = this._description; + } + + if (this._notes.length) { + description.notes = this._notes; + } + + if (this._tags.length) { + description.tags = this._tags; + } + + if (this._meta.length) { + description.meta = this._meta; + } + + if (this._examples.length) { + description.examples = this._examples; + } + + if (this._unit) { + description.unit = this._unit; + } + + const valids = this._valids.values(); + if (valids.length) { + description.valids = valids.map((v) => { + + return Ref.isRef(v) ? v.toString() : v; + }); + } + + const invalids = this._invalids.values(); + if (invalids.length) { + description.invalids = invalids.map((v) => { + + return Ref.isRef(v) ? v.toString() : v; + }); + } + + description.rules = []; + + for (let i = 0; i < this._tests.length; ++i) { + const validator = this._tests[i]; + const item = { name: validator.name }; + + if (validator.arg !== void 0) { + item.arg = Ref.isRef(validator.arg) ? validator.arg.toString() : validator.arg; + } + + const options = validator.options; + if (options) { + if (options.hasRef) { + item.arg = {}; + const keys = Object.keys(validator.arg); + for (let j = 0; j < keys.length; ++j) { + const key = keys[j]; + const value = validator.arg[key]; + item.arg[key] = Ref.isRef(value) ? value.toString() : value; + } + } + + if (typeof options.description === 'string') { + item.description = options.description; + } + else if (typeof options.description === 'function') { + item.description = options.description(item.arg); + } + } + + description.rules.push(item); + } + + if (!description.rules.length) { + delete description.rules; + } + + const label = this._getLabel(); + if (label) { + description.label = label; + } + + return description; + } + + label(name) { + + Hoek.assert(name && typeof name === 'string', 'Label name must be a non-empty string'); + + const obj = this.clone(); + obj._flags.label = name; + return obj; + } + + _getLabel(def) { + + return this._flags.label || def; + } + +}; + + +internals.Any.prototype.isImmutable = true; // Prevents Hoek from deep cloning schema objects + +// Aliases + +internals.Any.prototype.only = internals.Any.prototype.equal = internals.Any.prototype.valid; +internals.Any.prototype.disallow = internals.Any.prototype.not = internals.Any.prototype.invalid; +internals.Any.prototype.exist = internals.Any.prototype.required; + + +internals._try = function (fn, args) { + + let err; + let result; + + try { + result = fn.apply(null, args); + } + catch (e) { + err = e; + } + + return { + value: result, + error: err + }; +}; + +internals.concatSettings = function (target, source) { + + // Used to avoid cloning context + + if (!target && + !source) { + + return null; + } + + const obj = {}; + + if (target) { + Object.assign(obj, target); + } + + if (source) { + const sKeys = Object.keys(source); + for (let i = 0; i < sKeys.length; ++i) { + const key = sKeys[i]; + if (key !== 'language' || + !obj.hasOwnProperty(key)) { + + obj[key] = source[key]; + } + else { + obj[key] = Hoek.applyToDefaults(obj[key], source[key]); + } + } + } + + return obj; +}; diff --git a/node_modules/joi/lib/types/array/index.js b/node_modules/joi/lib/types/array/index.js new file mode 100644 index 00000000..ff6a148a --- /dev/null +++ b/node_modules/joi/lib/types/array/index.js @@ -0,0 +1,658 @@ +'use strict'; + +// Load modules + +const Any = require('../any'); +const Cast = require('../../cast'); +const Ref = require('../../ref'); +const Hoek = require('hoek'); + + +// Declare internals + +const internals = {}; + + +internals.fastSplice = function (arr, i) { + + let pos = i; + while (pos < arr.length) { + arr[pos++] = arr[pos]; + } + + --arr.length; +}; + + +internals.Array = class extends Any { + + constructor() { + + super(); + this._type = 'array'; + this._inner.items = []; + this._inner.ordereds = []; + this._inner.inclusions = []; + this._inner.exclusions = []; + this._inner.requireds = []; + this._flags.sparse = false; + } + + _base(value, state, options) { + + const result = { + value + }; + + if (typeof value === 'string' && + options.convert) { + + internals.safeParse(value, result); + } + + let isArray = Array.isArray(result.value); + const wasArray = isArray; + if (options.convert && this._flags.single && !isArray) { + result.value = [result.value]; + isArray = true; + } + + if (!isArray) { + result.errors = this.createError('array.base', null, state, options); + return result; + } + + if (this._inner.inclusions.length || + this._inner.exclusions.length || + this._inner.requireds.length || + this._inner.ordereds.length || + !this._flags.sparse) { + + // Clone the array so that we don't modify the original + if (wasArray) { + result.value = result.value.slice(0); + } + + result.errors = this._checkItems.call(this, result.value, wasArray, state, options); + + if (result.errors && wasArray && options.convert && this._flags.single) { + + // Attempt a 2nd pass by putting the array inside one. + const previousErrors = result.errors; + + result.value = [result.value]; + result.errors = this._checkItems.call(this, result.value, wasArray, state, options); + + if (result.errors) { + + // Restore previous errors and value since this didn't validate either. + result.errors = previousErrors; + result.value = result.value[0]; + } + } + } + + return result; + } + + _checkItems(items, wasArray, state, options) { + + const errors = []; + let errored; + + const requireds = this._inner.requireds.slice(); + const ordereds = this._inner.ordereds.slice(); + const inclusions = this._inner.inclusions.concat(requireds); + + let il = items.length; + for (let i = 0; i < il; ++i) { + errored = false; + const item = items[i]; + let isValid = false; + const key = wasArray ? i : state.key; + const path = wasArray ? state.path.concat(i) : state.path; + const localState = { key, path, parent: state.parent, reference: state.reference }; + let res; + + // Sparse + + if (!this._flags.sparse && item === undefined) { + errors.push(this.createError('array.sparse', null, { key: state.key, path: localState.path, pos: i }, options)); + + if (options.abortEarly) { + return errors; + } + + continue; + } + + // Exclusions + + for (let j = 0; j < this._inner.exclusions.length; ++j) { + res = this._inner.exclusions[j]._validate(item, localState, {}); // Not passing options to use defaults + + if (!res.errors) { + errors.push(this.createError(wasArray ? 'array.excludes' : 'array.excludesSingle', { pos: i, value: item }, { key: state.key, path: localState.path }, options)); + errored = true; + + if (options.abortEarly) { + return errors; + } + + break; + } + } + + if (errored) { + continue; + } + + // Ordered + if (this._inner.ordereds.length) { + if (ordereds.length > 0) { + const ordered = ordereds.shift(); + res = ordered._validate(item, localState, options); + if (!res.errors) { + if (ordered._flags.strip) { + internals.fastSplice(items, i); + --i; + --il; + } + else if (!this._flags.sparse && res.value === undefined) { + errors.push(this.createError('array.sparse', null, { key: state.key, path: localState.path, pos: i }, options)); + + if (options.abortEarly) { + return errors; + } + + continue; + } + else { + items[i] = res.value; + } + } + else { + errors.push(this.createError('array.ordered', { pos: i, reason: res.errors, value: item }, { key: state.key, path: localState.path }, options)); + if (options.abortEarly) { + return errors; + } + } + continue; + } + else if (!this._inner.items.length) { + errors.push(this.createError('array.orderedLength', { pos: i, limit: this._inner.ordereds.length }, { key: state.key, path: localState.path }, options)); + if (options.abortEarly) { + return errors; + } + continue; + } + } + + // Requireds + + const requiredChecks = []; + let jl = requireds.length; + for (let j = 0; j < jl; ++j) { + res = requiredChecks[j] = requireds[j]._validate(item, localState, options); + if (!res.errors) { + items[i] = res.value; + isValid = true; + internals.fastSplice(requireds, j); + --j; + --jl; + + if (!this._flags.sparse && res.value === undefined) { + errors.push(this.createError('array.sparse', null, { key: state.key, path: localState.path, pos: i }, options)); + + if (options.abortEarly) { + return errors; + } + } + + break; + } + } + + if (isValid) { + continue; + } + + // Inclusions + + const stripUnknown = options.stripUnknown + ? (options.stripUnknown === true ? true : !!options.stripUnknown.arrays) + : false; + + jl = inclusions.length; + for (let j = 0; j < jl; ++j) { + const inclusion = inclusions[j]; + + // Avoid re-running requireds that already didn't match in the previous loop + const previousCheck = requireds.indexOf(inclusion); + if (previousCheck !== -1) { + res = requiredChecks[previousCheck]; + } + else { + res = inclusion._validate(item, localState, options); + + if (!res.errors) { + if (inclusion._flags.strip) { + internals.fastSplice(items, i); + --i; + --il; + } + else if (!this._flags.sparse && res.value === undefined) { + errors.push(this.createError('array.sparse', null, { key: state.key, path: localState.path, pos: i }, options)); + errored = true; + } + else { + items[i] = res.value; + } + isValid = true; + break; + } + } + + // Return the actual error if only one inclusion defined + if (jl === 1) { + if (stripUnknown) { + internals.fastSplice(items, i); + --i; + --il; + isValid = true; + break; + } + + errors.push(this.createError(wasArray ? 'array.includesOne' : 'array.includesOneSingle', { pos: i, reason: res.errors, value: item }, { key: state.key, path: localState.path }, options)); + errored = true; + + if (options.abortEarly) { + return errors; + } + + break; + } + } + + if (errored) { + continue; + } + + if (this._inner.inclusions.length && !isValid) { + if (stripUnknown) { + internals.fastSplice(items, i); + --i; + --il; + continue; + } + + errors.push(this.createError(wasArray ? 'array.includes' : 'array.includesSingle', { pos: i, value: item }, { key: state.key, path: localState.path }, options)); + + if (options.abortEarly) { + return errors; + } + } + } + + if (requireds.length) { + this._fillMissedErrors.call(this, errors, requireds, state, options); + } + + if (ordereds.length) { + this._fillOrderedErrors.call(this, errors, ordereds, state, options); + } + + return errors.length ? errors : null; + } + + describe() { + + const description = Any.prototype.describe.call(this); + + if (this._inner.ordereds.length) { + description.orderedItems = []; + + for (let i = 0; i < this._inner.ordereds.length; ++i) { + description.orderedItems.push(this._inner.ordereds[i].describe()); + } + } + + if (this._inner.items.length) { + description.items = []; + + for (let i = 0; i < this._inner.items.length; ++i) { + description.items.push(this._inner.items[i].describe()); + } + } + + return description; + } + + items() { + + const obj = this.clone(); + + Hoek.flatten(Array.prototype.slice.call(arguments)).forEach((type, index) => { + + try { + type = Cast.schema(this._currentJoi, type); + } + catch (castErr) { + if (castErr.hasOwnProperty('path')) { + castErr.path = index + '.' + castErr.path; + } + else { + castErr.path = index; + } + castErr.message = castErr.message + '(' + castErr.path + ')'; + throw castErr; + } + + obj._inner.items.push(type); + + if (type._flags.presence === 'required') { + obj._inner.requireds.push(type); + } + else if (type._flags.presence === 'forbidden') { + obj._inner.exclusions.push(type.optional()); + } + else { + obj._inner.inclusions.push(type); + } + }); + + return obj; + } + + ordered() { + + const obj = this.clone(); + + Hoek.flatten(Array.prototype.slice.call(arguments)).forEach((type, index) => { + + try { + type = Cast.schema(this._currentJoi, type); + } + catch (castErr) { + if (castErr.hasOwnProperty('path')) { + castErr.path = index + '.' + castErr.path; + } + else { + castErr.path = index; + } + castErr.message = castErr.message + '(' + castErr.path + ')'; + throw castErr; + } + obj._inner.ordereds.push(type); + }); + + return obj; + } + + min(limit) { + + const isRef = Ref.isRef(limit); + + Hoek.assert((Number.isSafeInteger(limit) && limit >= 0) || isRef, 'limit must be a positive integer or reference'); + + return this._test('min', limit, function (value, state, options) { + + let compareTo; + if (isRef) { + compareTo = limit(state.reference || state.parent, options); + + if (!(Number.isSafeInteger(compareTo) && compareTo >= 0)) { + return this.createError('array.ref', { ref: limit.key }, state, options); + } + } + else { + compareTo = limit; + } + + if (value.length >= compareTo) { + return value; + } + + return this.createError('array.min', { limit, value }, state, options); + }); + } + + max(limit) { + + const isRef = Ref.isRef(limit); + + Hoek.assert((Number.isSafeInteger(limit) && limit >= 0) || isRef, 'limit must be a positive integer or reference'); + + return this._test('max', limit, function (value, state, options) { + + let compareTo; + if (isRef) { + compareTo = limit(state.reference || state.parent, options); + + if (!(Number.isSafeInteger(compareTo) && compareTo >= 0)) { + return this.createError('array.ref', { ref: limit.key }, state, options); + } + } + else { + compareTo = limit; + } + + if (value.length <= compareTo) { + return value; + } + + return this.createError('array.max', { limit, value }, state, options); + }); + } + + length(limit) { + + const isRef = Ref.isRef(limit); + + Hoek.assert((Number.isSafeInteger(limit) && limit >= 0) || isRef, 'limit must be a positive integer or reference'); + + return this._test('length', limit, function (value, state, options) { + + let compareTo; + if (isRef) { + compareTo = limit(state.reference || state.parent, options); + + if (!(Number.isSafeInteger(compareTo) && compareTo >= 0)) { + return this.createError('array.ref', { ref: limit.key }, state, options); + } + } + else { + compareTo = limit; + } + + if (value.length === compareTo) { + return value; + } + + return this.createError('array.length', { limit, value }, state, options); + }); + } + + unique(comparator) { + + Hoek.assert(comparator === undefined || + typeof comparator === 'function' || + typeof comparator === 'string', 'comparator must be a function or a string'); + + const settings = {}; + + if (typeof comparator === 'string') { + settings.path = comparator; + } + else if (typeof comparator === 'function') { + settings.comparator = comparator; + } + + return this._test('unique', settings, function (value, state, options) { + + const found = { + string: {}, + number: {}, + undefined: {}, + boolean: {}, + object: new Map(), + function: new Map(), + custom: new Map() + }; + + const compare = settings.comparator || Hoek.deepEqual; + + for (let i = 0; i < value.length; ++i) { + const item = settings.path ? Hoek.reach(value[i], settings.path) : value[i]; + const records = settings.comparator ? found.custom : found[typeof item]; + + // All available types are supported, so it's not possible to reach 100% coverage without ignoring this line. + // I still want to keep the test for future js versions with new types (eg. Symbol). + if (/* $lab:coverage:off$ */ records /* $lab:coverage:on$ */) { + if (records instanceof Map) { + const entries = records.entries(); + let current; + while (!(current = entries.next()).done) { + if (compare(current.value[0], item)) { + const localState = { + key: state.key, + path: state.path.concat(i), + parent: state.parent, + reference: state.reference + }; + + const context = { + pos: i, + value: value[i], + dupePos: current.value[1], + dupeValue: value[current.value[1]] + }; + + if (settings.path) { + context.path = settings.path; + } + + return this.createError('array.unique', context, localState, options); + } + } + + records.set(item, i); + } + else { + if (records[item] !== undefined) { + const localState = { + key: state.key, + path: state.path.concat(i), + parent: state.parent, + reference: state.reference + }; + + const context = { + pos: i, + value: value[i], + dupePos: records[item], + dupeValue: value[records[item]] + }; + + if (settings.path) { + context.path = settings.path; + } + + return this.createError('array.unique', context, localState, options); + } + + records[item] = i; + } + } + } + + return value; + }); + } + + sparse(enabled) { + + const value = enabled === undefined ? true : !!enabled; + + if (this._flags.sparse === value) { + return this; + } + + const obj = this.clone(); + obj._flags.sparse = value; + return obj; + } + + single(enabled) { + + const value = enabled === undefined ? true : !!enabled; + + if (this._flags.single === value) { + return this; + } + + const obj = this.clone(); + obj._flags.single = value; + return obj; + } + + _fillMissedErrors(errors, requireds, state, options) { + + const knownMisses = []; + let unknownMisses = 0; + for (let i = 0; i < requireds.length; ++i) { + const label = requireds[i]._getLabel(); + if (label) { + knownMisses.push(label); + } + else { + ++unknownMisses; + } + } + + if (knownMisses.length) { + if (unknownMisses) { + errors.push(this.createError('array.includesRequiredBoth', { knownMisses, unknownMisses }, { key: state.key, path: state.path }, options)); + } + else { + errors.push(this.createError('array.includesRequiredKnowns', { knownMisses }, { key: state.key, path: state.path }, options)); + } + } + else { + errors.push(this.createError('array.includesRequiredUnknowns', { unknownMisses }, { key: state.key, path: state.path }, options)); + } + } + + + _fillOrderedErrors(errors, ordereds, state, options) { + + const requiredOrdereds = []; + + for (let i = 0; i < ordereds.length; ++i) { + const presence = Hoek.reach(ordereds[i], '_flags.presence'); + if (presence === 'required') { + requiredOrdereds.push(ordereds[i]); + } + } + + if (requiredOrdereds.length) { + this._fillMissedErrors.call(this, errors, requiredOrdereds, state, options); + } + } + +}; + + +internals.safeParse = function (value, result) { + + try { + const converted = JSON.parse(value); + if (Array.isArray(converted)) { + result.value = converted; + } + } + catch (e) { } +}; + + +module.exports = new internals.Array(); diff --git a/node_modules/joi/lib/types/binary/index.js b/node_modules/joi/lib/types/binary/index.js new file mode 100644 index 00000000..e93f0a46 --- /dev/null +++ b/node_modules/joi/lib/types/binary/index.js @@ -0,0 +1,100 @@ +'use strict'; + +// Load modules + +const Any = require('../any'); +const Hoek = require('hoek'); + + +// Declare internals + +const internals = {}; + + +internals.Binary = class extends Any { + + constructor() { + + super(); + this._type = 'binary'; + } + + _base(value, state, options) { + + const result = { + value + }; + + if (typeof value === 'string' && + options.convert) { + + try { + result.value = new Buffer(value, this._flags.encoding); + } + catch (e) { + } + } + + result.errors = Buffer.isBuffer(result.value) ? null : this.createError('binary.base', null, state, options); + return result; + } + + encoding(encoding) { + + Hoek.assert(Buffer.isEncoding(encoding), 'Invalid encoding:', encoding); + + if (this._flags.encoding === encoding) { + return this; + } + + const obj = this.clone(); + obj._flags.encoding = encoding; + return obj; + } + + min(limit) { + + Hoek.assert(Number.isSafeInteger(limit) && limit >= 0, 'limit must be a positive integer'); + + return this._test('min', limit, function (value, state, options) { + + if (value.length >= limit) { + return value; + } + + return this.createError('binary.min', { limit, value }, state, options); + }); + } + + max(limit) { + + Hoek.assert(Number.isSafeInteger(limit) && limit >= 0, 'limit must be a positive integer'); + + return this._test('max', limit, function (value, state, options) { + + if (value.length <= limit) { + return value; + } + + return this.createError('binary.max', { limit, value }, state, options); + }); + } + + length(limit) { + + Hoek.assert(Number.isSafeInteger(limit) && limit >= 0, 'limit must be a positive integer'); + + return this._test('length', limit, function (value, state, options) { + + if (value.length === limit) { + return value; + } + + return this.createError('binary.length', { limit, value }, state, options); + }); + } + +}; + + +module.exports = new internals.Binary(); diff --git a/node_modules/joi/lib/types/boolean/index.js b/node_modules/joi/lib/types/boolean/index.js new file mode 100644 index 00000000..3f909998 --- /dev/null +++ b/node_modules/joi/lib/types/boolean/index.js @@ -0,0 +1,98 @@ +'use strict'; + +// Load modules + +const Any = require('../any'); +const Hoek = require('hoek'); + + +// Declare internals + +const internals = { + Set: require('../../set') +}; + + +internals.Boolean = class extends Any { + constructor() { + + super(); + this._type = 'boolean'; + this._flags.insensitive = true; + this._inner.truthySet = new internals.Set(); + this._inner.falsySet = new internals.Set(); + } + + _base(value, state, options) { + + const result = { + value + }; + + if (typeof value === 'string' && + options.convert) { + + const normalized = this._flags.insensitive ? value.toLowerCase() : value; + result.value = (normalized === 'true' ? true + : (normalized === 'false' ? false : value)); + } + + if (typeof result.value !== 'boolean') { + result.value = (this._inner.truthySet.has(value, null, null, this._flags.insensitive) ? true + : (this._inner.falsySet.has(value, null, null, this._flags.insensitive) ? false : value)); + } + + result.errors = (typeof result.value === 'boolean') ? null : this.createError('boolean.base', null, state, options); + return result; + } + + truthy() { + + const obj = this.clone(); + const values = Hoek.flatten(Array.prototype.slice.call(arguments)); + for (let i = 0; i < values.length; ++i) { + const value = values[i]; + + Hoek.assert(value !== undefined, 'Cannot call truthy with undefined'); + obj._inner.truthySet.add(value); + } + return obj; + } + + falsy() { + + const obj = this.clone(); + const values = Hoek.flatten(Array.prototype.slice.call(arguments)); + for (let i = 0; i < values.length; ++i) { + const value = values[i]; + + Hoek.assert(value !== undefined, 'Cannot call falsy with undefined'); + obj._inner.falsySet.add(value); + } + return obj; + } + + insensitive(enabled) { + + const insensitive = enabled === undefined ? true : !!enabled; + + if (this._flags.insensitive === insensitive) { + return this; + } + + const obj = this.clone(); + obj._flags.insensitive = insensitive; + return obj; + } + + describe() { + + const description = Any.prototype.describe.call(this); + description.truthy = [true].concat(this._inner.truthySet.values()); + description.falsy = [false].concat(this._inner.falsySet.values()); + return description; + } +}; + + +module.exports = new internals.Boolean(); diff --git a/node_modules/joi/lib/types/date/index.js b/node_modules/joi/lib/types/date/index.js new file mode 100644 index 00000000..00b93379 --- /dev/null +++ b/node_modules/joi/lib/types/date/index.js @@ -0,0 +1,177 @@ +'use strict'; + +// Load modules + +const Any = require('../any'); +const Ref = require('../../ref'); +const Hoek = require('hoek'); + + +// Declare internals + +const internals = {}; + +internals.isoDate = /^(?:[-+]\d{2})?(?:\d{4}(?!\d{2}\b))(?:(-?)(?:(?:0[1-9]|1[0-2])(?:\1(?:[12]\d|0[1-9]|3[01]))?|W(?:[0-4]\d|5[0-2])(?:-?[1-7])?|(?:00[1-9]|0[1-9]\d|[12]\d{2}|3(?:[0-5]\d|6[1-6])))(?![T]$|[T][\d]+Z$)(?:[T\s](?:(?:(?:[01]\d|2[0-3])(?:(:?)[0-5]\d)?|24\:?00)(?:[.,]\d+(?!:))?)(?:\2[0-5]\d(?:[.,]\d+)?)?(?:[Z]|(?:[+-])(?:[01]\d|2[0-3])(?::?[0-5]\d)?)?)?)?$/; +internals.invalidDate = new Date(''); +internals.isIsoDate = (() => { + + const isoString = internals.isoDate.toString(); + + return (date) => { + + return date && (date.toString() === isoString); + }; +})(); + +internals.Date = class extends Any { + + constructor() { + + super(); + this._type = 'date'; + } + + _base(value, state, options) { + + const result = { + value: (options.convert && internals.Date.toDate(value, this._flags.format, this._flags.timestamp, this._flags.multiplier)) || value + }; + + if (result.value instanceof Date && !isNaN(result.value.getTime())) { + result.errors = null; + } + else if (!options.convert) { + result.errors = this.createError('date.strict', null, state, options); + } + else { + let type; + if (internals.isIsoDate(this._flags.format)) { + type = 'isoDate'; + } + else if (this._flags.timestamp) { + type = `timestamp.${this._flags.timestamp}`; + } + else { + type = 'base'; + } + + result.errors = this.createError(`date.${type}`, null, state, options); + } + + return result; + } + + static toDate(value, format, timestamp, multiplier) { + + if (value instanceof Date) { + return value; + } + + if (typeof value === 'string' || + (typeof value === 'number' && !isNaN(value) && isFinite(value))) { + + if (typeof value === 'string' && + /^[+-]?\d+(\.\d+)?$/.test(value)) { + + value = parseFloat(value); + } + + let date; + if (format && internals.isIsoDate(format)) { + date = format.test(value) ? new Date(value) : internals.invalidDate; + } + else if (timestamp && multiplier) { + date = /^\s*$/.test(value) ? internals.invalidDate : new Date(value * multiplier); + } + else { + date = new Date(value); + } + + if (!isNaN(date.getTime())) { + return date; + } + } + + return null; + } + + iso() { + + if (this._flags.format === internals.isoDate) { + return this; + } + + const obj = this.clone(); + obj._flags.format = internals.isoDate; + return obj; + } + + timestamp(type) { + + type = type || 'javascript'; + + const allowed = ['javascript', 'unix']; + Hoek.assert(allowed.indexOf(type) !== -1, '"type" must be one of "' + allowed.join('", "') + '"'); + + if (this._flags.timestamp === type) { + return this; + } + + const obj = this.clone(); + obj._flags.timestamp = type; + obj._flags.multiplier = type === 'unix' ? 1000 : 1; + return obj; + } + + _isIsoDate(value) { + + return internals.isoDate.test(value); + } + +}; + +internals.compare = function (type, compare) { + + return function (date) { + + const isNow = date === 'now'; + const isRef = Ref.isRef(date); + + if (!isNow && !isRef) { + date = internals.Date.toDate(date); + } + + Hoek.assert(date, 'Invalid date format'); + + return this._test(type, date, function (value, state, options) { + + let compareTo; + if (isNow) { + compareTo = Date.now(); + } + else if (isRef) { + compareTo = internals.Date.toDate(date(state.reference || state.parent, options)); + + if (!compareTo) { + return this.createError('date.ref', { ref: date.key }, state, options); + } + + compareTo = compareTo.getTime(); + } + else { + compareTo = date.getTime(); + } + + if (compare(value.getTime(), compareTo)) { + return value; + } + + return this.createError('date.' + type, { limit: new Date(compareTo) }, state, options); + }); + }; +}; +internals.Date.prototype.min = internals.compare('min', (value, date) => value >= date); +internals.Date.prototype.max = internals.compare('max', (value, date) => value <= date); + + +module.exports = new internals.Date(); diff --git a/node_modules/joi/lib/types/func/index.js b/node_modules/joi/lib/types/func/index.js new file mode 100644 index 00000000..e9448386 --- /dev/null +++ b/node_modules/joi/lib/types/func/index.js @@ -0,0 +1,90 @@ +'use strict'; + +// Load modules + +const Hoek = require('hoek'); +const ObjectType = require('../object'); +const Ref = require('../../ref'); + + +// Declare internals + +const internals = {}; + + +internals.Func = class extends ObjectType.constructor { + + constructor() { + + super(); + this._flags.func = true; + } + + arity(n) { + + Hoek.assert(Number.isSafeInteger(n) && n >= 0, 'n must be a positive integer'); + + return this._test('arity', n, function (value, state, options) { + + if (value.length === n) { + return value; + } + + return this.createError('function.arity', { n }, state, options); + }); + } + + minArity(n) { + + Hoek.assert(Number.isSafeInteger(n) && n > 0, 'n must be a strict positive integer'); + + return this._test('minArity', n, function (value, state, options) { + + if (value.length >= n) { + return value; + } + + return this.createError('function.minArity', { n }, state, options); + }); + } + + maxArity(n) { + + Hoek.assert(Number.isSafeInteger(n) && n >= 0, 'n must be a positive integer'); + + return this._test('maxArity', n, function (value, state, options) { + + if (value.length <= n) { + return value; + } + + return this.createError('function.maxArity', { n }, state, options); + }); + } + + ref() { + + return this._test('ref', null, function (value, state, options) { + + if (Ref.isRef(value)) { + return value; + } + + return this.createError('function.ref', null, state, options); + }); + } + + class() { + + return this._test('class', null, function (value, state, options) { + + if ((/^\s*class\s/).test(value.toString())) { + return value; + } + + return this.createError('function.class', null, state, options); + }); + } +}; + +module.exports = new internals.Func(); diff --git a/node_modules/joi/lib/types/lazy/index.js b/node_modules/joi/lib/types/lazy/index.js new file mode 100644 index 00000000..b62054c6 --- /dev/null +++ b/node_modules/joi/lib/types/lazy/index.js @@ -0,0 +1,53 @@ +'use strict'; + +// Load modules + +const Any = require('../any'); +const Hoek = require('hoek'); + + +// Declare internals + +const internals = {}; + + +internals.Lazy = class extends Any { + + constructor() { + + super(); + this._type = 'lazy'; + } + + _base(value, state, options) { + + const result = { value }; + const lazy = this._flags.lazy; + + if (!lazy) { + result.errors = this.createError('lazy.base', null, state, options); + return result; + } + + const schema = lazy(); + + if (!(schema instanceof Any)) { + result.errors = this.createError('lazy.schema', null, state, options); + return result; + } + + return schema._validate(value, state, options); + } + + set(fn) { + + Hoek.assert(typeof fn === 'function', 'You must provide a function as first argument'); + + const obj = this.clone(); + obj._flags.lazy = fn; + return obj; + } + +}; + +module.exports = new internals.Lazy(); diff --git a/node_modules/joi/lib/types/number/index.js b/node_modules/joi/lib/types/number/index.js new file mode 100644 index 00000000..c1cfac09 --- /dev/null +++ b/node_modules/joi/lib/types/number/index.js @@ -0,0 +1,173 @@ +'use strict'; + +// Load modules + +const Any = require('../any'); +const Ref = require('../../ref'); +const Hoek = require('hoek'); + + +// Declare internals + +const internals = { + precisionRx: /(?:\.(\d+))?(?:[eE]([+-]?\d+))?$/ +}; + + +internals.Number = class extends Any { + + constructor() { + + super(); + this._type = 'number'; + this._invalids.add(Infinity); + this._invalids.add(-Infinity); + } + + _base(value, state, options) { + + const result = { + errors: null, + value + }; + + if (typeof value === 'string' && + options.convert) { + + const number = parseFloat(value); + result.value = (isNaN(number) || !isFinite(value)) ? NaN : number; + } + + const isNumber = typeof result.value === 'number' && !isNaN(result.value); + + if (options.convert && 'precision' in this._flags && isNumber) { + + // This is conceptually equivalent to using toFixed but it should be much faster + const precision = Math.pow(10, this._flags.precision); + result.value = Math.round(result.value * precision) / precision; + } + + result.errors = isNumber ? null : this.createError('number.base', null, state, options); + return result; + } + + multiple(base) { + + const isRef = Ref.isRef(base); + + if (!isRef) { + Hoek.assert(typeof base === 'number' && isFinite(base), 'multiple must be a number'); + Hoek.assert(base > 0, 'multiple must be greater than 0'); + } + + return this._test('multiple', base, function (value, state, options) { + + const divisor = isRef ? base(state.reference || state.parent, options) : base; + + if (isRef && (typeof divisor !== 'number' || !isFinite(divisor))) { + return this.createError('number.ref', { ref: base.key }, state, options); + } + + if (value % divisor === 0) { + return value; + } + + return this.createError('number.multiple', { multiple: base, value }, state, options); + }); + } + + integer() { + + return this._test('integer', undefined, function (value, state, options) { + + return Number.isSafeInteger(value) ? value : this.createError('number.integer', { value }, state, options); + }); + } + + negative() { + + return this._test('negative', undefined, function (value, state, options) { + + if (value < 0) { + return value; + } + + return this.createError('number.negative', { value }, state, options); + }); + } + + positive() { + + return this._test('positive', undefined, function (value, state, options) { + + if (value > 0) { + return value; + } + + return this.createError('number.positive', { value }, state, options); + }); + } + + precision(limit) { + + Hoek.assert(Number.isSafeInteger(limit), 'limit must be an integer'); + Hoek.assert(!('precision' in this._flags), 'precision already set'); + + const obj = this._test('precision', limit, function (value, state, options) { + + const places = value.toString().match(internals.precisionRx); + const decimals = Math.max((places[1] ? places[1].length : 0) - (places[2] ? parseInt(places[2], 10) : 0), 0); + if (decimals <= limit) { + return value; + } + + return this.createError('number.precision', { limit, value }, state, options); + }); + + obj._flags.precision = limit; + return obj; + } + +}; + + +internals.compare = function (type, compare) { + + return function (limit) { + + const isRef = Ref.isRef(limit); + const isNumber = typeof limit === 'number' && !isNaN(limit); + + Hoek.assert(isNumber || isRef, 'limit must be a number or reference'); + + return this._test(type, limit, function (value, state, options) { + + let compareTo; + if (isRef) { + compareTo = limit(state.reference || state.parent, options); + + if (!(typeof compareTo === 'number' && !isNaN(compareTo))) { + return this.createError('number.ref', { ref: limit.key }, state, options); + } + } + else { + compareTo = limit; + } + + if (compare(value, compareTo)) { + return value; + } + + return this.createError('number.' + type, { limit: compareTo, value }, state, options); + }); + }; +}; + + +internals.Number.prototype.min = internals.compare('min', (value, limit) => value >= limit); +internals.Number.prototype.max = internals.compare('max', (value, limit) => value <= limit); +internals.Number.prototype.greater = internals.compare('greater', (value, limit) => value > limit); +internals.Number.prototype.less = internals.compare('less', (value, limit) => value < limit); + + +module.exports = new internals.Number(); diff --git a/node_modules/joi/lib/types/object/index.js b/node_modules/joi/lib/types/object/index.js new file mode 100644 index 00000000..08b7efb9 --- /dev/null +++ b/node_modules/joi/lib/types/object/index.js @@ -0,0 +1,896 @@ +'use strict'; + +// Load modules + +const Hoek = require('hoek'); +const Topo = require('topo'); +const Any = require('../any'); +const Errors = require('../../errors'); +const Cast = require('../../cast'); + + +// Declare internals + +const internals = {}; + + +internals.Object = class extends Any { + + constructor() { + + super(); + this._type = 'object'; + this._inner.children = null; + this._inner.renames = []; + this._inner.dependencies = []; + this._inner.patterns = []; + } + + _base(value, state, options) { + + let target = value; + const errors = []; + const finish = () => { + + return { + value: target, + errors: errors.length ? errors : null + }; + }; + + if (typeof value === 'string' && + options.convert) { + + value = internals.safeParse(value); + } + + const type = this._flags.func ? 'function' : 'object'; + if (!value || + typeof value !== type || + Array.isArray(value)) { + + errors.push(this.createError(type + '.base', null, state, options)); + return finish(); + } + + // Skip if there are no other rules to test + + if (!this._inner.renames.length && + !this._inner.dependencies.length && + !this._inner.children && // null allows any keys + !this._inner.patterns.length) { + + target = value; + return finish(); + } + + // Ensure target is a local copy (parsed) or shallow copy + + if (target === value) { + if (type === 'object') { + target = Object.create(Object.getPrototypeOf(value)); + } + else { + target = function () { + + return value.apply(this, arguments); + }; + + target.prototype = Hoek.clone(value.prototype); + } + + const valueKeys = Object.keys(value); + for (let i = 0; i < valueKeys.length; ++i) { + target[valueKeys[i]] = value[valueKeys[i]]; + } + } + else { + target = value; + } + + // Rename keys + + const renamed = {}; + for (let i = 0; i < this._inner.renames.length; ++i) { + const rename = this._inner.renames[i]; + + if (rename.isRegExp) { + const targetKeys = Object.keys(target); + const matchedTargetKeys = []; + + for (let j = 0; j < targetKeys.length; ++j) { + if (rename.from.test(targetKeys[j])) { + matchedTargetKeys.push(targetKeys[j]); + } + } + + const allUndefined = matchedTargetKeys.every((key) => target[key] === undefined); + if (rename.options.ignoreUndefined && allUndefined) { + continue; + } + + if (!rename.options.multiple && + renamed[rename.to]) { + + errors.push(this.createError('object.rename.regex.multiple', { from: matchedTargetKeys, to: rename.to }, state, options)); + if (options.abortEarly) { + return finish(); + } + } + + if (Object.prototype.hasOwnProperty.call(target, rename.to) && + !rename.options.override && + !renamed[rename.to]) { + + errors.push(this.createError('object.rename.regex.override', { from: matchedTargetKeys, to: rename.to }, state, options)); + if (options.abortEarly) { + return finish(); + } + } + + if (allUndefined) { + delete target[rename.to]; + } + else { + target[rename.to] = target[matchedTargetKeys[matchedTargetKeys.length - 1]]; + } + + renamed[rename.to] = true; + + if (!rename.options.alias) { + for (let j = 0; j < matchedTargetKeys.length; ++j) { + delete target[matchedTargetKeys[j]]; + } + } + } + else { + if (rename.options.ignoreUndefined && target[rename.from] === undefined) { + continue; + } + + if (!rename.options.multiple && + renamed[rename.to]) { + + errors.push(this.createError('object.rename.multiple', { from: rename.from, to: rename.to }, state, options)); + if (options.abortEarly) { + return finish(); + } + } + + if (Object.prototype.hasOwnProperty.call(target, rename.to) && + !rename.options.override && + !renamed[rename.to]) { + + errors.push(this.createError('object.rename.override', { from: rename.from, to: rename.to }, state, options)); + if (options.abortEarly) { + return finish(); + } + } + + if (target[rename.from] === undefined) { + delete target[rename.to]; + } + else { + target[rename.to] = target[rename.from]; + } + + renamed[rename.to] = true; + + if (!rename.options.alias) { + delete target[rename.from]; + } + } + } + + // Validate schema + + if (!this._inner.children && // null allows any keys + !this._inner.patterns.length && + !this._inner.dependencies.length) { + + return finish(); + } + + const unprocessed = Hoek.mapToObject(Object.keys(target)); + + if (this._inner.children) { + const stripProps = []; + + for (let i = 0; i < this._inner.children.length; ++i) { + const child = this._inner.children[i]; + const key = child.key; + const item = target[key]; + + delete unprocessed[key]; + + const localState = { key, path: state.path.concat(key), parent: target, reference: state.reference }; + const result = child.schema._validate(item, localState, options); + if (result.errors) { + errors.push(this.createError('object.child', { key, child: child.schema._getLabel(key), reason: result.errors }, localState, options)); + + if (options.abortEarly) { + return finish(); + } + } + else { + if (child.schema._flags.strip || (result.value === undefined && result.value !== item)) { + stripProps.push(key); + target[key] = result.finalValue; + } + else if (result.value !== undefined) { + target[key] = result.value; + } + } + } + + for (let i = 0; i < stripProps.length; ++i) { + delete target[stripProps[i]]; + } + } + + // Unknown keys + + let unprocessedKeys = Object.keys(unprocessed); + if (unprocessedKeys.length && + this._inner.patterns.length) { + + for (let i = 0; i < unprocessedKeys.length; ++i) { + const key = unprocessedKeys[i]; + const localState = { key, path: state.path.concat(key), parent: target, reference: state.reference }; + const item = target[key]; + + for (let j = 0; j < this._inner.patterns.length; ++j) { + const pattern = this._inner.patterns[j]; + + if (pattern.regex.test(key)) { + delete unprocessed[key]; + + const result = pattern.rule._validate(item, localState, options); + if (result.errors) { + errors.push(this.createError('object.child', { key, child: pattern.rule._getLabel(key), reason: result.errors }, localState, options)); + + if (options.abortEarly) { + return finish(); + } + } + + if (result.value !== undefined) { + target[key] = result.value; + } + } + } + } + + unprocessedKeys = Object.keys(unprocessed); + } + + if ((this._inner.children || this._inner.patterns.length) && unprocessedKeys.length) { + if ((options.stripUnknown && this._flags.allowUnknown !== true) || + options.skipFunctions) { + + const stripUnknown = options.stripUnknown + ? (options.stripUnknown === true ? true : !!options.stripUnknown.objects) + : false; + + + for (let i = 0; i < unprocessedKeys.length; ++i) { + const key = unprocessedKeys[i]; + + if (stripUnknown) { + delete target[key]; + delete unprocessed[key]; + } + else if (typeof target[key] === 'function') { + delete unprocessed[key]; + } + } + + unprocessedKeys = Object.keys(unprocessed); + } + + if (unprocessedKeys.length && + (this._flags.allowUnknown !== undefined ? !this._flags.allowUnknown : !options.allowUnknown)) { + + for (let i = 0; i < unprocessedKeys.length; ++i) { + const unprocessedKey = unprocessedKeys[i]; + errors.push(this.createError('object.allowUnknown', { child: unprocessedKey }, { key: unprocessedKey, path: state.path.concat(unprocessedKey) }, options, {})); + } + } + } + + // Validate dependencies + + for (let i = 0; i < this._inner.dependencies.length; ++i) { + const dep = this._inner.dependencies[i]; + const err = internals[dep.type].call(this, dep.key !== null && target[dep.key], dep.peers, target, { key: dep.key, path: dep.key === null ? state.path : state.path.concat(dep.key) }, options); + if (err instanceof Errors.Err) { + errors.push(err); + if (options.abortEarly) { + return finish(); + } + } + } + + return finish(); + } + + keys(schema) { + + Hoek.assert(schema === null || schema === undefined || typeof schema === 'object', 'Object schema must be a valid object'); + Hoek.assert(!schema || !(schema instanceof Any), 'Object schema cannot be a joi schema'); + + const obj = this.clone(); + + if (!schema) { + obj._inner.children = null; + return obj; + } + + const children = Object.keys(schema); + + if (!children.length) { + obj._inner.children = []; + return obj; + } + + const topo = new Topo(); + if (obj._inner.children) { + for (let i = 0; i < obj._inner.children.length; ++i) { + const child = obj._inner.children[i]; + + // Only add the key if we are not going to replace it later + if (children.indexOf(child.key) === -1) { + topo.add(child, { after: child._refs, group: child.key }); + } + } + } + + for (let i = 0; i < children.length; ++i) { + const key = children[i]; + const child = schema[key]; + try { + const cast = Cast.schema(this._currentJoi, child); + topo.add({ key, schema: cast }, { after: cast._refs, group: key }); + } + catch (castErr) { + if (castErr.hasOwnProperty('path')) { + castErr.path = key + '.' + castErr.path; + } + else { + castErr.path = key; + } + throw castErr; + } + } + + obj._inner.children = topo.nodes; + + return obj; + } + + unknown(allow) { + + const value = allow !== false; + + if (this._flags.allowUnknown === value) { + return this; + } + + const obj = this.clone(); + obj._flags.allowUnknown = value; + return obj; + } + + length(limit) { + + Hoek.assert(Number.isSafeInteger(limit) && limit >= 0, 'limit must be a positive integer'); + + return this._test('length', limit, function (value, state, options) { + + if (Object.keys(value).length === limit) { + return value; + } + + return this.createError('object.length', { limit }, state, options); + }); + } + + min(limit) { + + Hoek.assert(Number.isSafeInteger(limit) && limit >= 0, 'limit must be a positive integer'); + + return this._test('min', limit, function (value, state, options) { + + if (Object.keys(value).length >= limit) { + return value; + } + + return this.createError('object.min', { limit }, state, options); + }); + } + + max(limit) { + + Hoek.assert(Number.isSafeInteger(limit) && limit >= 0, 'limit must be a positive integer'); + + return this._test('max', limit, function (value, state, options) { + + if (Object.keys(value).length <= limit) { + return value; + } + + return this.createError('object.max', { limit }, state, options); + }); + } + + pattern(pattern, schema) { + + Hoek.assert(pattern instanceof RegExp, 'Invalid regular expression'); + Hoek.assert(schema !== undefined, 'Invalid rule'); + + pattern = new RegExp(pattern.source, pattern.ignoreCase ? 'i' : undefined); // Future version should break this and forbid unsupported regex flags + + try { + schema = Cast.schema(this._currentJoi, schema); + } + catch (castErr) { + if (castErr.hasOwnProperty('path')) { + castErr.message = castErr.message + '(' + castErr.path + ')'; + } + + throw castErr; + } + + + const obj = this.clone(); + obj._inner.patterns.push({ regex: pattern, rule: schema }); + return obj; + } + + schema() { + + return this._test('schema', null, function (value, state, options) { + + if (value instanceof Any) { + return value; + } + + return this.createError('object.schema', null, state, options); + }); + } + + with(key, peers) { + + return this._dependency('with', key, peers); + } + + without(key, peers) { + + return this._dependency('without', key, peers); + } + + xor() { + + const peers = Hoek.flatten(Array.prototype.slice.call(arguments)); + return this._dependency('xor', null, peers); + } + + or() { + + const peers = Hoek.flatten(Array.prototype.slice.call(arguments)); + return this._dependency('or', null, peers); + } + + and() { + + const peers = Hoek.flatten(Array.prototype.slice.call(arguments)); + return this._dependency('and', null, peers); + } + + nand() { + + const peers = Hoek.flatten(Array.prototype.slice.call(arguments)); + return this._dependency('nand', null, peers); + } + + requiredKeys(children) { + + children = Hoek.flatten(Array.prototype.slice.call(arguments)); + return this.applyFunctionToChildren(children, 'required'); + } + + optionalKeys(children) { + + children = Hoek.flatten(Array.prototype.slice.call(arguments)); + return this.applyFunctionToChildren(children, 'optional'); + } + + forbiddenKeys(children) { + + children = Hoek.flatten(Array.prototype.slice.call(arguments)); + return this.applyFunctionToChildren(children, 'forbidden'); + } + + rename(from, to, options) { + + Hoek.assert(typeof from === 'string' || from instanceof RegExp, 'Rename missing the from argument'); + Hoek.assert(typeof to === 'string', 'Rename missing the to argument'); + Hoek.assert(to !== from, 'Cannot rename key to same name:', from); + + for (let i = 0; i < this._inner.renames.length; ++i) { + Hoek.assert(this._inner.renames[i].from !== from, 'Cannot rename the same key multiple times'); + } + + const obj = this.clone(); + + obj._inner.renames.push({ + from, + to, + options: Hoek.applyToDefaults(internals.renameDefaults, options || {}), + isRegExp: from instanceof RegExp + }); + + return obj; + } + + applyFunctionToChildren(children, fn, args, root) { + + children = [].concat(children); + Hoek.assert(children.length > 0, 'expected at least one children'); + + const groupedChildren = internals.groupChildren(children); + let obj; + + if ('' in groupedChildren) { + obj = this[fn].apply(this, args); + delete groupedChildren['']; + } + else { + obj = this.clone(); + } + + if (obj._inner.children) { + root = root ? (root + '.') : ''; + + for (let i = 0; i < obj._inner.children.length; ++i) { + const child = obj._inner.children[i]; + const group = groupedChildren[child.key]; + + if (group) { + obj._inner.children[i] = { + key: child.key, + _refs: child._refs, + schema: child.schema.applyFunctionToChildren(group, fn, args, root + child.key) + }; + + delete groupedChildren[child.key]; + } + } + } + + const remaining = Object.keys(groupedChildren); + Hoek.assert(remaining.length === 0, 'unknown key(s)', remaining.join(', ')); + + return obj; + } + + _dependency(type, key, peers) { + + peers = [].concat(peers); + for (let i = 0; i < peers.length; ++i) { + Hoek.assert(typeof peers[i] === 'string', type, 'peers must be a string or array of strings'); + } + + const obj = this.clone(); + obj._inner.dependencies.push({ type, key, peers }); + return obj; + } + + describe(shallow) { + + const description = Any.prototype.describe.call(this); + + if (description.rules) { + for (let i = 0; i < description.rules.length; ++i) { + const rule = description.rules[i]; + // Coverage off for future-proof descriptions, only object().assert() is use right now + if (/* $lab:coverage:off$ */rule.arg && + typeof rule.arg === 'object' && + rule.arg.schema && + rule.arg.ref /* $lab:coverage:on$ */) { + rule.arg = { + schema: rule.arg.schema.describe(), + ref: rule.arg.ref.toString() + }; + } + } + } + + if (this._inner.children && + !shallow) { + + description.children = {}; + for (let i = 0; i < this._inner.children.length; ++i) { + const child = this._inner.children[i]; + description.children[child.key] = child.schema.describe(); + } + } + + if (this._inner.dependencies.length) { + description.dependencies = Hoek.clone(this._inner.dependencies); + } + + if (this._inner.patterns.length) { + description.patterns = []; + + for (let i = 0; i < this._inner.patterns.length; ++i) { + const pattern = this._inner.patterns[i]; + description.patterns.push({ regex: pattern.regex.toString(), rule: pattern.rule.describe() }); + } + } + + if (this._inner.renames.length > 0) { + description.renames = Hoek.clone(this._inner.renames); + } + + return description; + } + + assert(ref, schema, message) { + + ref = Cast.ref(ref); + Hoek.assert(ref.isContext || ref.depth > 1, 'Cannot use assertions for root level references - use direct key rules instead'); + message = message || 'pass the assertion test'; + + try { + schema = Cast.schema(this._currentJoi, schema); + } + catch (castErr) { + if (castErr.hasOwnProperty('path')) { + castErr.message = castErr.message + '(' + castErr.path + ')'; + } + + throw castErr; + } + + const key = ref.path[ref.path.length - 1]; + const path = ref.path.join('.'); + + return this._test('assert', { schema, ref }, function (value, state, options) { + + const result = schema._validate(ref(value), null, options, value); + if (!result.errors) { + return value; + } + + const localState = Hoek.merge({}, state); + localState.key = key; + localState.path = ref.path; + return this.createError('object.assert', { ref: path, message }, localState, options); + }); + } + + type(constructor, name) { + + Hoek.assert(typeof constructor === 'function', 'type must be a constructor function'); + const typeData = { + name: name || constructor.name, + ctor: constructor + }; + + return this._test('type', typeData, function (value, state, options) { + + if (value instanceof constructor) { + return value; + } + + return this.createError('object.type', { type: typeData.name }, state, options); + }); + } +}; + +internals.safeParse = function (value) { + + try { + return JSON.parse(value); + } + catch (parseErr) {} + + return value; +}; + + +internals.renameDefaults = { + alias: false, // Keep old value in place + multiple: false, // Allow renaming multiple keys into the same target + override: false // Overrides an existing key +}; + + +internals.groupChildren = function (children) { + + children.sort(); + + const grouped = {}; + + for (let i = 0; i < children.length; ++i) { + const child = children[i]; + Hoek.assert(typeof child === 'string', 'children must be strings'); + const group = child.split('.')[0]; + const childGroup = grouped[group] = (grouped[group] || []); + childGroup.push(child.substring(group.length + 1)); + } + + return grouped; +}; + + +internals.keysToLabels = function (schema, keys) { + + const children = schema._inner.children; + + if (!children) { + return keys; + } + + const findLabel = function (key) { + + const matchingChild = children.find((child) => child.key === key); + return matchingChild ? matchingChild.schema._getLabel(key) : key; + }; + + if (Array.isArray(keys)) { + return keys.map(findLabel); + } + + return findLabel(keys); +}; + + +internals.with = function (value, peers, parent, state, options) { + + if (value === undefined) { + return value; + } + + for (let i = 0; i < peers.length; ++i) { + const peer = peers[i]; + if (!Object.prototype.hasOwnProperty.call(parent, peer) || + parent[peer] === undefined) { + + return this.createError('object.with', { + main: state.key, + mainWithLabel: internals.keysToLabels(this, state.key), + peer, + peerWithLabel: internals.keysToLabels(this, peer) + }, state, options); + } + } + + return value; +}; + + +internals.without = function (value, peers, parent, state, options) { + + if (value === undefined) { + return value; + } + + for (let i = 0; i < peers.length; ++i) { + const peer = peers[i]; + if (Object.prototype.hasOwnProperty.call(parent, peer) && + parent[peer] !== undefined) { + + return this.createError('object.without', { + main: state.key, + mainWithLabel: internals.keysToLabels(this, state.key), + peer, + peerWithLabel: internals.keysToLabels(this, peer) + }, state, options); + } + } + + return value; +}; + + +internals.xor = function (value, peers, parent, state, options) { + + const present = []; + for (let i = 0; i < peers.length; ++i) { + const peer = peers[i]; + if (Object.prototype.hasOwnProperty.call(parent, peer) && + parent[peer] !== undefined) { + + present.push(peer); + } + } + + if (present.length === 1) { + return value; + } + + const context = { peers, peersWithLabels: internals.keysToLabels(this, peers) }; + + if (present.length === 0) { + return this.createError('object.missing', context, state, options); + } + + return this.createError('object.xor', context, state, options); +}; + + +internals.or = function (value, peers, parent, state, options) { + + for (let i = 0; i < peers.length; ++i) { + const peer = peers[i]; + if (Object.prototype.hasOwnProperty.call(parent, peer) && + parent[peer] !== undefined) { + return value; + } + } + + return this.createError('object.missing', { + peers, + peersWithLabels: internals.keysToLabels(this, peers) + }, state, options); +}; + + +internals.and = function (value, peers, parent, state, options) { + + const missing = []; + const present = []; + const count = peers.length; + for (let i = 0; i < count; ++i) { + const peer = peers[i]; + if (!Object.prototype.hasOwnProperty.call(parent, peer) || + parent[peer] === undefined) { + + missing.push(peer); + } + else { + present.push(peer); + } + } + + const aon = (missing.length === count || present.length === count); + + if (!aon) { + + return this.createError('object.and', { + present, + presentWithLabels: internals.keysToLabels(this, present), + missing, + missingWithLabels: internals.keysToLabels(this, missing) + }, state, options); + } +}; + + +internals.nand = function (value, peers, parent, state, options) { + + const present = []; + for (let i = 0; i < peers.length; ++i) { + const peer = peers[i]; + if (Object.prototype.hasOwnProperty.call(parent, peer) && + parent[peer] !== undefined) { + + present.push(peer); + } + } + + const values = Hoek.clone(peers); + const main = values.splice(0, 1)[0]; + const allPresent = (present.length === peers.length); + return allPresent ? this.createError('object.nand', { + main, + mainWithLabel: internals.keysToLabels(this, main), + peers: values, + peersWithLabels: internals.keysToLabels(this, values) + }, state, options) : null; +}; + + +module.exports = new internals.Object(); diff --git a/node_modules/joi/lib/types/string/index.js b/node_modules/joi/lib/types/string/index.js new file mode 100755 index 00000000..e63199d8 --- /dev/null +++ b/node_modules/joi/lib/types/string/index.js @@ -0,0 +1,632 @@ +'use strict'; + +// Load modules + +const Net = require('net'); +const Hoek = require('hoek'); +let Isemail; // Loaded on demand +const Any = require('../any'); +const Ref = require('../../ref'); +const JoiDate = require('../date'); +const Uri = require('./uri'); +const Ip = require('./ip'); + +// Declare internals + +const internals = { + uriRegex: Uri.createUriRegex(), + ipRegex: Ip.createIpRegex(['ipv4', 'ipv6', 'ipvfuture'], 'optional'), + guidBrackets: { + '{': '}', '[': ']', '(': ')', '': '' + }, + guidVersions: { + uuidv1: '1', + uuidv2: '2', + uuidv3: '3', + uuidv4: '4', + uuidv5: '5' + }, + cidrPresences: ['required', 'optional', 'forbidden'], + normalizationForms: ['NFC', 'NFD', 'NFKC', 'NFKD'] +}; + +internals.String = class extends Any { + + constructor() { + + super(); + this._type = 'string'; + this._invalids.add(''); + } + + _base(value, state, options) { + + if (typeof value === 'string' && + options.convert) { + + if (this._flags.normalize) { + value = value.normalize(this._flags.normalize); + } + + if (this._flags.case) { + value = (this._flags.case === 'upper' ? value.toLocaleUpperCase() : value.toLocaleLowerCase()); + } + + if (this._flags.trim) { + value = value.trim(); + } + + if (this._inner.replacements) { + + for (let i = 0; i < this._inner.replacements.length; ++i) { + const replacement = this._inner.replacements[i]; + value = value.replace(replacement.pattern, replacement.replacement); + } + } + + if (this._flags.truncate) { + for (let i = 0; i < this._tests.length; ++i) { + const test = this._tests[i]; + if (test.name === 'max') { + value = value.slice(0, test.arg); + break; + } + } + } + } + + return { + value, + errors: (typeof value === 'string') ? null : this.createError('string.base', { value }, state, options) + }; + } + + insensitive() { + + if (this._flags.insensitive) { + return this; + } + + const obj = this.clone(); + obj._flags.insensitive = true; + return obj; + } + + creditCard() { + + return this._test('creditCard', undefined, function (value, state, options) { + + let i = value.length; + let sum = 0; + let mul = 1; + + while (i--) { + const char = value.charAt(i) * mul; + sum = sum + (char - (char > 9) * 9); + mul = mul ^ 3; + } + + const check = (sum % 10 === 0) && (sum > 0); + return check ? value : this.createError('string.creditCard', { value }, state, options); + }); + } + + regex(pattern, patternOptions) { + + Hoek.assert(pattern instanceof RegExp, 'pattern must be a RegExp'); + + const patternObject = { + pattern: new RegExp(pattern.source, pattern.ignoreCase ? 'i' : undefined) // Future version should break this and forbid unsupported regex flags + }; + + if (typeof patternOptions === 'string') { + patternObject.name = patternOptions; + } + else if (typeof patternOptions === 'object') { + patternObject.invert = !!patternOptions.invert; + + if (patternOptions.name) { + patternObject.name = patternOptions.name; + } + } + + const errorCode = ['string.regex', patternObject.invert ? '.invert' : '', patternObject.name ? '.name' : '.base'].join(''); + + return this._test('regex', patternObject, function (value, state, options) { + + const patternMatch = patternObject.pattern.test(value); + + if (patternMatch ^ patternObject.invert) { + return value; + } + + return this.createError(errorCode, { name: patternObject.name, pattern: patternObject.pattern, value }, state, options); + }); + } + + alphanum() { + + return this._test('alphanum', undefined, function (value, state, options) { + + if (/^[a-zA-Z0-9]+$/.test(value)) { + return value; + } + + return this.createError('string.alphanum', { value }, state, options); + }); + } + + token() { + + return this._test('token', undefined, function (value, state, options) { + + if (/^\w+$/.test(value)) { + return value; + } + + return this.createError('string.token', { value }, state, options); + }); + } + + email(isEmailOptions) { + + if (isEmailOptions) { + Hoek.assert(typeof isEmailOptions === 'object', 'email options must be an object'); + Hoek.assert(typeof isEmailOptions.checkDNS === 'undefined', 'checkDNS option is not supported'); + Hoek.assert(typeof isEmailOptions.tldWhitelist === 'undefined' || + typeof isEmailOptions.tldWhitelist === 'object', 'tldWhitelist must be an array or object'); + Hoek.assert( + typeof isEmailOptions.minDomainAtoms === 'undefined' || + Number.isSafeInteger(isEmailOptions.minDomainAtoms) && + isEmailOptions.minDomainAtoms > 0, + 'minDomainAtoms must be a positive integer' + ); + Hoek.assert( + typeof isEmailOptions.errorLevel === 'undefined' || + typeof isEmailOptions.errorLevel === 'boolean' || + ( + Number.isSafeInteger(isEmailOptions.errorLevel) && + isEmailOptions.errorLevel >= 0 + ), + 'errorLevel must be a non-negative integer or boolean' + ); + } + + return this._test('email', isEmailOptions, function (value, state, options) { + + Isemail = Isemail || require('isemail'); + + try { + const result = Isemail.validate(value, isEmailOptions); + if (result === true || result === 0) { + return value; + } + } + catch (e) { } + + return this.createError('string.email', { value }, state, options); + }); + } + + ip(ipOptions) { + + let regex = internals.ipRegex; + ipOptions = ipOptions || {}; + Hoek.assert(typeof ipOptions === 'object', 'options must be an object'); + + if (ipOptions.cidr) { + Hoek.assert(typeof ipOptions.cidr === 'string', 'cidr must be a string'); + ipOptions.cidr = ipOptions.cidr.toLowerCase(); + + Hoek.assert(Hoek.contain(internals.cidrPresences, ipOptions.cidr), 'cidr must be one of ' + internals.cidrPresences.join(', ')); + + // If we only received a `cidr` setting, create a regex for it. But we don't need to create one if `cidr` is "optional" since that is the default + if (!ipOptions.version && ipOptions.cidr !== 'optional') { + regex = Ip.createIpRegex(['ipv4', 'ipv6', 'ipvfuture'], ipOptions.cidr); + } + } + else { + + // Set our default cidr strategy + ipOptions.cidr = 'optional'; + } + + let versions; + if (ipOptions.version) { + if (!Array.isArray(ipOptions.version)) { + ipOptions.version = [ipOptions.version]; + } + + Hoek.assert(ipOptions.version.length >= 1, 'version must have at least 1 version specified'); + + versions = []; + for (let i = 0; i < ipOptions.version.length; ++i) { + let version = ipOptions.version[i]; + Hoek.assert(typeof version === 'string', 'version at position ' + i + ' must be a string'); + version = version.toLowerCase(); + Hoek.assert(Ip.versions[version], 'version at position ' + i + ' must be one of ' + Object.keys(Ip.versions).join(', ')); + versions.push(version); + } + + // Make sure we have a set of versions + versions = Hoek.unique(versions); + + regex = Ip.createIpRegex(versions, ipOptions.cidr); + } + + return this._test('ip', ipOptions, function (value, state, options) { + + if (regex.test(value)) { + return value; + } + + if (versions) { + return this.createError('string.ipVersion', { value, cidr: ipOptions.cidr, version: versions }, state, options); + } + + return this.createError('string.ip', { value, cidr: ipOptions.cidr }, state, options); + }); + } + + uri(uriOptions) { + + let customScheme = ''; + let allowRelative = false; + let relativeOnly = false; + let regex = internals.uriRegex; + + if (uriOptions) { + Hoek.assert(typeof uriOptions === 'object', 'options must be an object'); + + if (uriOptions.scheme) { + Hoek.assert(uriOptions.scheme instanceof RegExp || typeof uriOptions.scheme === 'string' || Array.isArray(uriOptions.scheme), 'scheme must be a RegExp, String, or Array'); + + if (!Array.isArray(uriOptions.scheme)) { + uriOptions.scheme = [uriOptions.scheme]; + } + + Hoek.assert(uriOptions.scheme.length >= 1, 'scheme must have at least 1 scheme specified'); + + // Flatten the array into a string to be used to match the schemes. + for (let i = 0; i < uriOptions.scheme.length; ++i) { + const scheme = uriOptions.scheme[i]; + Hoek.assert(scheme instanceof RegExp || typeof scheme === 'string', 'scheme at position ' + i + ' must be a RegExp or String'); + + // Add OR separators if a value already exists + customScheme = customScheme + (customScheme ? '|' : ''); + + // If someone wants to match HTTP or HTTPS for example then we need to support both RegExp and String so we don't escape their pattern unknowingly. + if (scheme instanceof RegExp) { + customScheme = customScheme + scheme.source; + } + else { + Hoek.assert(/[a-zA-Z][a-zA-Z0-9+-\.]*/.test(scheme), 'scheme at position ' + i + ' must be a valid scheme'); + customScheme = customScheme + Hoek.escapeRegex(scheme); + } + } + } + + if (uriOptions.allowRelative) { + allowRelative = true; + } + + if (uriOptions.relativeOnly) { + relativeOnly = true; + } + } + + if (customScheme || allowRelative || relativeOnly) { + regex = Uri.createUriRegex(customScheme, allowRelative, relativeOnly); + } + + return this._test('uri', uriOptions, function (value, state, options) { + + if (regex.test(value)) { + return value; + } + + if (relativeOnly) { + return this.createError('string.uriRelativeOnly', { value }, state, options); + } + + if (customScheme) { + return this.createError('string.uriCustomScheme', { scheme: customScheme, value }, state, options); + } + + return this.createError('string.uri', { value }, state, options); + }); + } + + isoDate() { + + return this._test('isoDate', undefined, function (value, state, options) { + + if (JoiDate._isIsoDate(value)) { + if (!options.convert) { + return value; + } + + const d = new Date(value); + if (!isNaN(d.getTime())) { + return d.toISOString(); + } + } + + return this.createError('string.isoDate', { value }, state, options); + }); + } + + guid(guidOptions) { + + let versionNumbers = ''; + + if (guidOptions && guidOptions.version) { + if (!Array.isArray(guidOptions.version)) { + guidOptions.version = [guidOptions.version]; + } + + Hoek.assert(guidOptions.version.length >= 1, 'version must have at least 1 valid version specified'); + const versions = new Set(); + + for (let i = 0; i < guidOptions.version.length; ++i) { + let version = guidOptions.version[i]; + Hoek.assert(typeof version === 'string', 'version at position ' + i + ' must be a string'); + version = version.toLowerCase(); + const versionNumber = internals.guidVersions[version]; + Hoek.assert(versionNumber, 'version at position ' + i + ' must be one of ' + Object.keys(internals.guidVersions).join(', ')); + Hoek.assert(!(versions.has(versionNumber)), 'version at position ' + i + ' must not be a duplicate.'); + + versionNumbers += versionNumber; + versions.add(versionNumber); + } + } + + const guidRegex = new RegExp(`^([\\[{\\(]?)[0-9A-F]{8}([:-]?)[0-9A-F]{4}\\2?[${versionNumbers || '0-9A-F'}][0-9A-F]{3}\\2?[${versionNumbers ? '89AB' : '0-9A-F'}][0-9A-F]{3}\\2?[0-9A-F]{12}([\\]}\\)]?)$`, 'i'); + + return this._test('guid', guidOptions, function (value, state, options) { + + const results = guidRegex.exec(value); + + if (!results) { + return this.createError('string.guid', { value }, state, options); + } + + // Matching braces + if (internals.guidBrackets[results[1]] !== results[results.length - 1]) { + return this.createError('string.guid', { value }, state, options); + } + + return value; + }); + } + + hex() { + + const regex = /^[a-f0-9]+$/i; + + return this._test('hex', regex, function (value, state, options) { + + if (regex.test(value)) { + return value; + } + + return this.createError('string.hex', { value }, state, options); + }); + } + + base64(base64Options) { + + base64Options = base64Options || {}; + + // Validation. + Hoek.assert(typeof base64Options === 'object', 'base64 options must be an object'); + Hoek.assert(typeof base64Options.paddingRequired === 'undefined' || typeof base64Options.paddingRequired === 'boolean', + 'paddingRequired must be boolean'); + + // Determine if padding is required. + const paddingRequired = base64Options.paddingRequired === false ? + base64Options.paddingRequired + : base64Options.paddingRequired || true; + + // Set validation based on preference. + const regex = paddingRequired ? + // Padding is required. + /^(?:[A-Za-z0-9+\/]{4})*(?:[A-Za-z0-9+\/]{2}==|[A-Za-z0-9+\/]{3}=)?$/ + // Padding is optional. + : /^(?:[A-Za-z0-9+\/]{4})*(?:[A-Za-z0-9+\/]{2}(==)?|[A-Za-z0-9+\/]{3}=?)?$/; + + return this._test('base64', regex, function (value, state, options) { + + if (regex.test(value)) { + return value; + } + + return this.createError('string.base64', { value }, state, options); + }); + } + + hostname() { + + const regex = /^(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9])\.)*([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9\-]*[A-Za-z0-9])$/; + + return this._test('hostname', undefined, function (value, state, options) { + + if ((value.length <= 255 && regex.test(value)) || + Net.isIPv6(value)) { + + return value; + } + + return this.createError('string.hostname', { value }, state, options); + }); + } + + normalize(form) { + + form = form || 'NFC'; + Hoek.assert(Hoek.contain(internals.normalizationForms, form), 'normalization form must be one of ' + internals.normalizationForms.join(', ')); + + const obj = this._test('normalize', form, function (value, state, options) { + + if (options.convert || + value === value.normalize(form)) { + + return value; + } + + return this.createError('string.normalize', { value, form }, state, options); + }); + + obj._flags.normalize = form; + return obj; + } + + lowercase() { + + const obj = this._test('lowercase', undefined, function (value, state, options) { + + if (options.convert || + value === value.toLocaleLowerCase()) { + + return value; + } + + return this.createError('string.lowercase', { value }, state, options); + }); + + obj._flags.case = 'lower'; + return obj; + } + + uppercase() { + + const obj = this._test('uppercase', undefined, function (value, state, options) { + + if (options.convert || + value === value.toLocaleUpperCase()) { + + return value; + } + + return this.createError('string.uppercase', { value }, state, options); + }); + + obj._flags.case = 'upper'; + return obj; + } + + trim() { + + const obj = this._test('trim', undefined, function (value, state, options) { + + if (options.convert || + value === value.trim()) { + + return value; + } + + return this.createError('string.trim', { value }, state, options); + }); + + obj._flags.trim = true; + return obj; + } + + replace(pattern, replacement) { + + if (typeof pattern === 'string') { + pattern = new RegExp(Hoek.escapeRegex(pattern), 'g'); + } + + Hoek.assert(pattern instanceof RegExp, 'pattern must be a RegExp'); + Hoek.assert(typeof replacement === 'string', 'replacement must be a String'); + + // This can not be considere a test like trim, we can't "reject" + // anything from this rule, so just clone the current object + const obj = this.clone(); + + if (!obj._inner.replacements) { + obj._inner.replacements = []; + } + + obj._inner.replacements.push({ + pattern, + replacement + }); + + return obj; + } + + truncate(enabled) { + + const value = enabled === undefined ? true : !!enabled; + + if (this._flags.truncate === value) { + return this; + } + + const obj = this.clone(); + obj._flags.truncate = value; + return obj; + } + +}; + +internals.compare = function (type, compare) { + + return function (limit, encoding) { + + const isRef = Ref.isRef(limit); + + Hoek.assert((Number.isSafeInteger(limit) && limit >= 0) || isRef, 'limit must be a positive integer or reference'); + Hoek.assert(!encoding || Buffer.isEncoding(encoding), 'Invalid encoding:', encoding); + + return this._test(type, limit, function (value, state, options) { + + let compareTo; + if (isRef) { + compareTo = limit(state.reference || state.parent, options); + + if (!Number.isSafeInteger(compareTo)) { + return this.createError('string.ref', { ref: limit.key }, state, options); + } + } + else { + compareTo = limit; + } + + if (compare(value, compareTo, encoding)) { + return value; + } + + return this.createError('string.' + type, { limit: compareTo, value, encoding }, state, options); + }); + }; +}; + + +internals.String.prototype.min = internals.compare('min', (value, limit, encoding) => { + + const length = encoding ? Buffer.byteLength(value, encoding) : value.length; + return length >= limit; +}); + + +internals.String.prototype.max = internals.compare('max', (value, limit, encoding) => { + + const length = encoding ? Buffer.byteLength(value, encoding) : value.length; + return length <= limit; +}); + + +internals.String.prototype.length = internals.compare('length', (value, limit, encoding) => { + + const length = encoding ? Buffer.byteLength(value, encoding) : value.length; + return length === limit; +}); + +// Aliases + +internals.String.prototype.uuid = internals.String.prototype.guid; + +module.exports = new internals.String(); diff --git a/node_modules/joi/lib/types/string/ip.js b/node_modules/joi/lib/types/string/ip.js new file mode 100644 index 00000000..8478c127 --- /dev/null +++ b/node_modules/joi/lib/types/string/ip.js @@ -0,0 +1,54 @@ +'use strict'; + +// Load modules + +const RFC3986 = require('./rfc3986'); + + +// Declare internals + +const internals = { + Ip: { + cidrs: { + ipv4: { + required: '\\/(?:' + RFC3986.ipv4Cidr + ')', + optional: '(?:\\/(?:' + RFC3986.ipv4Cidr + '))?', + forbidden: '' + }, + ipv6: { + required: '\\/' + RFC3986.ipv6Cidr, + optional: '(?:\\/' + RFC3986.ipv6Cidr + ')?', + forbidden: '' + }, + ipvfuture: { + required: '\\/' + RFC3986.ipv6Cidr, + optional: '(?:\\/' + RFC3986.ipv6Cidr + ')?', + forbidden: '' + } + }, + versions: { + ipv4: RFC3986.IPv4address, + ipv6: RFC3986.IPv6address, + ipvfuture: RFC3986.IPvFuture + } + } +}; + + +internals.Ip.createIpRegex = function (versions, cidr) { + + let regex; + for (let i = 0; i < versions.length; ++i) { + const version = versions[i]; + if (!regex) { + regex = '^(?:' + internals.Ip.versions[version] + internals.Ip.cidrs[version][cidr]; + } + else { + regex += '|' + internals.Ip.versions[version] + internals.Ip.cidrs[version][cidr]; + } + } + + return new RegExp(regex + ')$'); +}; + +module.exports = internals.Ip; diff --git a/node_modules/joi/lib/types/string/rfc3986.js b/node_modules/joi/lib/types/string/rfc3986.js new file mode 100644 index 00000000..106e457a --- /dev/null +++ b/node_modules/joi/lib/types/string/rfc3986.js @@ -0,0 +1,209 @@ +'use strict'; + +// Load modules + + +// Delcare internals + +const internals = { + rfc3986: {} +}; + + +internals.generate = function () { + + /** + * elements separated by forward slash ("/") are alternatives. + */ + const or = '|'; + + /** + * Rule to support zero-padded addresses. + */ + const zeroPad = '0?'; + + /** + * DIGIT = %x30-39 ; 0-9 + */ + const digit = '0-9'; + const digitOnly = '[' + digit + ']'; + + /** + * ALPHA = %x41-5A / %x61-7A ; A-Z / a-z + */ + const alpha = 'a-zA-Z'; + const alphaOnly = '[' + alpha + ']'; + + /** + * IPv4 + * cidr = DIGIT ; 0-9 + * / %x31-32 DIGIT ; 10-29 + * / "3" %x30-32 ; 30-32 + */ + internals.rfc3986.ipv4Cidr = digitOnly + or + '[1-2]' + digitOnly + or + '3' + '[0-2]'; + + /** + * IPv6 + * cidr = DIGIT ; 0-9 + * / %x31-39 DIGIT ; 10-99 + * / "1" %x0-1 DIGIT ; 100-119 + * / "12" %x0-8 ; 120-128 + */ + internals.rfc3986.ipv6Cidr = '(?:' + zeroPad + zeroPad + digitOnly + or + zeroPad + '[1-9]' + digitOnly + or + '1' + '[01]' + digitOnly + or + '12[0-8])'; + + /** + * HEXDIG = DIGIT / "A" / "B" / "C" / "D" / "E" / "F" + */ + const hexDigit = digit + 'A-Fa-f'; + const hexDigitOnly = '[' + hexDigit + ']'; + + /** + * unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~" + */ + const unreserved = alpha + digit + '-\\._~'; + + /** + * sub-delims = "!" / "$" / "&" / "'" / "(" / ")" / "*" / "+" / "," / ";" / "=" + */ + const subDelims = '!\\$&\'\\(\\)\\*\\+,;='; + + /** + * pct-encoded = "%" HEXDIG HEXDIG + */ + const pctEncoded = '%' + hexDigit; + + /** + * pchar = unreserved / pct-encoded / sub-delims / ":" / "@" + */ + const pchar = unreserved + pctEncoded + subDelims + ':@'; + const pcharOnly = '[' + pchar + ']'; + + /** + * dec-octet = DIGIT ; 0-9 + * / %x31-39 DIGIT ; 10-99 + * / "1" 2DIGIT ; 100-199 + * / "2" %x30-34 DIGIT ; 200-249 + * / "25" %x30-35 ; 250-255 + */ + const decOctect = '(?:' + zeroPad + zeroPad + digitOnly + or + zeroPad + '[1-9]' + digitOnly + or + '1' + digitOnly + digitOnly + or + '2' + '[0-4]' + digitOnly + or + '25' + '[0-5])'; + + /** + * IPv4address = dec-octet "." dec-octet "." dec-octet "." dec-octet + */ + internals.rfc3986.IPv4address = '(?:' + decOctect + '\\.){3}' + decOctect; + + /** + * h16 = 1*4HEXDIG ; 16 bits of address represented in hexadecimal + * ls32 = ( h16 ":" h16 ) / IPv4address ; least-significant 32 bits of address + * IPv6address = 6( h16 ":" ) ls32 + * / "::" 5( h16 ":" ) ls32 + * / [ h16 ] "::" 4( h16 ":" ) ls32 + * / [ *1( h16 ":" ) h16 ] "::" 3( h16 ":" ) ls32 + * / [ *2( h16 ":" ) h16 ] "::" 2( h16 ":" ) ls32 + * / [ *3( h16 ":" ) h16 ] "::" h16 ":" ls32 + * / [ *4( h16 ":" ) h16 ] "::" ls32 + * / [ *5( h16 ":" ) h16 ] "::" h16 + * / [ *6( h16 ":" ) h16 ] "::" + */ + const h16 = hexDigitOnly + '{1,4}'; + const ls32 = '(?:' + h16 + ':' + h16 + '|' + internals.rfc3986.IPv4address + ')'; + const IPv6SixHex = '(?:' + h16 + ':){6}' + ls32; + const IPv6FiveHex = '::(?:' + h16 + ':){5}' + ls32; + const IPv6FourHex = '(?:' + h16 + ')?::(?:' + h16 + ':){4}' + ls32; + const IPv6ThreeHex = '(?:(?:' + h16 + ':){0,1}' + h16 + ')?::(?:' + h16 + ':){3}' + ls32; + const IPv6TwoHex = '(?:(?:' + h16 + ':){0,2}' + h16 + ')?::(?:' + h16 + ':){2}' + ls32; + const IPv6OneHex = '(?:(?:' + h16 + ':){0,3}' + h16 + ')?::' + h16 + ':' + ls32; + const IPv6NoneHex = '(?:(?:' + h16 + ':){0,4}' + h16 + ')?::' + ls32; + const IPv6NoneHex2 = '(?:(?:' + h16 + ':){0,5}' + h16 + ')?::' + h16; + const IPv6NoneHex3 = '(?:(?:' + h16 + ':){0,6}' + h16 + ')?::'; + internals.rfc3986.IPv6address = '(?:' + IPv6SixHex + or + IPv6FiveHex + or + IPv6FourHex + or + IPv6ThreeHex + or + IPv6TwoHex + or + IPv6OneHex + or + IPv6NoneHex + or + IPv6NoneHex2 + or + IPv6NoneHex3 + ')'; + + /** + * IPvFuture = "v" 1*HEXDIG "." 1*( unreserved / sub-delims / ":" ) + */ + internals.rfc3986.IPvFuture = 'v' + hexDigitOnly + '+\\.[' + unreserved + subDelims + ':]+'; + + /** + * scheme = ALPHA *( ALPHA / DIGIT / "+" / "-" / "." ) + */ + internals.rfc3986.scheme = alphaOnly + '[' + alpha + digit + '+-\\.]*'; + + /** + * userinfo = *( unreserved / pct-encoded / sub-delims / ":" ) + */ + const userinfo = '[' + unreserved + pctEncoded + subDelims + ':]*'; + + /** + * IP-literal = "[" ( IPv6address / IPvFuture ) "]" + */ + const IPLiteral = '\\[(?:' + internals.rfc3986.IPv6address + or + internals.rfc3986.IPvFuture + ')\\]'; + + /** + * reg-name = *( unreserved / pct-encoded / sub-delims ) + */ + const regName = '[' + unreserved + pctEncoded + subDelims + ']{0,255}'; + + /** + * host = IP-literal / IPv4address / reg-name + */ + const host = '(?:' + IPLiteral + or + internals.rfc3986.IPv4address + or + regName + ')'; + + /** + * port = *DIGIT + */ + const port = digitOnly + '*'; + + /** + * authority = [ userinfo "@" ] host [ ":" port ] + */ + const authority = '(?:' + userinfo + '@)?' + host + '(?::' + port + ')?'; + + /** + * segment = *pchar + * segment-nz = 1*pchar + * path = path-abempty ; begins with "/" or is empty + * / path-absolute ; begins with "/" but not "//" + * / path-noscheme ; begins with a non-colon segment + * / path-rootless ; begins with a segment + * / path-empty ; zero characters + * path-abempty = *( "/" segment ) + * path-absolute = "/" [ segment-nz *( "/" segment ) ] + * path-rootless = segment-nz *( "/" segment ) + */ + const segment = pcharOnly + '*'; + const segmentNz = pcharOnly + '+'; + const segmentNzNc = '[' + unreserved + pctEncoded + subDelims + '@' + ']+'; + const pathEmpty = ''; + const pathAbEmpty = '(?:\\/' + segment + ')*'; + const pathAbsolute = '\\/(?:' + segmentNz + pathAbEmpty + ')?'; + const pathRootless = segmentNz + pathAbEmpty; + const pathNoScheme = segmentNzNc + pathAbEmpty; + + /** + * hier-part = "//" authority path + */ + internals.rfc3986.hierPart = '(?:' + '(?:\\/\\/' + authority + pathAbEmpty + ')' + or + pathAbsolute + or + pathRootless + ')'; + + /** + * relative-part = "//" authority path-abempty + * / path-absolute + * / path-noscheme + * / path-empty + */ + internals.rfc3986.relativeRef = '(?:' + '(?:\\/\\/' + authority + pathAbEmpty + ')' + or + pathAbsolute + or + pathNoScheme + or + pathEmpty + ')'; + + /** + * query = *( pchar / "/" / "?" ) + */ + internals.rfc3986.query = '[' + pchar + '\\/\\?]*(?=#|$)'; //Finish matching either at the fragment part or end of the line. + + /** + * fragment = *( pchar / "/" / "?" ) + */ + internals.rfc3986.fragment = '[' + pchar + '\\/\\?]*'; +}; + + +internals.generate(); + +module.exports = internals.rfc3986; diff --git a/node_modules/joi/lib/types/string/uri.js b/node_modules/joi/lib/types/string/uri.js new file mode 100644 index 00000000..f5622c50 --- /dev/null +++ b/node_modules/joi/lib/types/string/uri.js @@ -0,0 +1,46 @@ +'use strict'; + +// Load Modules + +const RFC3986 = require('./rfc3986'); + + +// Declare internals + +const internals = { + Uri: { + createUriRegex: function (optionalScheme, allowRelative, relativeOnly) { + + let scheme = RFC3986.scheme; + let prefix; + + if (relativeOnly) { + prefix = '(?:' + RFC3986.relativeRef + ')'; + } + else { + // If we were passed a scheme, use it instead of the generic one + if (optionalScheme) { + + // Have to put this in a non-capturing group to handle the OR statements + scheme = '(?:' + optionalScheme + ')'; + } + + const withScheme = '(?:' + scheme + ':' + RFC3986.hierPart + ')'; + + prefix = allowRelative ? '(?:' + withScheme + '|' + RFC3986.relativeRef + ')' : withScheme; + } + + /** + * URI = scheme ":" hier-part [ "?" query ] [ "#" fragment ] + * + * OR + * + * relative-ref = relative-part [ "?" query ] [ "#" fragment ] + */ + return new RegExp('^' + prefix + '(?:\\?' + RFC3986.query + ')?' + '(?:#' + RFC3986.fragment + ')?$'); + } + } +}; + + +module.exports = internals.Uri; diff --git a/node_modules/joi/package.json b/node_modules/joi/package.json new file mode 100644 index 00000000..89977673 --- /dev/null +++ b/node_modules/joi/package.json @@ -0,0 +1,33 @@ +{ + "name": "joi", + "description": "Object schema validation", + "version": "11.4.0", + "homepage": "https://github.com/hapijs/joi", + "repository": "git://github.com/hapijs/joi", + "main": "lib/index.js", + "keywords": [ + "hapi", + "schema", + "validation" + ], + "engines": { + "node": ">=4.0.0" + }, + "dependencies": { + "hoek": "4.x.x", + "isemail": "3.x.x", + "topo": "2.x.x" + }, + "devDependencies": { + "hapitoc": "1.x.x", + "lab": "14.x.x" + }, + "scripts": { + "test": "lab -t 100 -a code -L", + "test-debug": "lab -a code", + "test-cov-html": "lab -r html -o coverage.html -a code", + "toc": "hapitoc", + "version": "npm run toc && git add API.md README.md" + }, + "license": "BSD-3-Clause" +} -- cgit v1.2.3