[
  {
    "path": ".github/workflows/ci-module.yml",
    "content": "name: ci\n\non:\n  push:\n    branches:\n      - master\n      - v17\n  pull_request:\n  workflow_dispatch:\n\npermissions:\n  contents: read #  for actions/checkout\n\njobs:\n  test:\n    uses: hapijs/.github/.github/workflows/ci-module.yml@min-node-20-hapi-21\n"
  },
  {
    "path": ".gitignore",
    "content": "**/node_modules\n**/package-lock.json\n\ncoverage.*\n\n**/.DS_Store\n**/._*\n\n**/*.pem\n\n**/.vs\n**/.vscode\n**/.idea\n\nsandbox.js\n\ndist\n"
  },
  {
    "path": ".npmrc",
    "content": "package-lock=false\n"
  },
  {
    "path": "API.md",
    "content": "\n### Introduction\n\n**joi** lets you describe your data using a simple, intuitive, and readable language.\n\n#### Example\n\n```js\nconst Joi = require('joi');\n\nconst schema = Joi.object({\n    username: Joi.string()\n        .alphanum()\n        .min(3)\n        .max(30)\n        .required(),\n\n    password: Joi.string()\n        .pattern(new RegExp('^[a-zA-Z0-9]{3,30}$')),\n\n    repeat_password: Joi.ref('password'),\n\n    access_token: [\n        Joi.string(),\n        Joi.number()\n    ],\n\n    birth_year: Joi.number()\n        .integer()\n        .min(1900)\n        .max(2013),\n\n    email: Joi.string()\n        .email({ minDomainSegments: 2, tlds: { allow: ['com', 'net'] } })\n})\n    .with('username', 'birth_year')\n    .xor('password', 'access_token')\n    .with('password', 'repeat_password');\n\n\nschema.validate({ username: 'abc', birth_year: 1994 });\n// -> { value: { username: 'abc', birth_year: 1994 } }\n\nschema.validate({});\n// -> { value: {}, error: '\"username\" is required' }\n\n// Also -\n\ntry {\n    const value = await schema.validateAsync({ username: 'abc', birth_year: 1994 });\n}\ncatch (err) { }\n```\n\nThe above schema defines the following constraints:\n* `username`\n    * a required string\n    * must contain only alphanumeric characters\n    * at least 3 characters long but no more than 30\n    * must be accompanied by `birth_year`\n* `password`\n    * an optional string\n    * must satisfy the custom regex pattern\n    * cannot appear together with `access_token`\n    * must be accompanied by `repeat_password` and equal to it\n* `access_token`\n    * an optional, unconstrained string or number\n* `birth_year`\n    * an integer between 1900 and 2013\n* `email`\n    * a valid email address string\n    * must have two domain parts e.g. `example.com`\n    * TLD must be `.com` or `.net`\n\n### General Usage\n\nUsage is a two steps process:\n\nFirst, a schema is constructed using the provided types and constraints:\n\n```js\nconst schema = Joi.object({\n    a: Joi.string()\n});\n```\n\nNote that **joi** schema objects are immutable which means every additional rule added (e.g.\n`.min(5)`) will return a new schema object.\n\nSecond, the value is validated against the defined schema:\n\n```js\nconst { error, value } = schema.validate({ a: 'a string' });\n```\n\nIf the input is valid, then the `error` will be `undefined`. If the input is invalid, `error` is assigned\na [`ValidationError`](#validationerror) object\nproviding more information.\n\nThe schema can be a plain JavaScript object where every key is assigned a **joi** type, or it can be a **joi** type directly:\n\n```js\nconst schema = Joi.string().min(10);\n```\n\nIf the schema is a **joi** type, the `schema.validate(value)` can be called directly on the type. When passing a non-type schema object,\nthe module converts it internally to an object() type equivalent to:\n\n```js\nconst schema = Joi.object().keys({\n    a: Joi.string()\n});\n```\n\nWhen validating a schema:\n\n* Values (or keys in case of objects) are optional by default.\n\n    ```js\n    Joi.string().validate(undefined); // validates fine\n    ```\n\n    To disallow this behavior, you can either set the schema as `required()`, or set `presence` to `\"required\"` when passing `options`:\n\n    ```js\n    Joi.string().required().validate(undefined);\n    // or\n    Joi.string().validate(undefined, /* options */ { presence: \"required\" });\n    ```\n\n* Strings are utf-8 encoded by default.\n* Rules are defined in an additive fashion and evaluated in order, first the inclusive rules, then the exclusive rules.\n\n### `assert(value, schema, [message], [options])`\n\nValidates a value against a schema and [throws](#errors) if validation fails where:\n- `value` - the value to validate.\n- `schema` - the validation schema. Can be a **joi** type object or a plain object where every key is assigned a **joi** type object using [`Joi.compile`](#compileschema-options) (be careful of the cost of compiling repeatedly the same schemas).\n- `message` - optional message string prefix added in front of the error message. may also be an Error object.\n- `options` - optional options object, passed in to [`any.validate`](#anyvalidatevalue-options)\n\n```js\nJoi.assert('x', Joi.number());\n```\n\n### `attempt(value, schema, [message], [options])`\n\nValidates a value against a schema, returns valid object, and [throws](#errors) if validation fails where:\n- `value` - the value to validate.\n- `schema` - the validation schema. Can be a **joi** type object or a plain object where every key is assigned a **joi** type object using [`Joi.compile`](#compileschema-options) (be careful of the cost of compiling repeatedly the same schemas).\n- `message` - optional message string prefix added in front of the error message. may also be an Error object.\n- `options` - optional options object, passed in to [`any.validate`](#anyvalidatevalue-options)\n\n```js\nJoi.attempt('x', Joi.number()); // throws error\nconst result = Joi.attempt('4', Joi.number()); // result -> 4\n```\n\n### `cache.provision([options])`\n\nProvisions a simple LRU cache for caching simple inputs (`undefined`, `null`, strings, numbers, and\nbooleans) where:\n- `options` - optional settings:\n    - `max` - number of items to store in the cache before the least used items are dropped.\n      Defaults to `1000`.\n\n### `checkPreferences(prefs)`\n\nChecks if the provided preferences are valid where:\n- `prefs` - the preferences object to validate.\n\nThrows an exception if the `prefs` object is invalid.\n\nThe method is provided to perform inputs validation for the [`any.validate()`](#anyvalidatevalue-options)\nand [`any.validateAsync()`](#anyvalidateasyncvalue-options) methods. Validation is not performed\nautomatically for performance reasons. Instead, manually validate the preferences passed once and\nreuse.\n\n### `compile(schema, [options])`\n\nConverts literal schema definition to **joi** schema object (or returns the same back if already a\n**joi** schema object) where:\n- `schema` - the schema definition to compile.\n- `options` - optional settings:\n    - `legacy` - if `true` and the provided schema is (or contains parts) using an older version of\n      **joi**, will return a compiled schema that is compatible with the older version. If `false`,\n      the schema is always compiled using the current version and if older schema components are\n      found, an error is thrown.\n\n```js\nconst definition = ['key', 5, { a: true, b: [/^a/, 'boom'] }];\nconst schema = Joi.compile(definition);\n\n// Same as:\n\nconst schema = Joi.alternatives().try(\n    Joi.string().valid('key'),\n    Joi.number().valid(5),\n    Joi.object({\n        a: Joi.boolean().valid(true),\n        b: Joi.alternatives().try(\n            Joi.string().pattern(/^a/),\n            Joi.string().valid('boom')\n        )\n    })\n);\n```\n\n### `defaults(modifier)`\n\nCreates a new **joi** instance that applies the provided modifier function to every new schemas\nwhere:\n- `modifier` - a function with signature `function(schema)` that must return a schema object.\n\n```js\nconst custom = Joi.defaults((schema) => {\n\n    switch (schema.type) {\n        case 'string':\n            return schema.allow('');\n        case 'object':\n            return schema.min(1);\n        default:\n            return schema;\n    }\n});\n\nconst schema = custom.object();   // Returns Joi.object().min(1)\n```\n\n### `expression(template, [options])` - aliases: `x`\n\nGenerates a dynamic expression using a template string where:\n- `template` - the template string using the [template syntax](#template-syntax).\n- `options` - optional settings used when creating internal references. Supports the same options\n  as [`ref()`](#refkey-options), in addition to those options:\n  - `functions` - an object with keys being function names and values being their implementation that will be executed when used in the expression. Using the same name as a built-in function will result in a local override. Note: carefully check your arguments depending on the situation where the expression is used.\n\n#### Template syntax\n\nThe template syntax uses `{}` and `{{}}` enclosed formulas to reference values as well as perform number and string operations. Single braces `{}` leave the formula result as-is, while double braces `{{}}` HTML-escape the formula result (unless the template is used for error messages and the `errors.escapeHtml` preference flag is set to `false`).\n\nIf the formula is a single reference prefixed with `:` (e.g. `{:#ref}` or `{{:#ref}}`), its values will be wrapped according to the [`wrap`](#anyvalidatevalue-options) validation setting. The `#label` variable is always wrapped according to the `wrap` setting.\n\nThe formula uses a simple mathematical syntax such as `a + b * 2` where the named formula variables are references. Most references can be used as-is but some can create ambiguity with the formula syntax and must be enclosed in `[]` braces (e.g. `[.]`).\n\nThe formulas can only operate on `null`, booleans, numbers, and strings. If any operation involves a string, all other numbers will be casted to strings (as the internal implementation uses simple JavaScript operators). The supported operators are: `^`, `*`, `/`, `%`, `+`, `-`, `<`, `<=`, `>`, `>=`, `==`, `!=`, `&&`, `||`, and `??` (in this order of precedence).\n\nThe reference names can have one of the following prefixes:\n- `#` - indicates the variable references a local context value. For example, in errors this is the error context, while in rename operations, it is the regular expression matching groups.\n- `$` - indicates the variable references a global context value from the `context` preference object provided as an option to the validation function or set using [`any.prefs()`](#anyprefsoptions--aliases-preferences-options).\n- any other variable references a key within the current value being validated.\n\nThe formula syntax also supports built-in functions:\n- `if(condition, then, otherwise)` - returns `then` when `condition` is truthy, otherwise `otherwise`.\n- `length(item)` - return the length of an array or string, the number of keys of an object, otherwise `null`.\n- `msg(code)` - embeds another error code message.\n- `number(value)` - cast value to a number.\n\nAnd the following constants:\n- `null`\n- `true`\n- `false`\n\n### `extend(...extensions)`\n\nCreates a new customized instance of the **joi** module where:\n- `extensions` - the extensions configurations as described in [Extensions](#extensions).\n\nNote that the original **joi** module is not modified by this.\n\n### `in(ref, [options])`\n\nCreates a [reference](#refkey-options) that when resolved, is used as an array of values to match against the rule, where:\n- `ref` - same as [`Joi.ref()`](#refkey-options).\n- `options` - same as [`Joi.ref()`](#refkey-options).\n\nCan only be used in rules that support in-references.\n\n```js\nconst schema = Joi.object({\n    a: Joi.array().items(Joi.number()),\n    b: Joi.number().valid(Joi.in('a'))\n});\n```\n\n### `isError(err)`\n\nChecks whether or not the provided argument is a validation error.\n\n```js\nJoi.isError(new Error()); // returns false\n```\n\n### `isExpression(expression)`\n\nChecks whether or not the provided argument is an expression.\n\n```js\nconst expression = Joi.x('{a}');\nJoi.isExpression(expression); // returns true\n```\n\n### `isRef(ref)`\n\nChecks whether or not the provided argument is a reference. Useful if you want to post-process error messages.\n\n```js\nconst ref = Joi.ref('a');\nJoi.isRef(ref); // returns true\n```\n\n### `isSchema(schema, [options])`\n\nChecks whether or not the provided argument is a **joi** schema where:\n- `schema` - the value being checked.\n- `options` - optional settings:\n    - `legacy` - if `true`, will identify schemas from older versions of joi, otherwise will throw an error. Defaults to `false`.\n\n```js\nconst schema = Joi.any();\nJoi.isSchema(schema); // returns true\n```\n\n### `override`\n\nA special value used with `any.allow()`, `any.invalid()`, and `any.valid()` as the first value to reset any previously set values.\n\n```js\nJoi.valid(1).valid(Joi.override, 2);\n\n// Same as:\n\nJoi.valid(2);\n\n// Whereas:\n\nJoi.valid(1).valid(2);\n\n// Is the same as:\n\nJoi.valid(1, 2);\n```\n\n### `ref(key, [options])`\n\nGenerates a reference to the value of the named key. References are resolved at validation time and in order of dependency so that if one key validation depends on another, the dependent key is validated second after the reference is validated.\n\nReferences support the following arguments:\n- `key` - the reference target. References can point to sibling keys (`a.b`) or ancestor keys (`...a.b`) using the `.` separator. If a `key` starts with `$` is signifies a context reference which is looked up in the `context` option object. The `key` can start with one or more separator characters to indicate a [relative starting point](#Relative-references).\n- `options` - optional settings:\n    - `adjust` - a function with the signature `function(value)` where `value` is the resolved reference value and the return value is the adjusted value to use. For example `(value) => value + 5` will add 5 to the resolved value. Note that the `adjust` feature will not perform any type validation on the adjusted value and it must match the value expected by the rule it is used in. Cannot be used with `map`.\n    - `map` - an array of array pairs using the format `[[key, value], [key, value]]` used to maps the resolved reference value to another value. If the resolved value is not in the map, it is returned as-is. Cannot be used with `adjust`.\n    - `prefix` - overrides default prefix characters key string prefix. Can be set to `false` to disable all prefix parsing (treat keys as literal strings), or an object with specific overrides for:\n      - `global` - references to the globally provided `context` preference. Defaults to `'$'`.\n      - `local` - references to error-specific or rule specific context. Defaults to `'#'`.\n      - `root` - references to the root value being validated. Defaults to `'/'`.\n    - `separator` - overrides the default `.` hierarchy separator. Set to `false` to treat the `key` as a literal value.\n    - `ancestor` - if set to a number, sets the reference [relative starting point](#Relative-references). Cannot be combined with separator prefix characters. Defaults to the reference key prefix (or `1` if none present).\n    - `in` - creates an [in-reference](#inref-options).\n    - `iterables` - when `true`, the reference resolves by reaching into maps and sets.\n    - `render` - when `true`, the value of the reference is used instead of its name in error messages and template rendering. Defaults to `false`.\n\nNote that references can only be used where explicitly supported such as in `valid()` or `invalid()` rules. If upwards (parents) references are needed, use [`object.assert()`](#objectassertref-schema-message).\n\n```js\nconst schema = Joi.object({\n    a: Joi.ref('b.c'),\n    b: {\n        c: Joi.any()\n    },\n    c: Joi.ref('$x')\n});\n\nawait schema.validateAsync({ a: 5, b: { c: 5 } }, { context: { x: 5 } });\n```\n\n#### Relative references\n\nBy default, a reference is relative to the parent of the current value (the reference key is lookup\nup inside the parent). This means that in the schema:\n\n```js\n{\n    x: {\n        a: Joi.any(),\n        b: {\n            c: Joi.any(),\n            d: Joi.ref('c')\n        }\n    },\n    y: Joi.any()\n}\n```\n\nThe reference `Joi.ref('c')` points to `c` which is a sibling of `d` - the reference starting point\nis `d`'s parent which is `b`. This schema means that `d` must be equal to `c`.\n\nIn order to reference a parent peer, you can use a separator prefix where (using `.` as separator):\n- `.` - self\n- `..` - parent (same as no prefix)\n- `...` - grandparent\n- `....` - great-grandparent\n- etc.\n\nFor example:\n\n```js\n{\n    x: {\n        a: Joi.any(),\n        b: {\n            c: Joi.any(),\n            d: Joi.ref('c'),\n            e: Joi.ref('...a'),\n            f: Joi.ref('....y')\n        }\n    },\n    y: Joi.any()\n}\n```\n\nAnother way to specify the relative starting point is using the `ancestor` option where:\n- 0 - self\n- 1 - parent (this is the default value if no key prefix is present)\n- 2 - grandparent\n- 3 - great-grandparent\n- etc.\n\nFor example:\n\n```js\n{\n    x: {\n        a: Joi.any(),\n        b: {\n            c: Joi.any(),\n            d: Joi.ref('c', { ancestor: 1 }),\n            e: Joi.ref('a', { ancestor: 2 }),\n            f: Joi.ref('y', { ancestor: 3 })\n        }\n    },\n    y: Joi.any()\n}\n```\n\nNote that if a reference tries to reach beyond the value root, validation fails.\n\nTo specify an absolute path from the value root, use the `/` prefix:\n\n```js\n{\n    x: {\n        a: Joi.any(),\n        b: {\n            c: Joi.ref('/x.a')\n        }\n    }\n}\n```\n\n### `version`\n\nProperty showing the current version of **joi** being used.\n\n### `types()`\n\nReturns an object where each key is a plain joi schema type. Useful for creating type shortcuts\nusing deconstruction. Note that the types are already formed and do not need to be called as\nfunctions (e.g. `string`, not `string()`).\n\n```js\nconst Joi = require('joi');\nconst { object, string } = Joi.types();\n\nconst schema = object.keys({\n  property: string.min(4)\n});\n```\n\n### `any`\n\nGenerates a schema object that matches any data type.\n\n```js\nconst any = Joi.any();\nawait any.validateAsync('a');\n```\n\n#### `any.type`\n\nGets the type of the schema.\n\n```js\nconst schema = Joi.string();\n\nschema.type === 'string';   // === true\n```\n\n#### `any.allow(...values)`\n\nAllows values where:\n- `values` - one or more allowed values which can be of any type and will be matched against the validated value before applying any other rules. Supports [references](#refkey-options) and [in-references](#inref-options). If the first value is [`Joi.override`](#override), will override any previously set values.\n\nNote that this list of allowed values is in *addition* to any other permitted values.\nTo create an exclusive list of values, see [`any.valid(value)`](#anyvalidvalues---aliases-equal).\n\n```js\nconst schema = {\n    a: Joi.any().allow('a'),\n    b: Joi.any().allow('b', 'B')\n};\n```\n\n#### `any.alter(targets)`\n\nAssign target alteration options to a schema that are applied when [`any.tailor()`](#anytailortargets)\nis called where:\n- `targets` - an object where each key is a target name, and each value is a function with signature\n  `function(schema)` that returns a schema.\n\n```js\nconst schema = Joi.object({\n    key: Joi.string()\n        .alter({\n            get: (schema) => schema.required(),\n            post: (schema) => schema.forbidden()\n        })  \n});\n\nconst getSchema = schema.tailor('get');\nconst postSchema = schema.tailor('post');\n```\n\n#### `any.artifact(id)`\n\nAssigns the schema an artifact id which is included in the validation result if the rule passed validation where:\n- `id` - any value other than `undefined` which will be returned as-is in the result `artifacts` map.\n\n```js\nconst schema = {\n    a: [\n        Joi.number().max(10).artifact('under'),\n        Joi.number().min(11).artifact('over')\n    ]\n};\n```\n\n#### `any.cache([cache])`\n\nAdds caching to the schema which will attempt to cache the validation results (success and\nfailures) of incoming inputs where:\n- `cache` - an optional cache implementation compatible with the built-in cache provided by\n  [`cache.provision()`](#cacheprovisionoptions). If no `cache` is passed, a default cache\n  is provisioned by using [`cache.provision()`](#cacheprovisionoptions) internally.\n\nNote that deciding which inputs to cache is left to the cache implementation. The built-in\ncache will only store simple values such as `undefined`, `null`, strings, numbers, and booleans.\nAny changes to the schema after `any.cache()` is called will disable caching on the resulting\nschema. this means that if `.cache()` is not the last statement in a schema definition, caching\nwill be disabled.\n\nTo disable caching for an entire schema in runtime, pass the `cache` preference set to `false`.\n\nCaching ignores changes to runtime preference. This means that if you run `schema.validate()`\nonces using one set of preferences, and then again using another set (for example, changing the\nlanguage), the cached results will be based on the first set of preferences.\n\nBefore using caching, it is recommended to consider the performance gain as it will not speed up\nevery schema. Schemas using `.valid()` list will not benefit from caching.\n\nCaching will be ignored when the schema uses references outside of the value scope.\n\n##### Cache interface\n\nCustom cache implementation must implement the following interface:\n\n```js\nclass {\n    set(key, value) {}\n    get(key) { return found ? value : undefined; }\n}\n```\n\nNote that `key` and `value` can be anything including objects, array, etc. It is recommended to limit the size of the cache when validating external data in order to prevent an attacker from increasing the process memory usage by sending large amount of different data to validate.\n\n#### `any.cast(to)`\n\nCasts the validated value to the specified type where:\n- `to` - the value target type. Each **joi** schema type supports its own set of cast targets:\n    - `'map'` - supported by the `Joi.object()` type, converts the result to a `Map` object containing the object key-value pairs.\n    - `'number'` - supported by `Joi.boolean()` and `Joi.date()`, converts the result to a number. For dates, number of milliseconds since the epoch and for booleans, `0` for `false` and `1` for `true`.\n    - `'set'` - supported by the `Joi.array()` type, converts the result to a `Set` object containing the array values.\n    - `'string'` - supported by `Joi.binary()`, `Joi.boolean()`, `Joi.date()`, and `Joi.number()`, converts the result to a string.\n\n#### `any.concat(schema)`\n\nReturns a new type that is the result of adding the rules of one type to another where:\n- `schema` - a **joi** type to merge into the current schema. Can only be of the same type as the context type or `any`. If applied to an `any` type, the schema can be any other schema.\n\n```js\nconst a = Joi.string().valid('a');\nconst b = Joi.string().valid('b');\nconst ab = a.concat(b);\n```\n\n#### `any.custom(method, [description])`\n\nAdds a custom validation function to execute arbitrary code where:\n- `method` - the custom (synchronous only) validation function using signature `function(value, helpers)` where:\n    - `value` - the value being validated.\n    - `helpers` - an object with the following helpers:\n        - `schema` - the current schema.\n        - `state` - the current validation state.\n        - `prefs` - the current preferences.\n        - `original` - the original value passed into validation before any conversions.\n        - `error(code, [local], [localState])` - a method to generate error codes using a message code, optional local context and optional validation local state.\n        - `message(messages, [local])` - a method to generate an error with an internal `'custom'` error code and the provided messages object to use as override. Note that this is much slower than using the preferences `messages` option but is much simpler to write when performance is not important.\n        - `warn(code, [local])` - a method to add a warning using a message code and optional local context.\n\nNote: if the method fails to return a value, the value will be unset or returned as `undefined`.\n\n```js\nconst method = (value, helpers) => {\n\n    // Throw an error (will be replaced with 'any.custom' error)\n    if (value === '1') {\n        throw new Error('nope');\n    }\n\n    // Replace value with a new value\n    if (value === '2') {\n        return '3';\n    }\n\n    // Use error to return an existing error code\n    if (value === '4') {\n        return helpers.error('any.invalid');\n    }\n\n    // Override value with undefined to unset\n    if (value === '5') {\n        return undefined;\n    }\n\n    // Return the value unchanged\n    return value;\n};\n\nconst schema = Joi.string().custom(method, 'custom validation');\n```\n\nPossible validation errors: [`any.custom`](#anycustom)\n\n#### `any.default([value])`\n\nSets a default value if the original value is `undefined` where:\n- `value` - the default value. One of:\n    - a literal value (string, number, object, etc.).\n    - a [references](#refkey-options).\n    - a function which returns the default value using the signature `function(parent, helpers)` where:\n        - `parent` - a clone of the object containing the value being validated. Note that since specifying a `parent` argument performs cloning, do not declare format arguments if you are not using them.\n        - `helpers` - same as those described in [`any.custom()`](#anycustommethod_description).\n\nWhen called without any `value` on an object schema type, a default value will be automatically generated based on the default values of the object keys.\n\nNote that if `value` is an object, any changes to the object after `default()` is called will change the reference and any future assignment. Use a function when setting a dynamic value (e.g. the current time).\n\n```js\nconst generateUsername = (parent, helpers) => {\n\n  return parent.firstname.toLowerCase() + '-' + parent.lastname.toLowerCase();\n};\n\ngenerateUsername.description = 'generated username';\n\nconst schema = Joi.object({\n    username: Joi.string().default(generateUsername),\n    firstname: Joi.string(),\n    lastname: Joi.string(),\n    created: Joi.date().default(Date.now),\n    status: Joi.string().default('registered')\n});\n\nconst { value } = schema.validate({\n    firstname: 'Jane',\n    lastname: 'Doe'\n});\n\n// value.status === 'registered'\n// value.username === 'jane-doe'\n// value.created will be the time of validation\n```\n\nPossible validation errors: [`any.default`](#anydefault)\n\n#### `any.describe()`\n\nReturns an object that represents the internal configuration of the schema. Useful for debugging\nand exposing a schema's configuration to other systems, like valid values in a user interface.\n\n```js\nconst schema = Joi.any().valid('foo', 'bar');\nconsole.log(schema.describe());\n```\n\nResults in:\n\n```\n{ type: 'any',\n  flags: { only: true },\n  valids: [ 'foo', 'bar' ] }\n```\n\n#### `any.description(desc)`\n\nAnnotates the key where:\n- `desc` - the description string.\n\n```js\nconst schema = Joi.any().description('this key will match anything you give it');\n```\n\n#### `any.empty(schema)`\n\nConsiders anything that matches the schema to be empty (`undefined`). Overrides any previous calls to empty.\n- `schema` - an object, value, or **joi** schema to match or an array of objects, values, and **joi** schemas to match. An undefined schema unsets that rule.\n\n```js\nlet schema = Joi.string().empty('');\nschema.validate(''); // returns { error: null, value: undefined }\nschema = schema.empty();\nschema.validate(''); // returns { error: \"value\" is not allowed to be empty, value: '' }\n```\n\n#### `any.error(err)`\n\nOverrides the default **joi** error with a custom error if the rule fails where:\n- `err` can be:\n  - an instance of `Error` - the override error.\n  - a function with the signature `function(errors)`, where `errors` is an array of validation reports and it returns either a single `Error` or an array of validation reports.\n\nDo not use this method if you are simply trying to override the error message - use `any.message()` or `any.messages()` instead. This method is designed to override the **joi** validation error and return the exact override provided. It is useful when you want to return the result of validation directly (e.g. when using with a **hapi** server) and want to return a different HTTP error code than 400.\n\nNote that if you provide an `Error`, it will be returned as-is, unmodified and undecorated with any of the normal error properties. If validation fails and another error is found before the error override, that error will be returned and the override will be ignored (unless the `abortEarly` option has been set to `false`). If you set multiple errors on a single schema, only the last error is used.\n\n```js\nconst schema = Joi.string().error(new Error('Was REALLY expecting a string'));\nschema.validate(3);     // returns Error('Was REALLY expecting a string')\n```\n\n```js\nconst schema = Joi.object({\n    foo: Joi.number().min(0).error((errors) => new Error('\"foo\" requires a positive number'))\n});\nschema.validate({ foo: -2 });    // returns new Error('\"foo\" requires a positive number')\n```\n\n```js\nconst schema = Joi.object({\n    foo: Joi.number().min(0).error((errors) => {\n\n        return new Error('found errors with ' + errors.map((err) => `${err.local.key}(${err.local.limit}) with value ${err.local.value}`).join(' and '));\n    })\n});\nschema.validate({ foo: -2 });    // returns new Error('found errors with foo(0) with value -2')\n```\n\n#### `any.example(example, [options])`\n\nAdds examples to the schema where:\n- `example` - adds an example. Note that no validation is performed on the value.\n- `options` - optional settings:\n    - `override` - if `true`, replaces any existing examples. Defaults to `false`.\n\n```js\nconst schema = Joi.string().min(4).example('abcd');\n```\n\n#### `any.external(method, [description])`\n\nAdds an external validation rule where:\n- `method` - an async or sync function with signature `function(value, helpers)` which can either\n  return a replacement value, `undefined` to indicate no change, or throw an error, where:\n    - `value` - a clone of the object containing the value being validated.\n    - `helpers` - an object with the following helpers:\n        - `schema` - the current schema.\n        - `linked` - if the schema is a link, the schema it links to.\n        - `state` - the current validation state.\n        - `prefs` - the current preferences.\n        - `original` - the original value passed into validation before any conversions.\n        - `error(code, [local])` - a method to generate error codes using a message code and optional local context.\n        - `message(messages, [local])` - a method to generate an error with an internal `'external'` error code and the provided messages object to use as override. Note that this is much slower than using the preferences `messages` option but is much simpler to write when performance is not important.\n        - `warn(code, [local])` - a method to add a warning using a message code and optional local context.\n- `description` - optional string used to document the purpose of the method.\n\nNote that external validation rules are only called after the all other validation rules for the\nentire schema (from the value root) are checked. This means that any changes made to the value by\nthe external rules are not available to any other validation rules during the non-external\nvalidation phase.\n\nIf schema validation failed, no external validation rules are called.\n\n#### `any.extract(path)`\n\nReturns a sub-schema based on a path of object keys or schema ids where:\n- `path` - a dot `.` separated path string or a pre-split array of path keys. The keys must match\n  the sub-schema id or object key (if no id was explicitly set).\n\n```js\nconst schema = Joi.object({ foo: Joi.object({ bar: Joi.number() }) });\nconst number = schema.extract('foo.bar');\n\n//or\nconst result = schema.extract(['foo', 'bar']); //same as number\n```\n\n#### `any.failover(value)`\n\nSets a failover value if the original value fails passing validation where:\n- `value` - the failover value. `value` supports [references](#refkey-options). `value` may be\n  assigned a function which returns the default value. If `value` is specified as a function\n  that accepts a single parameter, that parameter will be a context object that can be used to\n  derive the resulting value.\n\nNote that if `value` is an object, any changes to the object after `failover()` is called will change\nthe reference and any future assignment. Use a function when setting a dynamic value (e.g. the\ncurrent time).\n\nUsing a function with a single argument performs some internal cloning which has a performance\nimpact. If you do not need access to the context, define the function without any arguments.\n\nPossible validation errors: [`any.failover`](#anyfailover)\n\n#### `any.forbidden()`\n\nMarks a key as forbidden which will not allow any value except `undefined`. Used to explicitly forbid keys.\n\n```js\nconst schema = {\n    a: Joi.any().forbidden()\n};\n```\n\nPossible validation errors: [`any.unknown`](#anyunknown)\n\n#### `any.fork(paths, adjuster)`\n\nReturns a new schema where each of the path keys listed have been modified where:\n- `paths` - an array of key strings, a single key string, or an array of arrays of pre-split\n  key strings. Key string paths use dot `.` to indicate key hierarchy.\n- `adjuster` - a function using the signature `function(schema)` which must return a modified\n  schema. For example, `(schema) => schema.required()`.\n\nThe method does not modify the original schema.\n\n#### `any.id(id)`\n\nSets a schema id for reaching into the schema via [`any.extract()`](#anyextractpath) where:\n- `id` - an alphanumeric string (plus `_`) used to identify the schema.\n\nIf no id is set, the schema id defaults to the object key it is associated with. If the schema is\nused in an array or alternatives type and no id is set, the schema is unreachable.\n\n#### `any.invalid(...values)` - aliases: `disallow`, `not`\n\nDisallows values where:\n- `values` - the forbidden values which can be of any type and will be matched against the validated value before applying any other rules. Supports [references](#refkey-options) and [in-references](#inref-options). If the first value is [`Joi.override`](#override), will override any previously set values.\n\n```js\nconst schema = {\n    a: Joi.any().invalid('a'),\n    b: Joi.any().invalid('b', 'B')\n};\n```\n\nPossible validation errors: [`any.invalid`](#anyinvalid)\n\n#### `any.isAsync()`\n\nReturns a boolean indicating whether this schema contains a rule that requires asynchronous validation.\n\n#### `any.keep()`\n\nSame as [`rule({ keep: true })`](#anyruleoptions).\n\nNote that `keep()` will terminate the current ruleset and cannot be followed by another\nrule option. Use [`rule()`](#anyruleoptions) to apply multiple rule options.\n\n#### `any.label(name)`\n\nOverrides the key name in error messages.\n- `name` - the name of the key.\n\n```js\nconst schema = {\n    first_name: Joi.string().label('First Name')\n};\n```\n\n#### `any.message(message)`\n\nSame as [`rule({ message })`](#anyruleoptions).\n\nNote that `message()` will terminate the current ruleset and cannot be followed by another\nrule option. Use [`rule()`](#anyruleoptions) to apply multiple rule options.\n\n#### `any.messages(messages)`\n\nSame as [`any.prefs({ messages })`](#anyprefsoptions---aliases-preferences-options).\n\nNote that while [`any.message()`](#anymessagemessage) applies only to the last rule or ruleset, `any.messages()` applies to the entire schema.\n\n#### `any.meta(meta)`\n\nAttaches metadata to the key where:\n- `meta` - the meta object to attach.\n\n```js\nconst schema = Joi.any().meta({ index: true });\n```\n\n#### `any.note(...notes)`\n\nAnnotates the key where:\n- `notes` - the note string or multiple notes as individual arguments.\n\n```js\nconst schema = Joi.any().note('this is special', 'this is important');\n```\n\n#### `any.only()`\n\nRequires the validated value to match of the provided `any.allow()` values. It has no effect when\ncalled together with `any.valid()` since it already sets the requirements. When used with\n`any.allow()` it converts it to an `any.valid()`.\n\n#### `any.optional()`\n\nMarks a key as optional which will allow `undefined` as values. Used to annotate the schema for readability as all keys are optional by default.\n\nNote: this does not allow a `null` value. To do that, use [`any.allow(value)`](#anyallowvalue). Or both!\n\n```js\nconst schema = Joi.any().optional();\n```\n\n#### `any.prefs(options)` - aliases: `preferences`, `options`\n\nOverrides the global `validate()` options for the current key and any sub-key where:\n- `options` - an object with the same optional keys as [`any.validate()`](#anyvalidatevalue-options).\n\n```js\nconst schema = Joi.any().prefs({ convert: false });\n```\n\n#### `any.presence(mode)`\n\nSets the presence mode for the schema where:\n- `mode` - can be one of `'optional'`, `'required'`, or `'forbidden'`\n\nSame as calling `any.optional()`, `any.required()`, or `any.forbidden()`.\n\n#### `any.raw([enabled])`\n\nOutputs the original untouched value instead of the casted value where:\n- `enabled` - if `true`, the original result is returned, otherwise the validated value. Defaults to `true`.\n\nNote that the raw value is only applied after validation and any references to the value use the\nvalidated value, not the raw value.\n\n```js\nconst timestampSchema = Joi.date().timestamp();\ntimestampSchema.validate('12376834097810'); // { error: null, value: Sat Mar 17 2362 04:28:17 GMT-0500 (CDT) }\n\nconst rawTimestampSchema = Joi.date().timestamp().raw();\nrawTimestampSchema.validate('12376834097810'); // { error: null, value: '12376834097810' }\n```\n\n#### `any.required()` - aliases: `exist`\n\nMarks a key as required which will not allow `undefined` as value. All keys are optional by default.\n\n```js\nconst schema = Joi.any().required();\n```\n\nPossible validation errors: [`any.required`](#anyrequired)\n\n#### `any.result(mode)`\n\nSet the result mode where:\n- `mode` - one of `'raw'` (same as `any.raw()`) or `'strip'` (same as `any.strip()`).\n\n#### `any.rule(options)`\n\nApplies a set of rule options to the current ruleset or last rule added where:\n- `options` - the rules to apply where:\n  - `keep` - if `true`, the rules will not be replaced by the same unique rule later. For example, `Joi.number().min(1).rule({ keep: true }).min(2)` will keep both `min()` rules instead of the later rule overriding the first. Defaults to `false`.\n  - `message` - a single message string or a messages object where each key is an error code and corresponding message string as value. The object is the same as the `messages` used as an option in [`any.validate()`](#anyvalidatevalue-options). The strings can be plain messages or a message template.\n  - `warn` - if `true`, turns any error generated by the ruleset to warnings.\n\nWhen applying rule options, the last rule (e.g. `min()`) is used unless there is an active ruleset defined (e.g. `$.min().max()`) in which case the options are applied to all the provided rules. Once `rule()` is called, the previous rules can no longer be modified and any active ruleset is terminated.\n\nRule modifications can only be applied to supported rules. Most of the `any` methods do not support rule modifications because they are implemented using schema flags (e.g. `required()`) or special internal implementation (e.g. `valid()`). In those cases, use the `any.messages()` method to override the error codes for the errors you want to customize.\n\n#### `any.ruleset` - aliases: `$`\n\nStarts a ruleset in order to apply multiple [rule options](#anyruleoptions). The set ends when\n[`rule()`](#anyruleoptions), [`keep()`](#anykeep), [`message()`](#anymessagemessage), or [`warn()`](#anywarn) is called.\n\n```js\nconst schema = Joi.number().ruleset.min(1).max(10).rule({ message: 'Number must be between 1 and 10' });\n```\n\n```js\nconst schema = Joi.number().$.min(1).max(10).rule({ message: 'Number must be between 1 and 10' });\n```\n\n#### `any.shared(schema)`\n\nRegisters a schema to be used by descendants of the current schema in named link references, where:\n- `schema` - a **joi** schema with an id.\n\n```js\n  const schema = Joi.object({\n      a: [Joi.string(), Joi.link('#x')],\n      b: Joi.link('#type.a')\n  })\n      .shared(Joi.number().id('x'))\n      .id('type');\n```\n\n#### `any.strict(isStrict)`\n\nStrict mode sets the `options.convert` options to `false` which prevent type casting for the current key and any child keys.\n- `isStrict` - whether strict mode is enabled or not. Defaults to true.\n\n```js\nconst schema = Joi.any().strict();\n```\n\n#### `any.strip([enabled])`\n\nMarks a key to be removed from a resulting object or array after validation to sanitize the output\nwhere:\n- `enabled` - if `true`, the value is stripped, otherwise the validated value is retained. Defaults\n  to `true`.\n\n```js\nconst schema = Joi.object({\n    username: Joi.string(),\n    password: Joi.string().strip()\n});\n\nschema.validate({ username: 'test', password: 'hunter2' }); // result.value = { username: 'test' }\n\nconst schema = Joi.array().items(Joi.string(), Joi.any().strip());\n\nschema.validate(['one', 'two', true, false, 1, 2]); // result.value = ['one', 'two']\n```\n\n#### `any.tag(...tags)`\n\nAnnotates the key where:\n- `tags` - the tag string or multiple tags (each as an argument).\n\n```js\nconst schema = Joi.any().tag('api', 'user');\n```\n\n#### `any.tailor(targets)`\n\nApplies any assigned target alterations to a copy of the schema that were applied via\n[`any.alter()`](#anyaltertargets) where:\n- `targets` - a single target string or array or target strings to apply.\n\n```js\nconst schema = Joi.object({\n    key: Joi.string()\n        .alter({\n            get: (schema) => schema.required(),\n            post: (schema) => schema.forbidden()\n        })  \n});\n\nconst getSchema = schema.tailor('get');\nconst postSchema = schema.tailor(['post']);\n```\n\n#### `any.unit(name)`\n\nAnnotates the key where:\n- `name` - the unit name of the value.\n\n```js\nconst schema = Joi.number().unit('milliseconds');\n```\n\n#### `any.valid(...values)` - aliases: `equal`\n\nAdds the provided values into the allowed values list and marks them as the only valid values allowed where:\n- `values` - one or more allowed values which can be of any type and will be matched against the validated value before applying any other rules. Supports [references](#refkey-options) and [in-references](#inref-options). If the first value is [`Joi.override`](#override), will override any previously set values. If the only value is [`Joi.override`](#override), will also remove the `only` flag from the schema.\n\n```js\nconst schema = {\n    a: Joi.any().valid('a'),\n    b: Joi.any().valid('b', 'B')\n};\n```\n\nPossible validation errors: [`any.only`](#anyonly)\n\n#### `any.validate(value, [options])`\n\nValidates a value using the current schema and options where:\n- `value` - the value being validated.\n- `options` - an optional object with the following optional keys:\n  - `abortEarly` - when `true`, stops validation on the first error, otherwise returns all the errors found. Defaults to `true`.\n  - `allowUnknown` - when `true`, allows object to contain unknown keys which are ignored. Defaults to `false`.\n  - `cache` - when `true`, schema caching is enabled (for schemas with explicit caching rules). Default to `true`.\n  - `context` - provides an external data set to be used in [references](#refkey-options). Can only be set as an external option to `validate()` and not using `any.prefs()`.\n  - `convert` - when `true`, attempts to cast values to the required types (e.g. a string to a number). Defaults to `true`.\n  - `dateFormat` - sets the string format used when converting dates to strings in error messages and casting. Options are:\n    - `'date'` - date string.\n    - `'iso'` - date time ISO string. This is the default.\n    - `'string'` - JS default date time string.\n    - `'time'` - time string.\n    - `'utc'` - UTC date time string.\n  - `debug` - when `true`, valid results and throw errors are decorated with a `debug` property which includes an array of the validation steps used to generate the returned result. Defaults to `false`.\n  - `errors` - error formatting settings:\n    - `escapeHtml` - when `true`, error message templates will escape special characters to HTML entities, for security purposes. Defaults to `false`.\n    - `label` - defines the value used to set the `label` context variable:\n      - `'path'` - the full path to the value being validated. This is the default value.\n      - `'key'` - the key of the value being validated.\n      - `false` - remove any label prefix from error message, including the `\"\"`.\n    - `language` - the preferred language code for error messages. The value is matched against keys at the root of the `messages` object, and then the error code as a child key of that. Can be a reference to the value, global context, or local context which is the root value passed to the validation function. Note that references to the value are usually not what you want as they move around the value structure relative to where the error happens. Instead, either use the global context, or the absolute value (e.g. `Joi.ref('/variable')`);\n    - `render` - when `false`, skips rendering error templates. Useful when error messages are generated elsewhere to save processing time. Defaults to `true`.\n    - `stack` - when `true`, the main error will possess a stack trace, otherwise it will be disabled. Defaults to `false` for performance reasons. Has no effect on platforms other than V8/node.js as it uses the [Stack trace API](https://v8.dev/docs/stack-trace-api).\n    - `wrap` - overrides the way values are wrapped (e.g. `[]` around arrays, `\"\"` around labels and variables prefixed with `:`). Each key can be set to a string with one (same character before and after the value) or two characters (first character before and second character after), or `false` to disable wrapping:\n        - `label` - the characters used around `{#label}` references. Defaults to `'\"'`.\n        - `array` - the characters used around array values. Defaults to `'[]'`.\n        - `string` - the characters used around each array string values. Defaults to `false`.\n    - `wrapArrays` - if `true`, array values in error messages are wrapped in `[]`. Defaults to `true`.\n  - `externals` - if `false`, the external rules set with [`any.external()`](#anyexternalmethod-description) are ignored, which is required to ignore any external validations in synchronous mode (or an exception is thrown). Defaults to `true`.\n  - `messages` - overrides individual error messages. Defaults to no override (`{}`). Use the `'*'` error code as a catch-all for all error codes that do not have a message provided in the override. Messages use the same rules as [templates](#template-syntax). Variables in double braces `{{var}}` are HTML escaped if the option `errors.escapeHtml` is set to `true`.\n  - `noDefaults` - when `true`, do not apply default values. Defaults to `false`.\n  - `nonEnumerables` - when `true`, inputs are shallow cloned to include non-enumerable properties. Defaults to `false`.\n  - `presence` - sets the default presence requirements. Supported modes: `'optional'`, `'required'`, and `'forbidden'`. Defaults to `'optional'`.\n  - `skipFunctions` - when `true`, ignores unknown keys with a function value. Defaults to `false`.\n  - `stripUnknown` - remove unknown elements from objects and arrays. Defaults to `false`.\n    - when an `object` :\n      - `arrays` - set to `true` to remove unknown items from arrays.\n      - `objects` - set to `true` to remove unknown keys from objects.\n    - when `true`, it is equivalent to having `{ arrays: false, objects: true }`.\n\nReturns an object with the following keys:\n- `value` - the validated and normalized value.\n- `error` - the validation errors if found.\n- `warning` - the generated warnings if any.\n- `artifacts` - a `Map` containing any passing rules' artifacts and their corresponding array of paths.\n\n```js\nconst schema = Joi.object({\n    a: Joi.number()\n});\n\nconst value = {\n    a: '123'\n};\n\nconst result = schema.validate(value);\n// result -> { value: { \"a\" : 123 } }\n```\n\n#### `any.validateAsync(value, [options])`\n\nValidates a value asynchronously using the current schema and options where:\n- `value` - the value being validated.\n- `options` - an optional object as described in [`any.validate()`](#anyvalidatevalue-options), with the following additional settings:\n    - `artifacts` - when `true`, artifacts are returned alongside the value (i.e. `{ value, artifacts }`). Defaults to `false`.\n    - `warnings` - when `true`, warnings are returned alongside the value (i.e. `{ value, warning }`). Defaults to `false`.\n\nReturns a Promise that resolves into the validated value when the value is valid. If the value is valid and the `warnings` or `debug` options are set to `true`, returns an object `{ value, warning, debug }`. If validation fails, the promise rejects with the validation error.\n\n```js\nconst schema = Joi.object({\n    a: Joi.number()\n});\n\nconst value = {\n    a: '123'\n};\n\ntry {\n  const value = await schema.validateAsync(value);\n  // value -> { \"a\" : 123 }\n}\ncatch (err) {\n}\n```\n\n#### `any.warn()`\n\nSame as [`rule({ warn: true })`](#anyruleoptions).\n\nNote that `warn()` will terminate the current ruleset and cannot be followed by another rule option. Use [`rule()`](#anyruleoptions) to apply multiple rule options.\n\n#### `any.warning(code, [context])`\n\nGenerates a warning where:\n- `code` - the warning code. Can be an existing error code or a custom code. If a custom code is used, a matching error message definition must be configured via [`any.message()`](#anymessagemessage), [`any.prefs()`](#anyprefsoptions--aliases-preferences-options), or validation `messages` option.\n- `context` - optional context object.\n\nWhen calling [`any.validateAsync()`](#anyvalidateasyncvalue-options), set the `warning` option to `true` to enable warnings. Warnings are reported separately from errors alongside the result value via the `warning` key (i.e. `{ value, warning }`). Warning are always included when calling [`any.validate()`](#anyvalidatevalue-options).\n\n```js\nconst schema = Joi.any()\n    .warning('custom.x', { w: 'world' })\n    .message({ 'custom.x': 'hello {#w}!' });\n\nconst { value, error, warning } = schema.validate('anything');\n\n// value -> 'anything';\n// error -> null\n// warning -> { message: 'hello world!', details: [...] }\n\n// or\n\ntry {\n    const { value, warning } = await schema.validateAsync('anything', { warnings: true });\n    // value -> 'anything';\n    // warning -> { message: 'hello world!', details: [...] }\n}\ncatch (err) { }\n```\n\n#### `any.when([condition], options)`\n\nAdds conditions that are evaluated during validation and modify the schema before it is applied to the value, where:\n- `condition` - a key name, [reference](#refkey-options), or a schema. If omitted, defaults to `Joi.ref('.')`.\n- `options` - an object with:\n    - `is` - the condition expressed as a **joi** schema. Anything that is not a **joi** schema will be converted using [Joi.compile](#compileschema-options). By default, the `is` condition schema allows for `undefined` values. Use `.required()` to override. For example, use `is: Joi.number().required()` to guarantee that a **joi** reference exists and is a number.\n    - `not` - the negative version of `is` (`then` and `otherwise` have reverse roles).\n    - `then` - if the condition is true, the **joi** schema to use.\n    - `otherwise` - if the condition is false, the **joi** schema to use.\n    - `switch` - an array of `{ is, then }` conditions that are evaluated against the `condition`. The last item in the array may also contain `otherwise`.\n    - `break` - stops processing all other conditions if the rule results in a `then`, `otherwise`, of `switch` match.\n\nIf `condition` is a reference:\n- if `is`, `not`, and `switch` are missing, `is` defaults to `Joi.invalid(null, false, 0, '').required()` (value must be a truthy).\n- `is` and `not` cannot be used together.\n- one of `then`, `otherwise`, or `switch` is required.\n- cannot use `is` or `then` with `switch`.\n- cannot specify `otherwise` both inside the last `switch` statement and outside.\n\nIf `condition` is a schema:\n- cannot specify `is` or `switch`.\n- one of `then` or `otherwise` is required.\n\nWhen `is`, `then`, or `otherwise` are assigned literal values, the values are compiled into override schemas (`'x'` is compiled into `Joi.valid(Joi.override, 'x')`). This means they will override any base schema the rule is applied to. To append a literal value, use the explicit `Joi.valid('x')` format.\n\nNotes:\n- an invalid combination of schema modifications (e.g. trying to add string rules or a number type) will cause validation to throw an error.\n- because the schema is constructed at validation time, it can have a significant performance impact. Run-time generated schemas are cached, but the first time of each generation will take longer than once it is cached.\n\n```js\nconst schema = {\n    a: Joi.any()\n        .valid('x')\n        .when('b', { is: Joi.exist(), then: Joi.valid('y'), otherwise: Joi.valid('z') })\n        .when('c', { is: Joi.number().min(10), then: Joi.forbidden() }),\n    b: Joi.any(),\n    c: Joi.number()\n};\n```\n\nOr with a schema:\n```js\nconst schema = Joi.object({\n    a: Joi.any().valid('x'),\n    b: Joi.any()\n})\n    .when(Joi.object({ b: Joi.exist() }).unknown(), {\n        then: Joi.object({\n            a: Joi.valid('y')\n        }),\n        otherwise: Joi.object({\n            a: Joi.valid('z')\n        })\n});\n```\n\nNote that this style is much more useful when your whole schema depends on the value of one of its\nproperty, or if you find yourself repeating the check for many keys of an object. For example to\nvalidate this logic:\n\n```js\nconst schema = Joi.object({\n    type: Joi.string()\n        .valid('A', 'B', 'C')\n        .required(),              // required if type == 'A'\n        \n    foo: Joi.when('type', {\n        is: 'A',\n        then: Joi.string()\n        .valid('X', 'Y', 'Z')\n        .required()\n    }),                           // required if type === 'A' and foo !== 'Z'\n    \n    bar: Joi.string()\n})\n    .when(Joi.object({ type: Joi.valid('A'), foo: Joi.not('Z') }).unknown(), {\n        then: Joi.object({ bar: Joi.required() })\n    });\n```\n\nAlternatively, if you want to specify a specific type such as `string`, `array`, etc, you can do so\nlike this:\n\n```js\nconst schema = {\n    a: Joi.valid('a', 'b', 'other'),\n    other: Joi.string()\n        .when('a', { is: 'other', then: Joi.required() }),\n};\n```\n\nIf you need to validate a child key inside a nested object based on a sibling's value, you can do\nso like this:\n\n```js\nconst schema = Joi.object({\n    a: Joi.boolean().required(),\n    b: Joi.object()\n        .keys({\n            c: Joi.string(),\n            d: Joi.number().required()\n        })\n        .required()\n        .when('a', {\n            is: true,\n            then: Joi.object({ c: Joi.required() })\t\t// b.c is required only when a is true\n        })\n});\n```\n\nIf you want to validate one key based on the existence of another key, you can do so like the\nfollowing (notice the use of `required()`):\n\n```js\nconst schema = Joi.object({\n    min: Joi.number(),\n    max: Joi.number().when('min', {\n        is: Joi.number().required(),\n        then: Joi.number().greater(Joi.ref('min')),\n    }),\n});\n```\n\nTo evaluate multiple values on a single reference:\n\n```js\nconst schema = Joi.object({\n    a: Joi.number().required(),\n    b: Joi.number()\n        .when('a', {\n            switch: [\n                { is: 0, then: Joi.valid(1) },\n                { is: 1, then: Joi.valid(2) },\n                { is: 2, then: Joi.valid(3) }\n            ],\n            otherwise: Joi.valid(4)\n        })\n});\n```\n\nOr shorter:\n\n```js\nconst schema = Joi.object({\n    a: Joi.number().required(),\n    b: Joi.number()\n        .when('a', [\n            { is: 0, then: 1 },\n            { is: 1, then: 2 },\n            { is: 2, then: 3, otherwise: 4 }\n        ])\n});\n```\n\n### `alternatives`\n\nGenerates a type that will match one of the provided alternative schemas via the [`try()`](#alternativestryschemas)\nmethod. If no schemas are added, the type will not match any value except for `undefined`.\n\nSupports the same methods of the [`any()`](#any) type.\n\nAlternatives can be expressed using the shorter `[]` notation.\n\n```js\nconst alt = Joi.alternatives().try(Joi.number(), Joi.string());\n// Same as [Joi.number(), Joi.string()]\n```\n\nNote that numeric strings would be casted to numbers in the example above (see [any.strict()](#anystrictisstrict)).\n\nPossible validation errors: [`alternatives.any`](#alternativesany), [`alternatives.all`](#alternativesall), [`alternatives.one`](#alternativesone), [`alternatives.types`](#alternativestypes), [`alternatives.match`](#alternativesmatch)\n\n#### `alternatives.conditional(condition, options)`\n\nAdds a conditional alternative schema type, either based on another key value, or a schema peeking into the current value, where:\n- `condition` - the key name or [reference](#refkey-options), or a schema.\n- `options` - an object with:\n    - `is` - the condition expressed as a **joi** schema. Anything that is not a **joi** schema will be converted using [Joi.compile](#compileschema-options).\n    - `not` - the negative version of `is` (`then` and `otherwise` have reverse roles).\n    - `then` - if the condition is true, the **joi** schema to use.\n    - `otherwise` - if the condition is false, the **joi** schema to use.\n    - `switch` - an array of `{ is, then }` conditions that are evaluated against the `condition`. The last item in the array may also contain `otherwise`.\n\nIf `condition` is a reference:\n- if `is`, `not`, and `switch` are missing, `is` defaults to `Joi.invalid(null, false, 0, '').required()` (value must be a truthy).\n- `is` and `not` cannot be used together.\n- one of `then`, `otherwise`, or `switch` is required.\n- cannot use `is` or `then` with `switch`.\n- cannot specify `otherwise` both inside the last `switch` statement and outside.\n\nIf `condition` is a schema:\n- cannot specify `is` or `switch`.\n- one of `then` or `otherwise` is required.\n\nWhen `is`, `then`, or `otherwise` are assigned literal values, the values are compiled into override schemas (`'x'` is compiled into `Joi.valid(Joi.override, 'x')`). This means they will override any base schema the rule is applied to. To append a literal value, use the explicit `Joi.valid('x')` format.\n\nNote that `alternatives.conditional()` is different than `any.when()`. When you use `any.when()` you end up with composite schema of all the matching conditions while `alternatives.conditional()` will use the first matching schema, ignoring other conditional statements.\n\n```js\nconst schema = {\n    a: Joi.alternatives().conditional('b', { is: 5, then: Joi.string(), otherwise: Joi.number() }),\n    b: Joi.any()\n};\n```\n\n```js\nconst schema = Joi.alternatives().conditional(Joi.object({ b: 5 }).unknown(), {\n    then: Joi.object({\n        a: Joi.string(),\n        b: Joi.any()\n    }),\n    otherwise: Joi.object({\n        a: Joi.number(),\n        b: Joi.any()\n    })\n});\n```\n\nNote that `conditional()` only adds additional alternatives to try and does not impact the overall type. Setting\na `required()` rule on a single alternative will not apply to the overall key. For example,\nthis definition of `a`:\n\n```js\nconst schema = {\n    a: Joi.alternatives().conditional('b', { is: true, then: Joi.required() }),\n    b: Joi.boolean()\n};\n```\n\nDoes not turn `a` into a required key when `b` is `true`. Instead, it tells the validator to try and match the\nvalue to anything that's not `undefined`. However, since `Joi.alternatives()` by itself allows `undefined`, the rule\ndoes not accomplish turning `a` to a required value. This rule is the same as `Joi.alternatives([Joi.required()])`\nwhen `b` is `true` which will allow any value including `undefined`.\n\nTo accomplish the desired result above use:\n\n```js\nconst schema = {\n    a: Joi.when('b', { is: true, then: Joi.required() }),\n    b: Joi.boolean()\n};\n```\n\n#### `alternatives.match(mode)`\n\nRequires the validated value to match a specific set of the provided `alternative.try()` schemas where:\n- `mode` - the match mode which can be one of:\n    - `'any'` - match any provided schema. This is the default value.\n    - `'all'` - match all of the provided schemas. Note that this will ignore any conversions performed by the matchin schemas and return the raw value provided regardless of the `convert` preference set.\n    - `'one'` - match one and only one of the provided schemas.\n\nNote: Cannot be combined with `alternatives.conditional()`.\n\nPossible validation errors: [`alternatives.any`](#alternativesany), [`alternatives.all`](#alternativesall), [`alternatives.one`](#alternativesone)\n\n#### `alternatives.try(...schemas)`\n\nAdds an alternative schema type for attempting to match against the validated value where:\n- `schemas` - alternative **joi** types, each as a separate argument.\n\n```js\nconst alt = Joi.alternatives().try(Joi.number(), Joi.string());\nawait alt.validateAsync('a');\n```\n\n### `array`\n\nGenerates a schema object that matches an array data type. Note that undefined values inside arrays are not allowed by\ndefault but can be by using `sparse()`.\n\nSupports the same methods of the [`any()`](#any) type.\n\n```js\nconst array = Joi.array().items(Joi.string().valid('a', 'b'));\nawait array.validateAsync(['a', 'b', 'a']);\n```\n\nPossible validation errors: [`array.base`](#arraybase)\n\n#### `array.has(schema)`\n\nVerifies that a schema validates at least one of the values in the array, where:\n- `schema` - the validation rules required to satisfy the check. If the `schema` includes references, they are resolved against\n  the array item being tested, not the value of the `ref` target.\n\n```js\nconst schema = Joi.array().items(\n  Joi.object({\n    a: Joi.string(),\n    b: Joi.number()\n  })\n).has(Joi.object({ a: Joi.string().valid('a'), b: Joi.number() }))\n```\n\nPossible validation errors: [`array.hasKnown`](#arrayhasknown), [`array.hasUnknown`](#arrayhasunknown)\n\n#### `array.items(...types)`\n\nLists the types allowed for the array values where:\n- `types` - one or more **joi** schema objects to validate each array item against.\n\nIf a given type is `.required()` then there must be a matching item in the array.\nIf a type is `.forbidden()` then it cannot appear in the array.\nRequired items can be added multiple times to signify that multiple items must be found.\nErrors will contain the number of items that didn't match. Any unmatched item having a [label](#anylabelname) will be mentioned explicitly.\n\n```js\nconst schema = Joi.array().items(Joi.string(), Joi.number()); // array may contain strings and numbers\nconst schema = Joi.array().items(Joi.string().required(), Joi.string().required()); // array must contain at least two strings\nconst schema = Joi.array().items(Joi.string().valid('not allowed').forbidden(), Joi.string()); // array may contain strings, but none of those strings can match 'not allowed'\nconst schema = Joi.array().items(Joi.string().label('My string').required(), Joi.number().required()); // If this fails it can result in `[ValidationError: \"value\" does not contain [My string] and 1 other required value(s)]`\n```\n\nPossible validation errors: [`array.excludes`](#arrayexcludes), [`array.includesRequiredBoth`](#arrayincludesrequiredboth), [`array.includesRequiredKnowns`](#arrayincludesrequiredknowns), [`array.includesRequiredUnknowns`](#arrayincludesrequiredunknowns), [`array.includes`](#arrayincludes)\n\n#### `array.length(limit)`\n\nSpecifies the exact number of items in the array where:\n- `limit` - the number of array items allowed or a reference.\n\n```js\nconst schema = Joi.array().length(5);\n```\n\n```js\nconst schema = Joi.object({\n  limit: Joi.number().integer().required(),\n  numbers: Joi.array().length(Joi.ref('limit')).required()\n});\n```\n\nPossible validation errors: [`array.length`](#arraylength), [`array.ref`](#arrayref)\n\n#### `array.max(limit)`\n\nSpecifies the maximum number of items in the array where:\n- `limit` - the highest number of array items allowed or a reference.\n\n```js\nconst schema = Joi.array().max(10);\n```\n\n```js\nconst schema = Joi.object({\n  limit: Joi.number().integer().required(),\n  numbers: Joi.array().max(Joi.ref('limit')).required()\n});\n```\n\nPossible validation errors: [`array.max`](#arraymax), [`array.ref`](#arrayref)\n\n#### `array.min(limit)`\n\nSpecifies the minimum number of items in the array where:\n- `limit` - the lowest number of array items allowed or a reference.\n\n```js\nconst schema = Joi.array().min(2);\n```\n\n```js\nconst schema = Joi.object({\n  limit: Joi.number().integer().required(),\n  numbers: Joi.array().min(Joi.ref('limit')).required()\n});\n```\n\nPossible validation errors: [`array.min`](#arraymin), [`array.ref`](#arrayref)\n\n#### `array.ordered(...type)`\n\nLists the types in sequence order for the array values where:\n- `types` - one or more **joi** schema objects to validate against each array item in sequence order.\n\nIf a given type is `.required()` then there must be a matching item with the same index position in the array.\nErrors will contain the number of items that didn't match. Any unmatched item having a [label](#anylabelname) will be mentioned explicitly.\n\n```js\nconst schema = Joi.array().ordered(Joi.string().required(), Joi.number().required()); // array must have first item as string and second item as number\nconst schema = Joi.array().ordered(Joi.string().required()).items(Joi.number().required()); // array must have first item as string and 1 or more subsequent items as number\nconst schema = Joi.array().ordered(Joi.string().required(), Joi.number()); // array must have first item as string and optionally second item as number\n```\n\nPossible validation errors: [`array.excludes`](#arrayexcludes), [`array.includes`](#arrayincludes), [`array.orderedLength`](#arrayorderedlength)\n\n#### `array.single([enabled])`\n\nAllows single values to be checked against rules as if it were provided as an array.\n\n`enabled` can be used with a falsy value to go back to the default behavior.\n\n```js\nconst schema = Joi.array().items(Joi.number()).single();\nschema.validate([4]); // returns `{ error: null, value: [ 4 ] }`\nschema.validate(4); // returns `{ error: null, value: [ 4 ] }`\n```\n\nPossible validation errors: [`array.excludes`](#arrayexcludes), [`array.includes`](#arrayincludes)\n\n#### `array.sort([options])`\n\nRequires the array to comply with the specified sort order where:\n- `options` - optional settings:\n    - `order` - the sort order. Allowed values:\n        - `'ascending'` - sort the array in ascending order. This is the default.\n        - `'descending'` - sort the array in descending order.\n    - `by` - a key name or reference to sort array objects by. Defaults to the entire value.\n\nNotes:\n- if the `convert` preference is `true`, the array is modified to match the required sort order.\n- `undefined` values are always placed at the end of the array regardless of the sort order.\n- can only sort string and number items or item key values.\n\nPossible validation errors: [`array.sort`](#arraysort), [`array.sort.unsupported`](#arraysortunsupported), [`array.sort.mismatching`](#arraysortmismatching)\n\n#### `array.sparse([enabled])`\n\nAllows this array to be sparse. `enabled` can be used with a falsy value to go back to the default behavior.\n\n```js\nlet schema = Joi.array().sparse(); // undefined values are now allowed\nschema = schema.sparse(false); // undefined values are now denied\n```\n\nPossible validation errors: [`array.sparse`](#arraysparse)\n\n#### `array.unique([comparator, [options]])`\n\nRequires the array values to be unique where:\n- `comparator` - an optional custom `comparator` that is either:\n    - a function that takes 2 parameters to compare. This function should return whether the 2\n      parameters are equal or not, you are also **responsible** for this function not to fail, any\n      `Error` would bubble out of Joi.\n    - a string in dot notation representing the path of the element to do uniqueness check on. Any\n      missing path will be considered undefined, and can as well only exist once.\n- `options` - optional settings:\n    - `ignoreUndefined` - if `true`, undefined values for the dot notation string comparator will\n      not cause the array to fail on uniqueness. Defaults to `false`.\n    - `separator` - overrides the default `.` hierarchy separator. Set to `false` to treat the\n      `key` as a literal value.\n\nNote: remember that if you provide a custom comparator function, different types can be passed as parameter depending on the rules you set on items.\n\nBe aware that a deep equality is performed on elements of the array having a type of `object`, a performance penalty is to be expected for this kind of operation.\n\n```js\nconst schema = Joi.array().unique();\n```\n\n```js\nconst schema = Joi.array().unique((a, b) => a.property === b.property);\n```\n\n```js\nconst schema = Joi.array().unique('customer.id');\n```\n\n```js\nlet schema = Joi.array().unique('identifier');\n\nschema.validate([{}, {}]);\n// ValidationError: \"value\" position 1 contains a duplicate value\n\nschema = Joi.array().unique('identifier', { ignoreUndefined: true });\n\nschema.validate([{}, {}]);\n// error: null\n```\n\nPossible validation errors: [`array.unique`](#arrayunique)\n\n### `binary`\n\nGenerates a schema object that matches a Buffer data type. If the validation `convert` option is on (enabled by default), a string\nwill be converted to a Buffer if specified.\n\nSupports the same methods of the [`any()`](#any) type.\n\n```js\nconst schema = Joi.binary();\n```\n\nPossible validation errors: [`binary.base`](#binarybase)\n\n#### `binary.encoding(encoding)`\n\nSets the string encoding format if a string input is converted to a buffer where:\n- `encoding` - the encoding scheme.\n\n```js\nconst schema = Joi.binary().encoding('base64');\n```\n\n#### `binary.length(limit)`\n\nSpecifies the exact length of the buffer:\n- `limit` - the size of buffer allowed or a reference.\n\n```js\nconst schema = Joi.binary().length(5);\n```\n\nPossible validation errors: [`binary.length`](#binarylength), [`binary.ref`](#binaryref)\n\n#### `binary.max(limit)`\n\nSpecifies the maximum length of the buffer where:\n- `limit` - the highest size of the buffer or a reference.\n\n```js\nconst schema = Joi.binary().max(10);\n```\n\nPossible validation errors: [`binary.max`](#binarymax), [`binary.ref`](#binaryref)\n\n#### `binary.min(limit)`\n\nSpecifies the minimum length of the buffer where:\n- `limit` - the lowest size of the buffer or a reference.\n\n```js\nconst schema = Joi.binary().min(2);\n```\n\nPossible validation errors: [`binary.min`](#binarymin), [`binary.ref`](#binaryref)\n\n### `boolean`\n\nGenerates a schema object that matches a boolean data type. Can also be called via `bool()`. If the validation `convert`\noption is on (enabled by default), a string (either \"true\" or \"false\") will be converted to a `boolean` if specified.\n\nSupports the same methods of the [`any()`](#any) type.\n\n```js\nconst boolean = Joi.boolean();\n\nawait boolean.validateAsync(true); // Valid\nawait boolean.validateAsync(1);    // Throws\n```\n\nPossible validation errors: [`boolean.base`](#booleanbase)\n\n#### `boolean.falsy(...values)`\n\nAllows for additional values to be considered valid booleans by converting them to `false` during validation.\nRequires the validation `convert` option to be `true`.\n\nString comparisons are by default case insensitive, see [`boolean.sensitive()`](#booleansensitiveenabled) to change this behavior.\n\n```js\nconst boolean = Joi.boolean().falsy('N');\nawait boolean.validateAsync('N'); // Valid\n```\n\n#### `boolean.sensitive([enabled])`\n\nRestrict the values provided to `truthy` and `falsy` as well as the `'true'` and `'false'` default conversions (when not in `strict()` mode) to be matched in a case sensitive manner, where:\n- `enabled` - when `false`, allows insensitive comparison. Defaults to `true`.\n\n```js\nconst schema = Joi.boolean().truthy('yes').falsy('no').sensitive();\n```\n\n#### `boolean.truthy(...values)`\n\nAllows for additional values to be considered valid booleans by converting them to `true` during validation.\nRequires the validation `convert` option to be `true`.\n\nString comparisons are by default case insensitive, see [`boolean.sensitive()`](#booleansensitiveenabled) to change this behavior.\n\n```js\nconst boolean = Joi.boolean().truthy('Y');\nawait boolean.validateAsync('Y'); // Valid\n```\n\n### `date`\n\nGenerates a schema object that matches a date type (as well as a JavaScript date string or number of milliseconds). If the validation `convert` option is on (enabled by default), a string or number will be converted to a Date if specified. Note that some invalid date strings will be accepted if they can be adjusted to valid dates (e.g. `'2/31/2019'` will be converted to `'3/3/2019'`) by the internal JS `Date.parse()` implementation.\n\nNote: When **joi** is used in a browser environment, the parsing of date strings with the Date\nconstructor (and Date.parse(), which works the same way) is strongly discouraged due to browser\ndifferences and inconsistencies. For example `'1-1-1910'` is valid in Chrome but will error in Safari.\n\nSupports the same methods of the [`any()`](#any) type.\n\n```js\nconst date = Joi.date();\nawait date.validateAsync('12-21-2012');\n```\n\nPossible validation errors: [`date.base`](#datebase), [`date.strict`](#datestrict)\n\n#### `date.greater(date)`\n\nSpecifies that the value must be greater than `date` (or a reference).\n\n```js\nconst schema = Joi.date().greater('1-1-1974');\n```\n\nNotes: `'now'` can be passed in lieu of `date` so as to always compare relatively to the current date, allowing to explicitly ensure a date is either in the past or in the future. When using `'now'` note that is includes the current time and the two values are compared based on their UTC milliseconds timestamp.\n\n```js\nconst schema = Joi.date().greater('now');\n```\n\n```js\nconst schema = Joi.object({\n  from: Joi.date().required(),\n  to: Joi.date().greater(Joi.ref('from')).required()\n});\n```\n\nPossible validation errors: [`date.greater`](#dategreater), [`date.ref`](#dateref)\n\n#### `date.iso()`\n\nRequires the string value to be in valid ISO 8601 date format.\n\n```js\nconst schema = Joi.date().iso();\n```\n\nPossible validation errors: [`date.format`](#dateformat)\n\n#### `date.less(date)`\n\nSpecifies that the value must be less than `date` (or a reference).\n\n```js\nconst schema = Joi.date().less('12-31-2020');\n```\n\nNotes: `'now'` can be passed in lieu of `date` so as to always compare relatively to the current date, allowing to explicitly ensure a date is either in the past or in the future.\n\n```js\nconst schema = Joi.date().less('now');\n```\n\n```js\nconst schema = Joi.object({\n  from: Joi.date().less(Joi.ref('to')).required(),\n  to: Joi.date().required()\n});\n```\n\nPossible validation errors: [`date.less`](#dateless), [`date.ref`](#dateref)\n\n#### `date.max(date)`\n\nSpecifies the latest date allowed where:\n- `date` - the latest date allowed or a reference.\n\n```js\nconst schema = Joi.date().max('12-31-2020');\n```\n\nNotes: `'now'` can be passed in lieu of `date` so as to always compare relatively to the current date, allowing to explicitly ensure a date is either in the past or in the future.\n\n```js\nconst schema = Joi.date().max('now');\n```\n\n```js\nconst schema = Joi.object({\n  from: Joi.date().max(Joi.ref('to')).required(),\n  to: Joi.date().required()\n});\n```\n\nPossible validation errors: [`date.max`](#datemax), [`date.ref`](#dateref)\n\n#### `date.min(date)`\n\nSpecifies the oldest date allowed where:\n- `date` - the oldest date allowed or a reference.\n\n```js\nconst schema = Joi.date().min('1-1-1974');\n```\n\nNotes: `'now'` can be passed in lieu of `date` so as to always compare relatively to the current date, allowing to explicitly ensure a date is either in the past or in the future.\n\n```js\nconst schema = Joi.date().min('now');\n```\n\n```js\nconst schema = Joi.object({\n  from: Joi.date().required(),\n  to: Joi.date().min(Joi.ref('from')).required()\n});\n```\n\nPossible validation errors: [`date.min`](#datemin), [`date.ref`](#dateref)\n\n#### `date.timestamp([type])`\n\nRequires the value to be a timestamp interval from [Unix Time](https://en.wikipedia.org/wiki/Unix_time).\n\n- `type` - the type of timestamp (allowed values are `unix` or `javascript` [default])\n\n```js\nconst schema = Joi.date().timestamp(); // defaults to javascript timestamp\nconst schema = Joi.date().timestamp('javascript'); // also, for javascript timestamp (milliseconds)\nconst schema = Joi.date().timestamp('unix'); // for unix timestamp (seconds)\n```\n\nPossible validation errors: [`date.format`](#dateformat)\n\n### `function` - inherits from `object`\n\nGenerates a schema object that matches a function type.\n\nSupports the same methods of the [`object()`](#object) type. Note that validating a function keys will cause the function\nto be cloned. While the function will retain its prototype and closure, it will lose its `length` property value (will be\nset to `0`).\n\n```js\nconst func = Joi.function();\nawait func.validateAsync(function () {});\n```\n\nPossible validation errors: [`object.base`](#objectbase)\n\n#### `function.arity(n)`\n\nSpecifies the arity of the function where:\n- `n` - the arity expected.\n\n```js\nconst schema = Joi.function().arity(2);\n```\n\nPossible validation errors: [`function.arity`](#functionarity)\n\n#### <a /> `function.class()`\n\nRequires the function to be a class.\n\n```js\nconst schema = Joi.function().class();\n```\n\nPossible validation errors: [`function.class`](#functionclass)\n\n#### <a></a> `function.maxArity(n)`\n\nSpecifies the maximal arity of the function where:\n- `n` - the maximum arity expected.\n\n```js\nconst schema = Joi.function().maxArity(3);\n```\n\nPossible validation errors: [`function.maxArity`](#functionmaxarity)\n\n#### <a /> `function.minArity(n)`\n\nSpecifies the minimal arity of the function where:\n- `n` - the minimal arity expected.\n\n```js\nconst schema = Joi.function().minArity(1);\n```\n\nPossible validation errors: [`function.minArity`](#functionminarity)\n\n### `link(ref)`\n\nLinks to another schema node and reuses it for validation, typically for creative recursive schemas, where:\n- `ref` - the reference to the linked schema node. Cannot reference itself or its children as well as other links. Links can be expressed in relative terms like value references (`Joi.link('...')`), in absolute terms from the schema run-time root (`Joi.link('/a')`), or using schema ids implicitly using object keys or explicitly using `any.id()` (`Joi.link('#a.b.c')`).\n\nSupports the methods of the [`any()`](#any) type.\n\nWhen links are combined with `any.when()` rules, the rules are applied after the link is resolved to the linked schema.\n\nNamed links are recommended for most use cases as they are easy to reason and understand, and when mistakes are made, they simply error with invalid link message. Relative links are often hard to follow, especially when they are nested in array or alternatives rules. Absolute links are useful only when the schema is never reused inside another schema as the root is the run-time root of the schema being validated, not the current schema root.\n\nNote that named links must be found in a direct ancestor of the link. The names are searched by iterating over the chain of schemas from the current schema to the root. To reach an uncle or cousin, you must use the name of a common ancestor such as a grandparent and then walk down the tree.\n\nLinks are resolved once (per runtime) and the result schema cached. If you reuse a link in different places, the first time it is resolved at run-time, the result will be used by all other instances. If you want each link to resolve relative to the place it is used, use a separate `Joi.link()` statement in each place or set the `relative()` flag.\n\nNamed links:\n\n```js\nconst person = Joi.object({\n    firstName: Joi.string().required(),\n    lastName: Joi.string().required(),\n    children: Joi.array()\n        .items(Joi.link('#person'))\n})\n  .id('person');\n```\n\nRelative links:\n\n```js\nconst person = Joi.object({\n    firstName: Joi.string().required(),\n    lastName: Joi.string().required(),\n    children: Joi.array()\n        .items(Joi.link('...'))\n        // . - the link\n        // .. - the children array\n        // ... - the person object\n});\n```\n\nAbsolute links:\n\n```js\nconst person = Joi.object({\n    firstName: Joi.string().required(),\n    lastName: Joi.string().required(),\n    children: Joi.array()\n        .items(Joi.link('/'))\n});\n```\n\n#### `link.ref(ref)`\n\nInitializes the schema after constructions for cases where the schema has to be constructed first and\nthen initialized. If `ref` was not passed to the constructor, `link.ref()` must be called prior to usage.\n\nWill throw an error during validation if left uninitialized (e.g. `Joi.link()` called without a link and `link.ref()` not called).\n\n```js\nconst schema = Joi.object({\n    a: [Joi.string(), Joi.number()],\n    b: Joi.link().ref('#type.a')\n})\n    .id('type');\n```\n\n#### `link.concat(schema)`\n\nSame as [`any.concat()`](#anyconcatschema) but the schema is merged after the link is resolved which allows merging with schemas of the same type as the resolved link. Will throw an exception during validation if the merged types are not compatible.\n\n### `number`\n\nGenerates a schema object that matches a number data type (as well as strings that can be converted to numbers).\n\nBy default, it only allows safe numbers, see [`number.unsafe()`](#numberunsafeenabled).\n\nIf the validation `convert` option is on (enabled by default), a string will be converted to a `number` if specified. Also, if\n`convert` is on and `number.precision()` is used, the value will be converted to the specified `precision` as well.\n\n`Infinity` and `-Infinity` are invalid by default, you can change that behavior by calling `allow(Infinity, -Infinity)`.\n\nSupports the same methods of the [`any()`](#any) type.\n\n```js\nconst number = Joi.number();\nawait number.validateAsync(5);\n```\n\nPossible validation errors: [`number.base`](#numberbase), [`number.infinity`](#numberinfinity)\n\n#### `number.greater(limit)`\n\nSpecifies that the value must be greater than `limit` or a reference.\n\n```js\nconst schema = Joi.number().greater(5);\n```\n\n```js\nconst schema = Joi.object({\n  min: Joi.number().required(),\n  max: Joi.number().greater(Joi.ref('min')).required()\n});\n```\n\nPossible validation errors: [`number.greater`](#numbergreater), [`number.ref`](#numberref)\n\n#### `number.integer()`\n\nRequires the number to be an integer (no floating point).\n\n```js\nconst schema = Joi.number().integer();\n```\n\nPossible validation errors: [`number.base`](#numberbase)\n\n#### `number.less(limit)`\n\nSpecifies that the value must be less than `limit` or a reference.\n\n```js\nconst schema = Joi.number().less(10);\n```\n\n```js\nconst schema = Joi.object({\n  min: Joi.number().less(Joi.ref('max')).required(),\n  max: Joi.number().required()\n});\n```\n\nPossible validation errors: [`number.less`](#numberless), [`number.ref`](#numberref)\n\n#### `number.max(limit)`\n\nSpecifies the maximum value where:\n- `limit` - the maximum value allowed or a reference.\n\n```js\nconst schema = Joi.number().max(10);\n```\n\n```js\nconst schema = Joi.object({\n  min: Joi.number().max(Joi.ref('max')).required(),\n  max: Joi.number().required()\n});\n```\n\nPossible validation errors: [`number.max`](#numbermax), [`number.ref`](#numberref)\n\n#### `number.min(limit)`\n\nSpecifies the minimum value where:\n- `limit` - the minimum value allowed or a reference.\n\n```js\nconst schema = Joi.number().min(2);\n```\n\n```js\nconst schema = Joi.object({\n  min: Joi.number().required(),\n  max: Joi.number().min(Joi.ref('min')).required()\n});\n```\n\nPossible validation errors: [`number.min`](#numbermin), [`number.ref`](#numberref)\n\n#### `number.multiple(base)`\n\nSpecifies that the value must be a multiple of `base` (or a reference):\n\n```js\nconst schema = Joi.number().multiple(3);\n```\n\nNotes: `Joi.number.multiple(base)` _uses the modulo operator (%) to determine if a number is multiple of another number.\nTherefore, it has the normal limitations of Javascript modulo operator. The results with decimal/floats may be incorrect._\n\nPossible validation errors: [`number.multiple`](#numbermultiple), [`number.ref`](#numberref)\n\n#### `number.negative()`\n\nRequires the number to be negative.\n\n```js\nconst schema = Joi.number().negative();\n```\n\nPossible validation errors: [`number.negative`](#numbernegative-1)\n\n#### `number.port()`\n\nRequires the number to be a TCP port, so between 0 and 65535.\n\n```js\nconst schema = Joi.number().port();\n```\n\nPossible validation errors: [`number.port`](#numberport-1)\n\n#### `number.positive()`\n\nRequires the number to be positive.\n\n```js\nconst schema = Joi.number().positive();\n```\n\nPossible validation errors: [`number.positive`](#numberpositive-1)\n\n#### `number.precision(limit)`\n\nSpecifies the maximum number of decimal places where:\n- `limit` - the maximum number of decimal places allowed.\n\n```js\nconst schema = Joi.number().precision(2);\n```\n\nPossible validation errors: [`number.integer`](#numberinteger-1)\n\n#### `number.sign(sign)`\n\nRequires the number to be negative or positive where:\n`sign` - one of `'negative'` or `'positive'`.\n\nPossible validation errors: [`number.negative`](#numbernegative-1), [`number.positive`](#numberpositive-1)\n\n#### `number.unsafe([enabled])`\n\nBy default, numbers must be within JavaScript's safety range (`Number.MIN_SAFE_INTEGER` & `Number.MAX_SAFE_INTEGER`), and when given a string, should be converted without loss of information. You can allow unsafe numbers at your own risks by calling `number.unsafe()`.\n\nParameters are:\n- `enabled` - optional parameter defaulting to `true` which allows you to reset the behavior of unsafe by providing a falsy value.\n\n```js\nconst safeNumber = Joi.number();\nsafeNumber.validate(90071992547409924);\n// error -> \"value\" must be a safe number\n\nconst unsafeNumber = Joi.number().unsafe();\nunsafeNumber.validate(90071992547409924);\n// error -> null\n// value -> 90071992547409920\n```\n\nPossible validation errors: [`number.unsafe`](#numberunsafe)\n\n### `object`\n\nGenerates a schema object that matches an object data type. Defaults to allowing any child key.\n\nSupports the same methods of the [`any()`](#any) type.\n\n```js\nconst object = Joi.object({\n    a: Joi.number().min(1).max(10).integer(),\n    b: 'some string'\n});\n\nawait object.validateAsync({ a: 5 });\n```\n\nNote that when an object schema type is passed as an input to another joi method (e.g. array\nitem) or is set as a key definition, the `Joi.object()` constructor may be omitted. For example:\n\n```js\nconst schema = Joi.array().items({ a: Joi.string() });\n```\n\nPossible validation errors: [`object.base`](#objectbase)\n\n#### `object.and(...peers, [options])`\n\nDefines an all-or-nothing relationship between keys where if one of the peers is present, all of\nthem are required as well where:\n- `peers` - the string key names of which if one present, all are required.\n- `options` - optional settings:\n    - `separator` - overrides the default `.` hierarchy separator. Set to `false` to treat the `key` as a literal value.\n    - `isPresent` - function that overrides the default check for an empty value. Default: `(resolved) => resolved !== undefined`\n\n```js\nconst schema = Joi.object({\n    a: Joi.any(),\n    b: Joi.any()\n}).and('a', 'b');\n```\n\nPossible validation errors: [`object.and`](#objectand)\n\n#### `object.append([schema])`\n\nAppends the allowed object keys where:\n- `schema` - optional object where each key is assigned a **joi** type object. If `schema` is `null`,`undefined` or `{}` no changes will be applied. Uses object.keys([schema]) to append keys.\n\n```js\n// Validate key a\nconst base = Joi.object({\n    a: Joi.number()\n});\n// Validate keys a, b.\nconst extended = base.append({\n    b: Joi.string()\n});\n```\n\n#### `object.assert(subject, schema, [message])`\n\nVerifies an assertion where:\n- `subject` - the key name, [reference](#refkey-options), or template expression to validate. Note that the reference is resolved against the object itself as value, which means if you want to reference a key of the object being validated, you have to prefix the reference path with `.`.\n- `schema` - the validation rules required to satisfy the assertion. If the `schema` includes references, they are resolved against\n  the object value, not the value of the `subject` target.\n- `message` - optional human-readable message used when the assertion fails. Defaults to 'failed to pass the assertion test'.\n\n```js\nconst schema = Joi.object({\n    a: {\n        b: Joi.string(),\n        c: Joi.number()\n    },\n    d: {\n        e: Joi.any()\n    }\n}).assert('.d.e', Joi.ref('a.c'), 'equal to a.c');\n```\n\nPossible validation errors: [`object.assert`](#objectassert)\n\n#### `object.instance(constructor, [name])`\n\nRequires the object to be an instance of a given constructor where:\n- `constructor` - the constructor function that the object must be an instance of.\n- `name` - an alternate name to use in validation errors. This is useful when the constructor function does not have a name.\n\n```js\nconst schema = Joi.object().instance(RegExp);\n```\n\nPossible validation errors: [`object.instance`](#objectinstance)\n\n#### `object.keys([schema])`\n\nSets or extends the allowed object keys where:\n- `schema` - optional object where each key is assigned a **joi** type object. If `schema` is `{}` no keys allowed.\n  If `schema` is `null` or `undefined`, any key allowed. If `schema` is an object with keys, the keys are added to any\n  previously defined keys (but narrows the selection if all keys previously allowed). Defaults to 'undefined' which\n  allows any child key.\n\n```js\nconst base = Joi.object().keys({\n    a: Joi.number(),\n    b: Joi.string()\n});\n// Validate keys a, b and c.\nconst extended = base.keys({\n    c: Joi.boolean()\n});\n```\n\nPossible validation errors: [`object.unknown`](#objectunknown)\n\n#### `object.length(limit)`\n\nSpecifies the exact number of keys in the object where or a reference:\n- `limit` - the number of object keys allowed.\n\n```js\nconst schema = Joi.object().length(5);\n```\n\nPossible validation errors: [`object.length`](#objectlength), [`object.ref`](#objectref)\n\n#### `object.max(limit)`\n\nSpecifies the maximum number of keys in the object where:\n- `limit` - the highest number of object keys allowed or a reference.\n\n```js\nconst schema = Joi.object().max(10);\n```\n\nPossible validation errors: [`object.max`](#objectmax), [`object.ref`](#objectref)\n\n#### `object.min(limit)`\n\nSpecifies the minimum number of keys in the object where:\n- `limit` - the lowest number of keys allowed or a reference.\n\n```js\nconst schema = Joi.object().min(2);\n```\n\nPossible validation errors: [`object.min`](#objectmin), [`object.ref`](#objectref)\n\n#### `object.nand(...peers, [options])`\n\nDefines a relationship between keys where not all peers can be present at the same time where:\n- `peers` - the key names of which if one present, the others may not all be present.\n- `options` - optional settings:\n    - `separator` - overrides the default `.` hierarchy separator. Set to `false` to treat the `key` as a literal value.\n    - `isPresent` - function that overrides the default check for an empty value. Default: `(resolved) => resolved !== undefined`\n\n```js\nconst schema = Joi.object({\n    a: Joi.any(),\n    b: Joi.any()\n}).nand('a', 'b');\n```\n\nPossible validation errors: [`object.nand`](#objectnand)\n\n#### `object.or(...peers, [options])`\n\nDefines a relationship between keys where one of the peers is required (and more than one is\nallowed) where:\n- `peers` - the key names of which at least one must appear.\n- `options` - optional settings:\n    - `separator` - overrides the default `.` hierarchy separator. Set to `false` to treat the `key` as a literal value.\n    - `isPresent` - function that overrides the default check for an empty value. Default: `(resolved) => resolved !== undefined`\n\n```js\nconst schema = Joi.object({\n    a: Joi.any(),\n    b: Joi.any()\n}).or('a', 'b');\n```\n\nPossible validation errors: [`object.missing`](#objectmissing)\n\n#### `object.oxor(...peers, [options])`\n\nDefines an exclusive relationship between a set of keys where only one is allowed but none are\nrequired where:\n- `peers` - the exclusive key names that must not appear together but where none are required.\n- `options` - optional settings:\n    - `separator` - overrides the default `.` hierarchy separator. Set to `false` to treat the `key` as a literal value.\n    - `isPresent` - function that overrides the default check for an empty value. Default: `(resolved) => resolved !== undefined`\n\n```js\nconst schema = Joi.object({\n    a: Joi.any(),\n    b: Joi.any()\n}).oxor('a', 'b');\n```\n\nPossible validation errors: [`object.oxor`](#objectoxor)\n\n#### `object.pattern(pattern, schema, [options])`\n\nSpecify validation rules for unknown keys matching a pattern where:\n- `pattern` - a pattern that can be either a regular expression or a **joi** schema that will be tested against the unknown key names. Note that if the pattern is a regular expression, for it to match the entire key name, it must begin with `^` and end with `$`.\n- `schema` - the schema object matching keys must validate against.\n- `options` - options settings:\n    - `fallthrough` - if `true`, multiple matching patterns are tested against the key, otherwise once a pattern match is found, no other patterns are compared. Defaults to `false`.\n    - `matches` - a joi array schema used to validated the array of matching keys. For example, `Joi.object().pattern(/\\d/, Joi.boolean(), { matches: Joi.array().length(2) })` will require two matching keys. If the `matches` schema is not an array type schema, it will be converted to `Joi.array().items(matches)`. If the `matches` schema contains references, they are resolved against the ancestors as follows:\n        - self - the array of matching keys (`Joi.ref('.length')`)\n        - parent - the object value containing the keys (`Joi.ref('a')`)\n\n```js\nconst schema = Joi.object({\n    a: Joi.string()\n}).pattern(/\\w\\d/, Joi.boolean());\n\n// OR\n\nconst schema = Joi.object({\n    a: Joi.string()\n}).pattern(Joi.string().min(2).max(5), Joi.boolean());\n```\n\nPossible validation errors: [`object.pattern.match`](#objectpatternmatch)\n\n#### `object.ref()`\n\nRequires the object to be a **joi** reference.\n\n```js\nconst schema = Joi.object().ref();\n```\n\nPossible validation errors: [`object.refType`](#objectreftype)\n\n#### `object.regex()`\n\nRequires the object to be a `RegExp` object.\n\n```js\nconst schema = Joi.object().regex();\n```\n\nPossible validation errors: [`object.regex`](#objectregex)\n\n#### `object.rename(from, to, [options])`\n\nRenames a key to another name (deletes the renamed key) where:\n- `from` - the original key name or a regular expression matching keys.\n- `to` - the new key name. `to` can be set to a [`template`](#templatetemplate-options) which is rendered at runtime using the current value, global context, and local context if `from` is a regular expression (e.g. the expression `/^(\\d+)$/` will match any all-digits keys with a capture group that is accessible in the template via `{#1}`).\n- `options` - an optional object with the following optional keys:\n    - `alias` - if `true`, does not delete the old key name, keeping both the new and old keys in place. Defaults to `false`.\n    - `multiple` - if `true`, allows renaming multiple keys to the same destination where the last rename wins. Defaults to `false`.\n    - `override` - if `true`, allows renaming a key over an existing key. Defaults to `false`.\n    - `ignoreUndefined` - if `true`, skip renaming of a key if it's undefined. Defaults to `false`.\n\nKeys are renamed before any other validation rules are applied. If `to` is a template that references the object own keys (e.g. `'{.prefix}-{#1}'`), the value of these keys is the raw input value, not the value generated after validation. If a key is renamed and then its value fails to pass a validation rule, the error message will use the renamed key, not the original key which may be confusing for users (labels can help in some cases).\n\n```js\nconst object = Joi.object({\n    a: Joi.number()\n}).rename('b', 'a');\n\nawait object.validateAsync({ b: 5 });\n```\n\nUsing a regular expression:\n\n```js\nconst regex = /^foobar$/i;\n\nconst schema = Joi.object({\n  fooBar: Joi.string()\n}).rename(regex, 'fooBar');\n\nawait schema.validateAsync({ FooBar: 'a'});\n```\n\nUsing a regular expression with template:\n\n```js\nconst schema = Joi.object()\n    .rename(/^(\\d+)$/, Joi.expression('x{#1}x'))\n    .pattern(/^x\\d+x$/, Joi.any());\n\nconst input = {\n    123: 'x',\n    1: 'y',\n    0: 'z',\n    x4x: 'test'\n};\n\nconst value = await Joi.compile(schema).validateAsync(input);\n// value === { x123x: 'x', x1x: 'y', x0x: 'z', x4x: 'test' }\n```\n\nPossible validation errors: [`object.rename.multiple`](#objectrenamemultiple), [`object.rename.override`](#objectrenameoverride)\n\n#### `object.schema([type])`\n\nRequires the object to be a **joi** schema instance where:\n- `type` - optional **joi** schema to require.\n\n```js\nconst schema = Joi.object().schema();\n```\n\nPossible validation errors: [`object.schema`](#objectschema-1)\n\n#### `object.unknown([allow])`\n\nOverrides the handling of unknown keys for the scope of the current object only (does not apply to children) where:\n- `allow` - if `false`, unknown keys are not allowed, otherwise unknown keys are ignored.\n\n```js\nconst schema = Joi.object({ a: Joi.any() }).unknown();\n```\n\nPossible validation errors: [`object.unknown`](#objectunknown)\n\n#### `object.with(key, peers, [options])`\n\nRequires the presence of other keys whenever the specified key is present where:\n- `key` - the reference key.\n- `peers` - the required peer key names that must appear together with `key`. `peers` can be a\n  single string value or an array of string values.\n- `options` - optional settings:\n    - `separator` - overrides the default `.` hierarchy separator. Set to `false` to treat the `key` as a literal value.\n    - `isPresent` - function that overrides the default check for an empty value. Default: `(resolved) => resolved !== undefined`\n\nNote that unlike [`object.and()`](#objectandpeers-options), `with()` creates a dependency only between the `key` and each of the `peers`, not\nbetween the `peers` themselves.\n\n```js\nconst schema = Joi.object({\n    a: Joi.any(),\n    b: Joi.any()\n}).with('a', 'b');\n```\n\nPossible validation errors: [`object.with`](#objectwith)\n\n#### `object.without(key, peers, [options])`\n\nForbids the presence of other keys whenever the specified is present where:\n- `key` - the reference key.\n- `peers` - the forbidden peer key names that must not appear together with `key`. `peers` can be a\n  single string value or an array of string values.\n- `options` - optional settings:\n    - `separator` - overrides the default `.` hierarchy separator. Set to `false` to treat the `key` as a literal value.\n    - `isPresent` - function that overrides the default check for an empty value. Default: `(resolved) => resolved !== undefined`\n\n```js\nconst schema = Joi.object({\n    a: Joi.any(),\n    b: Joi.any()\n}).without('a', ['b']);\n```\n\nPossible validation errors: [`object.without`](#objectwithout)\n\n#### `object.xor(...peers, [options])`\n\nDefines an exclusive relationship between a set of keys where one of them is required but not at\nthe same time where:\n- `peers` - the exclusive key names that must not appear together but where one of them is required.\n- `options` - optional settings:\n    - `separator` - overrides the default `.` hierarchy separator. Set to `false` to treat the `key` as a literal value.\n    - `isPresent` - function that overrides the default check for an empty value. Default: `(resolved) => resolved !== undefined`\n\n```js\nconst schema = Joi.object({\n    a: Joi.any(),\n    b: Joi.any()\n}).xor('a', 'b');\n```\n\nPossible validation errors: [`object.xor`](#objectxor), [`object.missing`](#objectmissing)\n\n### `string`\n\nGenerates a schema object that matches a string data type.\n\n**Note that the empty string is not allowed by default and must be enabled with `allow('')`. Don't over think, just remember that the empty string is not a valid string by default. Also, don't ask to change it or argue why it doesn't make sense. This topic is closed.**\n\nTo specify a default value in case of the empty string use:\n\n```js\nJoi.string()\n    .empty('')\n    .default('default value');\n```\n\nIf the `convert` preference is `true` (the default value), a string will be converted using the specified modifiers\nfor `string.lowercase()`, `string.uppercase()`, `string.trim()`, and each replacement specified with `string.replace()`.\n\nSupports the same methods of the [`any()`](#any) type.\n\n```js\nconst schema = Joi.string().min(1).max(10);\nawait schema.validateAsync('12345');\n```\n\nPossible validation errors: [`string.base`](#stringbase), [`string.empty`](#stringempty)\n\n#### `string.alphanum()`\n\nRequires the string value to only contain a-z, A-Z, and 0-9.\n\n```js\nconst schema = Joi.string().alphanum();\n```\n\nPossible validation errors: [`string.alphanum`](#stringalphanum-1)\n\n#### `string.base64([options])`\n\nRequires the string value to be a valid base64 string; does not check the decoded value.\n\n- `options` - optional settings:\n    - `paddingRequired` - if `true`, the string must be properly padded with the `=` characters. Defaults to `true`.\n    - `urlSafe` - if `true`, uses the URI-safe base64 format which replaces `+` with `-` and `\\` with `_`. Defaults to `false`.\n\nPadding characters are not required for decoding, as the number of missing bytes can be inferred from the number of digits. With that said, try to use padding if at all possible.\n\n```js\nconst schema = Joi.string().base64();\nschema.validate('VE9PTUFOWVNFQ1JFVFM'); // ValidationError: \"value\" must be a valid base64 string\nschema.validate('VE9PTUFOWVNFQ1JFVFM='); // No Error\n\nconst paddingRequiredSchema = Joi.string().base64({ paddingRequired: true });\npaddingRequiredSchema.validate('VE9PTUFOWVNFQ1JFVFM'); // ValidationError: \"value\" must be a valid base64 string\npaddingRequiredSchema.validate('VE9PTUFOWVNFQ1JFVFM='); // No Error\n\nconst paddingOptionalSchema = Joi.string().base64({ paddingRequired: false });\npaddingOptionalSchema.validate('VE9PTUFOWVNFQ1JFVFM'); // No Error\npaddingOptionalSchema.validate('VE9PTUFOWVNFQ1JFVFM='); // No Error\n```\n\nPossible validation errors: [`string.base64`](#stringbase64)\n\n#### `string.case(direction)`\n\nSets the required string case where:\n- `direction` - can be either `'upper'` or `'lower'`.\n\n```js\nconst schema = Joi.string().case('lower');\n```\n\nPossible validation errors: [`string.lowercase`](#stringlowercase-1) [`string.uppercase`](#stringuppercase-1)\n\n#### `string.creditCard()`\n\nRequires the number to be a credit card number (Using [Luhn\nAlgorithm](http://en.wikipedia.org/wiki/Luhn_algorithm)).\n\n```js\nconst schema = Joi.string().creditCard();\n```\n\nPossible validation errors: [`string.creditCard`](#stringcreditcard-1)\n\n#### `string.dataUri([options])`\n\nRequires the string value to be a valid data URI string.\n\n- `options` - optional settings:\n    - `paddingRequired` - optional parameter defaulting to `true` which will require `=` padding if `true` or make padding optional if `false`.\n\n```js\nconst schema = Joi.string().dataUri();\nschema.validate('VE9PTUFOWVNFQ1JFVFM='); // ValidationError: \"value\" must be a valid dataUri string\nschema.validate('data:image/png;base64,VE9PTUFOWVNFQ1JFVFM='); // No Error\n```\n\nPossible validation errors: [`string.dataUri`](#stringdatauri)\n\n#### `string.domain([options])`\n\nRequires the string value to be a valid domain name.\n\n- `options` - optional settings:\n    - `allowFullyQualified` - if `true`, domains ending with a `.` character are permitted. Defaults to `false`.\n    - `allowUnicode` - if `true`, Unicode characters are permitted. Defaults to `true`.\n    - `allowUnderscore` - if `true`, underscores (`_`) are allowed in the domain name. Defaults to `false`.\n    - `minDomainSegments` - number of segments required for the domain. Defaults to `2`.\n    - `maxDomainSegments` - maximum number of allowed domain segments. Default to no limit.\n    - `tlds` - options for TLD (top level domain) validation. By default, the TLD must be a valid\n      name listed on the [IANA registry](http://data.iana.org/TLD/tlds-alpha-by-domain.txt). To\n      disable validation, set `tlds` to `false`. To customize how TLDs are validated, set one of\n      these:\n        - `allow` - one of:\n            - `true` to use the IANA list of registered TLDs. This is the default value.\n            - `false` to allow any TLD not listed in the `deny` list, if present.\n            - a `Set` or array of the allowed TLDs. Cannot be used together with `deny`.\n        - `deny` - one of:\n            - a `Set` or array of the forbidden TLDs. Cannot be used together with a custom `allow`\n              list.\n\n```js\nconst schema = Joi.string().domain();\n```\n\nPossible validation errors: [`string.domain`](#stringdomain)\n\n#### `string.email([options])`\n\nRequires the string value to be a valid email address.\n\n- `options` - optional settings:\n    - `allowFullyQualified` - if `true`, domains ending with a `.` character are permitted. Defaults to `false`.\n    - `allowUnicode` - if `true`, Unicode characters are permitted. Defaults to `true`.\n    - `allowUnderscore` - if `true`, underscores (`_`) are allowed in the domain name. Defaults to `false`.\n    - `ignoreLength` - if `true`, ignore invalid email length errors. Defaults to `false`.\n    - `minDomainSegments` - number of segments required for the domain. The default setting excludes\n      single segment domains such as `example@io` which is a valid email but very uncommon. Defaults\n      to `2`.\n    - `maxDomainSegments` - maximum number of allowed domain segments. Default to no limit.\n    - `multiple` - if `true`, allows multiple email addresses in a single string, separated by `,`\n      or the `separator` characters. Defaults to `false`.\n    - `separator` - when `multiple` is `true`, overrides the default `,` separator. String can be\n      a single character or multiple separator characters. Defaults to `','`.\n    - `tlds` - options for TLD (top level domain) validation. By default, the TLD must be a valid\n      name listed on the [IANA registry](http://data.iana.org/TLD/tlds-alpha-by-domain.txt). To\n      disable validation, set `tlds` to `false`. To customize how TLDs are validated, set one of\n      these:\n        - `allow` - one of:\n            - `true` to use the IANA list of registered TLDs. This is the default value.\n            - `false` to allow any TLD not listed in the `deny` list, if present.\n            - a `Set` or array of the allowed TLDs. Cannot be used together with `deny`.\n        - `deny` - one of:\n            - a `Set` or array of the forbidden TLDs. Cannot be used together with a custom `allow`\n              list.\n\n```js\nconst schema = Joi.string().email();\n```\n\nNote that quoted email addresses (e.g. `\"test\"@example.com`) are not supported and will fail validation.\n\nPossible validation errors: [`string.email`](#stringemail)\n\n#### `string.guid()` - aliases: `uuid`\n\nRequires the string value to be a valid GUID.\n\n-   `options` - optional settings:\n    -   `version` - specifies one or more acceptable versions. Can be an Array or String with the following values:\n        `uuidv1`, `uuidv2`, `uuidv3`, `uuidv4`, `uuidv5`, `uuidv6`, `uuidv7` or `uuidv8`. If no `version` is specified then it is assumed to be a generic `guid`\n        which will not validate the version or variant of the guid and just check for general structure format.\n    -   `separator` - defines the allowed or required GUID separator where:\n        -   `true` - a separator is required, can be either `:` or `-`.\n        -   `false` - separator is not allowed.\n        -   `'-'` - a dash separator is required.\n        -   `':'` - a colon separator is required.\n        -   defaults to optional `:` or `-` separator.\n    -   `wrapper` - defines the allowed or required GUID wrapper characters where:\n        -   `undefined` - (default) the GUID can be optionally wrapped with `{}`, `[]`, or `()`. The opening and closing characters must be a matching pair.\n        -   `true` - the GUID must be wrapped with `{}`, `[]`, or `()`. The opening and closing characters must be a matching pair.\n        -   `false` - wrapper characters are not allowed.\n        -   `'['`, `'{'`, or `'('` - a specific wrapper is required (e.g., if `wrapper` is `'['`, the GUID must be enclosed in square brackets).\n\n\n```js\nconst schema = Joi.string().guid({\n    version: [\n        'uuidv4',\n        'uuidv5'\n    ],\n     wrapper: false \n});\n\n```\n\nPossible validation errors: [`string.guid`](#stringguid)\n\n#### `string.hex([options])`\n\nRequires the string value to be a valid hexadecimal string.\n\n- `options` - optional settings:\n  - `byteAligned` - Boolean specifying whether you want to check that the hexadecimal string is byte aligned. If `convert` is `true`, a `0` will be added in front of the string in case it needs to be aligned. Defaults to `false`.\n  - `prefix` - Boolean or `optional`. When `true`, the string will be considered valid if prefixed with `0x` or `0X`. When `false`, the prefix is forbidden. When `optional`, the string will be considered valid if prefixed or not prefixed at all. Defaults to `false`.\n```js\nconst schema = Joi.string().hex({ prefix: 'optional' });\n```\n\nPossible validation errors: [`string.hex`](#stringhex), [`string.hexAlign`](#stringhexalign)\n\n#### `string.hostname()`\n\nRequires the string value to be a valid hostname as per [RFC1123](http://tools.ietf.org/html/rfc1123).\n\n```js\nconst schema = Joi.string().hostname();\n```\n\nPossible validation errors: [`string.hostname`](#stringhostname-1)\n\n#### `string.insensitive()`\n\nAllows the value to match any value in the allowed list or disallowed list in a case insensitive comparison.\n\n```js\nconst schema = Joi.string().valid('a').insensitive();\n```\n\n#### `string.ip([options])`\n\nRequires the string value to be a valid ip address.\n\n- `options` - optional settings:\n    - `version` - One or more IP address versions to validate against. Valid values: `ipv4`, `ipv6`, `ipvfuture`\n    - `cidr` - Used to determine if a CIDR is allowed or not. Valid values: `optional`, `required`, `forbidden`\n\n```js\n// Accept only ipv4 and ipv6 addresses with a CIDR\nconst schema = Joi.string().ip({\n  version: [\n    'ipv4',\n    'ipv6'\n  ],\n  cidr: 'required'\n});\n```\n\nPossible validation errors: [`string.ip`](#stringip), [`string.ipVersion`](#stringipversion)\n\n#### `string.isoDate()`\n\nRequires the string value to be in valid ISO 8601 date format.\n\nIf the validation `convert` option is on (enabled by default), the string will be forced to\nsimplified extended ISO format (ISO 8601). Be aware that this operation uses javascript Date\nobject, which does not support the full ISO format, so a few formats might not pass when using\n`convert`.\n\n```js\nconst schema = Joi.string().isoDate();\nschema.validate('2018-11-28T18:25:32+00:00'); // No Error\nschema.validate('20181-11-28T18:25:32+00:00'); // ValidationError: must be a valid 8601 date\nschema.validate(''); // ValidationError: must be a valid 8601 date\n```\n\nPossible validation errors: [`string.isoDate`](#stringisodate-1)\n\n#### `string.isoDuration()`\n\nRequires the string value to be in valid ISO 8601 duration format.\n\n```js\nconst schema = Joi.string().isoDuration();\nschema.validate('P3Y6M4DT12H30M5S'); // No Error\nschema.validate('2018-11-28T18:25:32+00:00'); // ValidationError: must be a valid ISO 8601 duration\nschema.validate(''); // ValidationError: must be a valid ISO 8601 duration\n```\n\nPossible validation errors: [`string.isoDuration`](#stringisoduration-1)\n\n#### `string.length(limit, [encoding])`\n\nSpecifies the exact string length required where:\n- `limit` - the required string length or a reference.\n- `encoding` - if specified, the string length is calculated in bytes using the provided encoding.\n\n```js\nconst schema = Joi.string().length(5);\n```\n\n```js\nconst schema = Joi.object({\n  length: Joi.string().required(),\n  value: Joi.string().length(Joi.ref('length'), 'utf8').required()\n});\n```\n\nPossible validation errors: [`string.length`](#stringlength), [`string.ref`](#stringref)\n\n#### `string.lowercase()`\n\nRequires the string value to be all lowercase. If the validation `convert` option is on (enabled by default), the string\nwill be forced to lowercase.\n\n```js\nconst schema = Joi.string().lowercase();\n```\n\nPossible validation errors: [`string.lowercase`](#stringlowercase-1)\n\n#### `string.max(limit, [encoding])`\n\nSpecifies the maximum number of string characters where:\n- `limit` - the maximum number of string characters allowed or a reference.\n- `encoding` - if specified, the string length is calculated in bytes using the provided encoding.\n\n```js\nconst schema = Joi.string().max(10);\n```\n\n```js\nconst schema = Joi.object({\n  max: Joi.string().required(),\n  value: Joi.string().max(Joi.ref('max'), 'utf8').required()\n});\n```\n\nPossible validation errors: [`string.max`](#stringmax), [`string.ref`](#stringref)\n\n#### `string.min(limit, [encoding])`\n\nSpecifies the minimum number string characters where:\n- `limit` - the minimum number of string characters required or a reference.\n- `encoding` - if specified, the string length is calculated in bytes using the provided encoding.\n\n```js\nconst schema = Joi.string().min(2);\n```\n\n```js\nconst schema = Joi.object({\n  min: Joi.string().required(),\n  value: Joi.string().min(Joi.ref('min'), 'utf8').required()\n});\n```\n\nPossible validation errors: [`string.min`](#stringmin), [`string.ref`](#stringref)\n\n#### `string.normalize([form])`\n\nRequires the string value to be in a [Unicode normalized](https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/String/normalize)\nform. If the validation `convert` option is on (enabled by default), the string will be normalized.\n\n- `form` - The Unicode normalization form to use. Valid values: `NFC` [default], `NFD`, `NFKC`, `NFKD`\n\n```js\nconst schema = Joi.string().normalize(); // defaults to NFC\nconst schema = Joi.string().normalize('NFC'); // canonical composition\nconst schema = Joi.string().normalize('NFD'); // canonical decomposition\nconst schema = Joi.string().normalize('NFKC'); // compatibility composition\nconst schema = Joi.string().normalize('NFKD'); // compatibility decomposition\n```\n\nPossible validation errors: [`string.normalize`](#stringnormalize)\n\n#### `string.pattern(regex, [name | options])` - aliases: `regex`\n\nDefines a pattern rule where:\n- `regex` - a regular expression object the string value must match against. Note that if the pattern is a regular expression, for it to match the entire key name, it must begin with `^` and end with `$`.\n- `name` - optional name for patterns (useful with multiple patterns).\n- `options` - an optional configuration object with the following supported properties:\n  - `name` - optional pattern name.\n  - `invert` - optional boolean flag. Defaults to `false` behavior. If specified as `true`, the\n    provided pattern will be disallowed instead of required.\n\n```js\nconst schema = Joi.string().pattern(/^[abc]+$/);\n\nconst inlineNamedSchema = Joi.string().pattern(/^[0-9]+$/, 'numbers');\ninlineNamedSchema.validate('alpha'); // ValidationError: \"value\" with value \"alpha\" fails to match the numbers pattern\n\nconst namedSchema = Joi.string().pattern(/^[0-9]+$/, { name: 'numbers'});\nnamedSchema.validate('alpha'); // ValidationError: \"value\" with value \"alpha\" fails to match the numbers pattern\n\nconst invertedSchema = Joi.string().pattern(/^[a-z]+$/, { invert: true });\ninvertedSchema.validate('lowercase'); // ValidationError: \"value\" with value \"lowercase\" matches the inverted pattern: [a-z]\n\nconst invertedNamedSchema = Joi.string().pattern(/^[a-z]+$/, { name: 'alpha', invert: true });\ninvertedNamedSchema.validate('lowercase'); // ValidationError: \"value\" with value \"lowercase\" matches the inverted alpha pattern\n```\n\nPossible validation errors: [`string.pattern.base`](#stringpatternbase), [`string.pattern.invert.base`](#stringpatterninvertbase), [`string.pattern.invert.name`](#stringpatterninvertname), [`string.pattern.name`](#stringpatternname)\n\n#### `string.replace(pattern, replacement)`\n\nReplace characters matching the given _pattern_ with the specified\n_replacement_ string where:\n- `pattern` - a regular expression object to match against, or a string of which _all_ occurrences will be replaced.\n- `replacement` - the string that will replace the pattern.\n\n\n```js\nconst schema = Joi.string().replace(/b/gi, 'x');\nawait schema.validateAsync('abBc');  // return value will be 'axxc'\n```\n\nWhen `pattern` is a _string_ all its occurrences will be replaced.\n\n#### `string.token()`\n\nRequires the string value to only contain a-z, A-Z, 0-9, and underscore _.\n\n```js\nconst schema = Joi.string().token();\n```\n\nPossible validation errors: [`string.token`](#stringtoken-1)\n\n#### `string.trim([enabled])`\n\nRequires the string value to contain no whitespace before or after. If the validation `convert` option is on (enabled by\ndefault), the string will be trimmed.\n\nParameters are:\n- `enabled` - optional parameter defaulting to `true` which allows you to reset the behavior of trim by providing a falsy value.\n\n```js\nconst schema = Joi.string().trim();\nconst schema = Joi.string().trim(false); // disable trim flag\n```\n\nPossible validation errors: [`string.trim`](#stringtrim)\n\n#### `string.truncate([enabled])`\n\nSpecifies whether the `string.max()` limit should be used as a truncation.\n\nParameters are:\n- `enabled` - optional parameter defaulting to `true` which allows you to reset the behavior of truncate by providing a falsy value.\n\n```js\nconst schema = Joi.string().max(5).truncate();\n```\n\n#### `string.uppercase()`\n\nRequires the string value to be all uppercase. If the validation `convert` option is on (enabled by default), the string\nwill be forced to uppercase.\n\n```js\nconst schema = Joi.string().uppercase();\n```\n\nPossible validation errors: [`string.uppercase`](#stringuppercase-1)\n\n#### `string.uri([options])`\n\nRequires the string value to be a valid [RFC 3986](http://tools.ietf.org/html/rfc3986) URI.\n\n- `options` - optional settings:\n    - `scheme` - Specifies one or more acceptable Schemes, should only include the scheme name. Can be an Array or String (strings are automatically escaped for use in a Regular Expression).\n    - `allowRelative` - Allow relative URIs. Defaults to `false`.\n    - `relativeOnly` - Restrict only relative URIs.  Defaults to `false`.\n    - `allowQuerySquareBrackets` - Allows unencoded square brackets inside the query string. This is **NOT** RFC 3986 compliant but query strings like `abc[]=123&abc[]=456` are very common these days. Defaults to `false`.\n    - `domain` - Validate the domain component using the options specified in [`string.domain()`](#stringdomainoptions).\n    - `encodeUri` - When `convert` is true, if the validation fails, attempts to encode the URI using [`encodeURI`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/encodeURI) before validating it again. This allows to provide, for example, unicode URIs, and have it encoded for you. Defaults to `false`.\n\n```js\n// Accept git or git http/https\nconst schema = Joi.string().uri({\n  scheme: [\n    'git',\n    /git\\+https?/\n  ]\n});\n```\n\nPossible validation errors: [`string.uri`](#stringuri), [`string.uriCustomScheme`](#stringuricustomscheme), [`string.uriRelativeOnly`](#stringurirelativeonly), [`string.domain`](#stringdomain)\n\n### `symbol`\n\nGenerates a schema object that matches a `Symbol` data type.\n\nIf the validation `convert` option is on (enabled by default), the mappings declared in `map()` will be tried for an eventual match.\n\nSupports the same methods of the [`any()`](#any) type.\n\n```js\nconst schema = Joi.symbol().map({ 'foo': Symbol('foo'), 'bar': Symbol('bar') });\nawait schema.validateAsync('foo');\n```\n\nPossible validation errors: [`symbol.base`](#symbolbase)\n\n#### `symbol.map(map)`\n\nAllows values to be transformed into `Symbol`s, where:\n- `map` - mapping declaration that can be:\n  - an object, where keys are strings, and values are `Symbol`s\n  - an array of arrays of length 2, where for each sub-array, the 1st element must be anything but an object, a function or a `Symbol`, and the 2nd element must be a Symbol\n  - a `Map`, following the same principles as the array above\n\n```js\nconst schema = Joi.symbol().map([\n    [1, Symbol('one')],\n    ['two', Symbol('two')]\n]);\n```\n\nPossible validation errors: [`symbol.map`](#symbolmap)\n\n## Extensions\n\nBefore writing your own extensions, it is useful to understand how input values are processed. When `validate()` is called, Joi does the following:\n\n- Generates a new schema if the current one contains dynamic construction rules such as `when()` or `link()`.\n- Merges the validation options with the ones passed via [`any.prefs()`](#anyprefsoptions---aliases-preferences-options).\n- Returns the result if caching is enabled and the input value is found in the cache. \n- Runs the `prepare` method defined below. If a validation error is returned, the process will be aborted regardless of `abortEarly`.\n- Coerces the input value using the `coerce` method defined below if `convert` is enabled. If a validation error is returned, the process will be aborted regardless of `abortEarly`.\n- If the input value matches the schema passed to [`any.empty()`](#anyemptyschema), it is converted to `undefined`.\n- Validates presences.\n- Validates allowed/valid/invalid values.\n- Runs base validation using the `validate` method defined below. If a validation error is returned, the process will be aborted regardless of `abortEarly`.\n- Runs validation rules.\n\n**Note that extending schemas do not change the order in which Joi performs the above steps**\n\nThe [`extend()`](#extendextensions) method adds custom types to **joi**. Extensions can be:\n- a single extension object.\n- a factory function generating an extension object.\n\nWhere:\n- `type`: The type of schema. Can be a string, or a regular expression that matches multiple types. \n- `base`: The base schema to extend from. This key is forbidden when `type` is a regular expression.\n- `messages`: A hash of error codes and their messages. To interpolate dynamic values, use the [template syntax](#template-syntax). \n- `flags`: A hash of flag names and their definitions where:\n    - `default`: The default value of the flag. When `describe()` is called and the current flag matches this default value, it will be omitted entirely from the description.\n- `prepare`: A function with signature `function (value, helpers) {}` that prepares the input value (for example, converts `,` to `.` to support multiple decimal representations) where:\n    - `value`: The input value.\n    - `helpers`: [Validation helpers](#validation-helpers)\n\n    Must return an object with one of the following keys:\n    - `value`: The modified value.\n    - `errors`: Validation error(s) generated by `$_createError()` or `helpers.error()`.\n\n    If `errors` is defined, validation will abort regardless of `abortEarly`. Refer to the validation process above for further information.\n- `coerce`: A function with signature `function (value, helpers) {}` that coerces the input value where:\n    - `value`: The input value.\n    - `helpers`: [Validation helpers](#validation-helpers)\n\n    You can also pass an object where:\n    - `from`: The type(s) to convert from. Can be a single string or an array of strings. Joi will only run `method` if the value `typeof` of the input value is equal to one of the provided values.\n    - `method`: A function with signature `function (value, helpers)` that coerces the input value where:\n        - `value`: The input value.\n        - `helpers`: [Validation helpers](#validation-helpers)\n\n    Must return an object with one of the following keys:\n    - `value`: The modified value.\n    - `errors`: Validation error(s) generated by `$_createError()` or `helpers.error()`.\n\n    If `errors` is defined, validation will abort regardless of `abortEarly`. Refer to the validation process above for further information.\n- `validate`: A function with signature `function (value, helpers) {}` that performs base validation on the input value where:\n    - `value`: The input value.\n    - `helpers`: [Validation helpers](#validation-helpers)\n\n    Must return an object with one of the following keys:\n    - `value`: The modified value.\n    - `errors`: Validation error(s) generated by `$_createError()` or `helpers.error()`.\n\n    If `errors` is defined, validation will abort regardless of `abortEarly`. Refer to the validation process above for further information.\n- `rules`: A hash of validation rule names and their implementation where:\n    - `alias`: Aliases of the rule. Can be a string or an array of strings.\n    - `args`: An array of argument names or an object that define the parameters the rule will accept where:\n        - `name`: The argument name.\n        - `ref`: Whether this argument allows references. Joi will resolve them before passing to `validate`. Defaults to `false`.\n        - `assert`: A function of signature `function (value) {}` that validates the argument by returning a boolean. Also accepts a Joi schema. This key is required if `ref` is set to `true`.\n        - `normalize`: A function of signature `function (value) {}` that normalizes the argument before passing it to `assert`.\n        - `message`: A message to throw if `assert` is a function. This key is forbidden if `assert` is a schema.\n    - `convert`: Whether this is a dual rule that converts the input value and validates it at the same time. Defaults to `false`.\n    - `manifest`: Whether this rule should be outputted in the schema's description. Defaults to `true`.\n    - `method`: The method that will be attached onto the schema instance. Useful when you need to set flags. If set to `undefined`, Joi will default to a function that when called will add the rule to the rules queue. If set to `false`, the no method will be added to the instance.\n    - `multi`: Whether this rule can be invoked multiple times. Defaults to `false`.\n    - `validate`: A function of signature `function (value, helpers, args, rule)` that validates the input value where:\n        - `value`: The input value.\n        - `helpers`: [Validation helpers](#validation-helpers)\n        - `args`: Resolved and validated arguments mapped by their names.\n        - `rule`: The rule definitions passed to `$_addRule` left untouched. Useful if you need access to the raw arguments before validation. \n- `overrides`: A hash of method names and their overridden implementation. To refer to the parent method, use [`$_parent()`](#_parentmethod-args)\n\n```js\nconst Joi = require('joi');\n\nconst custom = Joi.extend((joi) => {\n\n    return {\n        type: 'million',\n        base: joi.number(),\n        messages: {\n            'million.base': '{{#label}} must be at least a million',\n            'million.big': '{{#label}} must be at least five millions',\n            'million.round': '{{#label}} must be a round number',\n            'million.dividable': '{{#label}} must be dividable by {{#q}}'\n        },\n        coerce(value, helpers) {\n\n            // Only called when prefs.convert is true\n\n            if (helpers.schema.$_getRule('round')) {\n                return { value: Math.round(value) };\n            }\n        },\n        validate(value, helpers) {\n\n            // Base validation regardless of the rules applied\n\n            if (value < 1000000) {\n                return { value, errors: helpers.error('million.base') };\n            }\n\n            // Check flags for global state\n\n            if (helpers.schema.$_getFlag('big') &&\n                value < 5000000) {\n\n                return { value, errors: helpers.error('million.big') };\n            }\n        },\n        rules: {\n            big: {\n                alias: 'large',\n                method() {\n\n                    return this.$_setFlag('big', true);\n                }\n            },\n            round: {\n                convert: true,              // Dual rule: converts or validates\n                method() {\n\n                    return this.$_addRule('round');\n                },\n                validate(value, helpers, args, options) {\n\n                    // Only called when prefs.convert is false (due to rule convert option)\n\n                    if (value % 1 !== 0) {\n                        return helpers.error('million.round');\n                    }\n                }\n            },\n            dividable: {\n                multi: true,                // Rule supports multiple invocations\n                method(q) {\n\n                    return this.$_addRule({ name: 'dividable', args: { q } });\n                },\n                args: [\n                    {\n                        name: 'q',\n                        ref: true,\n                        assert: (value) => typeof value === 'number' && !isNaN(value),\n                        message: 'must be a number'\n                    }\n                ],\n                validate(value, helpers, args, options) {\n\n                    if (value % args.q === 0) {\n                        return value;       // Value is valid\n                    }\n\n                    return helpers.error('million.dividable', { q: args.q });\n                }\n            },\n            even: {\n                method() {\n\n                    // Rule with only method used to alias another rule\n\n                    return this.dividable(2);\n                }\n            }\n        }\n    };\n});\n\nconst schema = custom.object({\n    a: custom.million().round().dividable(Joi.ref('b')),\n    b: custom.number(),\n    c: custom.million().even().dividable(7),\n    d: custom.million().round().prefs({ convert: false }),\n    e: custom.million().large()\n});\n```\n\n### Validation helpers\n\n- `original`: The original value passed untouched to `validate()`.\n- `prefs`: The prepared validation options.\n- `schema`: The reference to the current schema. Useful if you need to use any of the [Advanced functions](#advanced-functions).\n- `state`: The current validation state. See [Validation state](#validation-state).\n- `error`: A function with signature `function (code, local, localState = currentState) {}` similar to [`$_createError()`](#_createerrorcode-value-local-state-prefs-options) but with the current value, validation options, current state passed where:\n    - `code`: The error code. \n    - `local`: Local context used to interpolate the message.\n    - `localState`: The localized state.\n- `errorsArray`: A function that creates an array that can be recognised by Joi as a valid error array. **Note that using a native JS array can cause Joi to output incorrect results.**\n- `warn`: TODO\n- `message`: TODO\n\n### Validation state\n\nThe validation state is an object that contains information about the validation process such as the current key name of the value and its ancestors. The following methods are supported:\n\n- `localize()`: TODO\n- `nest()`: TODO\n- `shadow()`: TODO\n- `snapshot()`: TODO\n- `restore()`: TODO\n\n### Advanced functions\n\n#### $_root\n\nA reference to the current Joi instance. Useful when you want access to the **extended instance**, not the default Joi module.\n\n#### $_parent(method, ...args)\n\nCalls the original method before overriding, similar to `super.method()` when overriding class methods where:\n- `method`: The name of the parent method.\n- `...args`: The arguments passed directly to the parent method.\n\n#### $_temp\n\nTODO\n\n#### $_terms\n\nTODO\n\n#### $_addRule(options)\n\nAdds a rule to the rules queue where:\n\n- `options`: A rule name string or rule options where:\n    - `name`: The name of the rule.\n    - `args`: The arguments to be processed.\n    - `method`: The name of another rule to reuse.\n    \n    You can also pass extra properties and they will be accessible within the `rule` argument of the `validate` method.\n\n#### $_compile(schema, options)\n\nCompiles a literal schema definition to a Joi schema object where:\n- `schema`: The schema to compile.\n- `options`: TODO\n\n#### $_createError(code, value, local, state, prefs, options)\n\nCreates a Joi validation error where:\n- `code`: The error code.\n- `value`: The current value being validated.\n- `local`: Local context used to interpolate the message.\n- `state`: [Validation state](#validation-state).\n- `prefs`: Prepared validation options.\n- `options`: Error options. TODO\n\n#### $_getFlag(name)\n\nGets a flag named `name`.\n\n#### $_getRule(name)\n\nGets a single (`multi` set to `false`) rule named `name`.\n\n#### $_mapLabels(path)\n\nTODO\n\n#### $_match(value, state, prefs, overrides)\n\nTODO\n\n#### $_modify(options)\n\nTODO\n\n#### $_mutateRebuild()\n\nTODO\n\n#### $_mutateRegister(schema, options)\n\nTODO\n\n#### $_property(name)\n\nTODO\n\n#### $_reach(path)\n\nTODO\n\n#### $_rootReferences()\n\nTODO\n\n#### $_setFlag(name, value, options)\n\nSets a flag where:\n- `name`: The flag name to set.\n- `value`: The value to set the flag to.\n- `options`: Optional options where:\n    - `clone`: Whether to clone the schema. Defaults to `true`. Only set to `false` if the schema has already been cloned before. \n\n#### $_validate(value, state, prefs)\n\nPerforms validation against the current schema without the extra overhead of merging validation options to a default set of values where:\n- `value`: The input value to validate.\n- `state`: [Validation state](#validation-state)\n- `prefs`: The prepared validation options.\n\n**Use this method to perform validation against nested schemas instead of `validate()`**\n\n\n## Errors\n\n### `ValidationError`\n\n**joi** throws or returns `ValidationError` objects containing :\n- `name` - `'ValidationError'`.\n- `isJoi` - `true`.\n- `details` - an array of errors :\n    - `message` - string with a description of the error.\n    - `path` - ordered array where each element is the accessor to the value where the error happened.\n    - `type` - type of the error.\n    - `context` - object providing context of the error containing:\n        - `key` - key of the value that erred, equivalent to the last element of `details.path`.\n        - `label` - label of the value that erred, or the `key` if any, or the default `messages.root`.\n        - `value` - the value that failed validation.\n        - other error specific properties as described for each error code.\n- `annotate()` - function that returns a string with an annotated version of the object pointing at\n  the places where errors occurred. Takes an optional parameter that, if truthy, will strip the\n  colors out of the output.\n\n### List of errors\n\n#### `alternatives.all`\n\nThe value did not match all of the alternative schemas.\n\n#### `alternatives.any`\n\nNo alternative was found to test against the input due to try criteria.\n\n#### `alternatives.match`\n\nNo alternative matched the input due to specific matching rules for at least one of the alternatives.\n\nAdditional local context properties:\n```ts\n{\n    details: Array<object>, // An array of details for each error found while trying to match to each of the alternatives\n    message: string // The combined error messages\n}\n```\n\n#### `alternatives.one`\n\nThe value matched more than one alternative schema.\n\n#### `alternatives.types`\n\nThe provided input did not match any of the allowed types.\n\nAdditional local context properties:\n```ts\n{\n    types: Array<string> // The list of expected types\n}\n```\n\n#### `any.custom`\n\nA custom validation method threw an exception.\n\nAdditional local context properties:\n```ts\n{\n    error: Error // The error thrown\n}\n```\n\n#### `any.default`\n\nIf your [`any.default()`](#anydefaultvalue-description) generator function throws error, you will have it here.\n\nAdditional local context properties:\n```ts\n{\n    error: Error // Error generated during the default value function call\n}\n```\n\n#### `any.failover`\n\nIf your [`any.failover()`](#anyfailovervalue-description) generator function throws error, you will have it here.\n\nAdditional local context properties:\n```ts\n{\n    error: Error // Error generated during the failover value function call\n}\n```\n\n#### `any.invalid`\n\nThe value matched a value listed in the invalid values.\n\nAdditional local context properties:\n```ts\n{\n    invalids: Array<any> // Contains the list of the invalid values that should be rejected\n}\n```\n\n#### `any.only`\n\nOnly some values were allowed, the input didn't match any of them.\n\nAdditional local context properties:\n```ts\n{\n    valids: Array<any> // Contains the list of the valid values that were expected\n}\n```\n\n#### `any.ref`\n\nA reference was used in rule argument and the value pointed to by that reference in the input is not valid.\n\nAdditional local context properties:\n```ts\n{\n    arg: string, // The argument name\n    reason: string, // The reason the referenced value is invalid\n    ref: Reference // Reference used\n}\n```\n\n#### `any.required`\n\nA required value wasn't present.\n\n#### `any.unknown`\n\nA value was present while it wasn't expected.\n\n#### `array.base`\n\nThe value is not of Array type or could not be cast to an Array from a string.\n\n#### `array.excludes`\n\nThe array contains a value that is part of the exclusion list.\n\nAdditional local context properties:\n```ts\n{\n    pos: number // Index where the value was found in the array\n}\n```\n\n#### `array.includesRequiredBoth`\n\nSome values were expected to be present in the array and are missing. This error happens when we have a mix of labeled and unlabeled schemas.\n\nAdditional local context properties:\n```ts\n{\n    knownMisses: Array<string>, // Labels of all the missing values\n    unknownMisses: number // Count of missing values that didn't have a label\n}\n```\n\n#### `array.includesRequiredKnowns`\n\nSome values were expected to be present in the array and are missing. This error happens when we only have labeled schemas.\n\nAdditional local context properties:\n```ts\n{\n    knownMisses: Array<string> // Labels of all the missing values\n}\n```\n\n#### `array.includesRequiredUnknowns`\n\nSome values were expected to be present in the array and are missing. This error happens when we only have unlabeled schemas.\n\nAdditional local context properties:\n```ts\n{\n    unknownMisses: number // Count of missing values that didn't have a label\n}\n```\n\n#### `array.includes`\n\nThe value didn't match any of the allowed types for that array.\n\nAdditional local context properties:\n```ts\n{\n    pos: number // Index where the value was found in the array\n}\n```\n\n#### `array.length`\n\nThe array is not of the expected length.\n\nAdditional local context properties:\n```ts\n{\n    limit: number // Length that was expected for this array\n}\n```\n\n#### `array.max`\n\nThe array has more elements than the maximum allowed.\n\nAdditional local context properties:\n```ts\n{\n    limit: number // Maximum length that was expected for this array\n}\n```\n\n#### `array.min`\n\nThe array has less elements than the minimum allowed.\n\nAdditional local context properties:\n```ts\n{\n    limit: number // Minimum length that was expected for this array\n}\n```\n\n#### `array.orderedLength`\n\nGiven an [`array.ordered()`](#arrayorderedtype), that array has more elements than it should.\n\nAdditional local context properties:\n```ts\n{\n    pos: number, // Index where the value was found in the array\n    limit: number // Maximum length that was expected for this array\n}\n```\n\n#### `array.sort`\n\nThe array did not match the required sort order.\n\nAdditional local context properties:\n```ts\n{\n    order: string, // 'ascending' or 'descending'\n    by: string // The object key used for comparison\n}\n```\n\n#### `array.sort.mismatching`\n\nFailed sorting the array due to mismatching item types.\n\n#### `array.sort.unsupported`\n\nFailed sorting the array due to unsupported item types.\n\nAdditional local context properties:\n```ts\n{\n    type: string // The unsupported array item type\n}\n```\n\n#### `array.sparse`\n\nAn `undefined` value was found in an array that shouldn't be sparse.\n\nAdditional local context properties:\n```ts\n{\n    pos: number // Index where an undefined value was found in the array\n}\n```\n\n#### `array.unique`\n\nA duplicate value was found in an array.\n\nAdditional local context properties:\n```ts\n{\n    pos: number, // Index where the duplicate value was found in the array\n    dupePos: number, // Index where the first appearance of the duplicate value was found in the array\n    dupeValue: any // Value with which the duplicate was met\n}\n```\n\n#### `array.hasKnown`\n\nThe schema on an [`array.has()`](#arrayhas) was not found in the array. This error happens when the schema is labeled.\n\nAdditional local context properties:\n```ts\n{\n    patternLabel: string // Label of assertion schema\n}\n```\n\n#### `array.hasUnknown`\n\nThe schema on an [`array.has()`](#arrayhas) was not found in the array. This error happens when the schema is unlabeled.\n\n#### `binary.base`\n\nThe value is either not a Buffer or could not be cast to a Buffer from a string.\n\n#### `binary.length`\n\nThe buffer was not of the specified length.\n\nAdditional local context properties:\n```ts\n{\n    limit: number // Length that was expected for this buffer\n}\n```\n\n#### `binary.max`\n\nThe buffer contains more bytes than expected.\n\nAdditional local context properties:\n```ts\n{\n    limit: number // Maximum length that was expected for this buffer\n}\n```\n\n#### `binary.min`\n\nThe buffer contains less bytes than expected.\n\nAdditional local context properties:\n```ts\n{\n    limit: number // Minimum length that was expected for this buffer\n}\n```\n\n#### `boolean.base`\n\nThe value is either not a boolean or could not be cast to a boolean from one of the truthy or falsy values.\n\n#### `date.base`\n\nThe value is either not a date or could not be cast to a date from a string or a number.\n\n#### `date.format`\n\nThe date does not match the required format.\n\nAdditional local context properties:\n```ts\n{\n    format: string // The required format\n}\n```\n\n#### `date.greater`\n\nThe date is over the limit that you set.\n\nAdditional local context properties:\n```ts\n{\n    limit: Date // Maximum date\n}\n```\n\n#### `date.less`\n\nThe date is under the limit that you set.\n\nAdditional local context properties:\n```ts\n{\n    limit: Date // Minimum date\n}\n```\n\n#### `date.max`\n\nThe date is over or equal to the limit that you set.\n\nAdditional local context properties:\n```ts\n{\n    limit: Date // Maximum date\n}\n```\n\n#### `date.min`\n\nThe date is under or equal to the limit that you set.\n\nAdditional local context properties:\n```ts\n{\n    limit: Date // Minimum date\n}\n```\n\n#### `date.strict`\n\nOccurs when the input is not a Date type and `convert` is disabled.\n\n#### `function.arity`\n\nThe number of arguments for the function doesn't match the required number.\n\nAdditional local context properties:\n```ts\n{\n    n: number // Expected arity\n}\n```\n\n#### `function.class`\n\nThe input is not a JavaScript class.\n\n#### `function.maxArity`\n\nThe number of arguments for the function is over the required number.\n\nAdditional local context properties:\n```ts\n{\n    n: number // Maximum expected arity\n}\n```\n\n#### `function.minArity`\n\nThe number of arguments for the function is under the required number.\n\nAdditional local context properties:\n```ts\n{\n    n: number // Minimum expected arity\n}\n```\n\n#### `number.base`\n\nThe value is not a number or could not be cast to a number.\n\n#### `number.greater`\n\nThe number is lower or equal to the limit that you set.\n\nAdditional local context properties:\n```ts\n{\n    limit: number // Minimum value that was expected for this number\n}\n```\n\n#### `number.infinity`\n\nThe number is `Infinity` or `-Infinity`.\n\n#### `number.integer`\n\nThe number is not a valid integer.\n\n#### `number.less`\n\nThe number is higher or equal to the limit that you set.\n\nAdditional local context properties:\n```ts\n{\n    limit: number // Maximum value that was expected for this number\n}\n```\n\n#### `number.max`\n\nThe number is higher than the limit that you set.\n\nAdditional local context properties:\n```ts\n{\n    limit: number // Maximum value that was expected for this number\n}\n```\n\n#### `number.min`\n\nThe number is lower than the limit that you set.\n\nAdditional local context properties:\n```ts\n{\n    limit: number // Minimum value that was expected for this number\n}\n```\n\n#### `number.multiple`\n\nThe number could not be divided by the multiple you provided.\n\nAdditional local context properties:\n```ts\n{\n    multiple: number // The number of which the input is supposed to be a multiple of\n}\n```\n\n#### `number.negative`\n\nThe number was positive.\n\n#### `number.port`\n\nThe number didn't look like a port number.\n\n#### `number.positive`\n\nThe number was negative.\n\n#### `number.precision`\n\nThe number didn't have the required precision.\n\nAdditional local context properties:\n```ts\n{\n    limit: number // The precision that it should have had\n}\n```\n\n#### `number.unsafe`\n\nThe number is not within the safe range of JavaScript numbers.\n\n#### `object.unknown`\n\nAn unexpected property was found in the object.\n\nAdditional local context properties:\n```ts\n{\n    child: string // Property that is unexpected\n}\n```\n\n#### `object.and`\n\nThe AND condition between the properties you specified was not satisfied in that object.\n\nAdditional local context properties:\n```ts\n{\n    present: Array<string>, // List of properties that are set\n    presentWithLabels: Array<string>, // List of labels for the properties that are set\n    missing: Array<string>, // List of properties that are not set\n    missingWithLabels: Array<string> // List of labels for the properties that are not set\n}\n```\n\n#### `object.assert`\n\nThe schema on an [`object.assert()`](#objectassertref-schema-message) failed to validate.\n\nAdditional local context properties:\n```ts\n{\n    subject: object, // The assertion subject. When it is a reference, use subject.key for the display path.\n    message: string // Custom message when provided\n}\n```\n\n#### `object.base`\n\nThe value is not of the expected type.\n\nAdditional local context properties:\n```ts\n{\n    type: string // The expected type\n}\n```\n\n#### `object.length`\n\nThe number of keys for this object is not of the expected length.\n\nAdditional local context properties:\n```ts\n{\n    limit: number // Number of keys that was expected for this object\n}\n```\n\n#### `object.max`\n\nThe number of keys for this object is over or equal to the limit that you set.\n\nAdditional local context properties:\n```ts\n{\n    limit: number // Maximum number of keys\n}\n```\n\n#### `object.min`\n\nThe number of keys for this object is under or equal to the limit that you set.\n\nAdditional local context properties:\n```ts\n{\n    limit: number // Minimum number of keys\n}\n```\n\n#### `object.missing`\n\nThe OR or XOR condition between the properties you specified was not satisfied in that object, none of them were set.\n\nAdditional local context properties:\n```ts\n{\n    peers: Array<string>, // List of properties where none of them were set\n    peersWithLabels: Array<string> // List of labels for the properties where none of them were set\n}\n```\n\n#### `object.nand`\n\nThe NAND condition between the properties you specified was not satisfied in that object.\n\nAdditional local context properties:\n```ts\n{\n    main: string, // One of the properties that was present\n    mainWithLabel: string, // The label of the `main` property\n    peers: Array<string>, // List of the other properties that were present\n    peersWithLabels: Array<string> // List of the labels of the other properties that were present\n}\n```\n\n#### `object.pattern.match`\n\nThe object keys failed to match a pattern's matches requirement.\n\nAdditional local context properties:\n```ts\n{\n    details: Array<object>, // An array of details for each error found while trying to match to each of the alternatives\n    message: string, // The combined error messages\n    matches: Array<string>  // The matching keys\n}\n```\n\n#### `object.refType`\n\nThe object is not a [`Joi.ref()`](#refkey-options).\n\n#### `object.regex`\n\nThe object is not a `RegExp` object.\n\n#### `object.rename.multiple`\n\nAnother rename was already done to the same target property.\n\nAdditional local context properties:\n```ts\n{\n    from: string, // Origin property name of the rename\n    to: string, // Target property of the rename\n    pattern: boolean // Indicates if the rename source was a pattern (regular expression)\n}\n```\n\n#### `object.rename.override`\n\nThe target property already exists and you disallowed overrides.\n\nAdditional local context properties:\n```ts\n{\n    from: string, // Origin property name of the rename\n    to: string, // Target property of the rename\n    pattern: boolean // Indicates if the rename source was a pattern (regular expression)\n}\n```\n\n#### `object.schema`\n\nThe object was not a **joi** schema.\n\nAdditional local context properties:\n```ts\n{\n    type: string // The required schema\n}\n```\n\n#### `object.instance`\n\nThe object is not of the type you specified.\n\nAdditional local context properties:\n```ts\n{\n    type: string // Type name the object should have been\n}\n```\n\n#### `object.with`\n\nProperty that should have been present at the same time as another one was missing.\n\nAdditional local context properties:\n```ts\n{\n    main: string, // Property that triggered the check\n    mainWithLabel: string, // Label of the property that triggered the check\n    peer: string, // Property that was missing\n    peerWithLabels: string // Label of the other property that was missing\n}\n```\n\n#### `object.without`\n\nProperty that should have been absent at the same time as another one was present.\n\nAdditional local context properties:\n```ts\n{\n    main: string, // Property that triggered the check\n    mainWithLabel: string, // Label of the property that triggered the check\n    peer: string, // Property that was present\n    peerWithLabels: string // Label of the other property that was present\n}\n```\n\n#### `object.xor`\n\nThe XOR condition between the properties you specified was not satisfied in that object.\n\nAdditional local context properties:\n```ts\n{\n    peers: Array<string>, // List of properties where none of it or too many of it was set\n    peersWithLabels: Array<string> // List of labels for the properties where none of it or too many of it was set\n}\n```\n\n#### `object.oxor`\n\nThe optional XOR condition between the properties you specified was not satisfied in that object.\n\nAdditional local context properties:\n```ts\n{\n    peers: Array<string>, // List of properties where too many of it was set\n    peersWithLabels: Array<string> // List of labels for the properties where too many of it was set\n}\n```\n\n#### `string.alphanum`\n\nThe string doesn't only contain alphanumeric characters.\n\n#### `string.base64`\n\nThe string isn't a valid base64 string.\n\n#### `string.base`\n\nThe input is not a string.\n\n#### `string.creditCard`\n\nThe string is not a valid credit card number.\n\n#### `string.dataUri`\n\nThe string is not a valid data URI.\n\n#### `string.domain`\n\nThe string is not a valid domain name.\n\n#### `string.email`\n\nThe string is not a valid e-mail.\n\nAdditional local context properties:\n```ts\n{\n    invalids: [string] // Array of invalid emails\n}\n```\n\n#### `string.empty`\n\nWhen an empty string is found and denied by invalid values.\n\n#### `string.guid`\n\nThe string is not a valid GUID.\n\n#### `string.hexAlign`\n\nThe string contains hexadecimal characters but they are not byte-aligned.\n\n#### `string.hex`\n\nThe string is not a valid hexadecimal string.\n\n#### `string.hostname`\n\nThe string is not a valid hostname.\n\n#### `string.ipVersion`\n\nThe string is not a valid IP address considering the provided constraints.\n\nAdditional local context properties:\n```ts\n{\n    cidr: string, // CIDR used for the validation\n    version: Array<string> // List of IP version accepted\n}\n```\n\n#### `string.ip`\n\nThe string is not a valid IP address.\n\nAdditional local context properties:\n```ts\n{\n    cidr: string // CIDR used for the validation\n}\n```\n\n#### `string.isoDate`\n\nThe string is not a valid ISO date string.\n\n#### `string.isoDuration`\n\nThe string must be a valid ISO 8601 duration.\n\n#### `string.length`\n\nThe string is not of the expected length.\n\nAdditional local context properties:\n```ts\n{\n    limit: number, // Length that was expected for this string\n    encoding: undefined | string // Encoding specified for the check if any\n}\n```\n\n#### `string.lowercase`\n\nThe string isn't all lower-cased.\n\n#### `string.max`\n\nThe string is longer than expected.\n\nAdditional local context properties:\n```ts\n{\n    limit: number, // Maximum length that was expected for this string\n    encoding: undefined | string // Encoding specified for the check if any\n}\n```\n\n#### `string.min`\n\nThe string is shorter than expected.\n\nAdditional local context properties:\n```ts\n{\n    limit: number, // Minimum length that was expected for this string\n    encoding: undefined | string // Encoding specified for the check if any\n}\n```\n\n#### `string.normalize`\n\nThe string isn't valid in regards of the normalization form expected.\n\nAdditional local context properties:\n```ts\n{\n    form: string // Normalization form that is expected\n}\n```\n\n#### `string.pattern.base`\n\nThe string didn't match the regular expression.\n\nAdditional local context properties:\n```ts\n{\n    name: undefined, // Undefined since the regular expression has no name\n    pattern: string // Regular expression\n}\n```\n\n#### `string.pattern.name`\n\nThe string didn't match the named regular expression.\n\nAdditional local context properties:\n```ts\n{\n    name: string, // Name of the regular expression\n    pattern: string // Regular expression\n}\n```\n\n#### `string.pattern.invert.base`\n\nThe string matched the regular expression while it shouldn't.\n\nAdditional local context properties:\n```ts\n{\n    name: undefined, // Undefined since the regular expression has no name\n    pattern: string // Regular expression\n}\n```\n\n#### `string.pattern.invert.name`\n\nThe string matched the named regular expression while it shouldn't.\n\nAdditional local context properties:\n```ts\n{\n    name: string, // Name of the regular expression\n    pattern: string // Regular expression\n}\n```\n\n#### `string.token`\n\nThe string isn't a token.\n\n#### `string.trim`\n\nThe string contains whitespace around it.\n\n#### `string.uppercase`\n\nThe string isn't all upper-cased.\n\n#### `string.uri`\n\nThe string isn't a valid URI.\n\n#### `string.uriCustomScheme`\n\nThe string isn't a valid URI considering the custom schemes.\n\nAdditional local context properties:\n```ts\n{\n    scheme: string // Scheme prefix that is expected in the URI\n}\n```\n\n#### `string.uriRelativeOnly`\n\nThe string is a valid relative URI.\n\n#### `symbol.base`\n\nThe input is not a Symbol.\n\n#### `symbol.map`\n\nThe input is not a Symbol or could not be converted to one.\n"
  },
  {
    "path": "LICENSE.md",
    "content": "Copyright (c) 2012-2022, Project contributors.\nCopyright (c) 2012-2022, Sideway. Inc.\nCopyright (c) 2012-2014, Walmart.\nAll rights reserved.\n\nRedistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:\n* Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.\n* 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.\n* The names of any contributors may not be used to endorse or promote products derived from this software without specific prior written permission.\n\nTHIS 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.\n"
  },
  {
    "path": "README.md",
    "content": "# joi\n\n#### The most powerful schema description language and data validator for JavaScript.\n\n## Installation\n`npm install joi`\n\n### Visit the [joi.dev](https://joi.dev) Developer Portal for tutorials, documentation, and support\n\n## Useful resources\n\n- [Documentation and API](https://joi.dev/api/)\n- [Versions status](https://joi.dev/resources/status/#joi)\n- [Changelog](https://joi.dev/resources/changelog/)\n- [Project policies](https://joi.dev/policies/)\n"
  },
  {
    "path": "benchmarks/.gitignore",
    "content": "results.json\n\n"
  },
  {
    "path": "benchmarks/.npmrc",
    "content": "package-lock=true\n"
  },
  {
    "path": "benchmarks/README.md",
    "content": "# Joi benchmarks\n\nThe benchmarks in this folder are there to do performance regression testing. This is not to compare joi to some other library as this is most of the time meaningless.\n\nRun it first with `npm run bench-update` to establish a baseline then run `npm run bench` or `npm test` to compare your modifications to the baseline.\n\nSignificant (> 10% by default) are put in colors in the report, the rest should be fairly obvious.\n"
  },
  {
    "path": "benchmarks/bench.js",
    "content": "'use strict';\n\nconst Fs = require('fs');\n\nconst { assert } = require('@hapi/hoek');\nconst Benchmark = require('benchmark');\nconst Bossy = require('@hapi/bossy');\nconst Chalk = require('chalk');\nconst CliTable = require('cli-table');\nconst D3 = require('d3-format');\n\n\nconst definition = {\n    c: {\n        alias: 'compare',\n        type: 'string'\n    },\n    s: {\n        alias: 'save',\n        type: 'string'\n    },\n    t: {\n        alias: 'threshold',\n        type: 'number',\n        default: 10\n    },\n    j: {\n        alias: 'joi',\n        type: 'string',\n        default: '..'\n    }\n};\n\nconst args = Bossy.parse(definition);\n\nlet compare;\nif (args.compare) {\n    try {\n        compare = JSON.parse(Fs.readFileSync(args.compare, 'utf8'));\n    }\n    catch {\n        // Ignore error\n    }\n}\n\nconst formats = {\n    number: D3.format(',d'),\n    percentage: D3.format('.2f'),\n    integer: D3.format(',')\n};\n\nBenchmark.options.minSamples = 100;\n\nconst Joi = require(args.joi);\n\nconst Suite = new Benchmark.Suite('joi');\n\nconst versionPick = (o) => {\n\n    if (typeof o === 'function') {\n        return o;\n    }\n\n    for (const k of Object.keys(o)) {\n        if (Joi.version.startsWith(k)) {\n            return o[k];\n        }\n    }\n\n    throw new Error(`Unsupported version ${Joi.version}`);\n};\n\nconst test = ([name, initFn, testFn]) => {\n\n    const [schema, valid, invalid] = versionPick(initFn)();\n\n    assert(valid === undefined || !testFn(schema, valid).error, 'validation must not fail for: ' + name);\n    assert(invalid === undefined || testFn(schema, invalid).error, 'validation must fail for: ' + name);\n\n    testFn = versionPick(testFn);\n    Suite.add(name + (valid !== undefined ? ' (valid)' : ''), () => {\n\n        testFn(schema, valid);\n    });\n\n    if (invalid !== undefined) {\n        Suite.add(name + ' (invalid)', () => {\n\n            testFn(schema, invalid);\n        });\n    }\n};\n\nrequire('./suite')(Joi).forEach(test);\n\nSuite\n    .on('complete', (benches) => {\n\n        const report = benches.currentTarget.map((bench) => {\n\n            const { name, hz, stats, error } = bench;\n            return { name, hz, rme: stats.rme, size: stats.sample.length, error };\n        });\n\n        if (args.save) {\n            Fs.writeFileSync(args.save, JSON.stringify(report, null, 2), 'utf8');\n        }\n\n        const tableDefinition = {\n            head: [Chalk.blue('Name'), '', Chalk.yellow('Ops/sec'), Chalk.yellow('MoE'), Chalk.yellow('Sample size')],\n            colAligns: ['left', '', 'right', 'right', 'right']\n        };\n\n        if (compare) {\n            tableDefinition.head.push('', Chalk.cyan('Previous ops/sec'), Chalk.cyan('Previous MoE'), Chalk.cyan('Previous sample size'), '', Chalk.whiteBright('% difference'));\n            tableDefinition.colAligns.push('', 'right', 'right', 'right', '', 'right');\n        }\n\n        const table = new CliTable(tableDefinition);\n\n        table.push(...report.map((s) => {\n\n            const row = [\n                s.error ? Chalk.redBright(s.name) : s.name,\n                '',\n                formats.number(s.hz),\n                `± ${formats.percentage(s.rme)} %`,\n                formats.integer(s.size)\n            ];\n\n            if (compare) {\n                const previousRun = compare.find((run) => run.name === s.name);\n                if (previousRun) {\n                    const difference = s.hz - previousRun.hz;\n                    const percentage = 100 * difference / previousRun.hz;\n                    const isSignificant = Math.abs(percentage) > args.threshold;\n                    const formattedDifference = `${percentage > 0 ? '+' : ''}${formats.percentage(percentage)} %`;\n                    row.push(\n                        '',\n                        formats.number(previousRun.hz),\n                        `± ${formats.percentage(previousRun.rme)} %`,\n                        formats.integer(previousRun.size),\n                        '',\n                        isSignificant\n                            ? Chalk[difference > 0 ? 'green' : 'red'](formattedDifference)\n                            : formattedDifference\n                    );\n                }\n            }\n\n            return row;\n        }));\n\n        console.log(table.toString());\n\n        const errors = report.filter((s) => s.error);\n        if (errors.length) {\n            console.log(Chalk.redBright.underline.bold('\\nErrors:'));\n            console.log(errors.map((e) => `> ${Chalk.italic(e.name)}\\n${e.error.stack}`).join('\\n'));\n        }\n    });\n\nSuite.run();\n"
  },
  {
    "path": "benchmarks/package.json",
    "content": "{\n  \"name\": \"benchmarks\",\n  \"scripts\": {\n    \"test\": \"npm run bench\",\n    \"bench\": \"node ./bench.js --compare results.json\",\n    \"bench-update\": \"npm run bench -- --save results.json\",\n    \"full-bench\": \"npm run bench-update -- --joi @hapi/joi && npm test\"\n  },\n  \"dependencies\": {\n    \"@hapi/bossy\": \"^6.0.1\",\n    \"@hapi/hoek\": \"^11.0.2\",\n    \"@hapi/joi\": \"^15.1.0\",\n    \"benchmark\": \"^2.1.4\",\n    \"chalk\": \"^2.4.1\",\n    \"cli-table\": \"^0.3.11\",\n    \"d3-format\": \"^1.3.2\",\n    \"joi\": \"^17.8.1\"\n  }\n}\n"
  },
  {
    "path": "benchmarks/suite.js",
    "content": "'use strict';\n\nmodule.exports = (Joi) => [\n    [\n        'Simple object',\n        () => [\n            Joi.object({\n                id: Joi.string().required(),\n                level: Joi.string()\n                    .valid('debug', 'info', 'notice')\n                    .required()\n            }).unknown(false),\n            { id: '1', level: 'info' },\n            { id: '2', level: 'warning' }\n        ],\n        (schema, value) => schema.validate(value, { convert: false })\n    ],\n    [\n        'Simple object with inlined prefs',\n        {\n            15: () => [\n                Joi.object({\n                    id: Joi.string().required(),\n                    level: Joi.string()\n                        .valid('debug', 'info', 'notice')\n                        .required()\n                }).unknown(false).options({ convert: false }),\n                { id: '1', level: 'info' },\n                { id: '2', level: 'warning' }\n            ],\n            16: () => [\n                Joi.object({\n                    id: Joi.string().required(),\n                    level: Joi.string()\n                        .valid('debug', 'info', 'notice')\n                        .required()\n                }).unknown(false).prefs({ convert: false }),\n                { id: '1', level: 'info' },\n                { id: '2', level: 'warning' }\n            ],\n            17: () => [\n                Joi.object({\n                    id: Joi.string().required(),\n                    level: Joi.string()\n                        .valid('debug', 'info', 'notice')\n                        .required()\n                }).unknown(false).prefs({ convert: false }),\n                { id: '1', level: 'info' },\n                { id: '2', level: 'warning' }\n            ]\n        },\n        (schema, value) => schema.validate(value)\n    ],\n    [\n        'Schema creation',\n        () => [],\n        {\n            15: () =>\n\n                Joi.object({\n                    foo: Joi.array().items(\n                        Joi.boolean().required(),\n                        Joi.string().allow(''),\n                        Joi.symbol()\n                    ).single().sparse().required(),\n                    bar: Joi.number().min(12).max(353).default(56).positive(),\n                    baz: Joi.date().timestamp('unix'),\n                    qux: [Joi.func().minArity(12).strict(), Joi.binary().max(345)],\n                    quxx: Joi.string().ip({ version: ['ipv6'] }),\n                    quxxx: [554, 'azerty', true]\n                })\n                    .xor('foo', 'bar')\n                    .or('bar', 'baz')\n                    .pattern(/b/, Joi.when('a', {\n                        is: true,\n                        then: Joi.options({ language: { 'any.required': 'oops' } })\n                    }))\n                    .meta('foo')\n                    .strip()\n                    .default(() => 'foo', 'Def')\n                    .optional(),\n            16: () =>\n\n                Joi.object({\n                    foo: Joi.array().items(\n                        Joi.boolean().required(),\n                        Joi.string().allow(''),\n                        Joi.symbol()\n                    ).single().sparse().required(),\n                    bar: Joi.number().min(12).max(353).default(56).positive(),\n                    baz: Joi.date().timestamp('unix'),\n                    qux: [Joi.function().minArity(12).strict(), Joi.binary().max(345)],\n                    quxx: Joi.string().ip({ version: ['ipv6'] }),\n                    quxxx: [554, 'azerty', true]\n                })\n                    .xor('foo', 'bar')\n                    .or('bar', 'baz')\n                    .pattern(/b/, Joi.when('a', {\n                        is: true,\n                        then: Joi.prefs({ messages: { 'any.required': 'oops' } })\n                    }))\n                    .meta('foo')\n                    .strip()\n                    .default(() => 'foo')\n                    .optional(),\n\n            17: () =>\n\n                Joi.object({\n                    foo: Joi.array().items(\n                        Joi.boolean().required(),\n                        Joi.string().allow(''),\n                        Joi.symbol()\n                    ).single().sparse().required(),\n                    bar: Joi.number().min(12).max(353).default(56).positive(),\n                    baz: Joi.date().timestamp('unix'),\n                    qux: [Joi.function().minArity(12).strict(), Joi.binary().max(345)],\n                    quxx: Joi.string().ip({ version: ['ipv6'] }),\n                    quxxx: [554, 'azerty', true]\n                })\n                    .xor('foo', 'bar')\n                    .or('bar', 'baz')\n                    .pattern(/b/, Joi.when('a', {\n                        is: true,\n                        then: Joi.prefs({ messages: { 'any.required': 'oops' } })\n                    }))\n                    .meta('foo')\n                    .strip()\n                    .default(() => 'foo')\n                    .optional()\n        }\n    ],\n    [\n        'Schema creation with long valid() list',\n        () => {\n\n            const list = [];\n            for (let i = 10000; i < 50000; ++i) {\n                list.push(i.toString());\n            }\n\n            return [list.filter((x) => !['12345', '23456', '34567', '456789'].includes(x))];\n        },\n        (list) => Joi.object().keys({ foo: Joi.string().valid(...list) })\n    ],\n    [\n        'String with long valid() list',\n        () => {\n\n            const list = [];\n            for (let i = 10000; i < 50000; ++i) {\n                list.push(i.toString());\n            }\n\n            const schema = Joi.string().valid(...list);\n\n            let i = 0;\n            const value = () => {\n\n                return `${10000 + (++i % 40000)}`;\n            };\n\n            return [schema, value, () => '5000'];\n        },\n        (schema, value) => schema.validate(value())\n    ],\n    [\n        'Complex object',\n        () =>\n            [\n                Joi.object({\n                    id: Joi.number()\n                        .min(0)\n                        .max(100)\n                        .required(),\n\n                    level: Joi.string()\n                        .min(1)\n                        .max(100)\n                        .lowercase()\n                        .required(),\n\n                    tags: Joi.array()\n                        .items(Joi.boolean())\n                        .min(2)\n                })\n                    .unknown(false),\n                { id: 1, level: 'info', tags: [true, false] }\n            ],\n        (schema, value) => schema.validate(value)\n    ],\n    [\n        'Dependency validation',\n        () => [\n            Joi.object({\n                'a': Joi.string(),\n                'b': Joi.string()\n            }),\n            { a: 'foo', b: 'bar' }\n        ],\n        (schema, value) => schema.validate(value)\n    ],\n    [\n        'Parsing of exponential numbers',\n        () => [\n            Joi.number(),\n            '+001231.0133210e003',\n            '90071992547409811e-1'\n        ],\n        (schema, value) => schema.validate(value)\n    ]\n];\n"
  },
  {
    "path": "browser/.gitignore",
    "content": "stats.json\n\n"
  },
  {
    "path": "browser/.npmrc",
    "content": "package-lock=false\n"
  },
  {
    "path": "browser/karma.conf.js",
    "content": "const Path = require('path');\n\nconst WebpackMocha = require('./webpack.mocha');\n\nconst internals = {\n    libs: Path.join(__dirname, '../lib/**/*.js'),\n    tests: Path.join(__dirname, './tests/**/*.js')\n};\n\nmodule.exports = function (config) {\n    config.set({\n        basePath: '',\n        frameworks: ['mocha'],\n        files: [\n            internals.libs,\n            internals.tests\n        ],\n        preprocessors: {\n            [internals.libs]: ['webpack', 'sourcemap'],\n            [internals.tests]: ['webpack', 'sourcemap']\n        },\n        reporters: ['progress'],\n        port: 9876,\n        colors: true,\n        logLevel: config.LOG_ERROR,\n        autoWatch: true,\n        browsers: ['ChromeHeadless'],\n        singleRun: true,\n        concurrency: Infinity,\n        webpack: WebpackMocha,\n        webpackMiddleware: {\n            noInfo: true,\n            stats: {\n                chunks: false\n            }\n        },\n    })\n};\n"
  },
  {
    "path": "browser/lib/version-loader.js",
    "content": "'use strict';\n\nconst Pkg = require('../../package.json');\n\n\nconst internals = {};\n\n\nmodule.exports = function () {\n\n    return `{ \"version\": \"${Pkg.version}\" }`;\n};\n"
  },
  {
    "path": "browser/package.json",
    "content": "{\n    \"scripts\": {\n        \"build\": \"webpack --mode production\",\n        \"build-dev\": \"webpack --mode development\",\n        \"build-analyze\": \"webpack --mode production --stats-optimization-bailout --profile --json > stats.json\",\n        \"postbuild-analyze\": \"webpack-bundle-analyzer stats.json\",\n        \"test\": \"karma start\"\n    },\n    \"devDependencies\": {\n        \"@babel/core\": \"^7.21.0\",\n        \"@babel/plugin-proposal-class-properties\": \"^7.18.6\",\n        \"@babel/preset-env\": \"^7.20.2\",\n        \"@mixer/webpack-bundle-compare\": \"^0.1.1\",\n        \"assert\": \"^2.0.0\",\n        \"babel-loader\": \"^9.1.2\",\n        \"karma\": \"^6.4.1\",\n        \"karma-chrome-launcher\": \"^3.1.1\",\n        \"karma-mocha\": \"^2.0.1\",\n        \"karma-sourcemap-loader\": \"^0.4.0\",\n        \"karma-webpack\": \"^5.0.0\",\n        \"mocha\": \"^8.4.0\",\n        \"mocha-loader\": \"^5.1.5\",\n        \"util\": \"^0.12.5\",\n        \"webpack\": \"^5.76.1\",\n        \"webpack-bundle-analyzer\": \"^3.4.1\",\n        \"webpack-cli\": \"^5.0.1\"\n    }\n}\n"
  },
  {
    "path": "browser/tests/index.js",
    "content": "const Assert = require('assert');\n\nconst Joi = require('../..');\n\n\ndescribe('Joi', () => {\n\n    it('should be able to create schemas', () => {\n\n        Joi.boolean().truthy('true');\n        Joi.number().min(5).max(10).multiple(2);\n        Joi.array().items(Joi.number().required());\n        Joi.object({\n            key: Joi.string().required()\n        });\n    });\n\n    it('should be able to validate data', () => {\n\n        const schema = Joi.string().min(5);\n        Assert.ok(!schema.validate('123456').error);\n        Assert.ok(schema.validate('123').error);\n    });\n\n    it('fails using binary', () => {\n\n        Assert.throws(() => Joi.binary().min(1));\n        Assert.strictEqual(Joi.binary, undefined);\n    });\n\n    it('validates email', () => {\n\n        const schema = Joi.string().email({ tlds: false }).required();\n        Assert.ok(!schema.validate('test@example.com').error);\n        Assert.ok(schema.validate('test@example.com ').error);\n        Assert.ok(!schema.validate('伊昭傑@郵件.商務').error);\n\n        const schema2 = Joi.string().email({ tlds: { allow: false } }).required();\n        Assert.ok(!schema2.validate('test@example.com').error);\n        Assert.ok(schema2.validate('test@example.com ').error);\n        Assert.ok(!schema2.validate('伊昭傑@郵件.商務').error);\n    });\n\n    it('validates domain', () => {\n\n        const schema = Joi.string().domain().required();\n        Assert.ok(!schema.validate('example.com').error);\n        Assert.ok(schema.validate('example.com ').error);\n        Assert.ok(!schema.validate('example.商務').error);\n\n        const schema2 = Joi.string().domain({ tlds: { allow: false } }).required();\n        Assert.ok(!schema2.validate('example.com').error);\n        Assert.ok(schema2.validate('example.com ').error);\n        Assert.ok(!schema2.validate('example.商務').error);\n    });\n});\n"
  },
  {
    "path": "browser/webpack.config.js",
    "content": "'use strict';\n\nconst Path = require('path');\n\nconst Webpack = require('webpack');\nconst { BundleComparisonPlugin } = require('@mixer/webpack-bundle-compare');\n\n\nmodule.exports = {\n    entry: '../lib/index.js',\n    output: {\n        filename: 'joi-browser.min.js',\n        path: Path.join(__dirname, '../dist'),\n        library: 'joi',\n        libraryTarget: 'umd'\n    },\n    plugins: [\n        new Webpack.DefinePlugin({\n            Buffer: false\n        }),\n        new BundleComparisonPlugin({\n            file: '../stats.msp.gz',\n            format: 'msgpack',\n            gzip: true,\n        })\n    ],\n    module: {\n        rules: [\n            {\n                use: './lib/version-loader',\n                include: [\n                    Path.join(__dirname, '../package.json')\n                ]\n            },\n            {\n                test: /\\.js$/,\n                use: {\n                    loader: 'babel-loader',\n                    options: {\n                        presets: [\n                            [\n                                '@babel/preset-env',\n                                {\n                                    'targets': '> 1%, not IE 11, not dead'\n                                }\n                            ]\n                        ],\n                        plugins: [\n                            '@babel/plugin-transform-class-properties',\n                            '@babel/plugin-transform-optional-chaining',\n                            '@babel/plugin-transform-nullish-coalescing-operator',\n                        ]\n                    }\n                }\n            },\n            {\n                test: /@(hapi|sideway)\\//,\n                sideEffects: false\n            }\n        ]\n    },\n    node: false,\n    resolve: {\n      alias: {\n          [Path.join(__dirname, '../lib/annotate.js')]: false,\n          [Path.join(__dirname, '../lib/manifest.js')]: false,\n          [Path.join(__dirname, '../lib/trace.js')]: false,\n          [Path.join(__dirname, '../lib/types/binary.js')]: false,\n          [Path.join(__dirname, '../node_modules/@hapi/tlds/esm/index.js')]: false,\n          [Path.join(__dirname, '../node_modules/@hapi/address/esm/decode.js')]: false,\n          [Path.join(__dirname, '../node_modules/@hapi/hoek/lib/bench.js')]: false,\n          [Path.join(__dirname, '../node_modules/@hapi/hoek/lib/block.js')]: false,\n          [Path.join(__dirname, '../node_modules/@hapi/hoek/lib/contain.js')]: false,\n          [Path.join(__dirname, '../node_modules/@hapi/hoek/lib/flatten.js')]: false,\n          [Path.join(__dirname, '../node_modules/@hapi/hoek/lib/intersect.js')]: false,\n          [Path.join(__dirname, '../node_modules/@hapi/hoek/lib/isPromise.js')]: false,\n          [Path.join(__dirname, '../node_modules/@hapi/hoek/lib/escapeHeaderAttribute.js')]: false,\n          [Path.join(__dirname, '../node_modules/@hapi/hoek/lib/escapeJson.js')]: false,\n          [Path.join(__dirname, '../node_modules/@hapi/hoek/lib/once.js')]: false,\n          [Path.join(__dirname, '../node_modules/@hapi/hoek/lib/reachTemplate.js')]: false,\n          [Path.join(__dirname, '../node_modules/@hapi/hoek/lib/wait.js')]: false,\n      },\n      fallback: {\n        url: false,\n        util: false,\n      }\n    }\n};\n"
  },
  {
    "path": "browser/webpack.mocha.js",
    "content": "const Path = require('path');\nconst Webpack = require('webpack');\n\nconst WebpackConfig = require('./webpack.config');\n\nWebpackConfig.mode = 'production';\nWebpackConfig.devServer = {\n    host: 'localhost',\n    port: 8081\n};\nWebpackConfig.devtool = 'inline-source-map';\nWebpackConfig.entry = [\n    `mocha-loader!${Path.join(__dirname, 'tests')}`\n];\nWebpackConfig.output.publicPath = 'http://localhost:8081';\nWebpackConfig.module.rules[1].use.options.presets[0][1].exclude = [\n    '@babel/plugin-transform-regenerator'\n];\n\n// Used in testing.\nWebpackConfig.plugins.push(new Webpack.DefinePlugin({\n  'process.env.NODE_DEBUG': false,\n}));\nWebpackConfig.node = {\n  global: true,\n};\nWebpackConfig.resolve.fallback.util = require.resolve('util/');\nWebpackConfig.resolve.fallback.assert = require.resolve('assert/');\n\nmodule.exports = WebpackConfig;\n"
  },
  {
    "path": "eslint.config.js",
    "content": "'use strict';\n\nconst HapiPlugin = require('@hapi/eslint-plugin');\n\nmodule.exports = [\n    {\n        ignores: ['browser', 'dist', 'sandbox.js']\n    },\n    ...HapiPlugin.configs.module\n];\n"
  },
  {
    "path": "lib/annotate.js",
    "content": "'use strict';\n\nconst { clone } = require('@hapi/hoek');\n\nconst Common = require('./common');\n\n\nconst internals = {\n    annotations: Symbol('annotations')\n};\n\n\nexports.error = function (stripColorCodes) {\n\n    if (!this._original ||\n        typeof this._original !== 'object') {\n\n        return this.details[0].message;\n    }\n\n    const redFgEscape = stripColorCodes ? '' : '\\u001b[31m';\n    const redBgEscape = stripColorCodes ? '' : '\\u001b[41m';\n    const endColor = stripColorCodes ? '' : '\\u001b[0m';\n\n    const obj = clone(this._original);\n\n    for (let i = this.details.length - 1; i >= 0; --i) {        // Reverse order to process deepest child first\n        const pos = i + 1;\n        const error = this.details[i];\n        const path = error.path;\n        let node = obj;\n        for (let j = 0; ; ++j) {\n            const seg = path[j];\n\n            if (Common.isSchema(node)) {\n                node = node.clone();                              // joi schemas are not cloned by hoek, we have to take this extra step\n            }\n\n            if (j + 1 < path.length &&\n                typeof node[seg] !== 'string') {\n\n                node = node[seg];\n            }\n            else {\n                const refAnnotations = node[internals.annotations] || { errors: {}, missing: {} };\n                node[internals.annotations] = refAnnotations;\n\n                const cacheKey = seg || error.context.key;\n\n                if (node[seg] !== undefined) {\n                    refAnnotations.errors[cacheKey] = refAnnotations.errors[cacheKey] || [];\n                    refAnnotations.errors[cacheKey].push(pos);\n                }\n                else {\n                    refAnnotations.missing[cacheKey] = pos;\n                }\n\n                break;\n            }\n        }\n    }\n\n    const replacers = {\n        key: /_\\$key\\$_([, \\d]+)_\\$end\\$_\"/g,\n        missing: /\"_\\$miss\\$_([^|]+)\\|(\\d+)_\\$end\\$_\": \"__missing__\"/g,\n        arrayIndex: /\\s*\"_\\$idx\\$_([, \\d]+)_\\$end\\$_\",?\\n(.*)/g,\n        specials: /\"\\[(NaN|Symbol.*|-?Infinity|function.*|\\(.*)]\"/g\n    };\n\n    let message = internals.safeStringify(obj, 2)\n        .replace(replacers.key, ($0, $1) => `\" ${redFgEscape}[${$1}]${endColor}`)\n        .replace(replacers.missing, ($0, $1, $2) => `${redBgEscape}\"${$1}\"${endColor}${redFgEscape} [${$2}]: -- missing --${endColor}`)\n        .replace(replacers.arrayIndex, ($0, $1, $2) => `\\n${$2} ${redFgEscape}[${$1}]${endColor}`)\n        .replace(replacers.specials, ($0, $1) => $1);\n\n    message = `${message}\\n${redFgEscape}`;\n\n    for (let i = 0; i < this.details.length; ++i) {\n        const pos = i + 1;\n        message = `${message}\\n[${pos}] ${this.details[i].message}`;\n    }\n\n    message = message + endColor;\n\n    return message;\n};\n\n\n// Inspired by json-stringify-safe\n\ninternals.safeStringify = function (obj, spaces) {\n\n    return JSON.stringify(obj, internals.serializer(), spaces);\n};\n\n\ninternals.serializer = function () {\n\n    const keys = [];\n    const stack = [];\n\n    const cycleReplacer = (key, value) => {\n\n        if (stack[0] === value) {\n            return '[Circular ~]';\n        }\n\n        return '[Circular ~.' + keys.slice(0, stack.indexOf(value)).join('.') + ']';\n    };\n\n    return function (key, value) {\n\n        if (stack.length > 0) {\n            const thisPos = stack.indexOf(this);\n            if (~thisPos) {\n                stack.length = thisPos + 1;\n                keys.length = thisPos + 1;\n                keys[thisPos] = key;\n            }\n            else {\n                stack.push(this);\n                keys.push(key);\n            }\n\n            if (~stack.indexOf(value)) {\n                value = cycleReplacer.call(this, key, value);\n            }\n        }\n        else {\n            stack.push(value);\n        }\n\n        if (value) {\n            const annotations = value[internals.annotations];\n            if (annotations) {\n                if (Array.isArray(value)) {\n                    const annotated = [];\n\n                    for (let i = 0; i < value.length; ++i) {\n                        if (annotations.errors[i]) {\n                            annotated.push(`_$idx$_${annotations.errors[i].sort().join(', ')}_$end$_`);\n                        }\n\n                        annotated.push(value[i]);\n                    }\n\n                    value = annotated;\n                }\n                else {\n                    for (const errorKey in annotations.errors) {\n                        value[`${errorKey}_$key$_${annotations.errors[errorKey].sort().join(', ')}_$end$_`] = value[errorKey];\n                        value[errorKey] = undefined;\n                    }\n\n                    for (const missingKey in annotations.missing) {\n                        value[`_$miss$_${missingKey}|${annotations.missing[missingKey]}_$end$_`] = '__missing__';\n                    }\n                }\n\n                return value;\n            }\n        }\n\n        if (value === Infinity ||\n            value === -Infinity ||\n            Number.isNaN(value) ||\n            typeof value === 'function' ||\n            typeof value === 'symbol') {\n\n            return '[' + value.toString() + ']';\n        }\n\n        return value;\n    };\n};\n"
  },
  {
    "path": "lib/base.js",
    "content": "'use strict';\n\nconst { assert, clone, deepEqual, merge } = require('@hapi/hoek');\n\nconst Cache = require('./cache');\nconst Common = require('./common');\nconst Compile = require('./compile');\nconst Errors = require('./errors');\nconst Extend = require('./extend');\nconst Manifest = require('./manifest');\nconst Messages = require('./messages');\nconst Modify = require('./modify');\nconst Ref = require('./ref');\nconst Trace = require('./trace');\nconst Validator = require('./validator');\nconst Values = require('./values');\n\n\nconst internals = {};\n\n\ninternals.Base = class {\n\n    constructor(type) {\n\n        // Naming: public, _private, $_extension, $_mutate{action}\n\n        this.type = type;\n\n        this.$_root = null;\n        this._definition = {};\n        this._reset();\n    }\n\n    _reset() {\n\n        this._ids = new Modify.Ids();\n        this._preferences = null;\n        this._refs = new Ref.Manager();\n        this._cache = null;\n\n        this._valids = null;\n        this._invalids = null;\n\n        this._flags = {};\n        this._rules = [];\n        this._singleRules = new Map();              // The rule options passed for non-multi rules\n\n        this.$_terms = {};                          // Hash of arrays of immutable objects (extended by other types)\n\n        this.$_temp = {                             // Runtime state (not cloned)\n            ruleset: null,                          // null: use last, false: error, number: start position\n            whens: {}                               // Runtime cache of generated whens\n        };\n    }\n\n    // Manifest\n\n    describe() {\n\n        assert(typeof Manifest.describe === 'function', 'Manifest functionality disabled');\n        return Manifest.describe(this);\n    }\n\n    // Rules\n\n    allow(...values) {\n\n        Common.verifyFlat(values, 'allow');\n        return this._values(values, '_valids');\n    }\n\n    alter(targets) {\n\n        assert(targets && typeof targets === 'object' && !Array.isArray(targets), 'Invalid targets argument');\n        assert(!this._inRuleset(), 'Cannot set alterations inside a ruleset');\n\n        const obj = this.clone();\n        obj.$_terms.alterations = obj.$_terms.alterations || [];\n        for (const target in targets) {\n            const adjuster = targets[target];\n            assert(typeof adjuster === 'function', 'Alteration adjuster for', target, 'must be a function');\n            obj.$_terms.alterations.push({ target, adjuster });\n        }\n\n        obj.$_temp.ruleset = false;\n        return obj;\n    }\n\n    artifact(id) {\n\n        assert(id !== undefined, 'Artifact cannot be undefined');\n        assert(!this._cache, 'Cannot set an artifact with a rule cache');\n\n        return this.$_setFlag('artifact', id);\n    }\n\n    cast(to) {\n\n        assert(to === false || typeof to === 'string', 'Invalid to value');\n        assert(to === false || this._definition.cast[to], 'Type', this.type, 'does not support casting to', to);\n\n        return this.$_setFlag('cast', to === false ? undefined : to);\n    }\n\n    default(value, options) {\n\n        return this._default('default', value, options);\n    }\n\n    description(desc) {\n\n        assert(desc && typeof desc === 'string', 'Description must be a non-empty string');\n\n        return this.$_setFlag('description', desc);\n    }\n\n    empty(schema) {\n\n        const obj = this.clone();\n\n        if (schema !== undefined) {\n            schema = obj.$_compile(schema, { override: false });\n        }\n\n        return obj.$_setFlag('empty', schema, { clone: false });\n    }\n\n    error(err) {\n\n        assert(err, 'Missing error');\n        assert(err instanceof Error || typeof err === 'function', 'Must provide a valid Error object or a function');\n\n        return this.$_setFlag('error', err);\n    }\n\n    example(example, options = {}) {\n\n        assert(example !== undefined, 'Missing example');\n        Common.assertOptions(options, ['override']);\n\n        return this._inner('examples', example, { single: true, override: options.override });\n    }\n\n    external(method, description) {\n\n        if (typeof method === 'object') {\n            assert(!description, 'Cannot combine options with description');\n            description = method.description;\n            method = method.method;\n        }\n\n        assert(typeof method === 'function', 'Method must be a function');\n        assert(description === undefined || description && typeof description === 'string', 'Description must be a non-empty string');\n\n        return this._inner('externals', { method, description }, { single: true });\n    }\n\n    failover(value, options) {\n\n        return this._default('failover', value, options);\n    }\n\n    forbidden() {\n\n        return this.presence('forbidden');\n    }\n\n    id(id) {\n\n        if (!id) {\n            return this.$_setFlag('id', undefined);\n        }\n\n        assert(typeof id === 'string', 'id must be a non-empty string');\n        assert(/^[^\\.]+$/.test(id), 'id cannot contain period character');\n\n        return this.$_setFlag('id', id);\n    }\n\n    invalid(...values) {\n\n        return this._values(values, '_invalids');\n    }\n\n    label(name) {\n\n        assert(name && typeof name === 'string', 'Label name must be a non-empty string');\n\n        return this.$_setFlag('label', name);\n    }\n\n    meta(meta) {\n\n        assert(meta !== undefined, 'Meta cannot be undefined');\n\n        return this._inner('metas', meta, { single: true });\n    }\n\n    note(...notes) {\n\n        assert(notes.length, 'Missing notes');\n        for (const note of notes) {\n            assert(note && typeof note === 'string', 'Notes must be non-empty strings');\n        }\n\n        return this._inner('notes', notes);\n    }\n\n    only(mode = true) {\n\n        assert(typeof mode === 'boolean', 'Invalid mode:', mode);\n\n        return this.$_setFlag('only', mode);\n    }\n\n    optional() {\n\n        return this.presence('optional');\n    }\n\n    prefs(prefs) {\n\n        assert(prefs, 'Missing preferences');\n        assert(prefs.context === undefined, 'Cannot override context');\n        assert(prefs.externals === undefined, 'Cannot override externals');\n        assert(prefs.warnings === undefined, 'Cannot override warnings');\n        assert(prefs.debug === undefined, 'Cannot override debug');\n\n        Common.checkPreferences(prefs);\n\n        const obj = this.clone();\n        obj._preferences = Common.preferences(obj._preferences, prefs);\n        return obj;\n    }\n\n    presence(mode) {\n\n        assert(['optional', 'required', 'forbidden'].includes(mode), 'Unknown presence mode', mode);\n\n        return this.$_setFlag('presence', mode);\n    }\n\n    raw(enabled = true) {\n\n        return this.$_setFlag('result', enabled ? 'raw' : undefined);\n    }\n\n    result(mode) {\n\n        assert(['raw', 'strip'].includes(mode), 'Unknown result mode', mode);\n\n        return this.$_setFlag('result', mode);\n    }\n\n    required() {\n\n        return this.presence('required');\n    }\n\n    strict(enabled) {\n\n        const obj = this.clone();\n\n        const convert = enabled === undefined ? false : !enabled;\n        obj._preferences = Common.preferences(obj._preferences, { convert });\n        return obj;\n    }\n\n    strip(enabled = true) {\n\n        return this.$_setFlag('result', enabled ? 'strip' : undefined);\n    }\n\n    tag(...tags) {\n\n        assert(tags.length, 'Missing tags');\n        for (const tag of tags) {\n            assert(tag && typeof tag === 'string', 'Tags must be non-empty strings');\n        }\n\n        return this._inner('tags', tags);\n    }\n\n    unit(name) {\n\n        assert(name && typeof name === 'string', 'Unit name must be a non-empty string');\n\n        return this.$_setFlag('unit', name);\n    }\n\n    valid(...values) {\n\n        Common.verifyFlat(values, 'valid');\n\n        const obj = this.allow(...values);\n        obj.$_setFlag('only', !!obj._valids, { clone: false });\n        return obj;\n    }\n\n    when(condition, options) {\n\n        const obj = this.clone();\n\n        if (!obj.$_terms.whens) {\n            obj.$_terms.whens = [];\n        }\n\n        const when = Compile.when(obj, condition, options);\n        if (!['any', 'link'].includes(obj.type)) {\n            const conditions = when.is ? [when] : when.switch;\n            for (const item of conditions) {\n                assert(!item.then || item.then.type === 'any' || item.then.type === obj.type, 'Cannot combine', obj.type, 'with', item.then && item.then.type);\n                assert(!item.otherwise || item.otherwise.type === 'any' || item.otherwise.type === obj.type, 'Cannot combine', obj.type, 'with', item.otherwise && item.otherwise.type);\n\n            }\n        }\n\n        obj.$_terms.whens.push(when);\n        return obj.$_mutateRebuild();\n    }\n\n    // Helpers\n\n    cache(cache) {\n\n        assert(!this._inRuleset(), 'Cannot set caching inside a ruleset');\n        assert(!this._cache, 'Cannot override schema cache');\n        assert(this._flags.artifact === undefined, 'Cannot cache a rule with an artifact');\n\n        const obj = this.clone();\n        obj._cache = cache || Cache.provider.provision();\n        obj.$_temp.ruleset = false;\n        return obj;\n    }\n\n    clone() {\n\n        const obj = Object.create(Object.getPrototypeOf(this));\n        return this._assign(obj);\n    }\n\n    concat(source) {\n\n        assert(Common.isSchema(source), 'Invalid schema object');\n        assert(this.type === 'any' || source.type === 'any' || source.type === this.type, 'Cannot merge type', this.type, 'with another type:', source.type);\n        assert(!this._inRuleset(), 'Cannot concatenate onto a schema with open ruleset');\n        assert(!source._inRuleset(), 'Cannot concatenate a schema with open ruleset');\n\n        let obj = this.clone();\n\n        if (this.type === 'any' &&\n            source.type !== 'any') {\n\n            // Change obj to match source type\n\n            const tmpObj = source.clone();\n            for (const key of Object.keys(obj)) {\n                if (key !== 'type') {\n                    tmpObj[key] = obj[key];\n                }\n            }\n\n            obj = tmpObj;\n        }\n\n        obj._ids.concat(source._ids);\n        obj._refs.register(source, Ref.toSibling);\n\n        obj._preferences = obj._preferences ? Common.preferences(obj._preferences, source._preferences) : source._preferences;\n        obj._valids = Values.merge(obj._valids, source._valids, source._invalids);\n        obj._invalids = Values.merge(obj._invalids, source._invalids, source._valids);\n\n        // Remove unique rules present in source\n\n        for (const name of source._singleRules.keys()) {\n            if (obj._singleRules.has(name)) {\n                obj._rules = obj._rules.filter((target) => target.keep || target.name !== name);\n                obj._singleRules.delete(name);\n            }\n        }\n\n        // Rules\n\n        for (const test of source._rules) {\n            if (!source._definition.rules[test.method].multi) {\n                obj._singleRules.set(test.name, test);\n            }\n\n            obj._rules.push(test);\n        }\n\n        // Flags\n\n        if (obj._flags.empty &&\n            source._flags.empty) {\n\n            obj._flags.empty = obj._flags.empty.concat(source._flags.empty);\n            const flags = Object.assign({}, source._flags);\n            delete flags.empty;\n            merge(obj._flags, flags);\n        }\n        else if (source._flags.empty) {\n            obj._flags.empty = source._flags.empty;\n            const flags = Object.assign({}, source._flags);\n            delete flags.empty;\n            merge(obj._flags, flags);\n        }\n        else {\n            merge(obj._flags, source._flags);\n        }\n\n        // Terms\n\n        for (const key in source.$_terms) {\n            const terms = source.$_terms[key];\n            if (!terms) {\n                if (!obj.$_terms[key]) {\n                    obj.$_terms[key] = terms;\n                }\n\n                continue;\n            }\n\n            if (!obj.$_terms[key]) {\n                obj.$_terms[key] = terms.slice();\n                continue;\n            }\n\n            obj.$_terms[key] = obj.$_terms[key].concat(terms);\n        }\n\n        // Tracing\n\n        if (this.$_root._tracer) {\n            this.$_root._tracer._combine(obj, [this, source]);\n        }\n\n        // Rebuild\n\n        return obj.$_mutateRebuild();\n    }\n\n    extend(options) {\n\n        assert(!options.base, 'Cannot extend type with another base');\n\n        return Extend.type(this, options);\n    }\n\n    extract(path) {\n\n        path = Array.isArray(path) ? path : path.split('.');\n        return this._ids.reach(path);\n    }\n\n    fork(paths, adjuster) {\n\n        assert(!this._inRuleset(), 'Cannot fork inside a ruleset');\n\n        let obj = this;                                             // eslint-disable-line consistent-this\n        for (let path of [].concat(paths)) {\n            path = Array.isArray(path) ? path : path.split('.');\n            obj = obj._ids.fork(path, adjuster, obj);\n        }\n\n        obj.$_temp.ruleset = false;\n        return obj;\n    }\n\n    isAsync() {\n\n        if (Boolean(this.$_terms.externals?.length)) {\n            return true;\n        }\n\n        if (this.$_terms.whens) {\n            for (const when of this.$_terms.whens) {\n                if (when.then?.isAsync()) {\n                    return true;\n                }\n\n                if (when.otherwise?.isAsync()) {\n                    return true;\n                }\n\n                if (when.switch) {\n                    for (const item of when.switch) {\n                        if (item.then?.isAsync()) {\n                            return true;\n                        }\n\n                        if (item.otherwise?.isAsync()) {\n                            return true;\n                        }\n                    }\n                }\n            }\n        }\n\n        return false;\n    }\n\n    rule(options) {\n\n        const def = this._definition;\n        Common.assertOptions(options, Object.keys(def.modifiers));\n\n        assert(this.$_temp.ruleset !== false, 'Cannot apply rules to empty ruleset or the last rule added does not support rule properties');\n        const start = this.$_temp.ruleset === null ? this._rules.length - 1 : this.$_temp.ruleset;\n        assert(start >= 0 && start < this._rules.length, 'Cannot apply rules to empty ruleset');\n\n        const obj = this.clone();\n\n        for (let i = start; i < obj._rules.length; ++i) {\n            const original = obj._rules[i];\n            const rule = clone(original);\n\n            for (const name in options) {\n                def.modifiers[name](rule, options[name]);\n                assert(rule.name === original.name, 'Cannot change rule name');\n            }\n\n            obj._rules[i] = rule;\n\n            if (obj._singleRules.get(rule.name) === original) {\n                obj._singleRules.set(rule.name, rule);\n            }\n        }\n\n        obj.$_temp.ruleset = false;\n        return obj.$_mutateRebuild();\n    }\n\n    get ruleset() {\n\n        assert(!this._inRuleset(), 'Cannot start a new ruleset without closing the previous one');\n\n        const obj = this.clone();\n        obj.$_temp.ruleset = obj._rules.length;\n        return obj;\n    }\n\n    get $() {\n\n        return this.ruleset;\n    }\n\n    tailor(targets) {\n\n        targets = [].concat(targets);\n\n        assert(!this._inRuleset(), 'Cannot tailor inside a ruleset');\n\n        let obj = this;                                                     // eslint-disable-line consistent-this\n\n        if (this.$_terms.alterations) {\n            for (const { target, adjuster } of this.$_terms.alterations) {\n                if (targets.includes(target)) {\n                    obj = adjuster(obj);\n                    assert(Common.isSchema(obj), 'Alteration adjuster for', target, 'failed to return a schema object');\n                }\n            }\n        }\n\n        obj = obj.$_modify({ each: (item) => item.tailor(targets), ref: false });\n        obj.$_temp.ruleset = false;\n        return obj.$_mutateRebuild();\n    }\n\n    tracer() {\n\n        return Trace.location ? Trace.location(this) : this;                // $lab:coverage:ignore$\n    }\n\n    validate(value, options) {\n\n        return Validator.entry(value, this, options);\n    }\n\n    validateAsync(value, options) {\n\n        return Validator.entryAsync(value, this, options);\n    }\n\n    // Extensions\n\n    $_addRule(options) {\n\n        // Normalize rule\n\n        if (typeof options === 'string') {\n            options = { name: options };\n        }\n\n        assert(options && typeof options === 'object', 'Invalid options');\n        assert(options.name && typeof options.name === 'string', 'Invalid rule name');\n\n        for (const key in options) {\n            assert(key[0] !== '_', 'Cannot set private rule properties');\n        }\n\n        const rule = Object.assign({}, options);        // Shallow cloned\n        rule._resolve = [];\n        rule.method = rule.method || rule.name;\n\n        const definition = this._definition.rules[rule.method];\n        const args = rule.args;\n\n        assert(definition, 'Unknown rule', rule.method);\n\n        // Args\n\n        const obj = this.clone();\n\n        if (args) {\n            assert(Object.keys(args).length === 1 || Object.keys(args).length === this._definition.rules[rule.name].args.length, 'Invalid rule definition for', this.type, rule.name);\n\n            for (const key in args) {\n                let arg = args[key];\n\n                if (definition.argsByName) {\n                    const resolver = definition.argsByName.get(key);\n\n                    if (resolver.ref &&\n                        Common.isResolvable(arg)) {\n\n                        rule._resolve.push(key);\n                        obj.$_mutateRegister(arg);\n                    }\n                    else {\n                        if (resolver.normalize) {\n                            arg = resolver.normalize(arg);\n                            args[key] = arg;\n                        }\n\n                        if (resolver.assert) {\n                            const error = Common.validateArg(arg, key, resolver);\n                            assert(!error, error, 'or reference');\n                        }\n                    }\n                }\n\n                if (arg === undefined) {\n                    delete args[key];\n                    continue;\n                }\n\n                args[key] = arg;\n            }\n        }\n\n        // Unique rules\n\n        if (!definition.multi) {\n            obj._ruleRemove(rule.name, { clone: false });\n            obj._singleRules.set(rule.name, rule);\n        }\n\n        if (obj.$_temp.ruleset === false) {\n            obj.$_temp.ruleset = null;\n        }\n\n        if (definition.priority) {\n            obj._rules.unshift(rule);\n        }\n        else {\n            obj._rules.push(rule);\n        }\n\n        return obj;\n    }\n\n    $_compile(schema, options) {\n\n        return Compile.schema(this.$_root, schema, options);\n    }\n\n    $_createError(code, value, local, state, prefs, options = {}) {\n\n        const flags = options.flags !== false ? this._flags : {};\n        const messages = options.messages ? Messages.merge(this._definition.messages, options.messages) : this._definition.messages;\n        return new Errors.Report(code, value, local, flags, messages, state, prefs);\n    }\n\n    $_getFlag(name) {\n\n        return this._flags[name];\n    }\n\n    $_getRule(name) {\n\n        return this._singleRules.get(name);\n    }\n\n    $_mapLabels(path) {\n\n        path = Array.isArray(path) ? path : path.split('.');\n        return this._ids.labels(path);\n    }\n\n    $_match(value, state, prefs, overrides) {\n\n        prefs = Object.assign({}, prefs);       // Shallow cloned\n        prefs.abortEarly = true;\n        prefs._externals = false;\n\n        state.snapshot();\n        const result = !Validator.validate(value, this, state, prefs, overrides).errors;\n        state.restore();\n\n        return result;\n    }\n\n    $_modify(options) {\n\n        Common.assertOptions(options, ['each', 'once', 'ref', 'schema']);\n        return Modify.schema(this, options) || this;\n    }\n\n    $_mutateRebuild() {\n\n        assert(!this._inRuleset(), 'Cannot add this rule inside a ruleset');\n\n        this._refs.reset();\n        this._ids.reset();\n\n        const each = (item, { source, name, path, key }) => {\n\n            const family = this._definition[source][name] && this._definition[source][name].register;\n            if (family !== false) {\n                this.$_mutateRegister(item, { family, key });\n            }\n        };\n\n        this.$_modify({ each });\n\n        if (this._definition.rebuild) {\n            this._definition.rebuild(this);\n        }\n\n        this.$_temp.ruleset = false;\n        return this;\n    }\n\n    $_mutateRegister(schema, { family, key } = {}) {\n\n        this._refs.register(schema, family);\n        this._ids.register(schema, { key });\n    }\n\n    $_property(name) {\n\n        return this._definition.properties[name];\n    }\n\n    $_reach(path) {\n\n        return this._ids.reach(path);\n    }\n\n    $_rootReferences() {\n\n        return this._refs.roots();\n    }\n\n    $_setFlag(name, value, options = {}) {\n\n        assert(name[0] === '_' || !this._inRuleset(), 'Cannot set flag inside a ruleset');\n\n        const flag = this._definition.flags[name] || {};\n        if (deepEqual(value, flag.default)) {\n            value = undefined;\n        }\n\n        if (deepEqual(value, this._flags[name])) {\n            return this;\n        }\n\n        const obj = options.clone !== false ? this.clone() : this;\n\n        if (value !== undefined) {\n            obj._flags[name] = value;\n            obj.$_mutateRegister(value);\n        }\n        else {\n            delete obj._flags[name];\n        }\n\n        if (name[0] !== '_') {\n            obj.$_temp.ruleset = false;\n        }\n\n        return obj;\n    }\n\n    $_parent(method, ...args) {\n\n        return this[method][Common.symbols.parent].call(this, ...args);\n    }\n\n    $_validate(value, state, prefs) {\n\n        return Validator.validate(value, this, state, prefs);\n    }\n\n    // Internals\n\n    _assign(target) {\n\n        target.type = this.type;\n\n        target.$_root = this.$_root;\n\n        target.$_temp = Object.assign({}, this.$_temp);\n        target.$_temp.whens = {};\n\n        target._ids = this._ids.clone();\n        target._preferences = this._preferences;\n        target._valids = this._valids && this._valids.clone();\n        target._invalids = this._invalids && this._invalids.clone();\n        target._rules = this._rules.slice();\n        target._singleRules = clone(this._singleRules, { shallow: true });\n        target._refs = this._refs.clone();\n        target._flags = Object.assign({}, this._flags);\n        target._cache = null;\n\n        target.$_terms = {};\n        for (const key in this.$_terms) {\n            target.$_terms[key] = this.$_terms[key] ? this.$_terms[key].slice() : null;\n        }\n\n        // Backwards compatibility\n\n        target.$_super = {};\n        for (const override in this.$_super) {\n            target.$_super[override] = this._super[override].bind(target);\n        }\n\n        return target;\n    }\n\n    _bare() {\n\n        const obj = this.clone();\n        obj._reset();\n\n        const terms = obj._definition.terms;\n        for (const name in terms) {\n            const term = terms[name];\n            obj.$_terms[name] = term.init;\n        }\n\n        return obj.$_mutateRebuild();\n    }\n\n    _default(flag, value, options = {}) {\n\n        Common.assertOptions(options, 'literal');\n\n        assert(value !== undefined, 'Missing', flag, 'value');\n        assert(typeof value === 'function' || !options.literal, 'Only function value supports literal option');\n\n        if (typeof value === 'function' &&\n            options.literal) {\n\n            value = {\n                [Common.symbols.literal]: true,\n                literal: value\n            };\n        }\n\n        const obj = this.$_setFlag(flag, value);\n        return obj;\n    }\n\n    _generate(value, state, prefs) {\n\n        if (!this.$_terms.whens) {\n            return { schema: this };\n        }\n\n        // Collect matching whens\n\n        const whens = [];\n        const ids = [];\n        for (let i = 0; i < this.$_terms.whens.length; ++i) {\n            const when = this.$_terms.whens[i];\n\n            if (when.concat) {\n                whens.push(when.concat);\n                ids.push(`${i}.concat`);\n                continue;\n            }\n\n            const input = when.ref ? when.ref.resolve(value, state, prefs) : value;\n            const tests = when.is ? [when] : when.switch;\n            const before = ids.length;\n\n            for (let j = 0; j < tests.length; ++j) {\n                const { is, then, otherwise } = tests[j];\n\n                const baseId = `${i}${when.switch ? '.' + j : ''}`;\n                if (is.$_match(input, state.nest(is, `${baseId}.is`), prefs)) {\n                    if (then) {\n                        const localState = state.localize([...state.path, `${baseId}.then`], state.ancestors, state.schemas);\n                        const { schema: generated, id } = then._generate(value, localState, prefs);\n                        whens.push(generated);\n                        ids.push(`${baseId}.then${id ? `(${id})` : ''}`);\n                        break;\n                    }\n                }\n                else if (otherwise) {\n                    const localState = state.localize([...state.path, `${baseId}.otherwise`], state.ancestors, state.schemas);\n                    const { schema: generated, id } = otherwise._generate(value, localState, prefs);\n                    whens.push(generated);\n                    ids.push(`${baseId}.otherwise${id ? `(${id})` : ''}`);\n                    break;\n                }\n            }\n\n            if (when.break &&\n                ids.length > before) {          // Something matched\n\n                break;\n            }\n        }\n\n        // Check cache\n\n        const id = ids.join(', ');\n        state.mainstay.tracer.debug(state, 'rule', 'when', id);\n\n        if (!id) {\n            return { schema: this };\n        }\n\n        if (!state.mainstay.tracer.active &&\n            this.$_temp.whens[id]) {\n\n            return { schema: this.$_temp.whens[id], id };\n        }\n\n        // Generate dynamic schema\n\n        let obj = this;                                             // eslint-disable-line consistent-this\n        if (this._definition.generate) {\n            obj = this._definition.generate(this, value, state, prefs);\n        }\n\n        // Apply whens\n\n        for (const when of whens) {\n            obj = obj.concat(when);\n        }\n\n        // Tracing\n\n        if (this.$_root._tracer) {\n            this.$_root._tracer._combine(obj, [this, ...whens]);\n        }\n\n        // Cache result\n\n        this.$_temp.whens[id] = obj;\n        return { schema: obj, id };\n    }\n\n    _inner(type, values, options = {}) {\n\n        assert(!this._inRuleset(), `Cannot set ${type} inside a ruleset`);\n\n        const obj = this.clone();\n        if (!obj.$_terms[type] ||\n            options.override) {\n\n            obj.$_terms[type] = [];\n        }\n\n        if (options.single) {\n            obj.$_terms[type].push(values);\n        }\n        else {\n            obj.$_terms[type].push(...values);\n        }\n\n        obj.$_temp.ruleset = false;\n        return obj;\n    }\n\n    _inRuleset() {\n\n        return this.$_temp.ruleset !== null && this.$_temp.ruleset !== false;\n    }\n\n    _ruleRemove(name, options = {}) {\n\n        if (!this._singleRules.has(name)) {\n            return this;\n        }\n\n        const obj = options.clone !== false ? this.clone() : this;\n\n        obj._singleRules.delete(name);\n\n        const filtered = [];\n        for (let i = 0; i < obj._rules.length; ++i) {\n            const test = obj._rules[i];\n            if (test.name === name &&\n                !test.keep) {\n\n                if (obj._inRuleset() &&\n                    i < obj.$_temp.ruleset) {\n\n                    --obj.$_temp.ruleset;\n                }\n\n                continue;\n            }\n\n            filtered.push(test);\n        }\n\n        obj._rules = filtered;\n        return obj;\n    }\n\n    _values(values, key) {\n\n        Common.verifyFlat(values, key.slice(1, -1));\n\n        const obj = this.clone();\n\n        const override = values[0] === Common.symbols.override;\n        if (override) {\n            values = values.slice(1);\n        }\n\n        if (!obj[key] &&\n            values.length) {\n\n            obj[key] = new Values();\n        }\n        else if (override) {\n            obj[key] = values.length ? new Values() : null;\n            obj.$_mutateRebuild();\n        }\n\n        if (!obj[key]) {\n            return obj;\n        }\n\n        if (override) {\n            obj[key].override();\n        }\n\n        for (const value of values) {\n            assert(value !== undefined, 'Cannot call allow/valid/invalid with undefined');\n            assert(value !== Common.symbols.override, 'Override must be the first value');\n\n            const other = key === '_invalids' ? '_valids' : '_invalids';\n            if (obj[other]) {\n                obj[other].remove(value);\n                if (!obj[other].length) {\n                    assert(key === '_valids' || !obj._flags.only, 'Setting invalid value', value, 'leaves schema rejecting all values due to previous valid rule');\n                    obj[other] = null;\n                }\n            }\n\n            obj[key].add(value, obj._refs);\n        }\n\n        return obj;\n    }\n\n    // Standard Schema\n\n    get '~standard'() {\n\n        const mapToStandardError = (error) => {\n\n            let issues;\n            if (Errors.ValidationError.isError(error)) {\n                issues = error.details.map(({ message, path }) => ({\n                    message,\n                    path\n                }));\n            }\n            else {\n                issues = [{\n                    message: error.message\n                }];\n            }\n\n            return {\n                issues\n            };\n        };\n\n        const mapToStandardValue = (value) => ({ value });\n\n        return {\n            version: 1,\n            vendor: 'joi',\n            validate: (value) => {\n\n                const result = Validator.standard(value, this);\n\n                if (result instanceof Promise) {\n                    return result\n                        .then(mapToStandardValue, mapToStandardError);\n                }\n\n                if (!result.error) {\n                    return mapToStandardValue(result.value);\n                }\n\n                return mapToStandardError(result.error);\n            }\n        };\n    }\n};\n\n\ninternals.Base.prototype[Common.symbols.any] = {\n    version: Common.version,\n    compile: Compile.compile,\n    root: '$_root'\n};\n\n\ninternals.Base.prototype.isImmutable = true;                // Prevents Hoek from deep cloning schema objects (must be on prototype)\n\n\n// Aliases\n\ninternals.Base.prototype.deny = internals.Base.prototype.invalid;\ninternals.Base.prototype.disallow = internals.Base.prototype.invalid;\ninternals.Base.prototype.equal = internals.Base.prototype.valid;\ninternals.Base.prototype.exist = internals.Base.prototype.required;\ninternals.Base.prototype.not = internals.Base.prototype.invalid;\ninternals.Base.prototype.options = internals.Base.prototype.prefs;\ninternals.Base.prototype.preferences = internals.Base.prototype.prefs;\n\n\nmodule.exports = new internals.Base();\n"
  },
  {
    "path": "lib/cache.js",
    "content": "'use strict';\n\nconst { assert, clone } = require('@hapi/hoek');\n\nconst Common = require('./common');\n\n\nconst internals = {\n    max: 1000,\n    supported: new Set(['undefined', 'boolean', 'number', 'string'])\n};\n\n\nexports.provider = {\n\n    provision(options) {\n\n        return new internals.Cache(options);\n    }\n};\n\n\n// Least Recently Used (LRU) Cache\n\ninternals.Cache = class {\n\n    constructor(options = {}) {\n\n        Common.assertOptions(options, ['max']);\n        assert(options.max === undefined || options.max && options.max > 0 && isFinite(options.max), 'Invalid max cache size');\n\n        this._max = options.max || internals.max;\n\n        this._map = new Map();                          // Map of nodes by key\n        this._list = new internals.List();              // List of nodes (most recently used in head)\n    }\n\n    get length() {\n\n        return this._map.size;\n    }\n\n    set(key, value) {\n\n        if (key !== null &&\n            !internals.supported.has(typeof key)) {\n\n            return;\n        }\n\n        let node = this._map.get(key);\n        if (node) {\n            node.value = value;\n            this._list.first(node);\n            return;\n        }\n\n        node = this._list.unshift({ key, value });\n        this._map.set(key, node);\n        this._compact();\n    }\n\n    get(key) {\n\n        const node = this._map.get(key);\n        if (node) {\n            this._list.first(node);\n            return clone(node.value);\n        }\n    }\n\n    _compact() {\n\n        if (this._map.size > this._max) {\n            const node = this._list.pop();\n            this._map.delete(node.key);\n        }\n    }\n};\n\n\ninternals.List = class {\n\n    constructor() {\n\n        this.tail = null;\n        this.head = null;\n    }\n\n    unshift(node) {\n\n        node.next = null;\n        node.prev = this.head;\n\n        if (this.head) {\n            this.head.next = node;\n        }\n\n        this.head = node;\n\n        if (!this.tail) {\n            this.tail = node;\n        }\n\n        return node;\n    }\n\n    first(node) {\n\n        if (node === this.head) {\n            return;\n        }\n\n        this._remove(node);\n        this.unshift(node);\n    }\n\n    pop() {\n\n        return this._remove(this.tail);\n    }\n\n    _remove(node) {\n\n        const { next, prev } = node;\n\n        next.prev = prev;\n\n        if (prev) {\n            prev.next = next;\n        }\n\n        if (node === this.tail) {\n            this.tail = next;\n        }\n\n        node.prev = null;\n        node.next = null;\n\n        return node;\n    }\n};\n"
  },
  {
    "path": "lib/common.js",
    "content": "'use strict';\n\nconst { assert: Assert, AssertError } = require('@hapi/hoek');\n\nconst Pkg = require('../package.json');\n\nlet Messages;\nlet Schemas;\n\n\nconst internals = {\n    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)?)?)?)?$/\n};\n\n\nexports.version = Pkg.version;\n\n\nexports.defaults = {\n    abortEarly: true,\n    allowUnknown: false,\n    artifacts: false,\n    cache: true,\n    context: null,\n    convert: true,\n    dateFormat: 'iso',\n    errors: {\n        escapeHtml: false,\n        label: 'path',\n        language: null,\n        render: true,\n        stack: false,\n        wrap: {\n            label: '\"',\n            array: '[]'\n        }\n    },\n    externals: true,\n    messages: {},\n    nonEnumerables: false,\n    noDefaults: false,\n    presence: 'optional',\n    skipFunctions: false,\n    stripUnknown: false,\n    warnings: false\n};\n\n\nexports.symbols = {\n    any: Symbol.for('@hapi/joi/schema'),            // Used to internally identify any-based types (shared with other joi versions)\n    arraySingle: Symbol('arraySingle'),\n    deepDefault: Symbol('deepDefault'),\n    errors: Symbol('errors'),\n    literal: Symbol('literal'),\n    override: Symbol('override'),\n    parent: Symbol('parent'),\n    prefs: Symbol('prefs'),\n    ref: Symbol('ref'),\n    template: Symbol('template'),\n    values: Symbol('values')\n};\n\n\nexports.assertOptions = function (options, keys, name = 'Options') {\n\n    Assert(options && typeof options === 'object' && !Array.isArray(options), 'Options must be of type object');\n    const unknownKeys = Object.keys(options).filter((k) => !keys.includes(k));\n    Assert(unknownKeys.length === 0, `${name} contain unknown keys: ${unknownKeys}`);\n};\n\n\nexports.checkPreferences = function (prefs) {\n\n    Schemas = Schemas || require('./schemas');\n\n    const result = Schemas.preferences.validate(prefs);\n\n    if (result.error) {\n        throw new AssertError([result.error.details[0].message]);\n    }\n};\n\n\nexports.compare = function (a, b, operator) {\n\n    switch (operator) {\n        case '=': return a === b;\n        case '>': return a > b;\n        case '<': return a < b;\n        case '>=': return a >= b;\n        case '<=': return a <= b;\n    }\n};\n\n\nexports.default = function (value, defaultValue) {\n\n    return value === undefined ? defaultValue : value;\n};\n\n\nexports.isIsoDate = function (date) {\n\n    return internals.isoDate.test(date);\n};\n\n\nexports.isNumber = function (value) {\n\n    return typeof value === 'number' && !isNaN(value);\n};\n\n\nexports.isResolvable = function (obj) {\n\n    if (!obj) {\n        return false;\n    }\n\n    return obj[exports.symbols.ref] || obj[exports.symbols.template];\n};\n\n\nexports.isSchema = function (schema, options = {}) {\n\n    const any = schema && schema[exports.symbols.any];\n    if (!any) {\n        return false;\n    }\n\n    Assert(options.legacy || any.version === exports.version, 'Cannot mix different versions of joi schemas');\n    return true;\n};\n\n\nexports.isValues = function (obj) {\n\n    return obj[exports.symbols.values];\n};\n\n\nexports.limit = function (value) {\n\n    return Number.isSafeInteger(value) && value >= 0;\n};\n\n\nexports.preferences = function (target, source) {\n\n    Messages = Messages || require('./messages');\n\n    target = target || {};\n    source = source || {};\n\n    const merged = Object.assign({}, target, source);\n    if (source.errors &&\n        target.errors) {\n\n        merged.errors = Object.assign({}, target.errors, source.errors);\n        merged.errors.wrap = Object.assign({}, target.errors.wrap, source.errors.wrap);\n    }\n\n    if (source.messages) {\n        merged.messages = Messages.compile(source.messages, target.messages);\n    }\n\n    delete merged[exports.symbols.prefs];\n    return merged;\n};\n\n\nexports.tryWithPath = function (fn, key, options = {}) {\n\n    try {\n        return fn();\n    }\n    catch (err) {\n        if (err.path !== undefined) {\n            err.path = key + '.' + err.path;\n        }\n        else {\n            err.path = key;\n        }\n\n        if (options.append) {\n            err.message = `${err.message} (${err.path})`;\n        }\n\n        throw err;\n    }\n};\n\n\nexports.validateArg = function (value, label, { assert, message }) {\n\n    if (exports.isSchema(assert)) {\n        const result = assert.validate(value);\n        if (!result.error) {\n            return;\n        }\n\n        return result.error.message;\n    }\n    else if (!assert(value)) {\n        return label ? `${label} ${message}` : message;\n    }\n};\n\n\nexports.verifyFlat = function (args, method) {\n\n    for (const arg of args) {\n        Assert(!Array.isArray(arg), 'Method no longer accepts array arguments:', method);\n    }\n};\n"
  },
  {
    "path": "lib/compile.js",
    "content": "'use strict';\n\nconst { assert } = require('@hapi/hoek');\n\nconst Common = require('./common');\nconst Ref = require('./ref');\n\n\nconst internals = {};\n\n\nexports.schema = function (Joi, config, options = {}) {\n\n    Common.assertOptions(options, ['appendPath', 'override']);\n\n    try {\n        return internals.schema(Joi, config, options);\n    }\n    catch (err) {\n        if (options.appendPath &&\n            err.path !== undefined) {\n\n            err.message = `${err.message} (${err.path})`;\n        }\n\n        throw err;\n    }\n};\n\n\ninternals.schema = function (Joi, config, options) {\n\n    assert(config !== undefined, 'Invalid undefined schema');\n\n    if (Array.isArray(config)) {\n        assert(config.length, 'Invalid empty array schema');\n\n        if (config.length === 1) {\n            config = config[0];\n        }\n    }\n\n    const valid = (base, ...values) => {\n\n        if (options.override !== false) {\n            return base.valid(Joi.override, ...values);\n        }\n\n        return base.valid(...values);\n    };\n\n    if (internals.simple(config)) {\n        return valid(Joi, config);\n    }\n\n    if (typeof config === 'function') {\n        return Joi.custom(config);\n    }\n\n    assert(typeof config === 'object', 'Invalid schema content:', typeof config);\n\n    if (Common.isResolvable(config)) {\n        return valid(Joi, config);\n    }\n\n    if (Common.isSchema(config)) {\n        return config;\n    }\n\n    if (Array.isArray(config)) {\n        for (const item of config) {\n            if (!internals.simple(item)) {\n                return Joi.alternatives().try(...config);\n            }\n        }\n\n        return valid(Joi, ...config);\n    }\n\n    if (config instanceof RegExp) {\n        return Joi.string().regex(config);\n    }\n\n    if (config instanceof Date) {\n        return valid(Joi.date(), config);\n    }\n\n    assert(Object.getPrototypeOf(config) === Object.getPrototypeOf({}), 'Schema can only contain plain objects');\n\n    return Joi.object().keys(config);\n};\n\n\nexports.ref = function (id, options) {\n\n    return Ref.isRef(id) ? id : Ref.create(id, options);\n};\n\n\nexports.compile = function (root, schema, options = {}) {\n\n    Common.assertOptions(options, ['legacy']);\n\n    // Compiled by any supported version\n\n    const any = schema && schema[Common.symbols.any];\n    if (any) {\n        assert(options.legacy || any.version === Common.version, 'Cannot mix different versions of joi schemas:', any.version, Common.version);\n        return schema;\n    }\n\n    // Uncompiled root\n\n    if (typeof schema !== 'object' ||\n        !options.legacy) {\n\n        return exports.schema(root, schema, { appendPath: true });          // Will error if schema contains other versions\n    }\n\n    // Scan schema for compiled parts\n\n    const compiler = internals.walk(schema);\n    if (!compiler) {\n        return exports.schema(root, schema, { appendPath: true });\n    }\n\n    return compiler.compile(compiler.root, schema);\n};\n\n\ninternals.walk = function (schema) {\n\n    if (typeof schema !== 'object') {\n        return null;\n    }\n\n    if (Array.isArray(schema)) {\n        for (const item of schema) {\n            const compiler = internals.walk(item);\n            if (compiler) {\n                return compiler;\n            }\n        }\n\n        return null;\n    }\n\n    const any = schema[Common.symbols.any];\n    if (any) {\n        return { root: schema[any.root], compile: any.compile };\n    }\n\n    assert(Object.getPrototypeOf(schema) === Object.getPrototypeOf({}), 'Schema can only contain plain objects');\n\n    for (const key in schema) {\n        const compiler = internals.walk(schema[key]);\n        if (compiler) {\n            return compiler;\n        }\n    }\n\n    return null;\n};\n\n\ninternals.simple = function (value) {\n\n    return value === null || ['boolean', 'string', 'number'].includes(typeof value);\n};\n\n\nexports.when = function (schema, condition, options) {\n\n    if (options === undefined) {\n        assert(condition && typeof condition === 'object', 'Missing options');\n\n        options = condition;\n        condition = Ref.create('.');\n    }\n\n    if (Array.isArray(options)) {\n        options = { switch: options };\n    }\n\n    Common.assertOptions(options, ['is', 'not', 'then', 'otherwise', 'switch', 'break']);\n\n    // Schema condition\n\n    if (Common.isSchema(condition)) {\n        assert(options.is === undefined, '\"is\" can not be used with a schema condition');\n        assert(options.not === undefined, '\"not\" can not be used with a schema condition');\n        assert(options.switch === undefined, '\"switch\" can not be used with a schema condition');\n\n        return internals.condition(schema, { is: condition, then: options.then, otherwise: options.otherwise, break: options.break });\n    }\n\n    // Single condition\n\n    assert(Ref.isRef(condition) || typeof condition === 'string', 'Invalid condition:', condition);\n    assert(options.not === undefined || options.is === undefined, 'Cannot combine \"is\" with \"not\"');\n\n    if (options.switch === undefined) {\n        let rule = options;\n        if (options.not !== undefined) {\n            rule = { is: options.not, then: options.otherwise, otherwise: options.then, break: options.break };\n        }\n\n        let is = rule.is !== undefined ? schema.$_compile(rule.is) : schema.$_root.invalid(null, false, 0, '').required();\n        assert(rule.then !== undefined || rule.otherwise !== undefined, 'options must have at least one of \"then\", \"otherwise\", or \"switch\"');\n        assert(rule.break === undefined || rule.then === undefined || rule.otherwise === undefined, 'Cannot specify then, otherwise, and break all together');\n\n        if (options.is !== undefined &&\n            !Ref.isRef(options.is) &&\n            !Common.isSchema(options.is)) {\n\n            is = is.required();                     // Only apply required if this wasn't already a schema or a ref\n        }\n\n        return internals.condition(schema, { ref: exports.ref(condition), is, then: rule.then, otherwise: rule.otherwise, break: rule.break });\n    }\n\n    // Switch statement\n\n    assert(Array.isArray(options.switch), '\"switch\" must be an array');\n    assert(options.is === undefined, 'Cannot combine \"switch\" with \"is\"');\n    assert(options.not === undefined, 'Cannot combine \"switch\" with \"not\"');\n    assert(options.then === undefined, 'Cannot combine \"switch\" with \"then\"');\n\n    const rule = {\n        ref: exports.ref(condition),\n        switch: [],\n        break: options.break\n    };\n\n    for (let i = 0; i < options.switch.length; ++i) {\n        const test = options.switch[i];\n        const last = i === options.switch.length - 1;\n\n        Common.assertOptions(test, last ? ['is', 'then', 'otherwise'] : ['is', 'then']);\n\n        assert(test.is !== undefined, 'Switch statement missing \"is\"');\n        assert(test.then !== undefined, 'Switch statement missing \"then\"');\n\n        const item = {\n            is: schema.$_compile(test.is),\n            then: schema.$_compile(test.then)\n        };\n\n        if (!Ref.isRef(test.is) &&\n            !Common.isSchema(test.is)) {\n\n            item.is = item.is.required();           // Only apply required if this wasn't already a schema or a ref\n        }\n\n        if (last) {\n            assert(options.otherwise === undefined || test.otherwise === undefined, 'Cannot specify \"otherwise\" inside and outside a \"switch\"');\n            const otherwise = options.otherwise !== undefined ? options.otherwise : test.otherwise;\n            if (otherwise !== undefined) {\n                assert(rule.break === undefined, 'Cannot specify both otherwise and break');\n                item.otherwise = schema.$_compile(otherwise);\n            }\n        }\n\n        rule.switch.push(item);\n    }\n\n    return rule;\n};\n\n\ninternals.condition = function (schema, condition) {\n\n    for (const key of ['then', 'otherwise']) {\n        if (condition[key] === undefined) {\n            delete condition[key];\n        }\n        else {\n            condition[key] = schema.$_compile(condition[key]);\n        }\n    }\n\n    return condition;\n};\n"
  },
  {
    "path": "lib/errors.js",
    "content": "'use strict';\n\nconst Annotate = require('./annotate');\nconst Common = require('./common');\nconst Template = require('./template');\n\n\nconst internals = {};\n\n\nexports.Report = class {\n\n    constructor(code, value, local, flags, messages, state, prefs) {\n\n        this.code = code;\n        this.flags = flags;\n        this.messages = messages;\n        this.path = state.path;\n        this.prefs = prefs;\n        this.state = state;\n        this.value = value;\n\n        this.message = null;\n        this.template = null;\n\n        this.local = local || {};\n        this.local.label = exports.label(this.flags, this.state, this.prefs, this.messages);\n\n        if (this.value !== undefined &&\n            !this.local.hasOwnProperty('value')) {\n\n            this.local.value = this.value;\n        }\n\n        if (this.path.length) {\n            const key = this.path[this.path.length - 1];\n            if (typeof key !== 'object') {\n                this.local.key = key;\n            }\n        }\n    }\n\n    _setTemplate(template) {\n\n        this.template = template;\n\n        if (!this.flags.label &&\n            this.path.length === 0) {\n\n            const localized = this._template(this.template, 'root');\n            if (localized) {\n                this.local.label = localized;\n            }\n        }\n    }\n\n    toString() {\n\n        if (this.message) {\n            return this.message;\n        }\n\n        const code = this.code;\n\n        if (!this.prefs.errors.render) {\n            return this.code;\n        }\n\n        const template = this._template(this.template) ||\n            this._template(this.prefs.messages) ||\n            this._template(this.messages);\n\n        if (template === undefined) {\n            return `Error code \"${code}\" is not defined, your custom type is missing the correct messages definition`;\n        }\n\n        // Render and cache result\n\n        this.message = template.render(this.value, this.state, this.prefs, this.local, { errors: this.prefs.errors, messages: [this.prefs.messages, this.messages] });\n        if (!this.prefs.errors.label) {\n            this.message = this.message.replace(/^\"\" /, '').trim();\n        }\n\n        return this.message;\n    }\n\n    _template(messages, code) {\n\n        return exports.template(this.value, messages, code || this.code, this.state, this.prefs);\n    }\n};\n\n\nexports.path = function (path) {\n\n    let label = '';\n    for (const segment of path) {\n        if (typeof segment === 'object') {          // Exclude array single path segment\n            continue;\n        }\n\n        if (typeof segment === 'string') {\n            if (label) {\n                label += '.';\n            }\n\n            label += segment;\n        }\n        else {\n            label += `[${segment}]`;\n        }\n    }\n\n    return label;\n};\n\n\nexports.template = function (value, messages, code, state, prefs) {\n\n    if (!messages) {\n        return;\n    }\n\n    if (Template.isTemplate(messages)) {\n        return code !== 'root' ? messages : null;\n    }\n\n    let lang = prefs.errors.language;\n    if (Common.isResolvable(lang)) {\n        lang = lang.resolve(value, state, prefs);\n    }\n\n    if (lang &&\n        messages[lang]) {\n\n        if (messages[lang][code] !== undefined) {\n            return messages[lang][code];\n        }\n\n        if (messages[lang]['*'] !== undefined) {\n            return messages[lang]['*'];\n        }\n    }\n\n    if (!messages[code]) {\n        return messages['*'];\n    }\n\n    return messages[code];\n};\n\n\nexports.label = function (flags, state, prefs, messages) {\n\n    if (!prefs.errors.label) {\n        return '';\n    }\n\n    if (flags.label) {\n        return flags.label;\n    }\n\n    let path = state.path;\n    if (prefs.errors.label === 'key' &&\n        state.path.length > 1) {\n\n        path = state.path.slice(-1);\n    }\n\n    const normalized = exports.path(path);\n    if (normalized) {\n        return normalized;\n    }\n\n    return exports.template(null, prefs.messages, 'root', state, prefs) ||\n        messages && exports.template(null, messages, 'root', state, prefs) ||\n        'value';\n};\n\n\nexports.process = function (errors, original, prefs) {\n\n    if (!errors) {\n        return null;\n    }\n\n    const { override, message, details } = exports.details(errors);\n    if (override) {\n        return override;\n    }\n\n    if (prefs.errors.stack) {\n        return new exports.ValidationError(message, details, original);\n    }\n\n    const limit = Error.stackTraceLimit;\n    Error.stackTraceLimit = 0;\n    const validationError = new exports.ValidationError(message, details, original);\n    Error.stackTraceLimit = limit;\n    return validationError;\n};\n\n\nexports.details = function (errors, options = {}) {\n\n    let messages = [];\n    const details = [];\n\n    for (const item of errors) {\n\n        // Override\n\n        if (item instanceof Error) {\n            if (options.override !== false) {\n                return { override: item };\n            }\n\n            const message = item.toString();\n            messages.push(message);\n\n            details.push({\n                message,\n                type: 'override',\n                context: { error: item }\n            });\n\n            continue;\n        }\n\n        // Report\n\n        const message = item.toString();\n        messages.push(message);\n\n        details.push({\n            message,\n            path: item.path.filter((v) => typeof v !== 'object'),\n            type: item.code,\n            context: item.local\n        });\n    }\n\n    if (messages.length > 1) {\n        messages = [...new Set(messages)];\n    }\n\n    return { message: messages.join('. '), details };\n};\n\n\nexports.ValidationError = class extends Error {\n\n    constructor(message, details, original) {\n\n        super(message);\n        this._original = original;\n        this.details = details;\n    }\n\n    static isError(err) {\n\n        return err instanceof exports.ValidationError;\n    }\n};\n\n\nexports.ValidationError.prototype.isJoi = true;\n\nexports.ValidationError.prototype.name = 'ValidationError';\n\nexports.ValidationError.prototype.annotate = Annotate.error;\n"
  },
  {
    "path": "lib/extend.js",
    "content": "'use strict';\n\nconst { assert, clone } = require('@hapi/hoek');\n\nconst Common = require('./common');\nconst Messages = require('./messages');\n\n\nconst internals = {};\n\n\nexports.type = function (from, options) {\n\n    const base = Object.getPrototypeOf(from);\n    const prototype = clone(base);\n    const schema = from._assign(Object.create(prototype));\n    const def = Object.assign({}, options);                                 // Shallow cloned\n    delete def.base;\n\n    prototype._definition = def;\n\n    const parent = base._definition || {};\n    def.messages = Messages.merge(parent.messages, def.messages);\n    def.properties = Object.assign({}, parent.properties, def.properties);\n\n    // Type\n\n    schema.type = def.type;\n\n    // Flags\n\n    def.flags = Object.assign({}, parent.flags, def.flags);\n\n    // Terms\n\n    const terms = Object.assign({}, parent.terms);\n    if (def.terms) {\n        for (const name in def.terms) {                                     // Only apply own terms\n            const term = def.terms[name];\n            assert(schema.$_terms[name] === undefined, 'Invalid term override for', def.type, name);\n            schema.$_terms[name] = term.init;\n            terms[name] = term;\n        }\n    }\n\n    def.terms = terms;\n\n    // Constructor arguments\n\n    if (!def.args) {\n        def.args = parent.args;\n    }\n\n    // Prepare\n\n    def.prepare = internals.prepare(def.prepare, parent.prepare);\n\n    // Coerce\n\n    if (def.coerce) {\n        if (typeof def.coerce === 'function') {\n            def.coerce = { method: def.coerce };\n        }\n\n        if (def.coerce.from &&\n            !Array.isArray(def.coerce.from)) {\n\n            def.coerce = { method: def.coerce.method, from: [].concat(def.coerce.from) };\n        }\n    }\n\n    def.coerce = internals.coerce(def.coerce, parent.coerce);\n\n    // Validate\n\n    def.validate = internals.validate(def.validate, parent.validate);\n\n    // Rules\n\n    const rules = Object.assign({}, parent.rules);\n    if (def.rules) {\n        for (const name in def.rules) {\n            const rule = def.rules[name];\n            assert(typeof rule === 'object', 'Invalid rule definition for', def.type, name);\n\n            let method = rule.method;\n            if (method === undefined) {\n                method = function () {\n\n                    return this.$_addRule(name);\n                };\n            }\n\n            if (method) {\n                assert(!prototype[name], 'Rule conflict in', def.type, name);\n                prototype[name] = method;\n            }\n\n            assert(!rules[name], 'Rule conflict in', def.type, name);\n            rules[name] = rule;\n\n            if (rule.alias) {\n                const aliases = [].concat(rule.alias);\n                for (const alias of aliases) {\n                    prototype[alias] = rule.method;\n                }\n            }\n\n            if (rule.args) {\n                rule.argsByName = new Map();\n                rule.args = rule.args.map((arg) => {\n\n                    if (typeof arg === 'string') {\n                        arg = { name: arg };\n                    }\n\n                    assert(!rule.argsByName.has(arg.name), 'Duplicated argument name', arg.name);\n\n                    if (Common.isSchema(arg.assert)) {\n                        arg.assert = arg.assert.strict().label(arg.name);\n                    }\n\n                    rule.argsByName.set(arg.name, arg);\n                    return arg;\n                });\n            }\n        }\n    }\n\n    def.rules = rules;\n\n    // Modifiers\n\n    const modifiers = Object.assign({}, parent.modifiers);\n    if (def.modifiers) {\n        for (const name in def.modifiers) {\n            assert(!prototype[name], 'Rule conflict in', def.type, name);\n\n            const modifier = def.modifiers[name];\n            assert(typeof modifier === 'function', 'Invalid modifier definition for', def.type, name);\n\n            const method = function (arg) {\n\n                return this.rule({ [name]: arg });\n            };\n\n            prototype[name] = method;\n            modifiers[name] = modifier;\n        }\n    }\n\n    def.modifiers = modifiers;\n\n    // Overrides\n\n    if (def.overrides) {\n        prototype._super = base;\n        schema.$_super = {};                                                            // Backwards compatibility\n        for (const override in def.overrides) {\n            assert(base[override], 'Cannot override missing', override);\n            def.overrides[override][Common.symbols.parent] = base[override];\n            schema.$_super[override] = base[override].bind(schema);                     // Backwards compatibility\n        }\n\n        Object.assign(prototype, def.overrides);\n    }\n\n    // Casts\n\n    def.cast = Object.assign({}, parent.cast, def.cast);\n\n    // Manifest\n\n    const manifest = Object.assign({}, parent.manifest, def.manifest);\n    manifest.build = internals.build(def.manifest && def.manifest.build, parent.manifest && parent.manifest.build);\n    def.manifest = manifest;\n\n    // Rebuild\n\n    def.rebuild = internals.rebuild(def.rebuild, parent.rebuild);\n\n    return schema;\n};\n\n\n// Helpers\n\ninternals.build = function (child, parent) {\n\n    if (!child ||\n        !parent) {\n\n        return child || parent;\n    }\n\n    return function (obj, desc) {\n\n        return parent(child(obj, desc), desc);\n    };\n};\n\n\ninternals.coerce = function (child, parent) {\n\n    if (!child ||\n        !parent) {\n\n        return child || parent;\n    }\n\n    return {\n        from: child.from && parent.from ? [...new Set([...child.from, ...parent.from])] : null,\n        method(value, helpers) {\n\n            let coerced;\n            if (!parent.from ||\n                parent.from.includes(typeof value)) {\n\n                coerced = parent.method(value, helpers);\n                if (coerced) {\n                    if (coerced.errors ||\n                        coerced.value === undefined) {\n\n                        return coerced;\n                    }\n\n                    value = coerced.value;\n                }\n            }\n\n            if (!child.from ||\n                child.from.includes(typeof value)) {\n\n                const own = child.method(value, helpers);\n                if (own) {\n                    return own;\n                }\n            }\n\n            return coerced;\n        }\n    };\n};\n\n\ninternals.prepare = function (child, parent) {\n\n    if (!child ||\n        !parent) {\n\n        return child || parent;\n    }\n\n    return function (value, helpers) {\n\n        const prepared = child(value, helpers);\n        if (prepared) {\n            if (prepared.errors ||\n                prepared.value === undefined) {\n\n                return prepared;\n            }\n\n            value = prepared.value;\n        }\n\n        return parent(value, helpers) || prepared;\n    };\n};\n\n\ninternals.rebuild = function (child, parent) {\n\n    if (!child ||\n        !parent) {\n\n        return child || parent;\n    }\n\n    return function (schema) {\n\n        parent(schema);\n        child(schema);\n    };\n};\n\n\ninternals.validate = function (child, parent) {\n\n    if (!child ||\n        !parent) {\n\n        return child || parent;\n    }\n\n    return function (value, helpers) {\n\n        const result = parent(value, helpers);\n        if (result) {\n            if (result.errors &&\n                (!Array.isArray(result.errors) || result.errors.length)) {\n\n                return result;\n            }\n\n            value = result.value;\n        }\n\n        return child(value, helpers) || result;\n    };\n};\n"
  },
  {
    "path": "lib/index.d.ts",
    "content": "// The following definitions have been copied (almost) as-is from:\n// https://github.com/DefinitelyTyped/DefinitelyTyped/tree/master/types/hapi__joi\n//\n// Note: This file is expected to change dramatically in the next major release and have been\n// imported here to make migrating back to the \"joi\" module name simpler. It include known bugs\n// and other issues. It does not include some new features included in version 17.2.0 or newer.\n//\n// TypeScript Version: 2.8\n\n// TODO express type of Schema in a type-parameter (.default, .valid, .example etc)\nimport type { StandardSchemaV1 } from \"@standard-schema/spec\";\n\ndeclare namespace Joi {\n  type Types =\n    | \"any\"\n    | \"alternatives\"\n    | \"array\"\n    | \"boolean\"\n    | \"binary\"\n    | \"date\"\n    | \"function\"\n    | \"link\"\n    | \"number\"\n    | \"object\"\n    | \"string\"\n    | \"symbol\";\n\n  type BasicType = boolean | number | string | any[] | object | null;\n\n  type LanguageMessages = Record<string, string | Record<string, string>>;\n\n  type PresenceMode = \"optional\" | \"required\" | \"forbidden\";\n\n  interface ErrorFormattingOptions {\n    /**\n     * when true, error message templates will escape special characters to HTML entities, for security purposes.\n     *\n     * @default false\n     */\n    escapeHtml?: boolean;\n    /**\n     * defines the value used to set the label context variable.\n     */\n    label?: \"path\" | \"key\" | false;\n    /**\n     * The preferred language code for error messages.\n     * The value is matched against keys at the root of the messages object, and then the error code as a child key of that.\n     * Can be a reference to the value, global context, or local context which is the root value passed to the validation function.\n     *\n     * Note that references to the value are usually not what you want as they move around the value structure relative to where the error happens.\n     * Instead, either use the global context, or the absolute value (e.g. `Joi.ref('/variable')`)\n     */\n    language?: keyof LanguageMessages;\n    /**\n     * when false, skips rendering error templates. Useful when error messages are generated elsewhere to save processing time.\n     *\n     * @default true\n     */\n    render?: boolean;\n    /**\n     * when true, the main error will possess a stack trace, otherwise it will be disabled.\n     * Defaults to false for performances reasons. Has no effect on platforms other than V8/node.js as it uses the Stack trace API.\n     *\n     * @default false\n     */\n    stack?: boolean;\n    /**\n     * overrides the way values are wrapped (e.g. `[]` around arrays, `\"\"` around labels).\n     * Each key can be set to a string with one (same character before and after the value) or two characters (first character\n     * before and second character after), or `false` to disable wrapping.\n     */\n    wrap?: {\n      /**\n       * the characters used around `{#label}` references. Defaults to `'\"'`.\n       *\n       * @default '\"'\n       */\n      label?: string | false;\n\n      /**\n       * the characters used around array values. Defaults to `'[]'`\n       *\n       * @default '[]'\n       */\n      array?: string | false;\n\n      /**\n       * the characters used around array string values. Defaults to no wrapping.\n       *\n       * @default false\n       */\n      string?: string | false;\n    };\n  }\n\n  interface BaseValidationOptions {\n    /**\n     * when true, stops validation on the first error, otherwise returns all the errors found.\n     *\n     * @default true\n     */\n    abortEarly?: boolean;\n    /**\n     * when true, allows object to contain unknown keys which are ignored.\n     *\n     * @default false\n     */\n    allowUnknown?: boolean;\n    /**\n     * when true, return artifacts alongside the value.\n     *\n     * @default false\n     */\n    artifacts?: boolean;\n    /**\n     * when true, schema caching is enabled (for schemas with explicit caching rules).\n     *\n     * @default false\n     */\n    cache?: boolean;\n    /**\n     * provides an external data set to be used in references\n     */\n    context?: Context;\n    /**\n     * when true, attempts to cast values to the required types (e.g. a string to a number).\n     *\n     * @default true\n     */\n    convert?: boolean;\n    /**\n     * sets the string format used when converting dates to strings in error messages and casting.\n     *\n     * @default 'iso'\n     */\n    dateFormat?: \"date\" | \"iso\" | \"string\" | \"time\" | \"utc\";\n    /**\n     * when true, valid results and throw errors are decorated with a debug property which includes an array of the validation steps used to generate the returned result.\n     *\n     * @default false\n     */\n    debug?: boolean;\n    /**\n     * error formatting settings.\n     */\n    errors?: ErrorFormattingOptions;\n    /**\n     * if false, the external rules set with `any.external()` are ignored, which is required to ignore any external validations in synchronous mode (or an exception is thrown).\n     *\n     * @default true\n     */\n    externals?: boolean;\n    /**\n     * when true, do not apply default values.\n     *\n     * @default false\n     */\n    noDefaults?: boolean;\n    /**\n     * when true, inputs are shallow cloned to include non-enumerable properties.\n     *\n     * @default false\n     */\n    nonEnumerables?: boolean;\n    /**\n     * sets the default presence requirements. Supported modes: 'optional', 'required', and 'forbidden'.\n     *\n     * @default 'optional'\n     */\n    presence?: PresenceMode;\n    /**\n     * when true, ignores unknown keys with a function value.\n     *\n     * @default false\n     */\n    skipFunctions?: boolean;\n    /**\n     * remove unknown elements from objects and arrays.\n     * - when true, all unknown elements will be removed\n     * - when an object:\n     *      - objects - set to true to remove unknown keys from objects\n     *\n     * @default false\n     */\n    stripUnknown?: boolean | { arrays?: boolean; objects?: boolean };\n  }\n\n  interface ValidationOptions extends BaseValidationOptions {\n    /**\n     * overrides individual error messages. Defaults to no override (`{}`).\n     * Messages use the same rules as templates.\n     * Variables in double braces `{{var}}` are HTML escaped if the option `errors.escapeHtml` is set to true.\n     *\n     * @default {}\n     */\n    messages?: LanguageMessages;\n  }\n\n  interface AsyncValidationOptions extends ValidationOptions {\n    /**\n     * when true, artifacts are returned alongside the value (i.e. `{ value, artifacts }`)\n     *\n     * @default false\n     */\n    artifacts?: boolean;\n    /**\n     * when true, warnings are returned alongside the value (i.e. `{ value, warning }`).\n     *\n     * @default false\n     */\n    warnings?: boolean;\n  }\n\n  interface LanguageMessageTemplate {\n    source: string;\n    rendered: string;\n  }\n\n  interface ErrorValidationOptions extends BaseValidationOptions {\n    messages?: Record<string, LanguageMessageTemplate>;\n  }\n\n  interface RenameOptions {\n    /**\n     * if true, does not delete the old key name, keeping both the new and old keys in place.\n     *\n     * @default false\n     */\n    alias?: boolean;\n    /**\n     * if true, allows renaming multiple keys to the same destination where the last rename wins.\n     *\n     * @default false\n     */\n    multiple?: boolean;\n    /**\n     * if true, allows renaming a key over an existing key.\n     *\n     * @default false\n     */\n    override?: boolean;\n    /**\n     * if true, skip renaming of a key if it's undefined.\n     *\n     * @default false\n     */\n    ignoreUndefined?: boolean;\n  }\n\n  interface TopLevelDomainOptions {\n    /**\n     * - `true` to use the IANA list of registered TLDs. This is the default value.\n     * - `false` to allow any TLD not listed in the `deny` list, if present.\n     * - A `Set` or array of the allowed TLDs. Cannot be used together with `deny`.\n     */\n    allow?: Set<string> | string[] | boolean;\n    /**\n     * - A `Set` or array of the forbidden TLDs. Cannot be used together with a custom `allow` list.\n     */\n    deny?: Set<string> | string[];\n  }\n\n  interface HierarchySeparatorOptions {\n    /**\n     * overrides the default `.` hierarchy separator. Set to false to treat the key as a literal value.\n     *\n     * @default '.'\n     */\n    separator?: string | false;\n  }\n\n  interface DependencyOptions extends HierarchySeparatorOptions {\n    /**\n     * overrides the default check for a present value.\n     *\n     * @default (resolved) => resolved !== undefined\n     */\n    isPresent?: (resolved: any) => boolean;\n  }\n\n  interface EmailOptions {\n    /**\n     * if `true`, domains ending with a `.` character are permitted\n     *\n     * @default false\n     */\n    allowFullyQualified?: boolean;\n    /**\n     * If `true`, Unicode characters are permitted\n     *\n     * @default true\n     */\n    allowUnicode?: boolean;\n    /**\n     * If `true`, underscores (`_`) are allowed in the domain name\n     *\n     * @default false\n     */\n    allowUnderscore?: boolean;\n    /**\n     * if `true`, ignore invalid email length errors.\n     *\n     * @default false\n     */\n    ignoreLength?: boolean;\n    /**\n     * if true, allows multiple email addresses in a single string, separated by , or the separator characters.\n     *\n     * @default false\n     */\n    multiple?: boolean;\n    /**\n     * when multiple is true, overrides the default , separator. String can be a single character or multiple separator characters.\n     *\n     * @default ','\n     */\n    separator?: string | string[];\n    /**\n     * Options for TLD (top level domain) validation. By default, the TLD must be a valid name listed on the [IANA registry](http://data.iana.org/TLD/tlds-alpha-by-domain.txt)\n     *\n     * @default { allow: true }\n     */\n    tlds?: TopLevelDomainOptions | false;\n    /**\n     * Number of segments required for the domain. Be careful since some domains, such as `io`, directly allow email.\n     *\n     * @default 2\n     */\n    minDomainSegments?: number;\n    /**\n     * The maximum number of domain segments (e.g. `x.y.z` has 3 segments) allowed. Defaults to no limit.\n     *\n     * @default Infinity\n     */\n    maxDomainSegments?: number;\n  }\n\n  interface DomainOptions {\n    /**\n     * if `true`, domains ending with a `.` character are permitted\n     *\n     * @default false\n     */\n    allowFullyQualified?: boolean;\n    /**\n     * If `true`, Unicode characters are permitted\n     *\n     * @default true\n     */\n    allowUnicode?: boolean;\n    /**\n     * If `true`, underscores (`_`) are allowed in the domain name\n     *\n     * @default false\n     */\n    allowUnderscore?: boolean;\n    /**\n     * Options for TLD (top level domain) validation. By default, the TLD must be a valid name listed on the [IANA registry](http://data.iana.org/TLD/tlds-alpha-by-domain.txt)\n     *\n     * @default { allow: true }\n     */\n    tlds?: TopLevelDomainOptions | false;\n    /**\n     * Number of segments required for the domain.\n     *\n     * @default 2\n     */\n    minDomainSegments?: number;\n    /**\n     * The maximum number of domain segments (e.g. `x.y.z` has 3 segments) allowed. Defaults to no limit.\n     *\n     * @default Infinity\n     */\n    maxDomainSegments?: number;\n  }\n\n  interface HexOptions {\n    /**\n     * hex decoded representation must be byte aligned.\n     * @default false\n     */\n    byteAligned?: boolean;\n    /**\n     * controls whether the prefix `0x` or `0X` is allowed (or required) on hex strings.\n     * When `true`, the prefix must be provided.\n     * When `false`, the prefix is forbidden.\n     * When `optional`, the prefix is allowed but not required.\n     *\n     * @default false\n     */\n    prefix?: boolean | \"optional\";\n  }\n\n  interface IpOptions {\n    /**\n     * One or more IP address versions to validate against. Valid values: ipv4, ipv6, ipvfuture\n     */\n    version?: string | string[];\n    /**\n     * Used to determine if a CIDR is allowed or not. Valid values: optional, required, forbidden\n     */\n    cidr?: PresenceMode;\n  }\n\n  type GuidVersions =\n    | \"uuidv1\"\n    | \"uuidv2\"\n    | \"uuidv3\"\n    | \"uuidv4\"\n    | \"uuidv5\"\n    | \"uuidv6\"\n    | \"uuidv7\"\n    | \"uuidv8\";\n\n  interface GuidOptions {\n    version?: GuidVersions[] | GuidVersions;\n    separator?: boolean | \"-\" | \":\";\n    /**\n     * Defines the allowed or required GUID wrapper characters where:\n     * - `undefined` - (default) the GUID can be optionally wrapped with `{}`, `[]`, or `()`. The opening and closing characters must be a matching pair.\n     * - `true` - the GUID must be wrapped with `{}`, `[]`, or `()`. The opening and closing characters must be a matching pair.\n     * - `false` - wrapper characters are not allowed.\n     * - `'['`, `'{'`, or `'('` - a specific wrapper is required (e.g., if `wrapper` is `'['`, the GUID must be enclosed in square brackets).\n     */\n    wrapper?: true | false | \"[\" | \"{\" | \"(\" | undefined;\n  }\n\n  interface UriOptions {\n    /**\n     * Specifies one or more acceptable Schemes, should only include the scheme name.\n     * Can be an Array or String (strings are automatically escaped for use in a Regular Expression).\n     */\n    scheme?: string | RegExp | Array<string | RegExp>;\n    /**\n     * Allow relative URIs.\n     *\n     * @default false\n     */\n    allowRelative?: boolean;\n    /**\n     * Restrict only relative URIs.\n     *\n     * @default false\n     */\n    relativeOnly?: boolean;\n    /**\n     * Allows unencoded square brackets inside the query string.\n     * This is NOT RFC 3986 compliant but query strings like abc[]=123&abc[]=456 are very common these days.\n     *\n     * @default false\n     */\n    allowQuerySquareBrackets?: boolean;\n    /**\n     * Validate the domain component using the options specified in `string.domain()`.\n     */\n    domain?: DomainOptions;\n    /**\n     * Encode URI before validation.\n     *\n     * @default false\n     */\n    encodeUri?: boolean;\n  }\n\n  interface DataUriOptions {\n    /**\n     * optional parameter defaulting to true which will require `=` padding if true or make padding optional if false\n     *\n     * @default true\n     */\n    paddingRequired?: boolean;\n  }\n\n  interface Base64Options extends Pick<DataUriOptions, \"paddingRequired\"> {\n    /**\n     * if true, uses the URI-safe base64 format which replaces `+` with `-` and `\\` with `_`.\n     *\n     * @default false\n     */\n    urlSafe?: boolean;\n  }\n\n  interface SwitchCases {\n    /**\n     * the required condition joi type.\n     */\n    is: SchemaLike;\n    /**\n     * the alternative schema type if the condition is true.\n     */\n    then: SchemaLike;\n  }\n\n  interface SwitchDefault {\n    /**\n     * the alternative schema type if no cases matched.\n     * Only one otherwise statement is allowed in switch as the last array item.\n     */\n    otherwise: SchemaLike;\n  }\n\n  interface WhenOptions<ThenSchema = any, OtherwiseSchema = any> {\n    /**\n     * the required condition joi type.\n     */\n    is?: SchemaLike;\n\n    /**\n     * the negative version of `is` (`then` and `otherwise` have reverse\n     * roles).\n     */\n    not?: SchemaLike;\n\n    /**\n     * the alternative schema type if the condition is true. Required if otherwise or switch are missing.\n     */\n    then?: SchemaLike<ThenSchema>;\n\n    /**\n     * the alternative schema type if the condition is false. Required if then or switch are missing.\n     */\n    otherwise?: SchemaLike<OtherwiseSchema>;\n\n    /**\n     * the list of cases. Required if then is missing.  Required if then or otherwise are missing.\n     */\n    switch?: Array<SwitchCases | SwitchDefault>;\n\n    /**\n     * whether to stop applying further conditions if the condition is true.\n     */\n    break?: boolean;\n  }\n\n  interface WhenSchemaOptions<ThenSchema = any, OtherwiseSchema = any> {\n    /**\n     * the alternative schema type if the condition is true. Required if otherwise is missing.\n     */\n    then?: SchemaLike<ThenSchema>;\n    /**\n     * the alternative schema type if the condition is false. Required if then is missing.\n     */\n    otherwise?: SchemaLike<OtherwiseSchema>;\n  }\n\n  interface Cache {\n    /**\n     * Add an item to the cache.\n     *\n     * Note that key and value can be anything including objects, array, etc.\n     */\n    set(key: any, value: any): void;\n\n    /**\n     * Retrieve an item from the cache.\n     *\n     * Note that key and value can be anything including objects, array, etc.\n     */\n    get(key: any): any;\n  }\n  interface CacheProvisionOptions {\n    /**\n     * number of items to store in the cache before the least used items are dropped.\n     *\n     * @default 1000\n     */\n    max: number;\n  }\n\n  interface CacheConfiguration {\n    /**\n     * Provisions a simple LRU cache for caching simple inputs (`undefined`, `null`, strings, numbers, and booleans).\n     */\n    provision(options?: CacheProvisionOptions): void;\n  }\n\n  interface CompileOptions {\n    /**\n     * If true and the provided schema is (or contains parts) using an older version of joi, will return a compiled schema that is compatible with the older version.\n     * If false, the schema is always compiled using the current version and if older schema components are found, an error is thrown.\n     */\n    legacy: boolean;\n  }\n\n  interface IsSchemaOptions {\n    /**\n     * If true, will identify schemas from older versions of joi, otherwise will throw an error.\n     *\n     * @default false\n     */\n    legacy: boolean;\n  }\n\n  interface ReferenceOptions extends HierarchySeparatorOptions {\n    /**\n     * a function with the signature `function(value)` where `value` is the resolved reference value and the return value is the adjusted value to use.\n     * Note that the adjust feature will not perform any type validation on the adjusted value and it must match the value expected by the rule it is used in.\n     * Cannot be used with `map`.\n     *\n     * @example `(value) => value + 5`\n     */\n    adjust?: (value: any) => any;\n\n    /**\n     * an array of array pairs using the format `[[key, value], [key, value]]` used to maps the resolved reference value to another value.\n     * If the resolved value is not in the map, it is returned as-is.\n     * Cannot be used with `adjust`.\n     */\n    map?: Array<[any, any]>;\n\n    /**\n     * overrides default prefix characters.\n     */\n    prefix?: {\n      /**\n       * references to the globally provided context preference.\n       *\n       * @default '$'\n       */\n      global?: string;\n\n      /**\n       * references to error-specific or rule specific context.\n       *\n       * @default '#'\n       */\n      local?: string;\n\n      /**\n       * references to the root value being validated.\n       *\n       * @default '/'\n       */\n      root?: string;\n    };\n\n    /**\n     * If set to a number, sets the reference relative starting point.\n     * Cannot be combined with separator prefix characters.\n     * Defaults to the reference key prefix (or 1 if none present)\n     */\n    ancestor?: number;\n\n    /**\n     * creates an in-reference.\n     */\n    in?: boolean;\n\n    /**\n     * when true, the reference resolves by reaching into maps and sets.\n     */\n    iterables?: boolean;\n\n    /**\n     * when true, the value of the reference is used instead of its name in error messages\n     * and template rendering. Defaults to false.\n     */\n    render?: boolean;\n  }\n\n  interface StringRegexOptions {\n    /**\n     * optional pattern name.\n     */\n    name?: string;\n\n    /**\n     * when true, the provided pattern will be disallowed instead of required.\n     *\n     * @default false\n     */\n    invert?: boolean;\n  }\n\n  interface RuleOptions {\n    /**\n     * if true, the rules will not be replaced by the same unique rule later.\n     *\n     * For example, `Joi.number().min(1).rule({ keep: true }).min(2)` will keep both `min()` rules instead of the later rule overriding the first.\n     *\n     * @default false\n     */\n    keep?: boolean;\n\n    /**\n     * a single message string or a messages object where each key is an error code and corresponding message string as value.\n     *\n     * The object is the same as the messages used as an option in `any.validate()`.\n     * The strings can be plain messages or a message template.\n     */\n    message?: string | LanguageMessages;\n\n    /**\n     * if true, turns any error generated by the ruleset to warnings.\n     */\n    warn?: boolean;\n  }\n\n  interface ErrorReport extends Error {\n    code: string;\n    flags: Record<string, ExtensionFlag>;\n    path: string[];\n    prefs: ErrorValidationOptions;\n    messages: LanguageMessages;\n    state: State;\n    value: any;\n    local: any;\n  }\n\n  interface ValidationError extends Error {\n    name: \"ValidationError\";\n\n    isJoi: boolean;\n\n    /**\n     * array of errors.\n     */\n    details: ValidationErrorItem[];\n\n    /**\n     * function that returns a string with an annotated version of the object pointing at the places where errors occurred.\n     *\n     * NOTE: This method does not exist in browser builds of Joi\n     *\n     * @param stripColors - if truthy, will strip the colors out of the output.\n     */\n    annotate(stripColors?: boolean): string;\n\n    _original: any;\n  }\n\n  interface ValidationErrorItem {\n    message: string;\n    path: Array<string | number>;\n    type: string;\n    context?: Context;\n  }\n\n  type ValidationErrorFunction = (\n    errors: ErrorReport[]\n  ) => string | ValidationErrorItem | Error | ErrorReport[];\n\n  interface ValidationWarning {\n    message: string;\n\n    details: ValidationErrorItem[];\n  }\n\n  type ValidationResult<TSchema = any> =\n    | {\n        error: undefined;\n        warning?: ValidationError;\n        value: TSchema;\n      }\n    | {\n        error: ValidationError;\n        warning?: ValidationError;\n        value: any;\n      };\n\n  interface CreateErrorOptions {\n    flags?: boolean;\n    messages?: LanguageMessages;\n  }\n\n  interface ModifyOptions {\n    each?: boolean;\n    once?: boolean;\n    ref?: boolean;\n    schema?: boolean;\n  }\n\n  interface MutateRegisterOptions {\n    family?: any;\n    key?: any;\n  }\n\n  interface SetFlagOptions {\n    clone: boolean;\n  }\n\n  interface CustomHelpers<V = any> {\n    schema: ExtensionBoundSchema;\n    state: State;\n    prefs: ValidationOptions;\n    original: V;\n    warn: (code: string, local?: Context) => void;\n    error: (code: string, local?: Context, localState?: State) => ErrorReport;\n    message: (messages: LanguageMessages, local?: Context) => ErrorReport;\n  }\n\n  type CustomValidator<V = any, R = V> = (\n    value: V,\n    helpers: CustomHelpers<R>\n  ) => R | ErrorReport;\n\n  interface ExternalHelpers<V = any> {\n    schema: ExtensionBoundSchema;\n    linked: ExtensionBoundSchema | null;\n    state: State;\n    prefs: ValidationOptions;\n    original: V;\n    warn: (code: string, local?: Context) => void;\n    error: (code: string, local?: Context) => ErrorReport;\n    message: (messages: LanguageMessages, local?: Context) => ErrorReport;\n  }\n\n  type ExternalValidationFunction<V = any, R = V> = (\n    value: V,\n    helpers: ExternalHelpers<R>\n  ) => R | undefined;\n\n  type Primitives = string | number | boolean | bigint | symbol | null;\n\n  type SchemaLikeWithoutArray<TSchema = any> = Exclude<\n    Primitives | Schema<TSchema> | SchemaMap<TSchema>,\n    any[]\n  >;\n  type SchemaLike<TSchema = any> = SchemaLikeWithoutArray<TSchema> | object;\n\n  type NullableType<T> = undefined | null | T;\n\n  type IsPrimitiveSubset<T> = [T] extends [string]\n    ? true\n    : [T] extends [number]\n    ? true\n    : [T] extends [bigint]\n    ? true\n    : [T] extends [boolean]\n    ? true\n    : [T] extends [symbol]\n    ? true\n    : [T] extends [null]\n    ? true\n    : [T] extends [undefined]\n    ? true\n    : false;\n\n  type IsUnion<T, U extends T = T> = T extends unknown\n    ? [U] extends [T]\n      ? false\n      : true\n    : false;\n\n  type IsNonPrimitiveSubsetUnion<T> = true extends IsUnion<T>\n    ? true extends IsPrimitiveSubset<T>\n      ? false\n      : true\n    : false;\n\n  type ObjectPropertiesSchema<T = any> = true extends IsNonPrimitiveSubsetUnion<\n    Exclude<T, undefined | null>\n  >\n    ? Joi.AlternativesSchema\n    : T extends NullableType<string>\n    ? Joi.StringSchema\n    : T extends NullableType<number>\n    ? Joi.NumberSchema\n    : T extends NullableType<bigint>\n    ? Joi.NumberSchema\n    : T extends NullableType<boolean>\n    ? Joi.BooleanSchema\n    : T extends NullableType<Date>\n    ? Joi.DateSchema\n    : T extends NullableType<Buffer>\n    ? Joi.BinarySchema\n    : T extends NullableType<Array<any>>\n    ? Joi.ArraySchema\n    : T extends NullableType<object>\n    ? StrictSchemaMap<T> | ObjectSchema<T>\n    : never;\n\n  type PartialSchemaMap<TSchema = any> = {\n    [key in keyof TSchema]?: SchemaLike | SchemaLike[];\n  };\n\n  type StrictSchemaMap<TSchema = any> = {\n    [key in keyof TSchema]-?: ObjectPropertiesSchema<TSchema[key]>;\n  };\n\n  type SchemaMap<TSchema = any, isStrict = false> = isStrict extends true\n    ? StrictSchemaMap<TSchema>\n    : PartialSchemaMap<TSchema>;\n\n  type Schema<P = any> =\n    | AnySchema<P>\n    | ArraySchema<P>\n    | AlternativesSchema<P>\n    | BinarySchema<P>\n    | BooleanSchema<P>\n    | DateSchema<P>\n    | FunctionSchema<P>\n    | NumberSchema<P>\n    | ObjectSchema<P>\n    | StringSchema<P>\n    | LinkSchema<P>\n    | SymbolSchema<P>;\n\n  type SchemaFunction = (schema: Schema) => Schema;\n\n  interface AddRuleOptions {\n    name: string;\n    args?: {\n      [key: string]: any;\n    };\n  }\n\n  interface GetRuleOptions {\n    args?: Record<string, any>;\n    method?: string;\n    name: string;\n    operator?: string;\n  }\n\n  interface SchemaInternals {\n    /**\n     * Parent schema object.\n     */\n    $_super: Schema;\n\n    /**\n     * Terms of current schema.\n     */\n    $_terms: Record<string, any>;\n\n    /**\n     * Adds a rule to current validation schema.\n     */\n    $_addRule(rule: string | AddRuleOptions): Schema;\n\n    /**\n     * Internally compiles schema.\n     */\n    $_compile(schema: SchemaLike, options?: CompileOptions): Schema;\n\n    /**\n     * Creates a joi error object.\n     */\n    $_createError(\n      code: string,\n      value: any,\n      context: Context,\n      state: State,\n      prefs: ValidationOptions,\n      options?: CreateErrorOptions\n    ): Err;\n\n    /**\n     * Get value from given flag.\n     */\n    $_getFlag(name: string): any;\n\n    /**\n     * Retrieve some rule configuration.\n     */\n    $_getRule(name: string): GetRuleOptions | undefined;\n\n    $_mapLabels(path: string | string[]): string;\n\n    /**\n     * Returns true if validations runs fine on given value.\n     */\n    $_match(value: any, state: State, prefs: ValidationOptions): boolean;\n\n    $_modify(options?: ModifyOptions): Schema;\n\n    /**\n     * Resets current schema.\n     */\n    $_mutateRebuild(): this;\n\n    $_mutateRegister(schema: Schema, options?: MutateRegisterOptions): void;\n\n    /**\n     * Get value from given property.\n     */\n    $_property(name: string): any;\n\n    /**\n     * Get schema at given path.\n     */\n    $_reach(path: string[]): Schema;\n\n    /**\n     * Get current schema root references.\n     */\n    $_rootReferences(): any;\n\n    /**\n     * Set flag to given value.\n     */\n    $_setFlag(flag: string, value: any, options?: SetFlagOptions): void;\n\n    /**\n     * Runs internal validations against given value.\n     */\n    $_validate(\n      value: any,\n      state: State,\n      prefs: ValidationOptions\n    ): ValidationResult;\n  }\n\n  interface AnySchema<TSchema = any>\n    extends SchemaInternals,\n      StandardSchemaV1<TSchema> {\n    /**\n     * Flags of current schema.\n     */\n    _flags: Record<string, any>;\n\n    /**\n     * Starts a ruleset in order to apply multiple rule options. The set ends when `rule()`, `keep()`, `message()`, or `warn()` is called.\n     */\n    $: this;\n\n    /**\n     * Starts a ruleset in order to apply multiple rule options. The set ends when `rule()`, `keep()`, `message()`, or `warn()` is called.\n     */\n    ruleset: this;\n\n    type?: Types | string;\n\n    /**\n     * Whitelists a value\n     */\n    allow(...values: any[]): this;\n\n    /**\n     * Assign target alteration options to a schema that are applied when `any.tailor()` is called.\n     * @param targets - an object where each key is a target name, and each value is a function that takes an schema and returns an schema.\n     */\n    alter(targets: Record<string, (schema: this) => Schema>): this;\n\n    /**\n     * Assigns the schema an artifact id which is included in the validation result if the rule passed validation.\n     * @param id - any value other than undefined which will be returned as-is in the result artifacts map.\n     */\n    artifact(id: any): this;\n\n    /**\n     * By default, some Joi methods to function properly need to rely on the Joi instance they are attached to because\n     * they use `this` internally.\n     * So `Joi.string()` works but if you extract the function from it and call `string()` it won't.\n     * `bind()` creates a new Joi instance where all the functions relying on `this` are bound to the Joi instance.\n     */\n    bind(): this;\n\n    /**\n     * Adds caching to the schema which will attempt to cache the validation results (success and failures) of incoming inputs.\n     * If no cache is passed, a default cache is provisioned by using `cache.provision()` internally.\n     */\n    cache(cache?: Cache): this;\n\n    /**\n     * Casts the validated value to the specified type.\n     */\n    cast(to: \"map\" | \"number\" | \"set\" | \"string\"): this;\n\n    /**\n     * Returns a new type that is the result of adding the rules of one type to another.\n     */\n    concat(schema: this): this;\n\n    /**\n     * Adds a custom validation function.\n     */\n    custom(fn: CustomValidator, description?: string): this;\n\n    /**\n     * Sets a default value if the original value is `undefined` where:\n     * @param value - the default value. One of:\n     *    - a literal value (string, number, object, etc.)\n     *    - a [references](#refkey-options)\n     *    - a function which returns the default value using the signature `function(parent, helpers)` where:\n     *        - `parent` - a clone of the object containing the value being validated. Note that since specifying a\n     *          `parent` argument performs cloning, do not declare format arguments if you are not using them.\n     *        - `helpers` - same as those described in [`any.custom()`](anycustomermethod_description)\n     *\n     * When called without any `value` on an object schema type, a default value will be automatically generated\n     * based on the default values of the object keys.\n     *\n     * Note that if value is an object, any changes to the object after `default()` is called will change the\n     *  reference and any future assignment.\n     */\n    default(\n      value?:\n        | BasicType\n        | Reference\n        | ((parent: any, helpers: CustomHelpers) => BasicType | Reference)\n    ): this;\n\n    /**\n     * Returns a plain object representing the schema's rules and properties\n     */\n    describe(): Description;\n\n    /**\n     * Annotates the key\n     */\n    description(desc: string): this;\n\n    /**\n     * Disallows values.\n     */\n    disallow(...values: any[]): this;\n\n    /**\n     * Considers anything that matches the schema to be empty (undefined). Overrides any previous calls to empty.\n     * @param schema - an object, value, or joi schema to match or an array of objects, values, and joi schemas to match. An undefined schema unsets that rule.\n     */\n    empty(schema?: SchemaLike): this;\n\n    /**\n     * Adds the provided values into the allowed whitelist and marks them as the only valid values allowed.\n     */\n    equal(...values: any[]): this;\n\n    /**\n     * Overrides the default joi error with a custom error if the rule fails where:\n     * @param err - can be:\n     *   an instance of `Error` - the override error.\n     *   a `function(errors)`, taking an array of errors as argument, where it must either:\n     *    return a `string` - substitutes the error message with this text\n     *    return a single ` object` or an `Array` of it, where:\n     *     `type` - optional parameter providing the type of the error (eg. `number.min`).\n     *     `message` - optional parameter if `template` is provided, containing the text of the error.\n     *     `template` - optional parameter if `message` is provided, containing a template string, using the same format as usual joi language errors.\n     *     `context` - optional parameter, to provide context to your error if you are using the `template`.\n     *    return an `Error` - same as when you directly provide an `Error`, but you can customize the error message based on the errors.\n     *\n     * Note that if you provide an `Error`, it will be returned as-is, unmodified and undecorated with any of the\n     * normal joi error properties. If validation fails and another error is found before the error\n     * override, that error will be returned and the override will be ignored (unless the `abortEarly`\n     * option has been set to `false`).\n     */\n    error(err: Error | ValidationErrorFunction): this;\n\n    /**\n     * Annotates the key with an example value, must be valid.\n     */\n    example(value: any, options?: { override: boolean }): this;\n\n    /**\n     * Marks a key as required which will not allow undefined as value. All keys are optional by default.\n     */\n    exist(): this;\n\n    /**\n     * Adds an external validation rule.\n     *\n     * Note that external validation rules are only called after the all other validation rules for the entire schema (from the value root) are checked.\n     * This means that any changes made to the value by the external rules are not available to any other validation rules during the non-external validation phase.\n     * If schema validation failed, no external validation rules are called.\n     */\n    external(method: ExternalValidationFunction, description?: string): this;\n\n    /**\n     * Returns a sub-schema based on a path of object keys or schema ids.\n     *\n     * @param path - a dot `.` separated path string or a pre-split array of path keys. The keys must match the sub-schema id or object key (if no id was explicitly set).\n     */\n    extract(path: string | string[]): Schema;\n\n    /**\n     * Sets a failover value if the original value fails passing validation.\n     *\n     * @param value - the failover value. value supports references. value may be assigned a function which returns the default value.\n     *\n     * If value is specified as a function that accepts a single parameter, that parameter will be a context object that can be used to derive the resulting value.\n     * Note that if value is an object, any changes to the object after `failover()` is called will change the reference and any future assignment.\n     * Use a function when setting a dynamic value (e.g. the current time).\n     * Using a function with a single argument performs some internal cloning which has a performance impact.\n     * If you do not need access to the context, define the function without any arguments.\n     */\n    failover(value: any): this;\n\n    /**\n     * Marks a key as forbidden which will not allow any value except undefined. Used to explicitly forbid keys.\n     */\n    forbidden(): this;\n\n    /**\n     * Returns a new schema where each of the path keys listed have been modified.\n     *\n     * @param key - an array of key strings, a single key string, or an array of arrays of pre-split key strings.\n     * @param adjuster - a function which must return a modified schema.\n     */\n    fork(key: string | string[] | string[][], adjuster: SchemaFunction): this;\n\n    /**\n     * Sets a schema id for reaching into the schema via `any.extract()`.\n     * If no id is set, the schema id defaults to the object key it is associated with.\n     * If the schema is used in an array or alternatives type and no id is set, the schema in unreachable.\n     */\n    id(name?: string): this;\n\n    /**\n     * Disallows values.\n     */\n    invalid(...values: any[]): this;\n\n    /**\n     * Returns a boolean indicating whether this schema contains a rule that requires asynchronous validation.\n     */\n    isAsync(): boolean;\n\n    /**\n     * Same as `rule({ keep: true })`.\n     *\n     * Note that `keep()` will terminate the current ruleset and cannot be followed by another rule option.\n     * Use `rule()` to apply multiple rule options.\n     */\n    keep(): this;\n\n    /**\n     * Overrides the key name in error messages.\n     */\n    label(name: string): this;\n\n    /**\n     * Same as `rule({ message })`.\n     *\n     * Note that `message()` will terminate the current ruleset and cannot be followed by another rule option.\n     * Use `rule()` to apply multiple rule options.\n     */\n    message(message: string): this;\n\n    /**\n     * Same as `any.prefs({ messages })`.\n     * Note that while `any.message()` applies only to the last rule or ruleset, `any.messages()` applies to the entire schema.\n     */\n    messages(messages: LanguageMessages): this;\n\n    /**\n     * Attaches metadata to the key.\n     */\n    meta(meta: object): this;\n\n    /**\n     * Disallows values.\n     */\n    not(...values: any[]): this;\n\n    /**\n     * Annotates the key\n     */\n    note(...notes: string[]): this;\n\n    /**\n     * Requires the validated value to match of the provided `any.allow()` values.\n     * It has not effect when called together with `any.valid()` since it already sets the requirements.\n     * When used with `any.allow()` it converts it to an `any.valid()`.\n     */\n    only(): this;\n\n    /**\n     * Marks a key as optional which will allow undefined as values. Used to annotate the schema for readability as all keys are optional by default.\n     */\n    optional(): this;\n\n    /**\n     * Overrides the global validate() options for the current key and any sub-key.\n     */\n    options(options: ValidationOptions): this;\n\n    /**\n     * Overrides the global validate() options for the current key and any sub-key.\n     */\n    prefs(options: ValidationOptions): this;\n\n    /**\n     * Overrides the global validate() options for the current key and any sub-key.\n     */\n    preferences(options: ValidationOptions): this;\n\n    /**\n     * Sets the presence mode for the schema.\n     */\n    presence(mode: PresenceMode): this;\n\n    /**\n     * Outputs the original untouched value instead of the casted value.\n     */\n    raw(enabled?: boolean): this;\n\n    /**\n     * Marks a key as required which will not allow undefined as value. All keys are optional by default.\n     */\n    required(): this;\n\n    /**\n     * Applies a set of rule options to the current ruleset or last rule added.\n     *\n     * When applying rule options, the last rule (e.g. `min()`) is used unless there is an active ruleset defined (e.g. `$.min().max()`)\n     * in which case the options are applied to all the provided rules.\n     * Once `rule()` is called, the previous rules can no longer be modified and any active ruleset is terminated.\n     *\n     * Rule modifications can only be applied to supported rules.\n     * Most of the `any` methods do not support rule modifications because they are implemented using schema flags (e.g. `required()`) or special\n     * internal implementation (e.g. `valid()`).\n     * In those cases, use the `any.messages()` method to override the error codes for the errors you want to customize.\n     */\n    rule(options: RuleOptions): this;\n\n    /**\n     * Registers a schema to be used by descendants of the current schema in named link references.\n     */\n    shared(ref: Schema): this;\n\n    /**\n     * Sets the options.convert options to false which prevent type casting for the current key and any child keys.\n     */\n    strict(isStrict?: boolean): this;\n\n    /**\n     * Marks a key to be removed from a resulting object or array after validation. Used to sanitize output.\n     * @param [enabled=true] - if true, the value is stripped, otherwise the validated value is retained. Defaults to true.\n     */\n    strip(enabled?: boolean): this;\n\n    /**\n     * Annotates the key\n     */\n    tag(...tags: string[]): this;\n\n    /**\n     * Applies any assigned target alterations to a copy of the schema that were applied via `any.alter()`.\n     */\n    tailor(targets: string | string[]): Schema;\n\n    /**\n     * Annotates the key with an unit name.\n     */\n    unit(name: string): this;\n\n    /**\n     * Adds the provided values into the allowed whitelist and marks them as the only valid values allowed.\n     */\n    valid(...values: any[]): this;\n\n    /**\n     * Validates a value using the schema and options.\n     */\n    validate(\n      value: any,\n      options?: ValidationOptions\n    ): ValidationResult<TSchema>;\n\n    /**\n     * Validates a value using the schema and options.\n     */\n    validateAsync<TOpts extends AsyncValidationOptions>(\n      value: any,\n      options?: TOpts\n    ): Promise<\n      TOpts extends { artifacts: true } | { warnings: true }\n        ? { value: TSchema } & (TOpts extends { artifacts: true }\n            ? { artifacts: Map<any, string[][]> }\n            : {}) &\n            (TOpts extends { warnings: true }\n              ? { warning: ValidationWarning }\n              : {})\n        : TSchema\n    >;\n\n    /**\n     * Same as `rule({ warn: true })`.\n     * Note that `warn()` will terminate the current ruleset and cannot be followed by another rule option.\n     * Use `rule()` to apply multiple rule options.\n     */\n    warn(): this;\n\n    /**\n     * Generates a warning.\n     * When calling `any.validateAsync()`, set the `warning` option to true to enable warnings.\n     * Warnings are reported separately from errors alongside the result value via the warning key (i.e. `{ value, warning }`).\n     * Warning are always included when calling `any.validate()`.\n     */\n    warning(code: string, context: Context): this;\n\n    /**\n     * Converts the type into an alternatives type where the conditions are merged into the type definition where:\n     */\n    when(ref: string | Reference, options: WhenOptions | WhenOptions[]): this;\n\n    /**\n     * Converts the type into an alternatives type where the conditions are merged into the type definition where:\n     */\n    when(ref: Schema, options: WhenSchemaOptions): this;\n  }\n\n  interface Description {\n    type?: Types | string;\n    label?: string;\n    description?: string;\n    flags?: object;\n    notes?: string[];\n    tags?: string[];\n    metas?: any[];\n    example?: any[];\n    valids?: any[];\n    invalids?: any[];\n    unit?: string;\n    options?: ValidationOptions;\n    [key: string]: any;\n  }\n\n  interface Context {\n    [key: string]: any;\n    key?: string;\n    label?: string;\n    value?: any;\n  }\n\n  interface State {\n    key?: string;\n    path?: (string | number)[];\n    parent?: any;\n    reference?: any;\n    ancestors?: any;\n    localize?(...args: any[]): State;\n  }\n\n  interface BooleanSchema<TSchema = boolean> extends AnySchema<TSchema> {\n    /**\n     * Allows for additional values to be considered valid booleans by converting them to false during validation.\n     * String comparisons are by default case insensitive,\n     * see `boolean.sensitive()` to change this behavior.\n     * @param values - strings, numbers or arrays of them\n     */\n    falsy(...values: Array<string | number | null>): this;\n\n    /**\n     * Allows the values provided to truthy and falsy as well as the \"true\" and \"false\" default conversion\n     * (when not in `strict()` mode) to be matched in a case insensitive manner.\n     */\n    sensitive(enabled?: boolean): this;\n\n    /**\n     * Allows for additional values to be considered valid booleans by converting them to true during validation.\n     * String comparisons are by default case insensitive, see `boolean.sensitive()` to change this behavior.\n     * @param values - strings, numbers or arrays of them\n     */\n    truthy(...values: Array<string | number | null>): this;\n  }\n\n  interface NumberSchema<TSchema = number> extends AnySchema<TSchema> {\n    /**\n     * Specifies that the value must be greater than limit.\n     * It can also be a reference to another field.\n     */\n    greater(limit: number | Reference): this;\n\n    /**\n     * Requires the number to be an integer (no floating point).\n     */\n    integer(): this;\n\n    /**\n     * Specifies that the value must be less than limit.\n     * It can also be a reference to another field.\n     */\n    less(limit: number | Reference): this;\n\n    /**\n     * Specifies the maximum value.\n     * It can also be a reference to another field.\n     */\n    max(limit: number | Reference): this;\n\n    /**\n     * Specifies the minimum value.\n     * It can also be a reference to another field.\n     */\n    min(limit: number | Reference): this;\n\n    /**\n     * Specifies that the value must be a multiple of base.\n     */\n    multiple(base: number | Reference): this;\n\n    /**\n     * Requires the number to be negative.\n     */\n    negative(): this;\n\n    /**\n     * Requires the number to be a TCP port, so between 0 and 65535.\n     */\n    port(): this;\n\n    /**\n     * Requires the number to be positive.\n     */\n    positive(): this;\n\n    /**\n     * Specifies the maximum number of decimal places where:\n     * @param limit - the maximum number of decimal places allowed.\n     */\n    precision(limit: number): this;\n\n    /**\n     * Requires the number to be negative or positive.\n     */\n    sign(sign: \"positive\" | \"negative\"): this;\n\n    /**\n     * Allows the number to be outside of JavaScript's safety range (Number.MIN_SAFE_INTEGER & Number.MAX_SAFE_INTEGER).\n     */\n    unsafe(enabled?: any): this;\n  }\n\n  interface StringSchema<TSchema = string> extends AnySchema<TSchema> {\n    /**\n     * Requires the string value to only contain a-z, A-Z, and 0-9.\n     */\n    alphanum(): this;\n\n    /**\n     * Requires the string value to be a valid base64 string; does not check the decoded value.\n     */\n    base64(options?: Base64Options): this;\n\n    /**\n     * Sets the required string case.\n     */\n    case(direction: \"upper\" | \"lower\"): this;\n\n    /**\n     * Requires the number to be a credit card number (Using Luhn Algorithm).\n     */\n    creditCard(): this;\n\n    /**\n     * Requires the string value to be a valid data URI string.\n     */\n    dataUri(options?: DataUriOptions): this;\n\n    /**\n     * Requires the string value to be a valid domain.\n     */\n    domain(options?: DomainOptions): this;\n\n    /**\n     * Requires the string value to be a valid email address.\n     */\n    email(options?: EmailOptions): this;\n\n    /**\n     * Requires the string value to be a valid GUID.\n     */\n    guid(options?: GuidOptions): this;\n\n    /**\n     * Requires the string value to be a valid hexadecimal string.\n     */\n    hex(options?: HexOptions): this;\n\n    /**\n     * Requires the string value to be a valid hostname as per RFC1123.\n     */\n    hostname(): this;\n\n    /**\n     * Allows the value to match any whitelist of blacklist item in a case insensitive comparison.\n     */\n    insensitive(): this;\n\n    /**\n     * Requires the string value to be a valid ip address.\n     */\n    ip(options?: IpOptions): this;\n\n    /**\n     * Requires the string value to be in valid ISO 8601 date format.\n     */\n    isoDate(): this;\n\n    /**\n     * Requires the string value to be in valid ISO 8601 duration format.\n     */\n    isoDuration(): this;\n\n    /**\n     * Specifies the exact string length required\n     * @param limit - the required string length. It can also be a reference to another field.\n     * @param encoding - if specified, the string length is calculated in bytes using the provided encoding.\n     */\n    length(limit: number | Reference, encoding?: string): this;\n\n    /**\n     * Requires the string value to be all lowercase. If the validation convert option is on (enabled by default), the string will be forced to lowercase.\n     */\n    lowercase(): this;\n\n    /**\n     * Specifies the maximum number of string characters.\n     * @param limit - the maximum number of string characters allowed. It can also be a reference to another field.\n     * @param encoding - if specified, the string length is calculated in bytes using the provided encoding.\n     */\n    max(limit: number | Reference, encoding?: string): this;\n\n    /**\n     * Specifies the minimum number string characters.\n     * @param limit - the minimum number of string characters required. It can also be a reference to another field.\n     * @param encoding - if specified, the string length is calculated in bytes using the provided encoding.\n     */\n    min(limit: number | Reference, encoding?: string): this;\n\n    /**\n     * Requires the string value to be in a unicode normalized form. If the validation convert option is on (enabled by default), the string will be normalized.\n     * @param [form='NFC'] - The unicode normalization form to use. Valid values: NFC [default], NFD, NFKC, NFKD\n     */\n    normalize(form?: \"NFC\" | \"NFD\" | \"NFKC\" | \"NFKD\"): this;\n\n    /**\n     * Defines a regular expression rule.\n     * @param pattern - a regular expression object the string value must match against.\n     * @param options - optional, can be:\n     *   Name for patterns (useful with multiple patterns). Defaults to 'required'.\n     *   An optional configuration object with the following supported properties:\n     *     name - optional pattern name.\n     *     invert - optional boolean flag. Defaults to false behavior. If specified as true, the provided pattern will be disallowed instead of required.\n     */\n    pattern(pattern: RegExp, options?: string | StringRegexOptions): this;\n\n    /**\n     * Defines a regular expression rule.\n     * @param pattern - a regular expression object the string value must match against.\n     * @param options - optional, can be:\n     *   Name for patterns (useful with multiple patterns). Defaults to 'required'.\n     *   An optional configuration object with the following supported properties:\n     *     name - optional pattern name.\n     *     invert - optional boolean flag. Defaults to false behavior. If specified as true, the provided pattern will be disallowed instead of required.\n     */\n    regex(pattern: RegExp, options?: string | StringRegexOptions): this;\n\n    /**\n     * Replace characters matching the given pattern with the specified replacement string where:\n     * @param pattern - a regular expression object to match against, or a string of which all occurrences will be replaced.\n     * @param replacement - the string that will replace the pattern.\n     */\n    replace(pattern: RegExp | string, replacement: string): this;\n\n    /**\n     * Requires the string value to only contain a-z, A-Z, 0-9, and underscore _.\n     */\n    token(): this;\n\n    /**\n     * Requires the string value to contain no whitespace before or after. If the validation convert option is on (enabled by default), the string will be trimmed.\n     * @param [enabled=true] - optional parameter defaulting to true which allows you to reset the behavior of trim by providing a falsy value.\n     */\n    trim(enabled?: any): this;\n\n    /**\n     * Specifies whether the string.max() limit should be used as a truncation.\n     * @param [enabled=true] - optional parameter defaulting to true which allows you to reset the behavior of truncate by providing a falsy value.\n     */\n    truncate(enabled?: boolean): this;\n\n    /**\n     * Requires the string value to be all uppercase. If the validation convert option is on (enabled by default), the string will be forced to uppercase.\n     */\n    uppercase(): this;\n\n    /**\n     * Requires the string value to be a valid RFC 3986 URI.\n     */\n    uri(options?: UriOptions): this;\n\n    /**\n     * Requires the string value to be a valid GUID.\n     */\n    uuid(options?: GuidOptions): this;\n  }\n\n  interface SymbolSchema<TSchema = Symbol> extends AnySchema<TSchema> {\n    // TODO: support number and symbol index\n    map(\n      iterable:\n        | Iterable<[string | number | boolean | symbol, symbol]>\n        | { [key: string]: symbol }\n    ): this;\n  }\n\n  interface ArraySortOptions {\n    /**\n     * @default 'ascending'\n     */\n    order?: \"ascending\" | \"descending\";\n    by?: string | Reference;\n  }\n\n  interface ArrayUniqueOptions extends HierarchySeparatorOptions {\n    /**\n     * if true, undefined values for the dot notation string comparator will not cause the array to fail on uniqueness.\n     *\n     * @default false\n     */\n    ignoreUndefined?: boolean;\n  }\n\n  type ComparatorFunction = (a: any, b: any) => boolean;\n\n  type UnwrapSchemaLikeWithoutArray<T> = T extends SchemaLikeWithoutArray<\n    infer U\n  >\n    ? U\n    : never;\n\n  type NoNestedArrays<T extends readonly unknown[]> = Extract<\n    T[number],\n    readonly unknown[]\n  > extends never\n    ? T\n    : never;\n\n  interface ArraySchema<TSchema = any[]> extends AnySchema<TSchema> {\n    /**\n     * Verifies that an assertion passes for at least one item in the array, where:\n     * `schema` - the validation rules required to satisfy the assertion. If the `schema` includes references, they are resolved against\n     * the array item being tested, not the value of the `ref` target.\n     */\n    has(schema: SchemaLike): this;\n\n    /**\n     * List the types allowed for the array values.\n     * If a given type is .required() then there must be a matching item in the array.\n     * If a type is .forbidden() then it cannot appear in the array.\n     * Required items can be added multiple times to signify that multiple items must be found.\n     * Errors will contain the number of items that didn't match.\n     * Any unmatched item having a label will be mentioned explicitly.\n     *\n     * @param type - a joi schema object to validate each array item against.\n     */\n    items<A>(a: SchemaLikeWithoutArray<A>): ArraySchema<A[]>;\n    items<A, B>(\n      a: SchemaLikeWithoutArray<A>,\n      b: SchemaLikeWithoutArray<B>\n    ): ArraySchema<(A | B)[]>;\n    items<A, B, C>(\n      a: SchemaLikeWithoutArray<A>,\n      b: SchemaLikeWithoutArray<B>,\n      c: SchemaLikeWithoutArray<C>\n    ): ArraySchema<(A | B | C)[]>;\n    items<A, B, C, D>(\n      a: SchemaLikeWithoutArray<A>,\n      b: SchemaLikeWithoutArray<B>,\n      c: SchemaLikeWithoutArray<C>,\n      d: SchemaLikeWithoutArray<D>\n    ): ArraySchema<(A | B | C | D)[]>;\n    items<A, B, C, D, E>(\n      a: SchemaLikeWithoutArray<A>,\n      b: SchemaLikeWithoutArray<B>,\n      c: SchemaLikeWithoutArray<C>,\n      d: SchemaLikeWithoutArray<D>,\n      e: SchemaLikeWithoutArray<E>\n    ): ArraySchema<(A | B | C | D | E)[]>;\n    items<A, B, C, D, E, F>(\n      a: SchemaLikeWithoutArray<A>,\n      b: SchemaLikeWithoutArray<B>,\n      c: SchemaLikeWithoutArray<C>,\n      d: SchemaLikeWithoutArray<D>,\n      e: SchemaLikeWithoutArray<E>,\n      f: SchemaLikeWithoutArray<F>\n    ): ArraySchema<(A | B | C | D | E | F)[]>;\n    items<\n      TItems,\n      TTItems extends SchemaLikeWithoutArray<TItems>[] = SchemaLikeWithoutArray<TItems>[]\n    >(\n      ...types: NoNestedArrays<TTItems>\n    ): ArraySchema<\n      {\n        [I in keyof TTItems]: UnwrapSchemaLikeWithoutArray<TTItems[I]>;\n      }[number][]\n    >;\n\n    /**\n     * Specifies the exact number of items in the array.\n     */\n    length(limit: number | Reference): this;\n\n    /**\n     * Specifies the maximum number of items in the array.\n     */\n    max(limit: number | Reference): this;\n\n    /**\n     * Specifies the minimum number of items in the array.\n     */\n    min(limit: number | Reference): this;\n\n    /**\n     * Lists the types in sequence order for the array values where:\n     * @param type - a joi schema object to validate against each array item in sequence order. type can be multiple values passed as individual arguments.\n     * If a given type is .required() then there must be a matching item with the same index position in the array.\n     * Errors will contain the number of items that didn't match.\n     * Any unmatched item having a label will be mentioned explicitly.\n     */\n    ordered(...types: SchemaLikeWithoutArray[]): this;\n\n    /**\n     * Allow single values to be checked against rules as if it were provided as an array.\n     * enabled can be used with a falsy value to go back to the default behavior.\n     */\n    single(enabled?: any): this;\n\n    /**\n     * Sorts the array by given order.\n     */\n    sort(options?: ArraySortOptions): this;\n\n    /**\n     * Allow this array to be sparse.\n     * enabled can be used with a falsy value to go back to the default behavior.\n     */\n    sparse(enabled?: any): this;\n\n    /**\n     * Requires the array values to be unique.\n     * Remember that if you provide a custom comparator function,\n     * different types can be passed as parameter depending on the rules you set on items.\n     * Be aware that a deep equality is performed on elements of the array having a type of object,\n     * a performance penalty is to be expected for this kind of operation.\n     */\n    unique(\n      comparator?: string | ComparatorFunction,\n      options?: ArrayUniqueOptions\n    ): this;\n  }\n\n  interface ObjectPatternOptions {\n    fallthrough?: boolean;\n    matches: SchemaLike | Reference;\n  }\n\n  interface ObjectSchema<TSchema = any> extends AnySchema<TSchema> {\n    /**\n     * Defines an all-or-nothing relationship between keys where if one of the peers is present, all of them are required as well.\n     *\n     * Optional settings must be the last argument.\n     */\n    and(...peers: Array<string | DependencyOptions>): this;\n\n    /**\n     * Appends the allowed object keys. If schema is null, undefined, or {}, no changes will be applied.\n     */\n    append(schema?: SchemaMap<TSchema>): this;\n    append<TSchemaExtended = any, T = TSchemaExtended>(\n      schema?: SchemaMap<T>\n    ): ObjectSchema<T>;\n\n    /**\n     * Verifies an assertion where.\n     */\n    assert(ref: string | Reference, schema: SchemaLike, message?: string): this;\n\n    /**\n     * Requires the object to be an instance of a given constructor.\n     *\n     * @param constructor - the constructor function that the object must be an instance of.\n     * @param name - an alternate name to use in validation errors. This is useful when the constructor function does not have a name.\n     */\n    // tslint:disable-next-line:ban-types\n    instance(constructor: Function, name?: string): this;\n\n    /**\n     * Sets or extends the allowed object keys.\n     */\n    keys(schema?: SchemaMap<TSchema>): this;\n\n    /**\n     * Specifies the exact number of keys in the object.\n     */\n    length(limit: number): this;\n\n    /**\n     * Specifies the maximum number of keys in the object.\n     */\n    max(limit: number | Reference): this;\n\n    /**\n     * Specifies the minimum number of keys in the object.\n     */\n    min(limit: number | Reference): this;\n\n    /**\n     * Defines a relationship between keys where not all peers can be present at the same time.\n     *\n     * Optional settings must be the last argument.\n     */\n    nand(...peers: Array<string | DependencyOptions>): this;\n\n    /**\n     * Defines a relationship between keys where one of the peers is required (and more than one is allowed).\n     *\n     * Optional settings must be the last argument.\n     */\n    or(...peers: Array<string | DependencyOptions>): this;\n\n    /**\n     * Defines an exclusive relationship between a set of keys where only one is allowed but none are required.\n     *\n     * Optional settings must be the last argument.\n     */\n    oxor(...peers: Array<string | DependencyOptions>): this;\n\n    /**\n     * Specify validation rules for unknown keys matching a pattern.\n     *\n     * @param pattern - a pattern that can be either a regular expression or a joi schema that will be tested against the unknown key names\n     * @param schema - the schema object matching keys must validate against\n     */\n    pattern(\n      pattern: RegExp | SchemaLike,\n      schema: SchemaLike,\n      options?: ObjectPatternOptions\n    ): this;\n\n    /**\n     * Requires the object to be a Joi reference.\n     */\n    ref(): this;\n\n    /**\n     * Requires the object to be a `RegExp` object.\n     */\n    regex(): this;\n\n    /**\n     * Renames a key to another name (deletes the renamed key).\n     */\n    rename(from: string | RegExp, to: string, options?: RenameOptions): this;\n\n    /**\n     * Requires the object to be a Joi schema instance.\n     */\n    schema(type?: SchemaLike): this;\n\n    /**\n     * Overrides the handling of unknown keys for the scope of the current object only (does not apply to children).\n     */\n    unknown(allow?: boolean): this;\n\n    /**\n     * Requires the presence of other keys whenever the specified key is present.\n     */\n    with(\n      key: string,\n      peers: string | string[],\n      options?: DependencyOptions\n    ): this;\n\n    /**\n     * Forbids the presence of other keys whenever the specified is present.\n     */\n    without(\n      key: string,\n      peers: string | string[],\n      options?: DependencyOptions\n    ): this;\n\n    /**\n     * Defines an exclusive relationship between a set of keys. one of them is required but not at the same time.\n     *\n     * Optional settings must be the last argument.\n     */\n    xor(...peers: Array<string | DependencyOptions>): this;\n  }\n\n  interface BinarySchema<TSchema = Buffer> extends AnySchema<TSchema> {\n    /**\n     * Sets the string encoding format if a string input is converted to a buffer.\n     */\n    encoding(encoding: string): this;\n\n    /**\n     * Specifies the minimum length of the buffer.\n     */\n    min(limit: number | Reference): this;\n\n    /**\n     * Specifies the maximum length of the buffer.\n     */\n    max(limit: number | Reference): this;\n\n    /**\n     * Specifies the exact length of the buffer:\n     */\n    length(limit: number | Reference): this;\n  }\n\n  interface DateSchema<TSchema = Date> extends AnySchema<TSchema> {\n    /**\n     * Specifies that the value must be greater than date.\n     * Notes: 'now' can be passed in lieu of date so as to always compare relatively to the current date,\n     * allowing to explicitly ensure a date is either in the past or in the future.\n     * It can also be a reference to another field.\n     */\n    greater(date: \"now\" | Date | number | string | Reference): this;\n\n    /**\n     * Requires the string value to be in valid ISO 8601 date format.\n     */\n    iso(): this;\n\n    /**\n     * Specifies that the value must be less than date.\n     * Notes: 'now' can be passed in lieu of date so as to always compare relatively to the current date,\n     * allowing to explicitly ensure a date is either in the past or in the future.\n     * It can also be a reference to another field.\n     */\n    less(date: \"now\" | Date | number | string | Reference): this;\n\n    /**\n     * Specifies the oldest date allowed.\n     * Notes: 'now' can be passed in lieu of date so as to always compare relatively to the current date,\n     * allowing to explicitly ensure a date is either in the past or in the future.\n     * It can also be a reference to another field.\n     */\n    min(date: \"now\" | Date | number | string | Reference): this;\n\n    /**\n     * Specifies the latest date allowed.\n     * Notes: 'now' can be passed in lieu of date so as to always compare relatively to the current date,\n     * allowing to explicitly ensure a date is either in the past or in the future.\n     * It can also be a reference to another field.\n     */\n    max(date: \"now\" | Date | number | string | Reference): this;\n\n    /**\n     * Requires the value to be a timestamp interval from Unix Time.\n     * @param type - the type of timestamp (allowed values are unix or javascript [default])\n     */\n    timestamp(type?: \"javascript\" | \"unix\"): this;\n  }\n\n  interface FunctionSchema<TSchema = Function> extends ObjectSchema<TSchema> {\n    /**\n     * Specifies the arity of the function where:\n     * @param n - the arity expected.\n     */\n    arity(n: number): this;\n\n    /**\n     * Requires the function to be a class.\n     */\n    class(): this;\n\n    /**\n     * Specifies the minimal arity of the function where:\n     * @param n - the minimal arity expected.\n     */\n    minArity(n: number): this;\n\n    /**\n     * Specifies the minimal arity of the function where:\n     * @param n - the minimal arity expected.\n     */\n    maxArity(n: number): this;\n  }\n\n  interface AlternativesSchema<TSchema = any> extends AnySchema<TSchema> {\n    /**\n     * Adds a conditional alternative schema type, either based on another key value, or a schema peeking into the current value.\n     */\n    conditional<ThenSchema, OtherwiseSchema>(\n      ref: string | Reference,\n      options: WhenOptions | WhenOptions[]\n    ): AlternativesSchema<ThenSchema | OtherwiseSchema>;\n    conditional<ThenSchema, OtherwiseSchema>(\n      ref: Schema,\n      options: WhenSchemaOptions<ThenSchema, OtherwiseSchema>\n    ): AlternativesSchema<ThenSchema | OtherwiseSchema>;\n\n    /**\n     * Requires the validated value to match a specific set of the provided alternative.try() schemas.\n     * Cannot be combined with `alternatives.conditional()`.\n     */\n    match(mode: \"any\" | \"all\" | \"one\"): this;\n\n    /**\n     * Adds an alternative schema type for attempting to match against the validated value.\n     */\n    try<A>(a: SchemaLikeWithoutArray<A>): AlternativesSchema<A>;\n    try<A, B>(\n      a: SchemaLikeWithoutArray<A>,\n      b: SchemaLikeWithoutArray<B>\n    ): AlternativesSchema<A | B>;\n    try<A, B, C>(\n      a: SchemaLikeWithoutArray<A>,\n      b: SchemaLikeWithoutArray<B>,\n      c: SchemaLikeWithoutArray<C>\n    ): AlternativesSchema<A | B | C>;\n    try<A, B, C, D>(\n      a: SchemaLikeWithoutArray<A>,\n      b: SchemaLikeWithoutArray<B>,\n      c: SchemaLikeWithoutArray<C>,\n      d: SchemaLikeWithoutArray<D>\n    ): AlternativesSchema<A | B | C | D>;\n    try<A, B, C, D, E>(\n      a: SchemaLikeWithoutArray<A>,\n      b: SchemaLikeWithoutArray<B>,\n      c: SchemaLikeWithoutArray<C>,\n      d: SchemaLikeWithoutArray<D>,\n      e: SchemaLikeWithoutArray<E>\n    ): AlternativesSchema<A | B | C | D | E>;\n    try<A, B, C, D, E, F>(\n      a: SchemaLikeWithoutArray<A>,\n      b: SchemaLikeWithoutArray<B>,\n      c: SchemaLikeWithoutArray<C>,\n      d: SchemaLikeWithoutArray<D>,\n      e: SchemaLikeWithoutArray<E>,\n      f: SchemaLikeWithoutArray<F>\n    ): AlternativesSchema<A | B | C | D | E | F>;\n    try(...types: SchemaLikeWithoutArray[]): this;\n  }\n\n  interface LinkSchema<TSchema = any> extends AnySchema<TSchema> {\n    /**\n     * Same as `any.concat()` but the schema is merged after the link is resolved which allows merging with schemas of the same type as the resolved link.\n     * Will throw an exception during validation if the merged types are not compatible.\n     */\n    concat(schema: Schema): this;\n\n    /**\n     * Initializes the schema after constructions for cases where the schema has to be constructed first and then initialized.\n     * If `ref` was not passed to the constructor, `link.ref()` must be called prior to usage.\n     */\n    ref(ref: string): this;\n  }\n\n  interface Reference extends Exclude<ReferenceOptions, \"prefix\"> {\n    depth: number;\n    type: string;\n    key: string;\n    root: string;\n    path: string[];\n    display: string;\n    toString(): string;\n  }\n\n  type ExtensionBoundSchema = Schema & SchemaInternals;\n\n  interface RuleArgs {\n    name: string;\n    ref?: boolean;\n    assert?: ((value: any) => boolean) | AnySchema;\n    message?: string;\n\n    /**\n     * Undocumented properties\n     */\n    normalize?(value: any): any;\n  }\n\n  type RuleMethod = (...args: any[]) => any;\n\n  interface ExtensionRule {\n    /**\n     * alternative name for this rule.\n     */\n    alias?: string;\n    /**\n     * whether rule supports multiple invocations.\n     */\n    multi?: boolean;\n    /**\n     * Dual rule: converts or validates.\n     */\n    convert?: boolean;\n    /**\n     * list of arguments accepted by `method`.\n     */\n    args?: Array<RuleArgs | string>;\n    /**\n     * rule body.\n     */\n    method?: RuleMethod | false;\n    /**\n     * validation function.\n     */\n    validate?(\n      value: any,\n      helpers: any,\n      args: Record<string, any>,\n      options: any\n    ): any;\n\n    /**\n     * undocumented flags.\n     */\n    priority?: boolean;\n    manifest?: boolean;\n  }\n\n  interface CoerceResult {\n    errors?: ErrorReport[];\n    value?: any;\n  }\n\n  type CoerceFunction = (value: any, helpers: CustomHelpers) => CoerceResult;\n\n  interface CoerceObject {\n    method: CoerceFunction;\n    from?: string | string[];\n  }\n\n  interface ExtensionFlag {\n    setter?: string;\n    default?: any;\n  }\n\n  interface ExtensionTermManifest {\n    mapped: {\n      from: string;\n      to: string;\n    };\n  }\n\n  interface ExtensionTerm {\n    init: any[] | null;\n    register?: any;\n    manifest?: Record<string, \"schema\" | \"single\" | ExtensionTermManifest>;\n  }\n\n  interface Extension {\n    type: string | RegExp;\n    args?(...args: SchemaLike[]): Schema;\n    base?: Schema;\n    coerce?: CoerceFunction | CoerceObject;\n    flags?: Record<string, ExtensionFlag>;\n    manifest?: {\n      build?(obj: ExtensionBoundSchema, desc: Record<string, any>): any;\n    };\n    messages?: LanguageMessages | string;\n    modifiers?: Record<string, (rule: any, enabled?: boolean) => any>;\n    overrides?: Record<string, (value: any) => Schema>;\n    prepare?(value: any, helpers: CustomHelpers): any;\n    rebuild?(schema: ExtensionBoundSchema): void;\n    rules?: Record<string, ExtensionRule & ThisType<SchemaInternals>>;\n    terms?: Record<string, ExtensionTerm>;\n    validate?(value: any, helpers: CustomHelpers): any;\n\n    /**\n     * undocumented options\n     */\n    cast?: Record<\n      string,\n      { from(value: any): any; to(value: any, helpers: CustomHelpers): any }\n    >;\n    properties?: Record<string, any>;\n  }\n\n  type ExtensionFactory = (joi: Root) => Extension;\n\n  interface Err {\n    toString(): string;\n  }\n\n  // --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- ---\n\n  interface Root {\n    /**\n     * Current version of the joi package.\n     */\n    version: string;\n\n    ValidationError: new (\n      message: string,\n      details: ValidationErrorItem[],\n      original: any\n    ) => ValidationError;\n\n    /**\n     * Generates a schema object that matches any data type.\n     */\n    any<TSchema = any>(): AnySchema<TSchema>;\n\n    /**\n     * Generates a schema object that matches an array data type.\n     */\n    array<TSchema = any[]>(): ArraySchema<TSchema>;\n\n    /**\n     * Generates a schema object that matches a boolean data type (as well as the strings 'true' and 'false'). Can also be called via boolean().\n     */\n    bool<TSchema = boolean>(): BooleanSchema<TSchema>;\n\n    /**\n     * Generates a schema object that matches a boolean data type (as well as the strings 'true' and 'false'). Can also be called via bool().\n     */\n    boolean<TSchema = boolean>(): BooleanSchema<TSchema>;\n\n    /**\n     * Generates a schema object that matches a Buffer data type (as well as the strings which will be converted to Buffers).\n     */\n    binary<TSchema = Buffer>(): BinarySchema<TSchema>;\n\n    /**\n     * Generates a schema object that matches a date type (as well as a JavaScript date string or number of milliseconds).\n     */\n    date<TSchema = Date>(): DateSchema<TSchema>;\n\n    /**\n     * Generates a schema object that matches a function type.\n     */\n    func<TSchema = Function>(): FunctionSchema<TSchema>;\n\n    /**\n     * Generates a schema object that matches a function type.\n     */\n    function<TSchema = Function>(): FunctionSchema<TSchema>;\n\n    /**\n     * Generates a schema object that matches a number data type (as well as strings that can be converted to numbers).\n     */\n    number<TSchema = number>(): NumberSchema<TSchema>;\n\n    /**\n     * Generates a schema object that matches an object data type (as well as JSON strings that have been parsed into objects).\n     */\n    // tslint:disable-next-line:no-unnecessary-generics\n    object<TSchema = any, isStrict = false, T = TSchema>(\n      schema?: SchemaMap<T, isStrict>\n    ): ObjectSchema<TSchema>;\n\n    /**\n     * Generates a schema object that matches a string data type. Note that empty strings are not allowed by default and must be enabled with allow('').\n     */\n    string<TSchema = string>(): StringSchema<TSchema>;\n\n    /**\n     * Generates a schema object that matches any symbol.\n     */\n    symbol<TSchema = Symbol>(): SymbolSchema<TSchema>;\n\n    /**\n     * Generates a type that will match one of the provided alternative schemas\n     */\n    alternatives<A, B>(\n      params: [SchemaLike<A>, SchemaLike<B>]\n    ): AlternativesSchema<A | B>;\n    alternatives<A, B, C>(\n      params: [SchemaLike<A>, SchemaLike<B>, SchemaLike<C>]\n    ): AlternativesSchema<A | B | C>;\n    alternatives<A, B, C, D>(\n      params: [SchemaLike<A>, SchemaLike<B>, SchemaLike<C>, SchemaLike<D>]\n    ): AlternativesSchema<A | B | C | D>;\n    alternatives<A, B, C, D, E>(\n      params: [\n        SchemaLike<A>,\n        SchemaLike<B>,\n        SchemaLike<C>,\n        SchemaLike<D>,\n        SchemaLike<E>\n      ]\n    ): AlternativesSchema<A | B | C | D | E>;\n    alternatives<A, B>(\n      a: SchemaLike<A>,\n      b: SchemaLike<B>\n    ): AlternativesSchema<A | B>;\n    alternatives<A, B, C>(\n      a: SchemaLike<A>,\n      b: SchemaLike<B>,\n      c: SchemaLike<C>\n    ): AlternativesSchema<A | B | C>;\n    alternatives<A, B, C, D>(\n      a: SchemaLike<A>,\n      b: SchemaLike<B>,\n      c: SchemaLike<C>,\n      d: SchemaLike<D>\n    ): AlternativesSchema<A | B | C | D>;\n    alternatives<A, B, C, D, E>(\n      a: SchemaLike<A>,\n      b: SchemaLike<B>,\n      c: SchemaLike<C>,\n      d: SchemaLike<D>,\n      e: SchemaLike<E>\n    ): AlternativesSchema<A | B | C | D | E>;\n    alternatives<TSchema = any>(\n      types: SchemaLike<TSchema>[]\n    ): AlternativesSchema<TSchema>;\n    alternatives<TSchema = any>(\n      ...types: SchemaLike<TSchema>[]\n    ): AlternativesSchema<TSchema>;\n\n    /**\n     * Alias for `alternatives`\n     */\n    alt<A, B>(\n      params: [SchemaLike<A>, SchemaLike<B>]\n    ): AlternativesSchema<A | B>;\n    alt<A, B, C>(\n      params: [SchemaLike<A>, SchemaLike<B>, SchemaLike<C>]\n    ): AlternativesSchema<A | B | C>;\n    alt<A, B, C, D>(\n      params: [SchemaLike<A>, SchemaLike<B>, SchemaLike<C>, SchemaLike<D>]\n    ): AlternativesSchema<A | B | C | D>;\n    alt<A, B, C, D, E>(\n      params: [\n        SchemaLike<A>,\n        SchemaLike<B>,\n        SchemaLike<C>,\n        SchemaLike<D>,\n        SchemaLike<E>\n      ]\n    ): AlternativesSchema<A | B | C | D | E>;\n    alt<A, B>(a: SchemaLike<A>, b: SchemaLike<B>): AlternativesSchema<A | B>;\n    alt<A, B, C>(\n      a: SchemaLike<A>,\n      b: SchemaLike<B>,\n      c: SchemaLike<C>\n    ): AlternativesSchema<A | B | C>;\n    alt<A, B, C, D>(\n      a: SchemaLike<A>,\n      b: SchemaLike<B>,\n      c: SchemaLike<C>,\n      d: SchemaLike<D>\n    ): AlternativesSchema<A | B | C | D>;\n    alt<A, B, C, D, E>(\n      a: SchemaLike<A>,\n      b: SchemaLike<B>,\n      c: SchemaLike<C>,\n      d: SchemaLike<D>,\n      e: SchemaLike<E>\n    ): AlternativesSchema<A | B | C | D | E>;\n    alt<TSchema = any>(types: SchemaLike[]): AlternativesSchema<TSchema>;\n    alt<TSchema = any>(...types: SchemaLike[]): AlternativesSchema<TSchema>;\n\n    /**\n     * Links to another schema node and reuses it for validation, typically for creative recursive schemas.\n     *\n     * @param ref - the reference to the linked schema node.\n     * Cannot reference itself or its children as well as other links.\n     * Links can be expressed in relative terms like value references (`Joi.link('...')`),\n     * in absolute terms from the schema run-time root (`Joi.link('/a')`),\n     * or using schema ids implicitly using object keys or explicitly using `any.id()` (`Joi.link('#a.b.c')`).\n     */\n    link<TSchema = any>(ref?: string): LinkSchema<TSchema>;\n\n    /**\n     * Validates a value against a schema and throws if validation fails.\n     *\n     * @param value - the value to validate.\n     * @param schema - the schema object.\n     * @param message - optional message string prefix added in front of the error message. may also be an Error object.\n     */\n    assert(value: any, schema: Schema, options?: ValidationOptions): void;\n    assert(\n      value: any,\n      schema: Schema,\n      message: string | Error,\n      options?: ValidationOptions\n    ): void;\n\n    /**\n     * Validates a value against a schema, returns valid object, and throws if validation fails.\n     *\n     * @param value - the value to validate.\n     * @param schema - the schema object.\n     * @param message - optional message string prefix added in front of the error message. may also be an Error object.\n     */\n    attempt<TSchema extends Schema>(\n      value: any,\n      schema: TSchema,\n      options?: ValidationOptions\n    ): TSchema extends Schema<infer Value> ? Value : never;\n    attempt<TSchema extends Schema>(\n      value: any,\n      schema: TSchema,\n      message: string | Error,\n      options?: ValidationOptions\n    ): TSchema extends Schema<infer Value> ? Value : never;\n\n    cache: CacheConfiguration;\n\n    /**\n     * Converts literal schema definition to joi schema object (or returns the same back if already a joi schema object).\n     */\n    compile(schema: SchemaLike, options?: CompileOptions): Schema;\n\n    /**\n     * Checks if the provided preferences are valid.\n     *\n     * Throws an exception if the prefs object is invalid.\n     *\n     * The method is provided to perform inputs validation for the `any.validate()` and `any.validateAsync()` methods.\n     * Validation is not performed automatically for performance reasons. Instead, manually validate the preferences passed once and reuse.\n     */\n    checkPreferences(prefs: ValidationOptions): void;\n\n    /**\n     * Creates a custom validation schema.\n     */\n    custom(fn: CustomValidator, description?: string): Schema;\n\n    /**\n     * Creates a new Joi instance that will apply defaults onto newly created schemas\n     * through the use of the fn function that takes exactly one argument, the schema being created.\n     *\n     * @param fn - The function must always return a schema, even if untransformed.\n     */\n    defaults(fn: SchemaFunction): Root;\n\n    /**\n     * Generates a dynamic expression using a template string.\n     */\n    expression(template: string, options?: ReferenceOptions): any;\n\n    /**\n     * Creates a new Joi instance customized with the extension(s) you provide included.\n     */\n    extend(...extensions: Array<Extension | ExtensionFactory>): any;\n\n    /**\n     * Creates a reference that when resolved, is used as an array of values to match against the rule.\n     */\n    in(ref: string, options?: ReferenceOptions): Reference;\n\n    /**\n     * Checks whether or not the provided argument is an instance of ValidationError\n     */\n    isError(error: any): error is ValidationError;\n\n    /**\n     * Checks whether or not the provided argument is an expression.\n     */\n    isExpression(expression: any): boolean;\n\n    /**\n     * Checks whether or not the provided argument is a reference. It's especially useful if you want to post-process error messages.\n     */\n    isRef(ref: any): ref is Reference;\n\n    /**\n     * Checks whether or not the provided argument is a joi schema.\n     */\n    isSchema(schema: any, options?: CompileOptions): schema is AnySchema;\n\n    /**\n     * A special value used with `any.allow()`, `any.invalid()`, and `any.valid()` as the first value to reset any previously set values.\n     */\n    override: symbol;\n\n    /**\n     * Generates a reference to the value of the named key.\n     */\n    ref(key: string, options?: ReferenceOptions): Reference;\n\n    /**\n     * Returns an object where each key is a plain joi schema type.\n     * Useful for creating type shortcuts using deconstruction.\n     * Note that the types are already formed and do not need to be called as functions (e.g. `string`, not `string()`).\n     */\n    types(): {\n      alternatives: AlternativesSchema;\n      any: AnySchema;\n      array: ArraySchema;\n      binary: BinarySchema;\n      boolean: BooleanSchema;\n      date: DateSchema;\n      function: FunctionSchema;\n      link: LinkSchema;\n      number: NumberSchema;\n      object: ObjectSchema;\n      string: StringSchema;\n      symbol: SymbolSchema;\n    };\n\n    /**\n     * Generates a dynamic expression using a template string.\n     */\n    x(template: string, options?: ReferenceOptions): any;\n\n    // --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- ---\n    // Below are undocumented APIs. use at your own risk\n    // --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- ---\n\n    /**\n     * Whitelists a value\n     */\n    allow(...values: any[]): Schema;\n\n    /**\n     * Adds the provided values into the allowed whitelist and marks them as the only valid values allowed.\n     */\n    valid(...values: any[]): Schema;\n    equal(...values: any[]): Schema;\n\n    /**\n     * Blacklists a value\n     */\n    invalid(...values: any[]): Schema;\n    disallow(...values: any[]): Schema;\n    not(...values: any[]): Schema;\n\n    /**\n     * Marks a key as required which will not allow undefined as value. All keys are optional by default.\n     */\n    required(): Schema;\n\n    /**\n     * Alias of `required`.\n     */\n    exist(): Schema;\n\n    /**\n     * Marks a key as optional which will allow undefined as values. Used to annotate the schema for readability as all keys are optional by default.\n     */\n    optional(): Schema;\n\n    /**\n     * Marks a key as forbidden which will not allow any value except undefined. Used to explicitly forbid keys.\n     */\n    forbidden(): Schema;\n\n    /**\n     * Overrides the global validate() options for the current key and any sub-key.\n     */\n    preferences(options: ValidationOptions): Schema;\n\n    /**\n     * Overrides the global validate() options for the current key and any sub-key.\n     */\n    prefs(options: ValidationOptions): Schema;\n\n    /**\n     * Converts the type into an alternatives type where the conditions are merged into the type definition where:\n     */\n    when(\n      ref: string | Reference,\n      options: WhenOptions | WhenOptions[]\n    ): AlternativesSchema;\n    when(ref: Schema, options: WhenSchemaOptions): AlternativesSchema;\n\n    /**\n     * Unsure, maybe alias for `compile`?\n     */\n    build(...args: any[]): any;\n\n    /**\n     * Unsure, maybe alias for `preferences`?\n     */\n    options(...args: any[]): any;\n\n    /**\n     * Unsure, maybe leaked from `@hapi/lab/coverage/initialize`\n     */\n    trace(...args: any[]): any;\n    untrace(...args: any[]): any;\n  }\n}\n\ndeclare const Joi: Joi.Root;\nexport = Joi;\n"
  },
  {
    "path": "lib/index.js",
    "content": "'use strict';\n\nconst { assert, clone } = require('@hapi/hoek');\n\nconst Cache = require('./cache');\nconst Common = require('./common');\nconst Compile = require('./compile');\nconst Errors = require('./errors');\nconst Extend = require('./extend');\nconst Manifest = require('./manifest');\nconst Ref = require('./ref');\nconst Template = require('./template');\nconst Trace = require('./trace');\n\nlet Schemas;\n\n\nconst internals = {\n    types: {\n        alternatives: require('./types/alternatives'),\n        any: require('./types/any'),\n        array: require('./types/array'),\n        boolean: require('./types/boolean'),\n        date: require('./types/date'),\n        function: require('./types/function'),\n        link: require('./types/link'),\n        number: require('./types/number'),\n        object: require('./types/object'),\n        string: require('./types/string'),\n        symbol: require('./types/symbol')\n    },\n    aliases: {\n        alt: 'alternatives',\n        bool: 'boolean',\n        func: 'function'\n    }\n};\n\n\nif (Buffer) {                                                           // $lab:coverage:ignore$\n    internals.types.binary = require('./types/binary');\n}\n\n\ninternals.root = function () {\n\n    const root = {\n        _types: new Set(Object.keys(internals.types))\n    };\n\n    // Types\n\n    for (const type of root._types) {\n        root[type] = function (...args) {\n\n            assert(!args.length || ['alternatives', 'link', 'object'].includes(type), 'The', type, 'type does not allow arguments');\n            return internals.generate(this, internals.types[type], args);\n        };\n    }\n\n    // Shortcuts\n\n    for (const method of ['allow', 'custom', 'disallow', 'equal', 'exist', 'forbidden', 'invalid', 'not', 'only', 'optional', 'options', 'prefs', 'preferences', 'required', 'strip', 'valid', 'when']) {\n        root[method] = function (...args) {\n\n            return this.any()[method](...args);\n        };\n    }\n\n    // Methods\n\n    Object.assign(root, internals.methods);\n\n    // Aliases\n\n    for (const alias in internals.aliases) {\n        const target = internals.aliases[alias];\n        root[alias] = root[target];\n    }\n\n    root.x = root.expression;\n\n    // Trace\n\n    if (Trace.setup) {                                          // $lab:coverage:ignore$\n        Trace.setup(root);\n    }\n\n    return root;\n};\n\n\ninternals.methods = {\n\n    ValidationError: Errors.ValidationError,\n    version: Common.version,\n    cache: Cache.provider,\n\n    assert(value, schema, ...args /* [message], [options] */) {\n\n        internals.assert(value, schema, true, args);\n    },\n\n    attempt(value, schema, ...args /* [message], [options] */) {\n\n        return internals.assert(value, schema, false, args);\n    },\n\n    build(desc) {\n\n        assert(typeof Manifest.build === 'function', 'Manifest functionality disabled');\n        return Manifest.build(this, desc);\n    },\n\n    checkPreferences(prefs) {\n\n        Common.checkPreferences(prefs);\n    },\n\n    compile(schema, options) {\n\n        return Compile.compile(this, schema, options);\n    },\n\n    defaults(modifier) {\n\n        assert(typeof modifier === 'function', 'modifier must be a function');\n\n        const joi = Object.assign({}, this);\n        for (const type of joi._types) {\n            const schema = modifier(joi[type]());\n            assert(Common.isSchema(schema), 'modifier must return a valid schema object');\n\n            joi[type] = function (...args) {\n\n                return internals.generate(this, schema, args);\n            };\n        }\n\n        return joi;\n    },\n\n    expression(...args) {\n\n        return new Template(...args);\n    },\n\n    extend(...extensions) {\n\n        Common.verifyFlat(extensions, 'extend');\n\n        Schemas = Schemas || require('./schemas');\n\n        assert(extensions.length, 'You need to provide at least one extension');\n        this.assert(extensions, Schemas.extensions);\n\n        const joi = Object.assign({}, this);\n        joi._types = new Set(joi._types);\n\n        for (let extension of extensions) {\n            if (typeof extension === 'function') {\n                extension = extension(joi);\n            }\n\n            this.assert(extension, Schemas.extension);\n\n            const expanded = internals.expandExtension(extension, joi);\n            for (const item of expanded) {\n                assert(joi[item.type] === undefined || joi._types.has(item.type), 'Cannot override name', item.type);\n\n                const base = item.base || this.any();\n                const schema = Extend.type(base, item);\n\n                joi._types.add(item.type);\n                joi[item.type] = function (...args) {\n\n                    return internals.generate(this, schema, args);\n                };\n            }\n        }\n\n        return joi;\n    },\n\n    isError: Errors.ValidationError.isError,\n    isExpression: Template.isTemplate,\n    isRef: Ref.isRef,\n    isSchema: Common.isSchema,\n\n    in(...args) {\n\n        return Ref.in(...args);\n    },\n\n    override: Common.symbols.override,\n\n    ref(...args) {\n\n        return Ref.create(...args);\n    },\n\n    types() {\n\n        const types = {};\n        for (const type of this._types) {\n            types[type] = this[type]();\n        }\n\n        for (const target in internals.aliases) {\n            types[target] = this[target]();\n        }\n\n        return types;\n    }\n};\n\n\n// Helpers\n\ninternals.assert = function (value, schema, annotate, args /* [message], [options] */) {\n\n    const message = args[0] instanceof Error || typeof args[0] === 'string' ? args[0] : null;\n    const options = message !== null ? args[1] : args[0];\n    const result = schema.validate(value, Common.preferences({ errors: { stack: true } }, options || {}));\n\n    let error = result.error;\n    if (!error) {\n        return result.value;\n    }\n\n    if (message instanceof Error) {\n        throw message;\n    }\n\n    const display = annotate && typeof error.annotate === 'function' ? error.annotate() : error.message;\n\n    if (error instanceof Errors.ValidationError === false) {\n        error = clone(error);\n    }\n\n    error.message = message ? `${message} ${display}` : display;\n    throw error;\n};\n\n\ninternals.generate = function (root, schema, args) {\n\n    assert(root, 'Must be invoked on a Joi instance.');\n\n    schema.$_root = root;\n\n    if (!schema._definition.args ||\n        !args.length) {\n\n        return schema;\n    }\n\n    return schema._definition.args(schema, ...args);\n};\n\n\ninternals.expandExtension = function (extension, joi) {\n\n    if (typeof extension.type === 'string') {\n        return [extension];\n    }\n\n    const extended = [];\n    for (const type of joi._types) {\n        if (extension.type.test(type)) {\n            const item = Object.assign({}, extension);\n            item.type = type;\n            item.base = joi[type]();\n            extended.push(item);\n        }\n    }\n\n    return extended;\n};\n\n\nmodule.exports = internals.root();\n"
  },
  {
    "path": "lib/manifest.js",
    "content": "'use strict';\n\nconst { assert, clone } = require('@hapi/hoek');\n\nconst Common = require('./common');\nconst Messages = require('./messages');\nconst Ref = require('./ref');\nconst Template = require('./template');\n\nlet Schemas;\n\n\nconst internals = {};\n\n\nexports.describe = function (schema) {\n\n    const def = schema._definition;\n\n    // Type\n\n    const desc = {\n        type: schema.type,\n        flags: {},\n        rules: []\n    };\n\n    // Flags\n\n    for (const flag in schema._flags) {\n        if (flag[0] !== '_') {\n            desc.flags[flag] = internals.describe(schema._flags[flag]);\n        }\n    }\n\n    if (!Object.keys(desc.flags).length) {\n        delete desc.flags;\n    }\n\n    // Preferences\n\n    if (schema._preferences) {\n        desc.preferences = clone(schema._preferences, { shallow: ['messages'] });\n        delete desc.preferences[Common.symbols.prefs];\n        if (desc.preferences.messages) {\n            desc.preferences.messages = Messages.decompile(desc.preferences.messages);\n        }\n    }\n\n    // Allow / Invalid\n\n    if (schema._valids) {\n        desc.allow = schema._valids.describe();\n    }\n\n    if (schema._invalids) {\n        desc.invalid = schema._invalids.describe();\n    }\n\n    // Rules\n\n    for (const rule of schema._rules) {\n        const ruleDef = def.rules[rule.name];\n        if (ruleDef.manifest === false) {                           // Defaults to true\n            continue;\n        }\n\n        const item = { name: rule.name };\n\n        for (const custom in def.modifiers) {\n            if (rule[custom] !== undefined) {\n                item[custom] = internals.describe(rule[custom]);\n            }\n        }\n\n        if (rule.args) {\n            item.args = {};\n            for (const key in rule.args) {\n                const arg = rule.args[key];\n                if (key === 'options' &&\n                    !Object.keys(arg).length) {\n\n                    continue;\n                }\n\n                item.args[key] = internals.describe(arg, { assign: key });\n            }\n\n            if (!Object.keys(item.args).length) {\n                delete item.args;\n            }\n        }\n\n        desc.rules.push(item);\n    }\n\n    if (!desc.rules.length) {\n        delete desc.rules;\n    }\n\n    // Terms (must be last to verify no name conflicts)\n\n    for (const term in schema.$_terms) {\n        if (term[0] === '_') {\n            continue;\n        }\n\n        assert(!desc[term], 'Cannot describe schema due to internal name conflict with', term);\n\n        const items = schema.$_terms[term];\n        if (!items) {\n            continue;\n        }\n\n        if (items instanceof Map) {\n            if (items.size) {\n                desc[term] = [...items.entries()];\n            }\n\n            continue;\n        }\n\n        if (Common.isValues(items)) {\n            desc[term] = items.describe();\n            continue;\n        }\n\n        assert(def.terms[term], 'Term', term, 'missing configuration');\n        const manifest = def.terms[term].manifest;\n        const mapped = typeof manifest === 'object';\n        if (!items.length &&\n            !mapped) {\n\n            continue;\n        }\n\n        const normalized = [];\n        for (const item of items) {\n            normalized.push(internals.describe(item));\n        }\n\n        // Mapped\n\n        if (mapped) {\n            const { from, to } = manifest.mapped;\n            desc[term] = {};\n            for (const item of normalized) {\n                desc[term][item[to]] = item[from];\n            }\n\n            continue;\n        }\n\n        // Single\n\n        if (manifest === 'single') {\n            assert(normalized.length === 1, 'Term', term, 'contains more than one item');\n            desc[term] = normalized[0];\n            continue;\n        }\n\n        // Array\n\n        desc[term] = normalized;\n    }\n\n    internals.validate(schema.$_root, desc);\n    return desc;\n};\n\n\ninternals.describe = function (item, options = {}) {\n\n    if (Array.isArray(item)) {\n        return item.map(internals.describe);\n    }\n\n    if (item === Common.symbols.deepDefault) {\n        return { special: 'deep' };\n    }\n\n    if (typeof item !== 'object' ||\n        item === null) {\n\n        return item;\n    }\n\n    if (options.assign === 'options') {\n        return clone(item);\n    }\n\n    if (Buffer && Buffer.isBuffer(item)) {                          // $lab:coverage:ignore$\n        return { buffer: item.toString('binary') };\n    }\n\n    if (item instanceof Date) {\n        return item.toISOString();\n    }\n\n    if (item instanceof Error) {\n        return item;\n    }\n\n    if (item instanceof RegExp) {\n        if (options.assign === 'regex') {\n            return item.toString();\n        }\n\n        return { regex: item.toString() };\n    }\n\n    if (item[Common.symbols.literal]) {\n        return { function: item.literal };\n    }\n\n    if (typeof item.describe === 'function') {\n        if (options.assign === 'ref') {\n            return item.describe().ref;\n        }\n\n        return item.describe();\n    }\n\n    const normalized = {};\n    for (const key in item) {\n        const value = item[key];\n        if (value === undefined) {\n            continue;\n        }\n\n        normalized[key] = internals.describe(value, { assign: key });\n    }\n\n    return normalized;\n};\n\n\nexports.build = function (joi, desc) {\n\n    const builder = new internals.Builder(joi);\n    return builder.parse(desc);\n};\n\n\ninternals.Builder = class {\n\n    constructor(joi) {\n\n        this.joi = joi;\n    }\n\n    parse(desc) {\n\n        internals.validate(this.joi, desc);\n\n        // Type\n\n        let schema = this.joi[desc.type]()._bare();\n        const def = schema._definition;\n\n        // Flags\n\n        if (desc.flags) {\n            for (const flag in desc.flags) {\n                const setter = def.flags[flag] && def.flags[flag].setter || flag;\n                assert(typeof schema[setter] === 'function', 'Invalid flag', flag, 'for type', desc.type);\n                schema = schema[setter](this.build(desc.flags[flag]));\n            }\n        }\n\n        // Preferences\n\n        if (desc.preferences) {\n            schema = schema.preferences(this.build(desc.preferences));\n        }\n\n        // Allow / Invalid\n\n        if (desc.allow) {\n            schema = schema.allow(...this.build(desc.allow));\n        }\n\n        if (desc.invalid) {\n            schema = schema.invalid(...this.build(desc.invalid));\n        }\n\n        // Rules\n\n        if (desc.rules) {\n            for (const rule of desc.rules) {\n                assert(typeof schema[rule.name] === 'function', 'Invalid rule', rule.name, 'for type', desc.type);\n\n                const args = [];\n                if (rule.args) {\n                    const built = {};\n                    for (const key in rule.args) {\n                        built[key] = this.build(rule.args[key], { assign: key });\n                    }\n\n                    const keys = Object.keys(built);\n                    const definition = def.rules[rule.name].args;\n                    if (definition) {\n                        assert(keys.length <= definition.length, 'Invalid number of arguments for', desc.type, rule.name, '(expected up to', definition.length, ', found', keys.length, ')');\n                        for (const { name } of definition) {\n                            args.push(built[name]);\n                        }\n                    }\n                    else {\n                        assert(keys.length === 1, 'Invalid number of arguments for', desc.type, rule.name, '(expected up to 1, found', keys.length, ')');\n                        args.push(built[keys[0]]);\n                    }\n                }\n\n                // Apply\n\n                schema = schema[rule.name](...args);\n\n                // Ruleset\n\n                const options = {};\n                for (const custom in def.modifiers) {\n                    if (rule[custom] !== undefined) {\n                        options[custom] = this.build(rule[custom]);\n                    }\n                }\n\n                if (Object.keys(options).length) {\n                    schema = schema.rule(options);\n                }\n            }\n        }\n\n        // Terms\n\n        const terms = {};\n        for (const key in desc) {\n            if (['allow', 'flags', 'invalid', 'whens', 'preferences', 'rules', 'type'].includes(key)) {\n                continue;\n            }\n\n            assert(def.terms[key], 'Term', key, 'missing configuration');\n            const manifest = def.terms[key].manifest;\n\n            if (manifest === 'schema') {\n                terms[key] = desc[key].map((item) => this.parse(item));\n                continue;\n            }\n\n            if (manifest === 'values') {\n                terms[key] = desc[key].map((item) => this.build(item));\n                continue;\n            }\n\n            if (manifest === 'single') {\n                terms[key] = this.build(desc[key]);\n                continue;\n            }\n\n            if (typeof manifest === 'object') {\n                terms[key] = {};\n                for (const name in desc[key]) {\n                    const value = desc[key][name];\n                    terms[key][name] = this.parse(value);\n                }\n\n                continue;\n            }\n\n            terms[key] = this.build(desc[key]);\n        }\n\n        if (desc.whens) {\n            terms.whens = desc.whens.map((when) => this.build(when));\n        }\n\n        schema = def.manifest.build(schema, terms);\n        schema.$_temp.ruleset = false;\n        return schema;\n    }\n\n    build(desc, options = {}) {\n\n        if (desc === null) {\n            return null;\n        }\n\n        if (Array.isArray(desc)) {\n            return desc.map((item) => this.build(item));\n        }\n\n        if (desc instanceof Error) {\n            return desc;\n        }\n\n        if (options.assign === 'options') {\n            return clone(desc);\n        }\n\n        if (options.assign === 'regex') {\n            return internals.regex(desc);\n        }\n\n        if (options.assign === 'ref') {\n            return Ref.build(desc);\n        }\n\n        if (typeof desc !== 'object') {\n            return desc;\n        }\n\n        if (Object.keys(desc).length === 1) {\n            if (desc.buffer) {\n                assert(Buffer, 'Buffers are not supported');\n                return Buffer && Buffer.from(desc.buffer, 'binary');                    // $lab:coverage:ignore$\n            }\n\n            if (desc.function) {\n                return { [Common.symbols.literal]: true, literal: desc.function };\n            }\n\n            if (desc.override) {\n                return Common.symbols.override;\n            }\n\n            if (desc.ref) {\n                return Ref.build(desc.ref);\n            }\n\n            if (desc.regex) {\n                return internals.regex(desc.regex);\n            }\n\n            if (desc.special) {\n                assert(['deep'].includes(desc.special), 'Unknown special value', desc.special);\n                return Common.symbols.deepDefault;\n            }\n\n            if (desc.value) {\n                return clone(desc.value);\n            }\n        }\n\n        if (desc.type) {\n            return this.parse(desc);\n        }\n\n        if (desc.template) {\n            return Template.build(desc);\n        }\n\n        const normalized = {};\n        for (const key in desc) {\n            normalized[key] = this.build(desc[key], { assign: key });\n        }\n\n        return normalized;\n    }\n};\n\n\ninternals.regex = function (string) {\n\n    const end = string.lastIndexOf('/');\n    const exp = string.slice(1, end);\n    const flags = string.slice(end + 1);\n    return new RegExp(exp, flags);\n};\n\n\ninternals.validate = function (joi, desc) {\n\n    Schemas = Schemas || require('./schemas');\n\n    joi.assert(desc, Schemas.description);\n};\n"
  },
  {
    "path": "lib/messages.js",
    "content": "'use strict';\n\nconst { assert, clone } = require('@hapi/hoek');\n\nconst Template = require('./template');\n\n\nconst internals = {};\n\n\nexports.compile = function (messages, target) {\n\n    // Single value string ('plain error message', 'template {error} message')\n\n    if (typeof messages === 'string') {\n        assert(!target, 'Cannot set single message string');\n        return new Template(messages);\n    }\n\n    // Single value template\n\n    if (Template.isTemplate(messages)) {\n        assert(!target, 'Cannot set single message template');\n        return messages;\n    }\n\n    // By error code { 'number.min': <string | template> }\n\n    assert(typeof messages === 'object' && !Array.isArray(messages), 'Invalid message options');\n\n    target = target ? clone(target) : {};\n\n    for (let code in messages) {\n        const message = messages[code];\n\n        if (code === 'root' ||\n            Template.isTemplate(message)) {\n\n            target[code] = message;\n            continue;\n        }\n\n        if (typeof message === 'string') {\n            target[code] = new Template(message);\n            continue;\n        }\n\n        // By language { english: { 'number.min': <string | template> } }\n\n        assert(typeof message === 'object' && !Array.isArray(message), 'Invalid message for', code);\n\n        const language = code;\n        target[language] = target[language] || {};\n\n        for (code in message) {\n            const localized = message[code];\n\n            if (code === 'root' ||\n                Template.isTemplate(localized)) {\n\n                target[language][code] = localized;\n                continue;\n            }\n\n            assert(typeof localized === 'string', 'Invalid message for', code, 'in', language);\n            target[language][code] = new Template(localized);\n        }\n    }\n\n    return target;\n};\n\n\nexports.decompile = function (messages) {\n\n    // By error code { 'number.min': <string | template> }\n\n    const target = {};\n    for (let code in messages) {\n        const message = messages[code];\n\n        if (code === 'root') {\n            target.root = message;\n            continue;\n        }\n\n        if (Template.isTemplate(message)) {\n            target[code] = message.describe({ compact: true });\n            continue;\n        }\n\n        // By language { english: { 'number.min': <string | template> } }\n\n        const language = code;\n        target[language] = {};\n\n        for (code in message) {\n            const localized = message[code];\n\n            if (code === 'root') {\n                target[language].root = localized;\n                continue;\n            }\n\n            target[language][code] = localized.describe({ compact: true });\n        }\n    }\n\n    return target;\n};\n\n\nexports.merge = function (base, extended) {\n\n    if (!base) {\n        return exports.compile(extended);\n    }\n\n    if (!extended) {\n        return base;\n    }\n\n    // Single value string\n\n    if (typeof extended === 'string') {\n        return new Template(extended);\n    }\n\n    // Single value template\n\n    if (Template.isTemplate(extended)) {\n        return extended;\n    }\n\n    // By error code { 'number.min': <string | template> }\n\n    const target = clone(base);\n\n    for (let code in extended) {\n        const message = extended[code];\n\n        if (code === 'root' ||\n            Template.isTemplate(message)) {\n\n            target[code] = message;\n            continue;\n        }\n\n        if (typeof message === 'string') {\n            target[code] = new Template(message);\n            continue;\n        }\n\n        // By language { english: { 'number.min': <string | template> } }\n\n        assert(typeof message === 'object' && !Array.isArray(message), 'Invalid message for', code);\n\n        const language = code;\n        target[language] = target[language] || {};\n\n        for (code in message) {\n            const localized = message[code];\n\n            if (code === 'root' ||\n                Template.isTemplate(localized)) {\n\n                target[language][code] = localized;\n                continue;\n            }\n\n            assert(typeof localized === 'string', 'Invalid message for', code, 'in', language);\n            target[language][code] = new Template(localized);\n        }\n    }\n\n    return target;\n};\n"
  },
  {
    "path": "lib/modify.js",
    "content": "'use strict';\n\nconst { assert } = require('@hapi/hoek');\n\nconst Common = require('./common');\nconst Ref = require('./ref');\n\n\nconst internals = {};\n\n\n\nexports.Ids = internals.Ids = class {\n\n    constructor() {\n\n        this._byId = new Map();\n        this._byKey = new Map();\n        this._schemaChain = false;\n    }\n\n    clone() {\n\n        const clone = new internals.Ids();\n        clone._byId = new Map(this._byId);\n        clone._byKey = new Map(this._byKey);\n        clone._schemaChain = this._schemaChain;\n        return clone;\n    }\n\n    concat(source) {\n\n        if (source._schemaChain) {\n            this._schemaChain = true;\n        }\n\n        for (const [id, value] of source._byId.entries()) {\n            assert(!this._byKey.has(id), 'Schema id conflicts with existing key:', id);\n            this._byId.set(id, value);\n        }\n\n        for (const [key, value] of source._byKey.entries()) {\n            assert(!this._byId.has(key), 'Schema key conflicts with existing id:', key);\n            this._byKey.set(key, value);\n        }\n    }\n\n    fork(path, adjuster, root) {\n\n        const chain = this._collect(path);\n        chain.push({ schema: root });\n        const tail = chain.shift();\n        let adjusted = { id: tail.id, schema: adjuster(tail.schema) };\n\n        assert(Common.isSchema(adjusted.schema), 'adjuster function failed to return a joi schema type');\n\n        for (const node of chain) {\n            adjusted = { id: node.id, schema: internals.fork(node.schema, adjusted.id, adjusted.schema) };\n        }\n\n        return adjusted.schema;\n    }\n\n    labels(path, behind = []) {\n\n        const current = path[0];\n        const node = this._get(current);\n        if (!node) {\n            return [...behind, ...path].join('.');\n        }\n\n        const forward = path.slice(1);\n        behind = [...behind, node.schema._flags.label || current];\n        if (!forward.length) {\n            return behind.join('.');\n        }\n\n        return node.schema._ids.labels(forward, behind);\n    }\n\n    reach(path, behind = []) {\n\n        const current = path[0];\n        const node = this._get(current);\n        assert(node, 'Schema does not contain path', [...behind, ...path].join('.'));\n\n        const forward = path.slice(1);\n        if (!forward.length) {\n            return node.schema;\n        }\n\n        return node.schema._ids.reach(forward, [...behind, current]);\n    }\n\n    register(schema, { key } = {}) {\n\n        if (!schema ||\n            !Common.isSchema(schema)) {\n\n            return;\n        }\n\n        if (schema.$_property('schemaChain') ||\n            schema._ids._schemaChain) {\n\n            this._schemaChain = true;\n        }\n\n        const id = schema._flags.id;\n        if (id) {\n            const existing = this._byId.get(id);\n            assert(!existing || existing.schema === schema, 'Cannot add different schemas with the same id:', id);\n            assert(!this._byKey.has(id), 'Schema id conflicts with existing key:', id);\n\n            this._byId.set(id, { schema, id });\n        }\n\n        if (key) {\n            assert(!this._byKey.has(key), 'Schema already contains key:', key);\n            assert(!this._byId.has(key), 'Schema key conflicts with existing id:', key);\n\n            this._byKey.set(key, { schema, id: key });\n        }\n    }\n\n    reset() {\n\n        this._byId = new Map();\n        this._byKey = new Map();\n        this._schemaChain = false;\n    }\n\n    _collect(path, behind = [], nodes = []) {\n\n        const current = path[0];\n        const node = this._get(current);\n        assert(node, 'Schema does not contain path', [...behind, ...path].join('.'));\n\n        nodes = [node, ...nodes];\n\n        const forward = path.slice(1);\n        if (!forward.length) {\n            return nodes;\n        }\n\n        return node.schema._ids._collect(forward, [...behind, current], nodes);\n    }\n\n    _get(id) {\n\n        return this._byId.get(id) || this._byKey.get(id);\n    }\n};\n\n\ninternals.fork = function (schema, id, replacement) {\n\n    const each = (item, { key }) => {\n\n        if (id === (item._flags.id || key)) {\n            return replacement;\n        }\n    };\n\n    const obj = exports.schema(schema, { each, ref: false });\n    return obj ? obj.$_mutateRebuild() : schema;\n};\n\n\nexports.schema = function (schema, options) {\n\n    let obj;\n\n    for (const name in schema._flags) {\n        if (name[0] === '_') {\n            continue;\n        }\n\n        const result = internals.scan(schema._flags[name], { source: 'flags', name }, options);\n        if (result !== undefined) {\n            obj = obj || schema.clone();\n            obj._flags[name] = result;\n        }\n    }\n\n    for (let i = 0; i < schema._rules.length; ++i) {\n        const rule = schema._rules[i];\n        const result = internals.scan(rule.args, { source: 'rules', name: rule.name }, options);\n        if (result !== undefined) {\n            obj = obj || schema.clone();\n            const clone = Object.assign({}, rule);\n            clone.args = result;\n            obj._rules[i] = clone;\n\n            const existingUnique = obj._singleRules.get(rule.name);\n            if (existingUnique === rule) {\n                obj._singleRules.set(rule.name, clone);\n            }\n        }\n    }\n\n    for (const name in schema.$_terms) {\n        if (name[0] === '_') {\n            continue;\n        }\n\n        const result = internals.scan(schema.$_terms[name], { source: 'terms', name }, options);\n        if (result !== undefined) {\n            obj = obj || schema.clone();\n            obj.$_terms[name] = result;\n        }\n    }\n\n    return obj;\n};\n\n\ninternals.scan = function (item, source, options, _path, _key) {\n\n    const path = _path || [];\n\n    if (item === null ||\n        typeof item !== 'object') {\n\n        return;\n    }\n\n    let clone;\n\n    if (Array.isArray(item)) {\n        for (let i = 0; i < item.length; ++i) {\n            const key = source.source === 'terms' && source.name === 'keys' && item[i].key;\n            const result = internals.scan(item[i], source, options, [i, ...path], key);\n            if (result !== undefined) {\n                clone = clone || item.slice();\n                clone[i] = result;\n            }\n        }\n\n        return clone;\n    }\n\n    if (options.schema !== false && Common.isSchema(item) ||\n        options.ref !== false && Ref.isRef(item)) {\n\n        const result = options.each(item, { ...source, path, key: _key });\n        if (result === item) {\n            return;\n        }\n\n        return result;\n    }\n\n    for (const key in item) {\n        if (key[0] === '_') {\n            continue;\n        }\n\n        const result = internals.scan(item[key], source, options, [key, ...path], _key);\n        if (result !== undefined) {\n            clone = clone || Object.assign({}, item);\n            clone[key] = result;\n        }\n    }\n\n    return clone;\n};\n"
  },
  {
    "path": "lib/ref.js",
    "content": "'use strict';\n\nconst { assert, clone, reach } = require('@hapi/hoek');\n\nconst Common = require('./common');\n\nlet Template;\n\n\nconst internals = {\n    symbol: Symbol('ref'),      // Used to internally identify references (shared with other joi versions)\n    defaults: {\n        adjust: null,\n        in: false,\n        iterables: null,\n        map: null,\n        separator: '.',\n        type: 'value'\n    }\n};\n\n\nexports.create = function (key, options = {}) {\n\n    assert(typeof key === 'string', 'Invalid reference key:', key);\n    Common.assertOptions(options, ['adjust', 'ancestor', 'in', 'iterables', 'map', 'prefix', 'render', 'separator']);\n    assert(!options.prefix || typeof options.prefix === 'object', 'options.prefix must be of type object');\n\n    const ref = Object.assign({}, internals.defaults, options);\n    delete ref.prefix;\n\n    const separator = ref.separator;\n    const context = internals.context(key, separator, options.prefix);\n    ref.type = context.type;\n    key = context.key;\n\n    if (ref.type === 'value') {\n        if (context.root) {\n            assert(!separator || key[0] !== separator, 'Cannot specify relative path with root prefix');\n            ref.ancestor = 'root';\n            if (!key) {\n                key = null;\n            }\n        }\n\n        if (separator &&\n            separator === key) {\n\n            key = null;\n            ref.ancestor = 0;\n        }\n        else {\n            if (ref.ancestor !== undefined) {\n                assert(!separator || !key || key[0] !== separator, 'Cannot combine prefix with ancestor option');\n            }\n            else {\n                const [ancestor, slice] = internals.ancestor(key, separator);\n                if (slice) {\n                    key = key.slice(slice);\n                    if (key === '') {\n                        key = null;\n                    }\n                }\n\n                ref.ancestor = ancestor;\n            }\n        }\n    }\n\n    ref.path = separator ? (key === null ? [] : key.split(separator)) : [key];\n\n    return new internals.Ref(ref);\n};\n\n\nexports.in = function (key, options = {}) {\n\n    return exports.create(key, { ...options, in: true });\n};\n\n\nexports.isRef = function (ref) {\n\n    return ref ? !!ref[Common.symbols.ref] : false;\n};\n\n\ninternals.Ref = class {\n\n    constructor(options) {\n\n        assert(typeof options === 'object', 'Invalid reference construction');\n        Common.assertOptions(options, [\n            'adjust', 'ancestor', 'in', 'iterables', 'map', 'path', 'render', 'separator', 'type',  // Copied\n            'depth', 'key', 'root', 'display'                                                       // Overridden\n        ]);\n\n        assert([false, undefined].includes(options.separator) || typeof options.separator === 'string' && options.separator.length === 1, 'Invalid separator');\n        assert(!options.adjust || typeof options.adjust === 'function', 'options.adjust must be a function');\n        assert(!options.map || Array.isArray(options.map), 'options.map must be an array');\n        assert(!options.map || !options.adjust, 'Cannot set both map and adjust options');\n\n        Object.assign(this, internals.defaults, options);\n\n        assert(this.type === 'value' || this.ancestor === undefined, 'Non-value references cannot reference ancestors');\n\n        if (Array.isArray(this.map)) {\n            this.map = new Map(this.map);\n        }\n\n        this.depth = this.path.length;\n        this.key = this.path.length ? this.path.join(this.separator) : null;\n        this.root = this.path[0];\n\n        this.updateDisplay();\n    }\n\n    resolve(value, state, prefs, local, options = {}) {\n\n        assert(!this.in || options.in, 'Invalid in() reference usage');\n\n        if (this.type === 'global') {\n            return this._resolve(prefs.context, state, options);\n        }\n\n        if (this.type === 'local') {\n            return this._resolve(local, state, options);\n        }\n\n        if (!this.ancestor) {\n            return this._resolve(value, state, options);\n        }\n\n        if (this.ancestor === 'root') {\n            return this._resolve(state.ancestors[state.ancestors.length - 1], state, options);\n        }\n\n        assert(this.ancestor <= state.ancestors.length, 'Invalid reference exceeds the schema root:', this.display);\n        return this._resolve(state.ancestors[this.ancestor - 1], state, options);\n    }\n\n    _resolve(target, state, options) {\n\n        let resolved;\n\n        if (this.type === 'value' &&\n            state.mainstay.shadow &&\n            options.shadow !== false) {\n\n            resolved = state.mainstay.shadow.get(this.absolute(state));\n        }\n\n        if (resolved === undefined) {\n            resolved = reach(target, this.path, { iterables: this.iterables, functions: true });\n        }\n\n        if (this.adjust) {\n            resolved = this.adjust(resolved);\n        }\n\n        if (this.map) {\n            const mapped = this.map.get(resolved);\n            if (mapped !== undefined) {\n                resolved = mapped;\n            }\n        }\n\n        if (state.mainstay) {\n            state.mainstay.tracer.resolve(state, this, resolved);\n        }\n\n        return resolved;\n    }\n\n    toString() {\n\n        return this.display;\n    }\n\n    absolute(state) {\n\n        return [...state.path.slice(0, -this.ancestor), ...this.path];\n    }\n\n    clone() {\n\n        return new internals.Ref(this);\n    }\n\n    describe() {\n\n        const ref = { path: this.path };\n\n        if (this.type !== 'value') {\n            ref.type = this.type;\n        }\n\n        if (this.separator !== '.') {\n            ref.separator = this.separator;\n        }\n\n        if (this.type === 'value' &&\n            this.ancestor !== 1) {\n\n            ref.ancestor = this.ancestor;\n        }\n\n        if (this.map) {\n            ref.map = [...this.map];\n        }\n\n        for (const key of ['adjust', 'iterables', 'render']) {\n            if (this[key] !== null &&\n                this[key] !== undefined) {\n\n                ref[key] = this[key];\n            }\n        }\n\n        if (this.in !== false) {\n            ref.in = true;\n        }\n\n        return { ref };\n    }\n\n    updateDisplay() {\n\n        const key = this.key !== null ? this.key : '';\n        if (this.type !== 'value') {\n            this.display = `ref:${this.type}:${key}`;\n            return;\n        }\n\n        if (!this.separator) {\n            this.display = `ref:${key}`;\n            return;\n        }\n\n        if (!this.ancestor) {\n            this.display = `ref:${this.separator}${key}`;\n            return;\n        }\n\n        if (this.ancestor === 'root') {\n            this.display = `ref:root:${key}`;\n            return;\n        }\n\n        if (this.ancestor === 1) {\n            this.display = `ref:${key || '..'}`;\n            return;\n        }\n\n        const lead = new Array(this.ancestor + 1).fill(this.separator).join('');\n        this.display = `ref:${lead}${key || ''}`;\n    }\n};\n\n\ninternals.Ref.prototype[Common.symbols.ref] = true;\n\n\nexports.build = function (desc) {\n\n    desc = Object.assign({}, internals.defaults, desc);\n    if (desc.type === 'value' &&\n        desc.ancestor === undefined) {\n\n        desc.ancestor = 1;\n    }\n\n    return new internals.Ref(desc);\n};\n\n\ninternals.context = function (key, separator, prefix = {}) {\n\n    key = key.trim();\n\n    if (prefix) {\n        const globalp = prefix.global === undefined ? '$' : prefix.global;\n        if (globalp !== separator &&\n            key.startsWith(globalp)) {\n\n            return { key: key.slice(globalp.length), type: 'global' };\n        }\n\n        const local = prefix.local === undefined ? '#' : prefix.local;\n        if (local !== separator &&\n            key.startsWith(local)) {\n\n            return { key: key.slice(local.length), type: 'local' };\n        }\n\n        const root = prefix.root === undefined ? '/' : prefix.root;\n        if (root !== separator &&\n            key.startsWith(root)) {\n\n            return { key: key.slice(root.length), type: 'value', root: true };\n        }\n    }\n\n    return { key, type: 'value' };\n};\n\n\ninternals.ancestor = function (key, separator) {\n\n    if (!separator) {\n        return [1, 0];              // 'a_b' -> 1 (parent)\n    }\n\n    if (key[0] !== separator) {     // 'a.b' -> 1 (parent)\n        return [1, 0];\n    }\n\n    if (key[1] !== separator) {     // '.a.b' -> 0 (self)\n        return [0, 1];\n    }\n\n    let i = 2;\n    while (key[i] === separator) {\n        ++i;\n    }\n\n    return [i - 1, i];              // '...a.b.' -> 2 (grandparent)\n};\n\n\nexports.toSibling = 0;\n\nexports.toParent = 1;\n\n\nexports.Manager = class {\n\n    constructor() {\n\n        this.refs = [];                     // 0: [self refs], 1: [parent refs], 2: [grandparent refs], ...\n    }\n\n    register(source, target) {\n\n        if (!source) {\n            return;\n        }\n\n        target = target === undefined ? exports.toParent : target;\n\n        // Array\n\n        if (Array.isArray(source)) {\n            for (const ref of source) {\n                this.register(ref, target);\n            }\n\n            return;\n        }\n\n        // Schema\n\n        if (Common.isSchema(source)) {\n            for (const item of source._refs.refs) {\n                if (item.ancestor - target >= 0) {\n                    this.refs.push({ ancestor: item.ancestor - target, root: item.root });\n                }\n            }\n\n            return;\n        }\n\n        // Reference\n\n        if (exports.isRef(source) &&\n            source.type === 'value' &&\n            source.ancestor - target >= 0) {\n\n            this.refs.push({ ancestor: source.ancestor - target, root: source.root });\n        }\n\n        // Template\n\n        Template = Template || require('./template');\n\n        if (Template.isTemplate(source)) {\n            this.register(source.refs(), target);\n        }\n    }\n\n    get length() {\n\n        return this.refs.length;\n    }\n\n    clone() {\n\n        const copy = new exports.Manager();\n        copy.refs = clone(this.refs);\n        return copy;\n    }\n\n    reset() {\n\n        this.refs = [];\n    }\n\n    roots() {\n\n        return this.refs.filter((ref) => !ref.ancestor).map((ref) => ref.root);\n    }\n};\n"
  },
  {
    "path": "lib/schemas.js",
    "content": "'use strict';\n\nconst Joi = require('./index');\n\n\nconst internals = {};\n\n\n// Preferences\n\ninternals.wrap = Joi.string()\n    .min(1)\n    .max(2)\n    .allow(false);\n\n\nexports.preferences = Joi.object({\n    allowUnknown: Joi.boolean(),\n    abortEarly: Joi.boolean(),\n    artifacts: Joi.boolean(),\n    cache: Joi.boolean(),\n    context: Joi.object(),\n    convert: Joi.boolean(),\n    dateFormat: Joi.valid('date', 'iso', 'string', 'time', 'utc'),\n    debug: Joi.boolean(),\n    errors: {\n        escapeHtml: Joi.boolean(),\n        label: Joi.valid('path', 'key', false),\n        language: [\n            Joi.string(),\n            Joi.object().ref()\n        ],\n        render: Joi.boolean(),\n        stack: Joi.boolean(),\n        wrap: {\n            label: internals.wrap,\n            array: internals.wrap,\n            string: internals.wrap\n        }\n    },\n    externals: Joi.boolean(),\n    messages: Joi.object(),\n    noDefaults: Joi.boolean(),\n    nonEnumerables: Joi.boolean(),\n    presence: Joi.valid('required', 'optional', 'forbidden'),\n    skipFunctions: Joi.boolean(),\n    stripUnknown: Joi.object({\n        arrays: Joi.boolean(),\n        objects: Joi.boolean()\n    })\n        .or('arrays', 'objects')\n        .allow(true, false),\n    warnings: Joi.boolean()\n})\n    .strict();\n\n\n// Extensions\n\ninternals.nameRx = /^[a-zA-Z0-9]\\w*$/;\n\n\ninternals.rule = Joi.object({\n    alias: Joi.array().items(Joi.string().pattern(internals.nameRx)).single(),\n    args: Joi.array().items(\n        Joi.string(),\n        Joi.object({\n            name: Joi.string().pattern(internals.nameRx).required(),\n            ref: Joi.boolean(),\n            assert: Joi.alternatives([\n                Joi.function(),\n                Joi.object().schema()\n            ])\n                .conditional('ref', { is: true, then: Joi.required() }),\n            normalize: Joi.function(),\n            message: Joi.string().when('assert', { is: Joi.function(), then: Joi.required() })\n        })\n    ),\n    convert: Joi.boolean(),\n    manifest: Joi.boolean(),\n    method: Joi.function().allow(false),\n    multi: Joi.boolean(),\n    validate: Joi.function()\n});\n\n\nexports.extension = Joi.object({\n    type: Joi.alternatives([\n        Joi.string(),\n        Joi.object().regex()\n    ])\n        .required(),\n    args: Joi.function(),\n    cast: Joi.object().pattern(internals.nameRx, Joi.object({\n        from: Joi.function().maxArity(1).required(),\n        to: Joi.function().minArity(1).maxArity(2).required()\n    })),\n    base: Joi.object().schema()\n        .when('type', { is: Joi.object().regex(), then: Joi.forbidden() }),\n    coerce: [\n        Joi.function().maxArity(3),\n        Joi.object({ method: Joi.function().maxArity(3).required(), from: Joi.array().items(Joi.string()).single() })\n    ],\n    flags: Joi.object().pattern(internals.nameRx, Joi.object({\n        setter: Joi.string(),\n        default: Joi.any()\n    })),\n    manifest: {\n        build: Joi.function().arity(2)\n    },\n    messages: [Joi.object(), Joi.string()],\n    modifiers: Joi.object().pattern(internals.nameRx, Joi.function().minArity(1).maxArity(2)),\n    overrides: Joi.object().pattern(internals.nameRx, Joi.function()),\n    prepare: Joi.function().maxArity(3),\n    rebuild: Joi.function().arity(1),\n    rules: Joi.object().pattern(internals.nameRx, internals.rule),\n    terms: Joi.object().pattern(internals.nameRx, Joi.object({\n        init: Joi.array().allow(null).required(),\n        manifest: Joi.object().pattern(/.+/, [\n            Joi.valid('schema', 'single'),\n            Joi.object({\n                mapped: Joi.object({\n                    from: Joi.string().required(),\n                    to: Joi.string().required()\n                })\n                    .required()\n            })\n        ])\n    })),\n    validate: Joi.function().maxArity(3)\n})\n    .strict();\n\n\nexports.extensions = Joi.array().items(Joi.object(), Joi.function().arity(1)).strict();\n\n\n// Manifest\n\ninternals.desc = {\n\n    buffer: Joi.object({\n        buffer: Joi.string()\n    }),\n\n    func: Joi.object({\n        function: Joi.function().required(),\n        options: {\n            literal: true\n        }\n    }),\n\n    override: Joi.object({\n        override: true\n    }),\n\n    ref: Joi.object({\n        ref: Joi.object({\n            type: Joi.valid('value', 'global', 'local'),\n            path: Joi.array().required(),\n            separator: Joi.string().length(1).allow(false),\n            ancestor: Joi.number().min(0).integer().allow('root'),\n            map: Joi.array().items(Joi.array().length(2)).min(1),\n            adjust: Joi.function(),\n            iterables: Joi.boolean(),\n            in: Joi.boolean(),\n            render: Joi.boolean()\n        })\n            .required()\n    }),\n\n    regex: Joi.object({\n        regex: Joi.string().min(3)\n    }),\n\n    special: Joi.object({\n        special: Joi.valid('deep').required()\n    }),\n\n    template: Joi.object({\n        template: Joi.string().required(),\n        options: Joi.object()\n    }),\n\n    value: Joi.object({\n        value: Joi.alternatives([Joi.object(), Joi.array()]).required()\n    })\n};\n\n\ninternals.desc.entity = Joi.alternatives([\n    Joi.array().items(Joi.link('...')),\n    Joi.boolean(),\n    Joi.function(),\n    Joi.number(),\n    Joi.string(),\n    internals.desc.buffer,\n    internals.desc.func,\n    internals.desc.ref,\n    internals.desc.regex,\n    internals.desc.special,\n    internals.desc.template,\n    internals.desc.value,\n    Joi.link('/')\n]);\n\n\ninternals.desc.values = Joi.array()\n    .items(\n        null,\n        Joi.boolean(),\n        Joi.function(),\n        Joi.number().allow(Infinity, -Infinity),\n        Joi.string().allow(''),\n        Joi.symbol(),\n        internals.desc.buffer,\n        internals.desc.func,\n        internals.desc.override,\n        internals.desc.ref,\n        internals.desc.regex,\n        internals.desc.template,\n        internals.desc.value\n    );\n\n\ninternals.desc.messages = Joi.object()\n    .pattern(/.+/, [\n        Joi.string(),\n        internals.desc.template,\n        Joi.object().pattern(/.+/, [Joi.string(), internals.desc.template])\n    ]);\n\n\nexports.description = Joi.object({\n    type: Joi.string().required(),\n    flags: Joi.object({\n        cast: Joi.string(),\n        default: Joi.any(),\n        description: Joi.string(),\n        empty: Joi.link('/'),\n        failover: internals.desc.entity,\n        id: Joi.string(),\n        label: Joi.string(),\n        only: true,\n        presence: ['optional', 'required', 'forbidden'],\n        result: ['raw', 'strip'],\n        strip: Joi.boolean(),\n        unit: Joi.string()\n    })\n        .unknown(),\n    preferences: {\n        allowUnknown: Joi.boolean(),\n        abortEarly: Joi.boolean(),\n        artifacts: Joi.boolean(),\n        cache: Joi.boolean(),\n        convert: Joi.boolean(),\n        dateFormat: ['date', 'iso', 'string', 'time', 'utc'],\n        errors: {\n            escapeHtml: Joi.boolean(),\n            label: ['path', 'key'],\n            language: [\n                Joi.string(),\n                internals.desc.ref\n            ],\n            wrap: {\n                label: internals.wrap,\n                array: internals.wrap\n            }\n        },\n        externals: Joi.boolean(),\n        messages: internals.desc.messages,\n        noDefaults: Joi.boolean(),\n        nonEnumerables: Joi.boolean(),\n        presence: ['required', 'optional', 'forbidden'],\n        skipFunctions: Joi.boolean(),\n        stripUnknown: Joi.object({\n            arrays: Joi.boolean(),\n            objects: Joi.boolean()\n        })\n            .or('arrays', 'objects')\n            .allow(true, false),\n        warnings: Joi.boolean()\n    },\n    allow: internals.desc.values,\n    invalid: internals.desc.values,\n    rules: Joi.array().min(1).items({\n        name: Joi.string().required(),\n        args: Joi.object().min(1),\n        keep: Joi.boolean(),\n        message: [\n            Joi.string(),\n            internals.desc.messages\n        ],\n        warn: Joi.boolean()\n    }),\n\n    // Terms\n\n    keys: Joi.object().pattern(/.*/, Joi.link('/')),\n    link: internals.desc.ref\n})\n    .pattern(/^[a-z]\\w*$/, Joi.any());\n"
  },
  {
    "path": "lib/state.js",
    "content": "'use strict';\n\nconst { clone, reach } = require('@hapi/hoek');\n\nconst Common = require('./common');\n\n\nconst internals = {\n    value: Symbol('value')\n};\n\n\nmodule.exports = internals.State = class {\n\n    constructor(path, ancestors, state) {\n\n        this.path = path;\n        this.ancestors = ancestors;                 // [parent, ..., root]\n\n        this.mainstay = state.mainstay;\n        this.schemas = state.schemas;               // [current, ..., root]\n        this.debug = null;\n    }\n\n    localize(path, ancestors = null, schema = null) {\n\n        const state = new internals.State(path, ancestors, this);\n\n        if (schema &&\n            state.schemas) {\n\n            state.schemas = [internals.schemas(schema), ...state.schemas];\n        }\n\n        return state;\n    }\n\n    nest(schema, debug) {\n\n        const state = new internals.State(this.path, this.ancestors, this);\n        state.schemas = state.schemas && [internals.schemas(schema), ...state.schemas];\n        state.debug = debug;\n        return state;\n    }\n\n    shadow(value, reason) {\n\n        this.mainstay.shadow = this.mainstay.shadow || new internals.Shadow();\n        this.mainstay.shadow.set(this.path, value, reason);\n    }\n\n    snapshot() {\n\n        if (this.mainstay.shadow) {\n            this._snapshot = clone(this.mainstay.shadow.node(this.path));\n        }\n\n        this.mainstay.snapshot();\n    }\n\n    restore() {\n\n        if (this.mainstay.shadow) {\n            this.mainstay.shadow.override(this.path, this._snapshot);\n            this._snapshot = undefined;\n        }\n\n        this.mainstay.restore();\n    }\n\n    commit() {\n\n        if (this.mainstay.shadow) {\n            this.mainstay.shadow.override(this.path, this._snapshot);\n            this._snapshot = undefined;\n        }\n\n        this.mainstay.commit();\n    }\n};\n\n\ninternals.schemas = function (schema) {\n\n    if (Common.isSchema(schema)) {\n        return { schema };\n    }\n\n    return schema;\n};\n\n\ninternals.Shadow = class {\n\n    constructor() {\n\n        this._values = null;\n    }\n\n    set(path, value, reason) {\n\n        if (!path.length) {                                     // No need to store root value\n            return;\n        }\n\n        if (reason === 'strip' &&\n            typeof path[path.length - 1] === 'number') {        // Cannot store stripped array values (due to shift)\n\n            return;\n        }\n\n        this._values = this._values || new Map();\n\n        let node = this._values;\n        for (let i = 0; i < path.length; ++i) {\n            const segment = path[i];\n            let next = node.get(segment);\n            if (!next) {\n                next = new Map();\n                node.set(segment, next);\n            }\n\n            node = next;\n        }\n\n        node[internals.value] = value;\n    }\n\n    get(path) {\n\n        const node = this.node(path);\n        if (node) {\n            return node[internals.value];\n        }\n    }\n\n    node(path) {\n\n        if (!this._values) {\n            return;\n        }\n\n        return reach(this._values, path, { iterables: true });\n    }\n\n    override(path, node) {\n\n        if (!this._values) {\n            return;\n        }\n\n        const parents = path.slice(0, -1);\n        const own = path[path.length - 1];\n        const parent = reach(this._values, parents, { iterables: true });\n\n        if (node) {\n            parent.set(own, node);\n            return;\n        }\n\n        if (parent) {\n            parent.delete(own);\n        }\n    }\n};\n"
  },
  {
    "path": "lib/template.js",
    "content": "'use strict';\n\nconst { assert, clone, escapeHtml } = require('@hapi/hoek');\nconst Formula = require('@hapi/formula');\n\nconst Common = require('./common');\nconst Errors = require('./errors');\nconst Ref = require('./ref');\n\n\nconst internals = {\n    symbol: Symbol('template'),\n\n    opens: new Array(1000).join('\\u0000'),\n    closes: new Array(1000).join('\\u0001'),\n\n    dateFormat: {\n        date: Date.prototype.toDateString,\n        iso: Date.prototype.toISOString,\n        string: Date.prototype.toString,\n        time: Date.prototype.toTimeString,\n        utc: Date.prototype.toUTCString\n    }\n};\n\n\nmodule.exports = exports = internals.Template = class {\n\n    constructor(source, options) {\n\n        assert(typeof source === 'string', 'Template source must be a string');\n        assert(!source.includes('\\u0000') && !source.includes('\\u0001'), 'Template source cannot contain reserved control characters');\n\n        this.source = source;\n        this.rendered = source;\n\n        this._template = null;\n\n        if (options) {\n            const { functions, ...opts } = options;\n            this._settings = Object.keys(opts).length ? clone(opts) : undefined;\n            this._functions = functions;\n            if (this._functions) {\n                assert(Object.keys(this._functions).every((key) => typeof key === 'string'), 'Functions keys must be strings');\n                assert(Object.values(this._functions).every((key) => typeof key === 'function'), 'Functions values must be functions');\n            }\n        }\n        else {\n            this._settings = undefined;\n            this._functions = undefined;\n        }\n\n        this._parse();\n    }\n\n    _parse() {\n\n        // 'text {raw} {{ref}} \\\\{{ignore}} {{ignore\\\\}} {{ignore {{ignore}'\n\n        if (!this.source.includes('{')) {\n            return;\n        }\n\n        // Encode escaped \\\\{{{{{\n\n        const encoded = internals.encode(this.source);\n\n        // Split on first { in each set\n\n        const parts = internals.split(encoded);\n\n        // Process parts\n\n        let refs = false;\n        const processed = [];\n        const head = parts.shift();\n        if (head) {\n            processed.push(head);\n        }\n\n        for (const part of parts) {\n            const raw = part[0] !== '{';\n            const ender = raw ? '}' : '}}';\n            const end = part.indexOf(ender);\n            if (end === -1 ||                               // Ignore non-matching closing\n                part[1] === '{') {                          // Ignore more than two {\n\n                processed.push(`{${internals.decode(part)}`);\n                continue;\n            }\n\n            let variable = part.slice(raw ? 0 : 1, end);\n            const wrapped = variable[0] === ':';\n            if (wrapped) {\n                variable = variable.slice(1);\n            }\n\n            const dynamic = this._ref(internals.decode(variable), { raw, wrapped });\n            processed.push(dynamic);\n            if (typeof dynamic !== 'string') {\n                refs = true;\n            }\n\n            const rest = part.slice(end + ender.length);\n            if (rest) {\n                processed.push(internals.decode(rest));\n            }\n        }\n\n        if (!refs) {\n            this.rendered = processed.join('');\n            return;\n        }\n\n        this._template = processed;\n    }\n\n    static date(date, prefs) {\n\n        return internals.dateFormat[prefs.dateFormat].call(date);\n    }\n\n    describe(options = {}) {\n\n        if (!this._settings &&\n            options.compact) {\n\n            return this.source;\n        }\n\n        const desc = { template: this.source };\n        if (this._settings) {\n            desc.options = this._settings;\n        }\n\n        if (this._functions) {\n            desc.functions = this._functions;\n        }\n\n        return desc;\n    }\n\n    static build(desc) {\n\n        return new internals.Template(desc.template, desc.options || desc.functions ? { ...desc.options, functions: desc.functions } : undefined);\n    }\n\n    isDynamic() {\n\n        return !!this._template;\n    }\n\n    static isTemplate(template) {\n\n        return template ? !!template[Common.symbols.template] : false;\n    }\n\n    refs() {\n\n        if (!this._template) {\n            return;\n        }\n\n        const refs = [];\n        for (const part of this._template) {\n            if (typeof part !== 'string') {\n                refs.push(...part.refs);\n            }\n        }\n\n        return refs;\n    }\n\n    resolve(value, state, prefs, local) {\n\n        if (this._template &&\n            this._template.length === 1) {\n\n            return this._part(this._template[0], /* context -> [*/ value, state, prefs, local, {} /*] */);\n        }\n\n        return this.render(value, state, prefs, local);\n    }\n\n    _part(part, ...args) {\n\n        if (part.ref) {\n            return part.ref.resolve(...args);\n        }\n\n        return part.formula.evaluate(args);\n    }\n\n    render(value, state, prefs, local, options = {}) {\n\n        if (!this.isDynamic()) {\n            return this.rendered;\n        }\n\n        const parts = [];\n        for (const part of this._template) {\n            if (typeof part === 'string') {\n                parts.push(part);\n            }\n            else {\n                const rendered = this._part(part, /* context -> [*/ value, state, prefs, local, options /*] */);\n                const string = internals.stringify(rendered, value, state, prefs, local, options);\n                if (string !== undefined) {\n                    const result = part.raw || (options.errors && options.errors.escapeHtml) === false ? string : escapeHtml(string);\n                    parts.push(internals.wrap(result, part.wrapped && prefs.errors.wrap.label));\n                }\n            }\n        }\n\n        return parts.join('');\n    }\n\n    _ref(content, { raw, wrapped }) {\n\n        const refs = [];\n        const reference = (variable) => {\n\n            const ref = Ref.create(variable, this._settings);\n            refs.push(ref);\n            return (context) => {\n\n                const resolved = ref.resolve(...context);\n                return resolved !== undefined ? resolved : null;\n            };\n        };\n\n        try {\n            const functions = this._functions ? { ...internals.functions, ...this._functions } : internals.functions;\n            var formula = new Formula.Parser(content, { reference, functions, constants: internals.constants });\n        }\n        catch (err) {\n            err.message = `Invalid template variable \"${content}\" fails due to: ${err.message}`;\n            throw err;\n        }\n\n        if (formula.single) {\n            if (formula.single.type === 'reference') {\n                const ref = refs[0];\n                return { ref, raw, refs, wrapped: wrapped || ref.type === 'local' && ref.key === 'label' };\n            }\n\n            return internals.stringify(formula.single.value);\n        }\n\n        return { formula, raw, refs };\n    }\n\n    toString() {\n\n        return this.source;\n    }\n};\n\n\ninternals.Template.prototype[Common.symbols.template] = true;\ninternals.Template.prototype.isImmutable = true;                // Prevents Hoek from deep cloning schema objects\n\n\ninternals.encode = function (string) {\n\n    return string\n        .replace(/\\\\(\\{+)/g, ($0, $1) => {\n\n            return internals.opens.slice(0, $1.length);\n        })\n        .replace(/\\\\(\\}+)/g, ($0, $1) => {\n\n            return internals.closes.slice(0, $1.length);\n        });\n};\n\n\ninternals.decode = function (string) {\n\n    return string\n        .replace(/\\u0000/g, '{')\n        .replace(/\\u0001/g, '}');\n};\n\n\ninternals.split = function (string) {\n\n    const parts = [];\n    let current = '';\n\n    for (let i = 0; i < string.length; ++i) {\n        const char = string[i];\n\n        if (char === '{') {\n            let next = '';\n            while (i + 1 < string.length &&\n                string[i + 1] === '{') {\n\n                next += '{';\n                ++i;\n            }\n\n            parts.push(current);\n            current = next;\n        }\n        else {\n            current += char;\n        }\n    }\n\n    parts.push(current);\n    return parts;\n};\n\n\ninternals.wrap = function (value, ends) {\n\n    if (!ends) {\n        return value;\n    }\n\n    if (ends.length === 1) {\n        return `${ends}${value}${ends}`;\n    }\n\n    return `${ends[0]}${value}${ends[1]}`;\n};\n\n\ninternals.stringify = function (value, original, state, prefs, local, options = {}) {\n\n    const type = typeof value;\n    const wrap = prefs && prefs.errors && prefs.errors.wrap || {};\n\n    let skipWrap = false;\n    if (Ref.isRef(value) &&\n        value.render) {\n\n        skipWrap = value.in;\n        value = value.resolve(original, state, prefs, local, { in: value.in, ...options });\n    }\n\n    if (value === null) {\n        return 'null';\n    }\n\n    if (type === 'string') {\n        return internals.wrap(value, options.arrayItems && wrap.string);\n    }\n\n    if (type === 'number' ||\n        type === 'function' ||\n        type === 'symbol') {\n\n        return value.toString();\n    }\n\n    if (type !== 'object') {\n        return JSON.stringify(value);\n    }\n\n    if (value instanceof Date) {\n        return internals.Template.date(value, prefs);\n    }\n\n    if (value instanceof Map) {\n        const pairs = [];\n        for (const [key, sym] of value.entries()) {\n            pairs.push(`${key.toString()} -> ${sym.toString()}`);\n        }\n\n        value = pairs;\n    }\n\n    if (!Array.isArray(value)) {\n        return value.toString();\n    }\n\n    const values = [];\n    for (const item of value) {\n        values.push(internals.stringify(item, original, state, prefs, local, { arrayItems: true, ...options }));\n    }\n\n    return internals.wrap(values.join(', '), !skipWrap && wrap.array);\n};\n\n\ninternals.constants = {\n\n    true: true,\n    false: false,\n    null: null,\n\n    second: 1000,\n    minute: 60 * 1000,\n    hour: 60 * 60 * 1000,\n    day: 24 * 60 * 60 * 1000\n};\n\n\ninternals.functions = {\n\n    if(condition, then, otherwise) {\n\n        return condition ? then : otherwise;\n    },\n\n    length(item) {\n\n        if (typeof item === 'string') {\n            return item.length;\n        }\n\n        if (!item || typeof item !== 'object') {\n            return null;\n        }\n\n        if (Array.isArray(item)) {\n            return item.length;\n        }\n\n        return Object.keys(item).length;\n    },\n\n    msg(code) {\n\n        const [value, state, prefs, local, options] = this;\n        const messages = options.messages;\n        if (!messages) {\n            return '';\n        }\n\n        const template = Errors.template(value, messages[0], code, state, prefs) || Errors.template(value, messages[1], code, state, prefs);\n        if (!template) {\n            return '';\n        }\n\n        return template.render(value, state, prefs, local, options);\n    },\n\n    number(value) {\n\n        if (typeof value === 'number') {\n            return value;\n        }\n\n        if (typeof value === 'string') {\n            return parseFloat(value);\n        }\n\n        if (typeof value === 'boolean') {\n            return value ? 1 : 0;\n        }\n\n        if (value instanceof Date) {\n            return value.getTime();\n        }\n\n        return null;\n    }\n};\n"
  },
  {
    "path": "lib/trace.js",
    "content": "'use strict';\n\nconst { deepEqual } = require('@hapi/hoek');\nconst Pinpoint = require('@hapi/pinpoint');\n\nconst Errors = require('./errors');\n\n\nconst internals = {\n    codes: {\n        error: 1,\n        pass: 2,\n        full: 3\n    },\n    labels: {\n        0: 'never used',\n        1: 'always error',\n        2: 'always pass'\n    }\n};\n\n\nexports.setup = function (root) {\n\n    const trace = function () {\n\n        root._tracer = root._tracer || new internals.Tracer();\n        return root._tracer;\n    };\n\n    root.trace = trace;\n    root[Symbol.for('@hapi/lab/coverage/initialize')] = trace;\n\n    root.untrace = () => {\n\n        root._tracer = null;\n    };\n};\n\n\nexports.location = function (schema) {\n\n    return schema.$_setFlag('_tracerLocation', Pinpoint.location(2));                       // base.tracer(), caller\n};\n\n\ninternals.Tracer = class {\n\n    constructor() {\n\n        this.name = 'Joi';\n        this._schemas = new Map();\n    }\n\n    _register(schema) {\n\n        const existing = this._schemas.get(schema);\n        if (existing) {\n            return existing.store;\n        }\n\n        const store = new internals.Store(schema);\n        const { filename, line } = schema._flags._tracerLocation || Pinpoint.location(5);   // internals.tracer(), internals.entry(), exports.entry(), validate(), caller\n        this._schemas.set(schema, { filename, line, store });\n        return store;\n    }\n\n    _combine(merged, sources) {\n\n        for (const { store } of this._schemas.values()) {\n            store._combine(merged, sources);\n        }\n    }\n\n    report(file) {\n\n        const coverage = [];\n\n        // Process each registered schema\n\n        for (const { filename, line, store } of this._schemas.values()) {\n            if (file &&\n                file !== filename) {\n\n                continue;\n            }\n\n            // Process sub schemas of the registered root\n\n            const missing = [];\n            const skipped = [];\n\n            for (const [schema, log] of store._sources.entries()) {\n\n                // Check if sub schema parent skipped\n\n                if (internals.sub(log.paths, skipped)) {\n                    continue;\n                }\n\n                // Check if sub schema reached\n\n                if (!log.entry) {\n                    missing.push({\n                        status: 'never reached',\n                        paths: [...log.paths]\n                    });\n\n                    skipped.push(...log.paths);\n                    continue;\n                }\n\n                // Check values\n\n                for (const type of ['valid', 'invalid']) {\n                    const set = schema[`_${type}s`];\n                    if (!set) {\n                        continue;\n                    }\n\n                    const values = new Set(set._values);\n                    const refs = new Set(set._refs);\n                    for (const { value, ref } of log[type]) {\n                        values.delete(value);\n                        refs.delete(ref);\n                    }\n\n                    if (values.size ||\n                        refs.size) {\n\n                        missing.push({\n                            status: [...values, ...[...refs].map((ref) => ref.display)],\n                            rule: `${type}s`\n                        });\n                    }\n                }\n\n                // Check rules status\n\n                const rules = schema._rules.map((rule) => rule.name);\n                for (const type of ['default', 'failover']) {\n                    if (schema._flags[type] !== undefined) {\n                        rules.push(type);\n                    }\n                }\n\n                for (const name of rules) {\n                    const status = internals.labels[log.rule[name] || 0];\n                    if (status) {\n                        const report = { rule: name, status };\n                        if (log.paths.size) {\n                            report.paths = [...log.paths];\n                        }\n\n                        missing.push(report);\n                    }\n                }\n            }\n\n            if (missing.length) {\n                coverage.push({\n                    filename,\n                    line,\n                    missing,\n                    severity: 'error',\n                    message: `Schema missing tests for ${missing.map(internals.message).join(', ')}`\n                });\n            }\n        }\n\n        return coverage.length ? coverage : null;\n    }\n};\n\n\ninternals.Store = class {\n\n    constructor(schema) {\n\n        this.active = true;\n        this._sources = new Map();          // schema -> { paths, entry, rule, valid, invalid }\n        this._combos = new Map();           // merged -> [sources]\n        this._scan(schema);\n    }\n\n    debug(state, source, name, result) {\n\n        state.mainstay.debug && state.mainstay.debug.push({ type: source, name, result, path: state.path });\n    }\n\n    entry(schema, state) {\n\n        internals.debug(state, { type: 'entry' });\n\n        this._record(schema, (log) => {\n\n            log.entry = true;\n        });\n    }\n\n    filter(schema, state, source, value) {\n\n        internals.debug(state, { type: source, ...value });\n\n        this._record(schema, (log) => {\n\n            log[source].add(value);\n        });\n    }\n\n    log(schema, state, source, name, result) {\n\n        internals.debug(state, { type: source, name, result: result === 'full' ? 'pass' : result });\n\n        this._record(schema, (log) => {\n\n            log[source][name] = log[source][name] || 0;\n            log[source][name] |= internals.codes[result];\n        });\n    }\n\n    resolve(state, ref, to) {\n\n        if (!state.mainstay.debug) {\n            return;\n        }\n\n        const log = { type: 'resolve', ref: ref.display, to, path: state.path };\n        state.mainstay.debug.push(log);\n    }\n\n    value(state, by, from, to, name) {\n\n        if (!state.mainstay.debug ||\n            deepEqual(from, to)) {\n\n            return;\n        }\n\n        const log = { type: 'value', by, from, to, path: state.path };\n        if (name) {\n            log.name = name;\n        }\n\n        state.mainstay.debug.push(log);\n    }\n\n    _record(schema, each) {\n\n        const log = this._sources.get(schema);\n        if (log) {\n            each(log);\n            return;\n        }\n\n        const sources = this._combos.get(schema);\n        for (const source of sources) {\n            this._record(source, each);\n        }\n    }\n\n    _scan(schema, _path) {\n\n        const path = _path || [];\n\n        let log = this._sources.get(schema);\n        if (!log) {\n            log = {\n                paths: new Set(),\n                entry: false,\n                rule: {},\n                valid: new Set(),\n                invalid: new Set()\n            };\n\n            this._sources.set(schema, log);\n        }\n\n        if (path.length) {\n            log.paths.add(path);\n        }\n\n        const each = (sub, source) => {\n\n            const subId = internals.id(sub, source);\n            this._scan(sub, path.concat(subId));\n        };\n\n        schema.$_modify({ each, ref: false });\n    }\n\n    _combine(merged, sources) {\n\n        this._combos.set(merged, sources);\n    }\n};\n\n\ninternals.message = function (item) {\n\n    const path = item.paths ? Errors.path(item.paths[0]) + (item.rule ? ':' : '') : '';\n    return `${path}${item.rule || ''} (${item.status})`;\n};\n\n\ninternals.id = function (schema, { source, name, path, key }) {\n\n    if (schema._flags.id) {\n        return schema._flags.id;\n    }\n\n    if (key) {\n        return key;\n    }\n\n    name = `@${name}`;\n\n    if (source === 'terms') {\n        return [name, path[Math.min(path.length - 1, 1)]];\n    }\n\n    return name;\n};\n\n\ninternals.sub = function (paths, skipped) {\n\n    for (const path of paths) {\n        for (const skip of skipped) {\n            if (deepEqual(path.slice(0, skip.length), skip)) {\n                return true;\n            }\n        }\n    }\n\n    return false;\n};\n\n\ninternals.debug = function (state, event) {\n\n    if (state.mainstay.debug) {\n        event.path = state.debug ? [...state.path, state.debug] : state.path;\n        state.mainstay.debug.push(event);\n    }\n};\n"
  },
  {
    "path": "lib/types/alternatives.js",
    "content": "'use strict';\n\nconst { assert, merge } = require('@hapi/hoek');\n\nconst Any = require('./any');\nconst Common = require('../common');\nconst Compile = require('../compile');\nconst Errors = require('../errors');\nconst Ref = require('../ref');\n\n\nconst internals = {};\n\n\nmodule.exports = Any.extend({\n\n    type: 'alternatives',\n\n    flags: {\n\n        match: { default: 'any' }                 // 'any', 'one', 'all'\n    },\n\n    terms: {\n\n        matches: { init: [], register: Ref.toSibling }\n    },\n\n    args(schema, ...schemas) {\n\n        if (schemas.length === 1) {\n            if (Array.isArray(schemas[0])) {\n                return schema.try(...schemas[0]);\n            }\n        }\n\n        return schema.try(...schemas);\n    },\n\n    validate(value, helpers) {\n\n        const { schema, error, state, prefs } = helpers;\n\n        // Match all or one\n\n        if (schema._flags.match) {\n            const matched = [];\n            const failed = [];\n\n            for (let i = 0; i < schema.$_terms.matches.length; ++i) {\n                const item = schema.$_terms.matches[i];\n                const localState = state.nest(item.schema, `match.${i}`);\n                localState.snapshot();\n\n                const result = item.schema.$_validate(value, localState, prefs);\n                if (!result.errors) {\n                    matched.push(result.value);\n                    localState.commit();\n                }\n                else {\n                    failed.push(result.errors);\n                    localState.restore();\n                }\n            }\n\n            if (matched.length === 0) {\n                const context = {\n                    details: failed.map((f) => Errors.details(f, { override: false }))\n                };\n\n                return { errors: error('alternatives.any', context) };\n            }\n\n            // Match one\n\n            if (schema._flags.match === 'one') {\n                return matched.length === 1 ? { value: matched[0] } : { errors: error('alternatives.one') };\n            }\n\n            // Match all\n\n            if (matched.length !== schema.$_terms.matches.length) {\n                const context = {\n                    details: failed.map((f) => Errors.details(f, { override: false }))\n                };\n\n                return { errors: error('alternatives.all', context) };\n            }\n\n            const isAnyObj = (alternative) => {\n\n                return alternative.$_terms.matches.some((v) => {\n\n                    return v.schema.type === 'object' ||\n                        (v.schema.type === 'alternatives' && isAnyObj(v.schema));\n                });\n            };\n\n            return isAnyObj(schema) ? { value: matched.reduce((acc, v) => merge(acc, v, { mergeArrays: false })) } : { value: matched[matched.length - 1] };\n        }\n\n        // Match any\n\n        const errors = [];\n        for (let i = 0; i < schema.$_terms.matches.length; ++i) {\n            const item = schema.$_terms.matches[i];\n\n            // Try\n\n            if (item.schema) {\n                const localState = state.nest(item.schema, `match.${i}`);\n                localState.snapshot();\n\n                const result = item.schema.$_validate(value, localState, prefs);\n                if (!result.errors) {\n                    localState.commit();\n                    return result;\n                }\n\n                localState.restore();\n                errors.push({ schema: item.schema, reports: result.errors });\n                continue;\n            }\n\n            // Conditional\n\n            const input = item.ref ? item.ref.resolve(value, state, prefs) : value;\n            const tests = item.is ? [item] : item.switch;\n\n            for (let j = 0; j < tests.length; ++j) {\n                const test = tests[j];\n                const { is, then, otherwise } = test;\n\n                const id = `match.${i}${item.switch ? '.' + j : ''}`;\n                if (!is.$_match(input, state.nest(is, `${id}.is`), prefs)) {\n                    if (otherwise) {\n                        return otherwise.$_validate(value, state.nest(otherwise, `${id}.otherwise`), prefs);\n                    }\n                }\n                else if (then) {\n                    return then.$_validate(value, state.nest(then, `${id}.then`), prefs);\n                }\n            }\n        }\n\n        return internals.errors(errors, helpers);\n    },\n\n    rules: {\n\n        conditional: {\n            method(condition, options) {\n\n                assert(!this._flags._endedSwitch, 'Unreachable condition');\n                assert(!this._flags.match, 'Cannot combine match mode', this._flags.match, 'with conditional rule');\n                assert(options.break === undefined, 'Cannot use break option with alternatives conditional');\n\n                const obj = this.clone();\n\n                const match = Compile.when(obj, condition, options);\n                const conditions = match.is ? [match] : match.switch;\n                for (const item of conditions) {\n                    if (item.then &&\n                        item.otherwise) {\n\n                        obj.$_setFlag('_endedSwitch', true, { clone: false });\n                        break;\n                    }\n                }\n\n                obj.$_terms.matches.push(match);\n                return obj.$_mutateRebuild();\n            }\n        },\n\n        match: {\n            method(mode) {\n\n                assert(['any', 'one', 'all'].includes(mode), 'Invalid alternatives match mode', mode);\n\n                if (mode !== 'any') {\n                    for (const match of this.$_terms.matches) {\n                        assert(match.schema, 'Cannot combine match mode', mode, 'with conditional rules');\n                    }\n                }\n\n                return this.$_setFlag('match', mode);\n            }\n        },\n\n        try: {\n            method(...schemas) {\n\n                assert(schemas.length, 'Missing alternative schemas');\n                Common.verifyFlat(schemas, 'try');\n\n                assert(!this._flags._endedSwitch, 'Unreachable condition');\n\n                const obj = this.clone();\n                for (const schema of schemas) {\n                    obj.$_terms.matches.push({ schema: obj.$_compile(schema) });\n                }\n\n                return obj.$_mutateRebuild();\n            }\n        }\n    },\n\n    overrides: {\n\n        label(name) {\n\n            const obj = this.$_parent('label', name);\n            const each = (item, source) => {\n\n                return source.path[0] !== 'is' && typeof item._flags.label !== 'string' ? item.label(name) : undefined;\n            };\n\n            return obj.$_modify({ each, ref: false });\n        },\n\n        isAsync() {\n\n            if (this.$_terms.externals?.length) {\n                return true;\n            }\n\n            for (const match of this.$_terms.matches) {\n\n                if (match.schema?.isAsync()) {\n                    return true;\n                }\n\n                if (match.then?.isAsync()) {\n                    return true;\n                }\n\n                if (match.otherwise?.isAsync()) {\n                    return true;\n                }\n            }\n\n            return false;\n        }\n    },\n\n    rebuild(schema) {\n\n        // Flag when an alternative type is an array\n\n        const each = (item) => {\n\n            if (Common.isSchema(item) &&\n                item.type === 'array') {\n\n                schema.$_setFlag('_arrayItems', true, { clone: false });\n            }\n        };\n\n        schema.$_modify({ each });\n    },\n\n    manifest: {\n\n        build(obj, desc) {\n\n            if (desc.matches) {\n                for (const match of desc.matches) {\n                    const { schema, ref, is, not, then, otherwise } = match;\n                    if (schema) {\n                        obj = obj.try(schema);\n                    }\n                    else if (ref) {\n                        obj = obj.conditional(ref, { is, then, not, otherwise, switch: match.switch });\n                    }\n                    else {\n                        obj = obj.conditional(is, { then, otherwise });\n                    }\n                }\n            }\n\n            return obj;\n        }\n    },\n\n    messages: {\n        'alternatives.all': '{{#label}} does not match all of the required types',\n        'alternatives.any': '{{#label}} does not match any of the allowed types',\n        'alternatives.match': '{{#label}} does not match any of the allowed types',\n        'alternatives.one': '{{#label}} matches more than one allowed type',\n        'alternatives.types': '{{#label}} must be one of {{#types}}'\n    }\n});\n\n\n// Helpers\n\ninternals.errors = function (failures, { error, state }) {\n\n    // Nothing matched due to type criteria rules\n\n    if (!failures.length) {\n        return { errors: error('alternatives.any') };\n    }\n\n    // Single error\n\n    if (failures.length === 1) {\n        return { errors: failures[0].reports };\n    }\n\n    // Analyze reasons\n\n    const valids = new Set();\n    const complex = [];\n\n    for (const { reports, schema } of failures) {\n\n        // Multiple errors (!abortEarly)\n\n        if (reports.length > 1) {\n            return internals.unmatched(failures, error);\n        }\n\n        // Custom error\n\n        const report = reports[0];\n        if (report instanceof Errors.Report === false) {\n            return internals.unmatched(failures, error);\n        }\n\n        // Internal object or array error\n\n        if (report.state.path.length !== state.path.length) {\n            complex.push({ type: schema.type, report });\n            continue;\n        }\n\n        // Valids\n\n        if (report.code === 'any.only') {\n            for (const valid of report.local.valids) {\n                valids.add(valid);\n            }\n\n            continue;\n        }\n\n        // Base type\n\n        const [type, code] = report.code.split('.');\n        if (code !== 'base') {\n            complex.push({ type: schema.type, report });\n        }\n        else if (report.code === 'object.base') {\n            valids.add(report.local.type);\n        }\n        else {\n            valids.add(type);\n        }\n    }\n\n    // All errors are base types or valids\n\n    if (!complex.length) {\n        return { errors: error('alternatives.types', { types: [...valids] }) };\n    }\n\n    // Single complex error\n\n    if (complex.length === 1) {\n        return { errors: complex[0].report };\n    }\n\n    return internals.unmatched(failures, error);\n};\n\n\ninternals.unmatched = function (failures, error) {\n\n    const errors = [];\n    for (const failure of failures) {\n        errors.push(...failure.reports);\n    }\n\n    return { errors: error('alternatives.match', Errors.details(errors, { override: false })) };\n};\n"
  },
  {
    "path": "lib/types/any.js",
    "content": "'use strict';\n\nconst { assert } = require('@hapi/hoek');\n\nconst Base = require('../base');\nconst Common = require('../common');\nconst Messages = require('../messages');\n\n\nconst internals = {};\n\n\nmodule.exports = Base.extend({\n\n    type: 'any',\n\n    flags: {\n\n        only: { default: false }\n    },\n\n    terms: {\n\n        alterations: { init: null },\n        examples: { init: null },\n        externals: { init: null },\n        metas: { init: [] },\n        notes: { init: [] },\n        shared: { init: null },\n        tags: { init: [] },\n        whens: { init: null }\n    },\n\n    rules: {\n\n        custom: {\n            method(method, description) {\n\n                assert(typeof method === 'function', 'Method must be a function');\n                assert(description === undefined || description && typeof description === 'string', 'Description must be a non-empty string');\n\n                return this.$_addRule({ name: 'custom', args: { method, description } });\n            },\n            validate(value, helpers, { method }) {\n\n                try {\n                    return method(value, helpers);\n                }\n                catch (err) {\n                    return helpers.error('any.custom', { error: err });\n                }\n            },\n            args: ['method', 'description'],\n            multi: true\n        },\n\n        messages: {\n            method(messages) {\n\n                return this.prefs({ messages });\n            }\n        },\n\n        shared: {\n            method(schema) {\n\n                assert(Common.isSchema(schema) && schema._flags.id, 'Schema must be a schema with an id');\n\n                const obj = this.clone();\n                obj.$_terms.shared = obj.$_terms.shared || [];\n                obj.$_terms.shared.push(schema);\n                obj.$_mutateRegister(schema);\n                return obj;\n            }\n        },\n\n        warning: {\n            method(code, local) {\n\n                assert(code && typeof code === 'string', 'Invalid warning code');\n\n                return this.$_addRule({ name: 'warning', args: { code, local }, warn: true });\n            },\n            validate(value, helpers, { code, local }) {\n\n                return helpers.error(code, local);\n            },\n            args: ['code', 'local'],\n            multi: true\n        }\n    },\n\n    modifiers: {\n\n        keep(rule, enabled = true) {\n\n            rule.keep = enabled;\n        },\n\n        message(rule, message) {\n\n            rule.message = Messages.compile(message);\n        },\n\n        warn(rule, enabled = true) {\n\n            rule.warn = enabled;\n        }\n    },\n\n    manifest: {\n\n        build(obj, desc) {\n\n            for (const key in desc) {\n                const values = desc[key];\n\n                if (['examples', 'externals', 'metas', 'notes', 'tags'].includes(key)) {\n                    for (const value of values) {\n                        obj = obj[key.slice(0, -1)](value);\n                    }\n\n                    continue;\n                }\n\n                if (key === 'alterations') {\n                    const alter = {};\n                    for (const { target, adjuster } of values) {\n                        alter[target] = adjuster;\n                    }\n\n                    obj = obj.alter(alter);\n                    continue;\n                }\n\n                if (key === 'whens') {\n                    for (const value of values) {\n                        const { ref, is, not, then, otherwise, concat } = value;\n                        if (concat) {\n                            obj = obj.concat(concat);\n                        }\n                        else if (ref) {\n                            obj = obj.when(ref, { is, not, then, otherwise, switch: value.switch, break: value.break });\n                        }\n                        else {\n                            obj = obj.when(is, { then, otherwise, break: value.break });\n                        }\n                    }\n\n                    continue;\n                }\n\n                if (key === 'shared') {\n                    for (const value of values) {\n                        obj = obj.shared(value);\n                    }\n                }\n            }\n\n            return obj;\n        }\n    },\n\n    messages: {\n        'any.custom': '{{#label}} failed custom validation because {{#error.message}}',\n        'any.default': '{{#label}} threw an error when running default method',\n        'any.failover': '{{#label}} threw an error when running failover method',\n        'any.invalid': '{{#label}} contains an invalid value',\n        'any.only': '{{#label}} must be {if(#valids.length == 1, \"\", \"one of \")}{{#valids}}',\n        'any.ref': '{{#label}} {{#arg}} references {{:#ref}} which {{#reason}}',\n        'any.required': '{{#label}} is required',\n        'any.unknown': '{{#label}} is not allowed'\n    }\n});\n"
  },
  {
    "path": "lib/types/array.js",
    "content": "'use strict';\n\nconst { assert, deepEqual, reach } = require('@hapi/hoek');\n\nconst Any = require('./any');\nconst Common = require('../common');\nconst Compile = require('../compile');\n\n\nconst internals = {};\n\n\nmodule.exports = Any.extend({\n\n    type: 'array',\n\n    flags: {\n\n        single: { default: false },\n        sparse: { default: false }\n    },\n\n    terms: {\n\n        items: { init: [], manifest: 'schema' },\n        ordered: { init: [], manifest: 'schema' },\n\n        _exclusions: { init: [] },\n        _inclusions: { init: [] },\n        _requireds: { init: [] }\n    },\n\n    coerce: {\n        from: 'object',\n        method(value, { schema, state, prefs }) {\n\n            if (!Array.isArray(value)) {\n                return;\n            }\n\n            const sort = schema.$_getRule('sort');\n            if (!sort) {\n                return;\n            }\n\n            return internals.sort(schema, value, sort.args.options, state, prefs);\n        }\n    },\n\n    validate(value, { schema, error }) {\n\n        if (!Array.isArray(value)) {\n            if (schema._flags.single) {\n                const single = [value];\n                single[Common.symbols.arraySingle] = true;\n                return { value: single };\n            }\n\n            return { errors: error('array.base') };\n        }\n\n        if (!schema.$_getRule('items') &&\n            !schema.$_terms.externals) {\n\n            return;\n        }\n\n        return { value: value.slice() };        // Clone the array so that we don't modify the original\n    },\n\n    rules: {\n\n        has: {\n            method(schema) {\n\n                schema = this.$_compile(schema, { appendPath: true });\n                const obj = this.$_addRule({ name: 'has', args: { schema } });\n                obj.$_mutateRegister(schema);\n                return obj;\n            },\n            validate(value, { state, prefs, error }, { schema: has }) {\n\n                const ancestors = [value, ...state.ancestors];\n                for (let i = 0; i < value.length; ++i) {\n                    const localState = state.localize([...state.path, i], ancestors, has);\n                    if (has.$_match(value[i], localState, prefs)) {\n                        return value;\n                    }\n                }\n\n                const patternLabel = has._flags.label;\n                if (patternLabel) {\n                    return error('array.hasKnown', { patternLabel });\n                }\n\n                return error('array.hasUnknown', null);\n            },\n            multi: true\n        },\n\n        items: {\n            method(...schemas) {\n\n                Common.verifyFlat(schemas, 'items');\n\n                const obj = this.$_addRule('items');\n\n                for (let i = 0; i < schemas.length; ++i) {\n                    const type = Common.tryWithPath(() => this.$_compile(schemas[i]), i, { append: true });\n                    obj.$_terms.items.push(type);\n                }\n\n                return obj.$_mutateRebuild();\n            },\n            validate(value, { schema, error, state, prefs, errorsArray }) {\n\n                const requireds = schema.$_terms._requireds.slice();\n                const ordereds = schema.$_terms.ordered.slice();\n                const inclusions = [...schema.$_terms._inclusions, ...requireds];\n\n                const wasArray = !value[Common.symbols.arraySingle];\n                delete value[Common.symbols.arraySingle];\n\n                const errors = errorsArray();\n\n                let il = value.length;\n                for (let i = 0; i < il; ++i) {\n                    const item = value[i];\n\n                    let errored = false;\n                    let isValid = false;\n\n                    const key = wasArray ? i : new Number(i);       // eslint-disable-line no-new-wrappers\n                    const path = [...state.path, key];\n\n                    // Sparse\n\n                    if (!schema._flags.sparse &&\n                        item === undefined) {\n\n                        errors.push(error('array.sparse', { key, path, pos: i, value: undefined }, state.localize(path)));\n                        if (prefs.abortEarly) {\n                            return errors;\n                        }\n\n                        ordereds.shift();\n                        continue;\n                    }\n\n                    // Exclusions\n\n                    const ancestors = [value, ...state.ancestors];\n\n                    for (const exclusion of schema.$_terms._exclusions) {\n                        if (!exclusion.$_match(item, state.localize(path, ancestors, exclusion), prefs, { presence: 'ignore' })) {\n                            continue;\n                        }\n\n                        errors.push(error('array.excludes', { pos: i, value: item }, state.localize(path)));\n                        if (prefs.abortEarly) {\n                            return errors;\n                        }\n\n                        errored = true;\n                        ordereds.shift();\n                        break;\n                    }\n\n                    if (errored) {\n                        continue;\n                    }\n\n                    // Ordered\n\n                    if (schema.$_terms.ordered.length) {\n                        if (ordereds.length) {\n                            const ordered = ordereds.shift();\n                            const res = ordered.$_validate(item, state.localize(path, ancestors, ordered), prefs);\n                            if (!res.errors) {\n                                if (ordered._flags.result === 'strip') {\n                                    internals.fastSplice(value, i);\n                                    --i;\n                                    --il;\n                                }\n                                else if (!schema._flags.sparse && res.value === undefined) {\n                                    errors.push(error('array.sparse', { key, path, pos: i, value: undefined }, state.localize(path)));\n                                    if (prefs.abortEarly) {\n                                        return errors;\n                                    }\n\n                                    continue;\n                                }\n                                else {\n                                    value[i] = res.value;\n                                }\n                            }\n                            else {\n                                errors.push(...res.errors);\n                                if (prefs.abortEarly) {\n                                    return errors;\n                                }\n                            }\n\n                            continue;\n                        }\n                        else if (!schema.$_terms.items.length) {\n                            errors.push(error('array.orderedLength', { pos: i, limit: schema.$_terms.ordered.length }));\n                            if (prefs.abortEarly) {\n                                return errors;\n                            }\n\n                            break;      // No reason to continue since there are no other rules to validate other than array.orderedLength\n                        }\n                    }\n\n                    // Requireds\n\n                    const requiredChecks = [];\n                    let jl = requireds.length;\n                    for (let j = 0; j < jl; ++j) {\n                        const localState = state.localize(path, ancestors, requireds[j]);\n                        localState.snapshot();\n\n                        const res = requireds[j].$_validate(item, localState, prefs);\n                        requiredChecks[j] = res;\n\n                        if (!res.errors) {\n                            localState.commit();\n                            value[i] = res.value;\n                            isValid = true;\n                            internals.fastSplice(requireds, j);\n                            --j;\n                            --jl;\n\n                            if (!schema._flags.sparse &&\n                                res.value === undefined) {\n\n                                errors.push(error('array.sparse', { key, path, pos: i, value: undefined }, state.localize(path)));\n                                if (prefs.abortEarly) {\n                                    return errors;\n                                }\n                            }\n\n                            break;\n                        }\n\n                        localState.restore();\n                    }\n\n                    if (isValid) {\n                        continue;\n                    }\n\n                    // Inclusions\n\n                    const stripUnknown = prefs.stripUnknown && !!prefs.stripUnknown.arrays || false;\n\n                    jl = inclusions.length;\n                    for (const inclusion of inclusions) {\n\n                        // Avoid re-running requireds that already didn't match in the previous loop\n\n                        let res;\n                        const previousCheck = requireds.indexOf(inclusion);\n                        if (previousCheck !== -1) {\n                            res = requiredChecks[previousCheck];\n                        }\n                        else {\n                            const localState = state.localize(path, ancestors, inclusion);\n                            localState.snapshot();\n\n                            res = inclusion.$_validate(item, localState, prefs);\n                            if (!res.errors) {\n                                localState.commit();\n                                if (inclusion._flags.result === 'strip') {\n                                    internals.fastSplice(value, i);\n                                    --i;\n                                    --il;\n                                }\n                                else if (!schema._flags.sparse &&\n                                    res.value === undefined) {\n\n                                    errors.push(error('array.sparse', { key, path, pos: i, value: undefined }, state.localize(path)));\n                                    errored = true;\n                                }\n                                else {\n                                    value[i] = res.value;\n                                }\n\n                                isValid = true;\n                                break;\n                            }\n\n                            localState.restore();\n                        }\n\n                        // Return the actual error if only one inclusion defined\n\n                        if (jl === 1) {\n                            if (stripUnknown) {\n                                internals.fastSplice(value, i);\n                                --i;\n                                --il;\n                                isValid = true;\n                                break;\n                            }\n\n                            errors.push(...res.errors);\n                            if (prefs.abortEarly) {\n                                return errors;\n                            }\n\n                            errored = true;\n                            break;\n                        }\n                    }\n\n                    if (errored) {\n                        continue;\n                    }\n\n                    if ((schema.$_terms._inclusions.length || schema.$_terms._requireds.length) &&\n                        !isValid) {\n\n                        if (stripUnknown) {\n                            internals.fastSplice(value, i);\n                            --i;\n                            --il;\n                            continue;\n                        }\n\n                        errors.push(error('array.includes', { pos: i, value: item }, state.localize(path)));\n                        if (prefs.abortEarly) {\n                            return errors;\n                        }\n                    }\n                }\n\n                if (requireds.length) {\n                    internals.fillMissedErrors(schema, errors, requireds, value, state, prefs);\n                }\n\n                if (ordereds.length) {\n                    internals.fillOrderedErrors(schema, errors, ordereds, value, state, prefs);\n\n                    if (!errors.length) {\n                        internals.fillDefault(ordereds, value, state, prefs);\n                    }\n                }\n\n                return errors.length ? errors : value;\n            },\n\n            priority: true,\n            manifest: false\n        },\n\n        length: {\n            method(limit) {\n\n                return this.$_addRule({ name: 'length', args: { limit }, operator: '=' });\n            },\n            validate(value, helpers, { limit }, { name, operator, args }) {\n\n                if (Common.compare(value.length, limit, operator)) {\n                    return value;\n                }\n\n                return helpers.error('array.' + name, { limit: args.limit, value });\n            },\n            args: [\n                {\n                    name: 'limit',\n                    ref: true,\n                    assert: Common.limit,\n                    message: 'must be a positive integer'\n                }\n            ]\n        },\n\n        max: {\n            method(limit) {\n\n                return this.$_addRule({ name: 'max', method: 'length', args: { limit }, operator: '<=' });\n            }\n        },\n\n        min: {\n            method(limit) {\n\n                return this.$_addRule({ name: 'min', method: 'length', args: { limit }, operator: '>=' });\n            }\n        },\n\n        ordered: {\n            method(...schemas) {\n\n                Common.verifyFlat(schemas, 'ordered');\n\n                const obj = this.$_addRule('items');\n\n                for (let i = 0; i < schemas.length; ++i) {\n                    const type = Common.tryWithPath(() => this.$_compile(schemas[i]), i, { append: true });\n                    internals.validateSingle(type, obj);\n\n                    obj.$_mutateRegister(type);\n                    obj.$_terms.ordered.push(type);\n                }\n\n                return obj.$_mutateRebuild();\n            }\n        },\n\n        single: {\n            method(enabled) {\n\n                const value = enabled === undefined ? true : !!enabled;\n                assert(!value || !this._flags._arrayItems, 'Cannot specify single rule when array has array items');\n\n                return this.$_setFlag('single', value);\n            }\n        },\n\n        sort: {\n            method(options = {}) {\n\n                Common.assertOptions(options, ['by', 'order']);\n\n                const settings = {\n                    order: options.order || 'ascending'\n                };\n\n                if (options.by) {\n                    settings.by = Compile.ref(options.by, { ancestor: 0 });\n                    assert(!settings.by.ancestor, 'Cannot sort by ancestor');\n                }\n\n                return this.$_addRule({ name: 'sort', args: { options: settings } });\n            },\n            validate(value, { error, state, prefs, schema }, { options }) {\n\n                const { value: sorted, errors } = internals.sort(schema, value, options, state, prefs);\n                if (errors) {\n                    return errors;\n                }\n\n                for (let i = 0; i < value.length; ++i) {\n                    if (value[i] !== sorted[i]) {\n                        return error('array.sort', { order: options.order, by: options.by ? options.by.key : 'value' });\n                    }\n                }\n\n                return value;\n            },\n            convert: true\n        },\n\n        sparse: {\n            method(enabled) {\n\n                const value = enabled === undefined ? true : !!enabled;\n\n                if (this._flags.sparse === value) {\n                    return this;\n                }\n\n                const obj = value ? this.clone() : this.$_addRule('items');\n                return obj.$_setFlag('sparse', value, { clone: false });\n            }\n        },\n\n        unique: {\n            method(comparator, options = {}) {\n\n                assert(!comparator || typeof comparator === 'function' || typeof comparator === 'string', 'comparator must be a function or a string');\n                Common.assertOptions(options, ['ignoreUndefined', 'separator']);\n\n                const rule = { name: 'unique', args: { options, comparator } };\n\n                if (comparator) {\n                    if (typeof comparator === 'string') {\n                        const separator = Common.default(options.separator, '.');\n                        rule.path = separator ? comparator.split(separator) : [comparator];\n                    }\n                    else {\n                        rule.comparator = comparator;\n                    }\n                }\n\n                return this.$_addRule(rule);\n            },\n            validate(value, { state, error, schema }, { comparator: raw, options }, { comparator, path }) {\n\n                const found = {\n                    string: Object.create(null),\n                    number: Object.create(null),\n                    undefined: Object.create(null),\n                    boolean: Object.create(null),\n                    bigint: Object.create(null),\n                    object: new Map(),\n                    function: new Map(),\n                    custom: new Map()\n                };\n\n                const compare = comparator || deepEqual;\n                const ignoreUndefined = options.ignoreUndefined;\n\n                for (let i = 0; i < value.length; ++i) {\n                    const item = path ? reach(value[i], path) : value[i];\n                    const records = comparator ? found.custom : found[typeof item];\n                    assert(records, 'Failed to find unique map container for type', typeof item);\n\n                    if (records instanceof Map) {\n                        const entries = records.entries();\n                        let current;\n                        while (!(current = entries.next()).done) {\n                            if (compare(current.value[0], item)) {\n                                const localState = state.localize([...state.path, i], [value, ...state.ancestors]);\n                                const context = {\n                                    pos: i,\n                                    value: value[i],\n                                    dupePos: current.value[1],\n                                    dupeValue: value[current.value[1]]\n                                };\n\n                                if (path) {\n                                    context.path = raw;\n                                }\n\n                                return error('array.unique', context, localState);\n                            }\n                        }\n\n                        records.set(item, i);\n                    }\n                    else {\n                        if ((!ignoreUndefined || item !== undefined) &&\n                            records[item] !== undefined) {\n\n                            const context = {\n                                pos: i,\n                                value: value[i],\n                                dupePos: records[item],\n                                dupeValue: value[records[item]]\n                            };\n\n                            if (path) {\n                                context.path = raw;\n                            }\n\n                            const localState = state.localize([...state.path, i], [value, ...state.ancestors]);\n                            return error('array.unique', context, localState);\n                        }\n\n                        records[item] = i;\n                    }\n                }\n\n                return value;\n            },\n            args: ['comparator', 'options'],\n            multi: true\n        }\n    },\n\n    overrides: {\n\n        isAsync() {\n\n            if (this.$_terms.externals?.length) {\n                return true;\n            }\n\n            for (const item of this.$_terms.items) {\n                if (item.isAsync()) {\n                    return true;\n                }\n            }\n\n            for (const item of this.$_terms.ordered) {\n                if (item.isAsync()) {\n                    return true;\n                }\n            }\n\n            return false;\n        }\n    },\n\n    cast: {\n        set: {\n            from: Array.isArray,\n            to(value, helpers) {\n\n                return new Set(value);\n            }\n        }\n    },\n\n    rebuild(schema) {\n\n        schema.$_terms._inclusions = [];\n        schema.$_terms._exclusions = [];\n        schema.$_terms._requireds = [];\n\n        for (const type of schema.$_terms.items) {\n            internals.validateSingle(type, schema);\n\n            if (type._flags.presence === 'required') {\n                schema.$_terms._requireds.push(type);\n            }\n            else if (type._flags.presence === 'forbidden') {\n                schema.$_terms._exclusions.push(type);\n            }\n            else {\n                schema.$_terms._inclusions.push(type);\n            }\n        }\n\n        for (const type of schema.$_terms.ordered) {\n            internals.validateSingle(type, schema);\n        }\n    },\n\n    manifest: {\n\n        build(obj, desc) {\n\n            if (desc.items) {\n                obj = obj.items(...desc.items);\n            }\n\n            if (desc.ordered) {\n                obj = obj.ordered(...desc.ordered);\n            }\n\n            return obj;\n        }\n    },\n\n    messages: {\n        'array.base': '{{#label}} must be an array',\n        'array.excludes': '{{#label}} contains an excluded value',\n        'array.hasKnown': '{{#label}} does not contain at least one required match for type {:#patternLabel}',\n        'array.hasUnknown': '{{#label}} does not contain at least one required match',\n        'array.includes': '{{#label}} does not match any of the allowed types',\n        'array.includesRequiredBoth': '{{#label}} does not contain {{#knownMisses}} and {{#unknownMisses}} other required value(s)',\n        'array.includesRequiredKnowns': '{{#label}} does not contain {{#knownMisses}}',\n        'array.includesRequiredUnknowns': '{{#label}} does not contain {{#unknownMisses}} required value(s)',\n        'array.length': '{{#label}} must contain {{#limit}} items',\n        'array.max': '{{#label}} must contain less than or equal to {{#limit}} items',\n        'array.min': '{{#label}} must contain at least {{#limit}} items',\n        'array.orderedLength': '{{#label}} must contain at most {{#limit}} items',\n        'array.sort': '{{#label}} must be sorted in {#order} order by {{#by}}',\n        'array.sort.mismatching': '{{#label}} cannot be sorted due to mismatching types',\n        'array.sort.unsupported': '{{#label}} cannot be sorted due to unsupported type {#type}',\n        'array.sparse': '{{#label}} must not be a sparse array item',\n        'array.unique': '{{#label}} contains a duplicate value'\n    }\n});\n\n\n// Helpers\n\ninternals.fillMissedErrors = function (schema, errors, requireds, value, state, prefs) {\n\n    const knownMisses = [];\n    let unknownMisses = 0;\n    for (const required of requireds) {\n        const label = required._flags.label;\n        if (label) {\n            knownMisses.push(label);\n        }\n        else {\n            ++unknownMisses;\n        }\n    }\n\n    if (knownMisses.length) {\n        if (unknownMisses) {\n            errors.push(schema.$_createError('array.includesRequiredBoth', value, { knownMisses, unknownMisses }, state, prefs));\n        }\n        else {\n            errors.push(schema.$_createError('array.includesRequiredKnowns', value, { knownMisses }, state, prefs));\n        }\n    }\n    else {\n        errors.push(schema.$_createError('array.includesRequiredUnknowns', value, { unknownMisses }, state, prefs));\n    }\n};\n\n\ninternals.fillOrderedErrors = function (schema, errors, ordereds, value, state, prefs) {\n\n    const requiredOrdereds = [];\n\n    for (const ordered of ordereds) {\n        if (ordered._flags.presence === 'required') {\n            requiredOrdereds.push(ordered);\n        }\n    }\n\n    if (requiredOrdereds.length) {\n        internals.fillMissedErrors(schema, errors, requiredOrdereds, value, state, prefs);\n    }\n};\n\n\ninternals.fillDefault = function (ordereds, value, state, prefs) {\n\n    const overrides = [];\n    let trailingUndefined = true;\n\n    for (let i = ordereds.length - 1; i >= 0; --i) {\n        const ordered = ordereds[i];\n        const ancestors = [value, ...state.ancestors];\n        const override = ordered.$_validate(undefined, state.localize(state.path, ancestors, ordered), prefs).value;\n\n        if (trailingUndefined) {\n            if (override === undefined) {\n                continue;\n            }\n\n            trailingUndefined = false;\n        }\n\n        overrides.unshift(override);\n    }\n\n    if (overrides.length) {\n        value.push(...overrides);\n    }\n};\n\n\ninternals.fastSplice = function (arr, i) {\n\n    let pos = i;\n    while (pos < arr.length) {\n        arr[pos++] = arr[pos];\n    }\n\n    --arr.length;\n};\n\n\ninternals.validateSingle = function (type, obj) {\n\n    if (type.type === 'array' ||\n        type._flags._arrayItems) {\n\n        assert(!obj._flags.single, 'Cannot specify array item with single rule enabled');\n        obj.$_setFlag('_arrayItems', true, { clone: false });\n    }\n};\n\n\ninternals.sort = function (schema, value, settings, state, prefs) {\n\n    const order = settings.order === 'ascending' ? 1 : -1;\n    const aFirst = -1 * order;\n    const bFirst = order;\n\n    const sort = (a, b) => {\n\n        let compare = internals.compare(a, b, aFirst, bFirst);\n        if (compare !== null) {\n            return compare;\n        }\n\n        if (settings.by) {\n            a = settings.by.resolve(a, state, prefs);\n            b = settings.by.resolve(b, state, prefs);\n        }\n\n        compare = internals.compare(a, b, aFirst, bFirst);\n        if (compare !== null) {\n            return compare;\n        }\n\n        const type = typeof a;\n        if (type !== typeof b) {\n            throw schema.$_createError('array.sort.mismatching', value, null, state, prefs);\n        }\n\n        if (type !== 'number' &&\n            type !== 'string') {\n\n            throw schema.$_createError('array.sort.unsupported', value, { type }, state, prefs);\n        }\n\n        if (type === 'number') {\n            return (a - b) * order;\n        }\n\n        return a < b ? aFirst : bFirst;\n    };\n\n    try {\n        return { value: value.slice().sort(sort) };\n    }\n    catch (err) {\n        return { errors: err };\n    }\n};\n\n\ninternals.compare = function (a, b, aFirst, bFirst) {\n\n    if (a === b) {\n        return 0;\n    }\n\n    if (a === undefined) {\n        return 1;           // Always last regardless of sort order\n    }\n\n    if (b === undefined) {\n        return -1;           // Always last regardless of sort order\n    }\n\n    if (a === null) {\n        return bFirst;\n    }\n\n    if (b === null) {\n        return aFirst;\n    }\n\n    return null;\n};\n"
  },
  {
    "path": "lib/types/binary.js",
    "content": "'use strict';\n\nconst { assert } = require('@hapi/hoek');\n\nconst Any = require('./any');\nconst Common = require('../common');\n\n\nconst internals = {};\n\n\nmodule.exports = Any.extend({\n\n    type: 'binary',\n\n    coerce: {\n        from: ['string', 'object'],\n        method(value, { schema }) {\n\n            if (typeof value === 'string' || (value !== null && value.type === 'Buffer')) {\n                try {\n                    return { value: Buffer.from(value, schema._flags.encoding) };\n                }\n                catch { }\n            }\n        }\n    },\n\n    validate(value, { error }) {\n\n        if (!Buffer.isBuffer(value)) {\n            return { value, errors: error('binary.base') };\n        }\n    },\n\n    rules: {\n        encoding: {\n            method(encoding) {\n\n                assert(Buffer.isEncoding(encoding), 'Invalid encoding:', encoding);\n\n                return this.$_setFlag('encoding', encoding);\n            }\n        },\n\n        length: {\n            method(limit) {\n\n                return this.$_addRule({ name: 'length', method: 'length', args: { limit }, operator: '=' });\n            },\n            validate(value, helpers, { limit }, { name, operator, args }) {\n\n                if (Common.compare(value.length, limit, operator)) {\n                    return value;\n                }\n\n                return helpers.error('binary.' + name, { limit: args.limit, value });\n            },\n            args: [\n                {\n                    name: 'limit',\n                    ref: true,\n                    assert: Common.limit,\n                    message: 'must be a positive integer'\n                }\n            ]\n        },\n\n        max: {\n            method(limit) {\n\n                return this.$_addRule({ name: 'max', method: 'length', args: { limit }, operator: '<=' });\n            }\n        },\n\n        min: {\n            method(limit) {\n\n                return this.$_addRule({ name: 'min', method: 'length', args: { limit }, operator: '>=' });\n            }\n        }\n    },\n\n    cast: {\n        string: {\n            from: (value) => Buffer.isBuffer(value),\n            to(value, helpers) {\n\n                return value.toString();\n            }\n        }\n    },\n\n    messages: {\n        'binary.base': '{{#label}} must be a buffer or a string',\n        'binary.length': '{{#label}} must be {{#limit}} bytes',\n        'binary.max': '{{#label}} must be less than or equal to {{#limit}} bytes',\n        'binary.min': '{{#label}} must be at least {{#limit}} bytes'\n    }\n});\n"
  },
  {
    "path": "lib/types/boolean.js",
    "content": "'use strict';\n\nconst { assert } = require('@hapi/hoek');\n\nconst Any = require('./any');\nconst Common = require('../common');\nconst Values = require('../values');\n\n\nconst internals = {};\n\n\ninternals.isBool = function (value) {\n\n    return typeof value === 'boolean';\n};\n\n\nmodule.exports = Any.extend({\n\n    type: 'boolean',\n\n    flags: {\n\n        sensitive: { default: false }\n    },\n\n    terms: {\n\n        falsy: {\n            init: null,\n            manifest: 'values'\n        },\n\n        truthy: {\n            init: null,\n            manifest: 'values'\n        }\n    },\n\n    coerce(value, { schema }) {\n\n        if (typeof value === 'boolean') {\n            return;\n        }\n\n        if (typeof value === 'string') {\n            const trimmedValue = value.trim();\n            const normalized = schema._flags.sensitive ? trimmedValue : trimmedValue.toLowerCase();\n            value = normalized === 'true' ? true : (normalized === 'false' ? false : value);\n        }\n\n        if (typeof value !== 'boolean') {\n            value = schema.$_terms.truthy && schema.$_terms.truthy.has(value, null, null, !schema._flags.sensitive) ||\n                (schema.$_terms.falsy && schema.$_terms.falsy.has(value, null, null, !schema._flags.sensitive) ? false : value);\n        }\n\n        return { value };\n    },\n\n    validate(value, { error }) {\n\n        if (typeof value !== 'boolean') {\n            return { value, errors: error('boolean.base') };\n        }\n    },\n\n    rules: {\n        truthy: {\n            method(...values) {\n\n                Common.verifyFlat(values, 'truthy');\n\n                const obj = this.clone();\n                obj.$_terms.truthy = obj.$_terms.truthy || new Values();\n\n                for (let i = 0; i < values.length; ++i) {\n                    const value = values[i];\n\n                    assert(value !== undefined, 'Cannot call truthy with undefined');\n                    obj.$_terms.truthy.add(value);\n                }\n\n                return obj;\n            }\n        },\n\n        falsy: {\n            method(...values) {\n\n                Common.verifyFlat(values, 'falsy');\n\n                const obj = this.clone();\n                obj.$_terms.falsy = obj.$_terms.falsy || new Values();\n\n                for (let i = 0; i < values.length; ++i) {\n                    const value = values[i];\n\n                    assert(value !== undefined, 'Cannot call falsy with undefined');\n                    obj.$_terms.falsy.add(value);\n                }\n\n                return obj;\n            }\n        },\n\n        sensitive: {\n            method(enabled = true) {\n\n                return this.$_setFlag('sensitive', enabled);\n            }\n        }\n    },\n\n    cast: {\n        number: {\n            from: internals.isBool,\n            to(value, helpers) {\n\n                return value ? 1 : 0;\n            }\n        },\n        string: {\n            from: internals.isBool,\n            to(value, helpers) {\n\n                return value ? 'true' : 'false';\n            }\n        }\n    },\n\n    manifest: {\n\n        build(obj, desc) {\n\n            if (desc.truthy) {\n                obj = obj.truthy(...desc.truthy);\n            }\n\n            if (desc.falsy) {\n                obj = obj.falsy(...desc.falsy);\n            }\n\n            return obj;\n        }\n    },\n\n    messages: {\n        'boolean.base': '{{#label}} must be a boolean'\n    }\n});\n"
  },
  {
    "path": "lib/types/date.js",
    "content": "'use strict';\n\nconst { assert } = require('@hapi/hoek');\n\nconst Any = require('./any');\nconst Common = require('../common');\nconst Template = require('../template');\n\n\nconst internals = {};\n\n\ninternals.isDate = function (value) {\n\n    return value instanceof Date;\n};\n\n\nmodule.exports = Any.extend({\n\n    type: 'date',\n\n    coerce: {\n        from: ['number', 'string'],\n        method(value, { schema }) {\n\n            return { value: internals.parse(value, schema._flags.format) || value };\n        }\n    },\n\n    validate(value, { schema, error, prefs }) {\n\n        if (value instanceof Date &&\n            !isNaN(value.getTime())) {\n\n            return;\n        }\n\n        const format = schema._flags.format;\n\n        if (!prefs.convert ||\n            !format ||\n            typeof value !== 'string') {\n\n            return { value, errors: error('date.base') };\n        }\n\n        return { value, errors: error('date.format', { format }) };\n    },\n\n    rules: {\n\n        compare: {\n            method: false,\n            validate(value, helpers, { date }, { name, operator, args }) {\n\n                const to = date === 'now' ? Date.now() : date.getTime();\n                if (Common.compare(value.getTime(), to, operator)) {\n                    return value;\n                }\n\n                return helpers.error('date.' + name, { limit: args.date, value });\n            },\n            args: [\n                {\n                    name: 'date',\n                    ref: true,\n                    normalize: (date) => {\n\n                        return date === 'now' ? date : internals.parse(date);\n                    },\n                    assert: (date) => date !== null,\n                    message: 'must have a valid date format'\n                }\n            ]\n        },\n\n        format: {\n            method(format) {\n\n                assert(['iso', 'javascript', 'unix'].includes(format), 'Unknown date format', format);\n\n                return this.$_setFlag('format', format);\n            }\n        },\n\n        greater: {\n            method(date) {\n\n                return this.$_addRule({ name: 'greater', method: 'compare', args: { date }, operator: '>' });\n            }\n        },\n\n        iso: {\n            method() {\n\n                return this.format('iso');\n            }\n        },\n\n        less: {\n            method(date) {\n\n                return this.$_addRule({ name: 'less', method: 'compare', args: { date }, operator: '<' });\n            }\n        },\n\n        max: {\n            method(date) {\n\n                return this.$_addRule({ name: 'max', method: 'compare', args: { date }, operator: '<=' });\n            }\n        },\n\n        min: {\n            method(date) {\n\n                return this.$_addRule({ name: 'min', method: 'compare', args: { date }, operator: '>=' });\n            }\n        },\n\n        timestamp: {\n            method(type = 'javascript') {\n\n                assert(['javascript', 'unix'].includes(type), '\"type\" must be one of \"javascript, unix\"');\n\n                return this.format(type);\n            }\n        }\n    },\n\n    cast: {\n        number: {\n            from: internals.isDate,\n            to(value, helpers) {\n\n                return value.getTime();\n            }\n        },\n        string: {\n            from: internals.isDate,\n            to(value, { prefs }) {\n\n                return Template.date(value, prefs);\n            }\n        }\n    },\n\n    messages: {\n        'date.base': '{{#label}} must be a valid date',\n        'date.format': '{{#label}} must be in {msg(\"date.format.\" + #format) || #format} format',\n        'date.greater': '{{#label}} must be greater than {{:#limit}}',\n        'date.less': '{{#label}} must be less than {{:#limit}}',\n        'date.max': '{{#label}} must be less than or equal to {{:#limit}}',\n        'date.min': '{{#label}} must be greater than or equal to {{:#limit}}',\n\n        // Messages used in date.format\n\n        'date.format.iso': 'ISO 8601 date',\n        'date.format.javascript': 'timestamp or number of milliseconds',\n        'date.format.unix': 'timestamp or number of seconds'\n    }\n});\n\n\n// Helpers\n\ninternals.parse = function (value, format) {\n\n    if (value instanceof Date) {\n        return value;\n    }\n\n    if (typeof value !== 'string' &&\n        (isNaN(value) || !isFinite(value))) {\n\n        return null;\n    }\n\n    if (/^\\s*$/.test(value)) {\n        return null;\n    }\n\n    // ISO\n\n    if (format === 'iso') {\n        if (!Common.isIsoDate(value)) {\n            return null;\n        }\n\n        return internals.date(value.toString());\n    }\n\n    // Normalize number string\n\n    const original = value;\n    if (typeof value === 'string' &&\n        /^[+-]?\\d+(\\.\\d+)?$/.test(value)) {\n\n        value = parseFloat(value);\n    }\n\n    // Timestamp\n\n    if (format) {\n        if (format === 'javascript') {\n            return internals.date(1 * value);        // Casting to number\n        }\n\n        if (format === 'unix') {\n            return internals.date(1000 * value);\n        }\n\n        if (typeof original === 'string') {\n            return null;\n        }\n    }\n\n    // Plain\n\n    return internals.date(value);\n};\n\n\ninternals.date = function (value) {\n\n    const date = new Date(value);\n    if (!isNaN(date.getTime())) {\n        return date;\n    }\n\n    return null;\n};\n"
  },
  {
    "path": "lib/types/function.js",
    "content": "'use strict';\n\nconst { assert } = require('@hapi/hoek');\n\nconst Keys = require('./keys');\n\n\nconst internals = {};\n\n\nmodule.exports = Keys.extend({\n\n    type: 'function',\n\n    properties: {\n        typeof: 'function'\n    },\n\n    rules: {\n        arity: {\n            method(n) {\n\n                assert(Number.isSafeInteger(n) && n >= 0, 'n must be a positive integer');\n\n                return this.$_addRule({ name: 'arity', args: { n } });\n            },\n            validate(value, helpers, { n }) {\n\n                if (value.length === n) {\n                    return value;\n                }\n\n                return helpers.error('function.arity', { n });\n            }\n        },\n\n        class: {\n            method() {\n\n                return this.$_addRule('class');\n            },\n            validate(value, helpers) {\n\n                if ((/^\\s*class\\s/).test(value.toString())) {\n                    return value;\n                }\n\n                return helpers.error('function.class', { value });\n            }\n        },\n\n        minArity: {\n            method(n) {\n\n                assert(Number.isSafeInteger(n) && n > 0, 'n must be a strict positive integer');\n\n                return this.$_addRule({ name: 'minArity', args: { n } });\n            },\n            validate(value, helpers, { n }) {\n\n                if (value.length >= n) {\n                    return value;\n                }\n\n                return helpers.error('function.minArity', { n });\n            }\n        },\n\n        maxArity: {\n            method(n) {\n\n                assert(Number.isSafeInteger(n) && n >= 0, 'n must be a positive integer');\n\n                return this.$_addRule({ name: 'maxArity', args: { n } });\n            },\n            validate(value, helpers, { n }) {\n\n                if (value.length <= n) {\n                    return value;\n                }\n\n                return helpers.error('function.maxArity', { n });\n            }\n        }\n    },\n\n    messages: {\n        'function.arity': '{{#label}} must have an arity of {{#n}}',\n        'function.class': '{{#label}} must be a class',\n        'function.maxArity': '{{#label}} must have an arity lesser or equal to {{#n}}',\n        'function.minArity': '{{#label}} must have an arity greater or equal to {{#n}}'\n    }\n});\n"
  },
  {
    "path": "lib/types/keys.js",
    "content": "'use strict';\n\nconst { applyToDefaults, assert, clone: Clone } = require('@hapi/hoek');\nconst Topo = require('@hapi/topo');\n\nconst Any = require('./any');\nconst Common = require('../common');\nconst Compile = require('../compile');\nconst Errors = require('../errors');\nconst Ref = require('../ref');\nconst Template = require('../template');\n\n\nconst internals = {\n    renameDefaults: {\n        alias: false,                   // Keep old value in place\n        multiple: false,                // Allow renaming multiple keys into the same target\n        override: false                 // Overrides an existing key\n    }\n};\n\n\nmodule.exports = Any.extend({\n\n    type: '_keys',\n\n    properties: {\n\n        typeof: 'object'\n    },\n\n    flags: {\n\n        unknown: { default: undefined }\n    },\n\n    terms: {\n\n        dependencies: { init: null },\n        keys: { init: null, manifest: { mapped: { from: 'schema', to: 'key' } } },\n        patterns: { init: null },\n        renames: { init: null }\n    },\n\n    args(schema, keys) {\n\n        return schema.keys(keys);\n    },\n\n    validate(value, { schema, error, state, prefs }) {\n\n        if (!value ||\n            typeof value !== schema.$_property('typeof') ||\n            Array.isArray(value)) {\n\n            return { value, errors: error('object.base', { type: schema.$_property('typeof') }) };\n        }\n\n        // Skip if there are no other rules to test\n\n        if (!schema.$_terms.renames &&\n            !schema.$_terms.dependencies &&\n            !schema.$_terms.keys &&                       // null allows any keys\n            !schema.$_terms.patterns &&\n            !schema.$_terms.externals) {\n\n            return;\n        }\n\n        // Shallow clone value\n\n        value = internals.clone(value, prefs);\n        const errors = [];\n\n        // Rename keys\n\n        if (schema.$_terms.renames &&\n            !internals.rename(schema, value, state, prefs, errors)) {\n\n            return { value, errors };\n        }\n\n        // Anything allowed\n\n        if (!schema.$_terms.keys &&                       // null allows any keys\n            !schema.$_terms.patterns &&\n            !schema.$_terms.dependencies) {\n\n            return { value, errors };\n        }\n\n        // Defined keys\n\n        const unprocessed = new Set(Object.keys(value));\n\n        if (schema.$_terms.keys) {\n            const ancestors = [value, ...state.ancestors];\n\n            for (const child of schema.$_terms.keys) {\n                const key = child.key;\n                const item = value[key];\n\n                unprocessed.delete(key);\n\n                const localState = state.localize([...state.path, key], ancestors, child);\n                const result = child.schema.$_validate(item, localState, prefs);\n\n                if (result.errors) {\n                    if (prefs.abortEarly) {\n                        return { value, errors: result.errors };\n                    }\n\n                    if (result.value !== undefined) {\n                        value[key] = result.value;\n                    }\n\n                    errors.push(...result.errors);\n                }\n                else if (child.schema._flags.result === 'strip' ||\n                    result.value === undefined && item !== undefined) {\n\n                    delete value[key];\n                }\n                else if (result.value !== undefined) {\n                    value[key] = result.value;\n                }\n            }\n        }\n\n        // Unknown keys\n\n        if (unprocessed.size ||\n            schema._flags._hasPatternMatch) {\n\n            const early = internals.unknown(schema, value, unprocessed, errors, state, prefs);\n            if (early) {\n                return early;\n            }\n        }\n\n        // Validate dependencies\n\n        if (schema.$_terms.dependencies) {\n            for (const dep of schema.$_terms.dependencies) {\n                if (\n                    dep.key !== null &&\n                    internals.isPresent(dep.options)(dep.key.resolve(value, state, prefs, null, { shadow: false })) === false\n                ) {\n\n                    continue;\n                }\n\n                const failed = internals.dependencies[dep.rel](schema, dep, value, state, prefs);\n                if (failed) {\n                    const report = schema.$_createError(failed.code, value, failed.context, state, prefs);\n                    if (prefs.abortEarly) {\n                        return { value, errors: report };\n                    }\n\n                    errors.push(report);\n                }\n            }\n        }\n\n        return { value, errors };\n    },\n\n    rules: {\n\n        and: {\n            method(...peers /*, [options] */) {\n\n                Common.verifyFlat(peers, 'and');\n\n                return internals.dependency(this, 'and', null, peers);\n            }\n        },\n\n        append: {\n            method(schema) {\n\n                if (schema === null ||\n                    schema === undefined ||\n                    Object.keys(schema).length === 0) {\n\n                    return this;\n                }\n\n                return this.keys(schema);\n            }\n        },\n\n        assert: {\n            method(subject, schema, message) {\n\n                if (!Template.isTemplate(subject)) {\n                    subject = Compile.ref(subject);\n                }\n\n                assert(message === undefined || typeof message === 'string', 'Message must be a string');\n\n                schema = this.$_compile(schema, { appendPath: true });\n\n                const obj = this.$_addRule({ name: 'assert', args: { subject, schema, message } });\n                obj.$_mutateRegister(subject);\n                obj.$_mutateRegister(schema);\n                return obj;\n            },\n            validate(value, { error, prefs, state }, { subject, schema, message }) {\n\n                const about = subject.resolve(value, state, prefs);\n                const path = Ref.isRef(subject) ? subject.absolute(state) : [];\n                if (schema.$_match(about, state.localize(path, [value, ...state.ancestors], schema), prefs)) {\n                    return value;\n                }\n\n                return error('object.assert', { subject, message });\n            },\n            args: ['subject', 'schema', 'message'],\n            multi: true\n        },\n\n        instance: {\n            method(constructor, name) {\n\n                assert(typeof constructor === 'function', 'constructor must be a function');\n\n                name = name || constructor.name;\n\n                return this.$_addRule({ name: 'instance', args: { constructor, name } });\n            },\n            validate(value, helpers, { constructor, name }) {\n\n                if (value instanceof constructor) {\n                    return value;\n                }\n\n                return helpers.error('object.instance', { type: name, value });\n            },\n            args: ['constructor', 'name']\n        },\n\n        keys: {\n            method(schema) {\n\n                assert(schema === undefined || typeof schema === 'object', 'Object schema must be a valid object');\n                assert(!Common.isSchema(schema), 'Object schema cannot be a joi schema');\n\n                const obj = this.clone();\n\n                if (!schema) {                                      // Allow all\n                    obj.$_terms.keys = null;\n                }\n                else if (!Object.keys(schema).length) {             // Allow none\n                    obj.$_terms.keys = new internals.Keys();\n                }\n                else {\n                    obj.$_terms.keys = obj.$_terms.keys ? obj.$_terms.keys.filter((child) => !schema.hasOwnProperty(child.key)) : new internals.Keys();\n                    for (const key in schema) {\n                        Common.tryWithPath(() => obj.$_terms.keys.push({ key, schema: this.$_compile(schema[key]) }), key);\n                    }\n                }\n\n                return obj.$_mutateRebuild();\n            }\n        },\n\n        length: {\n            method(limit) {\n\n                return this.$_addRule({ name: 'length', args: { limit }, operator: '=' });\n            },\n            validate(value, helpers, { limit }, { name, operator, args }) {\n\n                if (Common.compare(Object.keys(value).length, limit, operator)) {\n                    return value;\n                }\n\n                return helpers.error('object.' + name, { limit: args.limit, value });\n            },\n            args: [\n                {\n                    name: 'limit',\n                    ref: true,\n                    assert: Common.limit,\n                    message: 'must be a positive integer'\n                }\n            ]\n        },\n\n        max: {\n            method(limit) {\n\n                return this.$_addRule({ name: 'max', method: 'length', args: { limit }, operator: '<=' });\n            }\n        },\n\n        min: {\n            method(limit) {\n\n                return this.$_addRule({ name: 'min', method: 'length', args: { limit }, operator: '>=' });\n            }\n        },\n\n        nand: {\n            method(...peers /*, [options] */) {\n\n                Common.verifyFlat(peers, 'nand');\n\n                return internals.dependency(this, 'nand', null, peers);\n            }\n        },\n\n        or: {\n            method(...peers /*, [options] */) {\n\n                Common.verifyFlat(peers, 'or');\n\n                return internals.dependency(this, 'or', null, peers);\n            }\n        },\n\n        oxor: {\n            method(...peers /*, [options] */) {\n\n                return internals.dependency(this, 'oxor', null, peers);\n            }\n        },\n\n        pattern: {\n            method(pattern, schema, options = {}) {\n\n                const isRegExp = pattern instanceof RegExp;\n                if (!isRegExp) {\n                    pattern = this.$_compile(pattern, { appendPath: true });\n                }\n\n                assert(schema !== undefined, 'Invalid rule');\n                Common.assertOptions(options, ['fallthrough', 'matches']);\n\n                if (isRegExp) {\n                    assert(!pattern.flags.includes('g') && !pattern.flags.includes('y'), 'pattern should not use global or sticky mode');\n                }\n\n                schema = this.$_compile(schema, { appendPath: true });\n\n                const obj = this.clone();\n                obj.$_terms.patterns = obj.$_terms.patterns || [];\n                const config = { [isRegExp ? 'regex' : 'schema']: pattern, rule: schema };\n                if (options.matches) {\n                    config.matches = this.$_compile(options.matches);\n                    if (config.matches.type !== 'array') {\n                        config.matches = config.matches.$_root.array().items(config.matches);\n                    }\n\n                    obj.$_mutateRegister(config.matches);\n                    obj.$_setFlag('_hasPatternMatch', true, { clone: false });\n                }\n\n                if (options.fallthrough) {\n                    config.fallthrough = true;\n                }\n\n                obj.$_terms.patterns.push(config);\n                obj.$_mutateRegister(schema);\n                return obj;\n            }\n        },\n\n        ref: {\n            method() {\n\n                return this.$_addRule('ref');\n            },\n            validate(value, helpers) {\n\n                if (Ref.isRef(value)) {\n                    return value;\n                }\n\n                return helpers.error('object.refType', { value });\n            }\n        },\n\n        regex: {\n            method() {\n\n                return this.$_addRule('regex');\n            },\n            validate(value, helpers) {\n\n                if (value instanceof RegExp) {\n                    return value;\n                }\n\n                return helpers.error('object.regex', { value });\n            }\n        },\n\n        rename: {\n            method(from, to, options = {}) {\n\n                assert(typeof from === 'string' || from instanceof RegExp, 'Rename missing the from argument');\n                assert(typeof to === 'string' || to instanceof Template, 'Invalid rename to argument');\n                assert(to !== from, 'Cannot rename key to same name:', from);\n\n                Common.assertOptions(options, ['alias', 'ignoreUndefined', 'override', 'multiple']);\n\n                const obj = this.clone();\n\n                obj.$_terms.renames = obj.$_terms.renames || [];\n                for (const rename of obj.$_terms.renames) {\n                    assert(rename.from !== from, 'Cannot rename the same key multiple times');\n                }\n\n                if (to instanceof Template) {\n                    obj.$_mutateRegister(to);\n                }\n\n                obj.$_terms.renames.push({\n                    from,\n                    to,\n                    options: applyToDefaults(internals.renameDefaults, options)\n                });\n\n                return obj;\n            }\n        },\n\n        schema: {\n            method(type = 'any') {\n\n                return this.$_addRule({ name: 'schema', args: { type } });\n            },\n            validate(value, helpers, { type }) {\n\n                if (Common.isSchema(value) &&\n                    (type === 'any' || value.type === type)) {\n\n                    return value;\n                }\n\n                return helpers.error('object.schema', { type });\n            }\n        },\n\n        unknown: {\n            method(allow) {\n\n                return this.$_setFlag('unknown', allow !== false);\n            }\n        },\n\n        with: {\n            method(key, peers, options = {}) {\n\n                return internals.dependency(this, 'with', key, peers, options);\n            }\n        },\n\n        without: {\n            method(key, peers, options = {}) {\n\n                return internals.dependency(this, 'without', key, peers, options);\n            }\n        },\n\n        xor: {\n            method(...peers /*, [options] */) {\n\n                Common.verifyFlat(peers, 'xor');\n\n                return internals.dependency(this, 'xor', null, peers);\n            }\n        }\n    },\n\n    overrides: {\n\n        default(value, options) {\n\n            if (value === undefined) {\n                value = Common.symbols.deepDefault;\n            }\n\n            return this.$_parent('default', value, options);\n        },\n\n        isAsync() {\n\n            if (this.$_terms.externals?.length) {\n                return true;\n            }\n\n            if (this.$_terms.keys?.length) {\n                for (const key of this.$_terms.keys) {\n                    if (key.schema.isAsync()) {\n                        return true;\n                    }\n                }\n            }\n\n            if (this.$_terms.patterns?.length) {\n                for (const pattern of this.$_terms.patterns) {\n                    if (pattern.rule.isAsync()) {\n                        return true;\n                    }\n                }\n            }\n\n            return false;\n        }\n    },\n\n    rebuild(schema) {\n\n        if (schema.$_terms.keys) {\n            const topo = new Topo.Sorter();\n            for (const child of schema.$_terms.keys) {\n                Common.tryWithPath(() => topo.add(child, { after: child.schema.$_rootReferences(), group: child.key }), child.key);\n            }\n\n            schema.$_terms.keys = new internals.Keys(...topo.nodes);\n        }\n    },\n\n    manifest: {\n\n        build(obj, desc) {\n\n            if (desc.keys) {\n                obj = obj.keys(desc.keys);\n            }\n\n            if (desc.dependencies) {\n                for (const { rel, key = null, peers, options } of desc.dependencies) {\n                    obj = internals.dependency(obj, rel, key, peers, options);\n                }\n            }\n\n            if (desc.patterns) {\n                for (const { regex, schema, rule, fallthrough, matches } of desc.patterns) {\n                    obj = obj.pattern(regex || schema, rule, { fallthrough, matches });\n                }\n            }\n\n            if (desc.renames) {\n                for (const { from, to, options } of desc.renames) {\n                    obj = obj.rename(from, to, options);\n                }\n            }\n\n            return obj;\n        }\n    },\n\n    messages: {\n        'object.and': '{{#label}} contains {{#presentWithLabels}} without its required peers {{#missingWithLabels}}',\n        'object.assert': '{{#label}} is invalid because {if(#subject.key, `\"` + #subject.key + `\" failed to ` + (#message || \"pass the assertion test\"), #message || \"the assertion failed\")}',\n        'object.base': '{{#label}} must be of type {{#type}}',\n        'object.instance': '{{#label}} must be an instance of {{:#type}}',\n        'object.length': '{{#label}} must have {{#limit}} key{if(#limit == 1, \"\", \"s\")}',\n        'object.max': '{{#label}} must have less than or equal to {{#limit}} key{if(#limit == 1, \"\", \"s\")}',\n        'object.min': '{{#label}} must have at least {{#limit}} key{if(#limit == 1, \"\", \"s\")}',\n        'object.missing': '{{#label}} must contain at least one of {{#peersWithLabels}}',\n        'object.nand': '{{:#mainWithLabel}} must not exist simultaneously with {{#peersWithLabels}}',\n        'object.oxor': '{{#label}} contains a conflict between optional exclusive peers {{#peersWithLabels}}',\n        'object.pattern.match': '{{#label}} keys failed to match pattern requirements',\n        'object.refType': '{{#label}} must be a Joi reference',\n        'object.regex': '{{#label}} must be a RegExp object',\n        'object.rename.multiple': '{{#label}} cannot rename {{:#from}} because multiple renames are disabled and another key was already renamed to {{:#to}}',\n        'object.rename.override': '{{#label}} cannot rename {{:#from}} because override is disabled and target {{:#to}} exists',\n        'object.schema': '{{#label}} must be a Joi schema of {{#type}} type',\n        'object.unknown': '{{#label}} is not allowed',\n        'object.with': '{{:#mainWithLabel}} missing required peer {{:#peerWithLabel}}',\n        'object.without': '{{:#mainWithLabel}} conflict with forbidden peer {{:#peerWithLabel}}',\n        'object.xor': '{{#label}} contains a conflict between exclusive peers {{#peersWithLabels}}'\n    }\n});\n\n\n// Helpers\n\ninternals.clone = function (value, prefs) {\n\n    // Object\n\n    if (typeof value === 'object') {\n        if (prefs.nonEnumerables) {\n            return Clone(value, { shallow: true });\n        }\n\n        const clone = Object.create(Object.getPrototypeOf(value));\n        Object.assign(clone, value);\n        return clone;\n    }\n\n    // Function\n\n    const clone = function (...args) {\n\n        return value.apply(this, args);\n    };\n\n    clone.prototype = Clone(value.prototype);\n    Object.defineProperty(clone, 'name', { value: value.name, writable: false });\n    Object.defineProperty(clone, 'length', { value: value.length, writable: false });\n    Object.assign(clone, value);\n    return clone;\n};\n\n\ninternals.dependency = function (schema, rel, key, peers, options) {\n\n    assert(key === null || typeof key === 'string', rel, 'key must be a strings');\n\n    // Extract options from peers array\n\n    if (!options) {\n        options = peers.length > 1 && typeof peers[peers.length - 1] === 'object' ? peers.pop() : {};\n    }\n\n    Common.assertOptions(options, ['separator', 'isPresent']);\n\n    peers = [].concat(peers);\n\n    // Cast peer paths\n\n    const separator = Common.default(options.separator, '.');\n    const paths = [];\n    for (const peer of peers) {\n        assert(typeof peer === 'string', rel, 'peers must be strings');\n        paths.push(Compile.ref(peer, { separator, ancestor: 0, prefix: false }));\n    }\n\n    // Cast key\n\n    if (key !== null) {\n        key = Compile.ref(key, { separator, ancestor: 0, prefix: false });\n    }\n\n    // Add rule\n\n    const obj = schema.clone();\n    obj.$_terms.dependencies = obj.$_terms.dependencies || [];\n    obj.$_terms.dependencies.push(new internals.Dependency(rel, key, paths, peers, options));\n    return obj;\n};\n\n\ninternals.dependencies = {\n\n    and(schema, dep, value, state, prefs) {\n\n        const missing = [];\n        const present = [];\n        const count = dep.peers.length;\n        const isPresent = internals.isPresent(dep.options);\n        for (const peer of dep.peers) {\n            if (isPresent(peer.resolve(value, state, prefs, null, { shadow: false })) === false) {\n                missing.push(peer.key);\n            }\n            else {\n                present.push(peer.key);\n            }\n        }\n\n        if (missing.length !== count &&\n            present.length !== count) {\n\n            return {\n                code: 'object.and',\n                context: {\n                    present,\n                    presentWithLabels: internals.keysToLabels(schema, present),\n                    missing,\n                    missingWithLabels: internals.keysToLabels(schema, missing)\n                }\n            };\n        }\n    },\n\n    nand(schema, dep, value, state, prefs) {\n\n        const present = [];\n        const isPresent = internals.isPresent(dep.options);\n        for (const peer of dep.peers) {\n            if (isPresent(peer.resolve(value, state, prefs, null, { shadow: false }))) {\n                present.push(peer.key);\n            }\n        }\n\n        if (present.length !== dep.peers.length) {\n            return;\n        }\n\n        const main = dep.paths[0];\n        const values = dep.paths.slice(1);\n        return {\n            code: 'object.nand',\n            context: {\n                main,\n                mainWithLabel: internals.keysToLabels(schema, main),\n                peers: values,\n                peersWithLabels: internals.keysToLabels(schema, values)\n            }\n        };\n    },\n\n    or(schema, dep, value, state, prefs) {\n\n        const isPresent = internals.isPresent(dep.options);\n        for (const peer of dep.peers) {\n            if (isPresent(peer.resolve(value, state, prefs, null, { shadow: false }))) {\n                return;\n            }\n        }\n\n        return {\n            code: 'object.missing',\n            context: {\n                peers: dep.paths,\n                peersWithLabels: internals.keysToLabels(schema, dep.paths)\n            }\n        };\n    },\n\n    oxor(schema, dep, value, state, prefs) {\n\n        const present = [];\n        const isPresent = internals.isPresent(dep.options);\n        for (const peer of dep.peers) {\n            if (isPresent(peer.resolve(value, state, prefs, null, { shadow: false }))) {\n                present.push(peer.key);\n            }\n        }\n\n        if (!present.length ||\n            present.length === 1) {\n\n            return;\n        }\n\n        const context = { peers: dep.paths, peersWithLabels: internals.keysToLabels(schema, dep.paths) };\n        context.present = present;\n        context.presentWithLabels = internals.keysToLabels(schema, present);\n        return { code: 'object.oxor', context };\n    },\n\n    with(schema, dep, value, state, prefs) {\n\n        const isPresent = internals.isPresent(dep.options);\n        for (const peer of dep.peers) {\n            if (isPresent(peer.resolve(value, state, prefs, null, { shadow: false })) === false) {\n                return {\n                    code: 'object.with',\n                    context: {\n                        main: dep.key.key,\n                        mainWithLabel: internals.keysToLabels(schema, dep.key.key),\n                        peer: peer.key,\n                        peerWithLabel: internals.keysToLabels(schema, peer.key)\n                    }\n                };\n            }\n        }\n    },\n\n    without(schema, dep, value, state, prefs) {\n\n        const isPresent = internals.isPresent(dep.options);\n        for (const peer of dep.peers) {\n            if (isPresent(peer.resolve(value, state, prefs, null, { shadow: false }))) {\n                return {\n                    code: 'object.without',\n                    context: {\n                        main: dep.key.key,\n                        mainWithLabel: internals.keysToLabels(schema, dep.key.key),\n                        peer: peer.key,\n                        peerWithLabel: internals.keysToLabels(schema, peer.key)\n                    }\n                };\n            }\n        }\n    },\n\n    xor(schema, dep, value, state, prefs) {\n\n        const present = [];\n        const isPresent = internals.isPresent(dep.options);\n        for (const peer of dep.peers) {\n            if (isPresent(peer.resolve(value, state, prefs, null, { shadow: false }))) {\n                present.push(peer.key);\n            }\n        }\n\n        if (present.length === 1) {\n            return;\n        }\n\n        const context = { peers: dep.paths, peersWithLabels: internals.keysToLabels(schema, dep.paths) };\n        if (present.length === 0) {\n            return { code: 'object.missing', context };\n        }\n\n        context.present = present;\n        context.presentWithLabels = internals.keysToLabels(schema, present);\n        return { code: 'object.xor', context };\n    }\n};\n\n\ninternals.keysToLabels = function (schema, keys) {\n\n    if (Array.isArray(keys)) {\n        return keys.map((key) => schema.$_mapLabels(key));\n    }\n\n    return schema.$_mapLabels(keys);\n};\n\n\ninternals.isPresent = function (options) {\n\n    return typeof options.isPresent === 'function' ? options.isPresent : (resolved) => resolved !== undefined;\n};\n\n\ninternals.rename = function (schema, value, state, prefs, errors) {\n\n    const renamed = {};\n    for (const rename of schema.$_terms.renames) {\n        const matches = [];\n        const pattern = typeof rename.from !== 'string';\n\n        if (!pattern) {\n            if (Object.prototype.hasOwnProperty.call(value, rename.from) &&\n                (value[rename.from] !== undefined || !rename.options.ignoreUndefined)) {\n\n                matches.push(rename);\n            }\n        }\n        else {\n            for (const from in value) {\n                if (value[from] === undefined &&\n                    rename.options.ignoreUndefined) {\n\n                    continue;\n                }\n\n                if (from === rename.to) {\n                    continue;\n                }\n\n                const match = rename.from.exec(from);\n                if (!match) {\n                    continue;\n                }\n\n                matches.push({ from, to: rename.to, match });\n            }\n        }\n\n        for (const match of matches) {\n            const from = match.from;\n            let to = match.to;\n            if (to instanceof Template) {\n                to = to.render(value, state, prefs, match.match);\n            }\n\n            if (from === to) {\n                continue;\n            }\n\n            if (!rename.options.multiple &&\n                renamed[to]) {\n\n                errors.push(schema.$_createError('object.rename.multiple', value, { from, to, pattern }, state, prefs));\n                if (prefs.abortEarly) {\n                    return false;\n                }\n            }\n\n            if (Object.prototype.hasOwnProperty.call(value, to) &&\n                !rename.options.override &&\n                !renamed[to]) {\n\n                errors.push(schema.$_createError('object.rename.override', value, { from, to, pattern }, state, prefs));\n                if (prefs.abortEarly) {\n                    return false;\n                }\n            }\n\n            if (value[from] === undefined) {\n                delete value[to];\n            }\n            else {\n                value[to] = value[from];\n            }\n\n            renamed[to] = true;\n\n            if (!rename.options.alias) {\n                delete value[from];\n            }\n        }\n    }\n\n    return true;\n};\n\n\ninternals.unknown = function (schema, value, unprocessed, errors, state, prefs) {\n\n    if (schema.$_terms.patterns) {\n        let hasMatches = false;\n        const matches = schema.$_terms.patterns.map((pattern) => {\n\n            if (pattern.matches) {\n                hasMatches = true;\n                return [];\n            }\n        });\n\n        const ancestors = [value, ...state.ancestors];\n\n        for (const key of unprocessed) {\n            const item = value[key];\n            const path = [...state.path, key];\n\n            for (let i = 0; i < schema.$_terms.patterns.length; ++i) {\n                const pattern = schema.$_terms.patterns[i];\n                if (pattern.regex) {\n                    const match = pattern.regex.test(key);\n                    state.mainstay.tracer.debug(state, 'rule', `pattern.${i}`, match ? 'pass' : 'error');\n                    if (!match) {\n                        continue;\n                    }\n                }\n                else {\n                    if (!pattern.schema.$_match(key, state.nest(pattern.schema, `pattern.${i}`), prefs)) {\n                        continue;\n                    }\n                }\n\n                unprocessed.delete(key);\n\n                const localState = state.localize(path, ancestors, { schema: pattern.rule, key });\n                const result = pattern.rule.$_validate(item, localState, prefs);\n                if (result.errors) {\n                    if (prefs.abortEarly) {\n                        return { value, errors: result.errors };\n                    }\n\n                    errors.push(...result.errors);\n                }\n\n                if (pattern.matches) {\n                    matches[i].push(key);\n                }\n\n                value[key] = result.value;\n                if (!pattern.fallthrough) {\n                    break;\n                }\n            }\n        }\n\n        // Validate pattern matches rules\n\n        if (hasMatches) {\n            for (let i = 0; i < matches.length; ++i) {\n                const match = matches[i];\n                if (!match) {\n                    continue;\n                }\n\n                const stpm = schema.$_terms.patterns[i].matches;\n                const localState = state.localize(state.path, ancestors, stpm);\n                const result = stpm.$_validate(match, localState, prefs);\n                if (result.errors) {\n                    const details = Errors.details(result.errors, { override: false });\n                    details.matches = match;\n                    const report = schema.$_createError('object.pattern.match', value, details, state, prefs);\n                    if (prefs.abortEarly) {\n                        return { value, errors: report };\n                    }\n\n                    errors.push(report);\n                }\n            }\n        }\n    }\n\n    if (!unprocessed.size ||\n        !schema.$_terms.keys && !schema.$_terms.patterns) {     // If no keys or patterns specified, unknown keys allowed\n\n        return;\n    }\n\n    if (prefs.stripUnknown && typeof schema._flags.unknown === 'undefined' ||\n        prefs.skipFunctions) {\n\n        const stripUnknown = prefs.stripUnknown ? (prefs.stripUnknown === true ? true : !!prefs.stripUnknown.objects) : false;\n\n        for (const key of unprocessed) {\n            if (stripUnknown) {\n                delete value[key];\n                unprocessed.delete(key);\n            }\n            else if (typeof value[key] === 'function') {\n                unprocessed.delete(key);\n            }\n        }\n    }\n\n    const forbidUnknown = !Common.default(schema._flags.unknown, prefs.allowUnknown);\n    if (forbidUnknown) {\n        for (const unprocessedKey of unprocessed) {\n            const localState = state.localize([...state.path, unprocessedKey], []);\n            const report = schema.$_createError('object.unknown', value[unprocessedKey], { child: unprocessedKey }, localState, prefs, { flags: false });\n            if (prefs.abortEarly) {\n                return { value, errors: report };\n            }\n\n            errors.push(report);\n        }\n    }\n};\n\n\ninternals.Dependency = class {\n\n    constructor(rel, key, peers, paths, options) {\n\n        this.rel = rel;\n        this.key = key;\n        this.peers = peers;\n        this.paths = paths;\n        this.options = options;\n    }\n\n    describe() {\n\n        const desc = {\n            rel: this.rel,\n            peers: this.paths\n        };\n\n        if (this.key !== null) {\n            desc.key = this.key.key;\n        }\n\n        if (this.peers[0].separator !== '.') {\n            desc.options = { ...desc.options, separator: this.peers[0].separator };\n        }\n\n        if (this.options.isPresent) {\n            desc.options = { ...desc.options, isPresent: this.options.isPresent };\n        }\n\n        return desc;\n    }\n};\n\n\ninternals.Keys = class extends Array {\n\n    concat(source) {\n\n        const result = this.slice();\n\n        const keys = new Map();\n        for (let i = 0; i < result.length; ++i) {\n            keys.set(result[i].key, i);\n        }\n\n        for (const item of source) {\n            const key = item.key;\n            const pos = keys.get(key);\n            if (pos !== undefined) {\n                result[pos] = { key, schema: result[pos].schema.concat(item.schema) };\n            }\n            else {\n                result.push(item);\n            }\n        }\n\n        return result;\n    }\n};\n"
  },
  {
    "path": "lib/types/link.js",
    "content": "'use strict';\n\nconst { assert } = require('@hapi/hoek');\n\nconst Any = require('./any');\nconst Common = require('../common');\nconst Compile = require('../compile');\nconst Errors = require('../errors');\n\n\nconst internals = {};\n\n\nmodule.exports = Any.extend({\n\n    type: 'link',\n\n    properties: {\n        schemaChain: true\n    },\n\n    terms: {\n\n        link: { init: null, manifest: 'single', register: false }\n    },\n\n    args(schema, ref) {\n\n        return schema.ref(ref);\n    },\n\n    validate(value, { schema, state, prefs }) {\n\n        assert(schema.$_terms.link, 'Uninitialized link schema');\n\n        const linked = internals.generate(schema, value, state, prefs);\n        const ref = schema.$_terms.link[0].ref;\n        return linked.$_validate(value, state.nest(linked, `link:${ref.display}:${linked.type}`), prefs);\n    },\n\n    generate(schema, value, state, prefs) {\n\n        return internals.generate(schema, value, state, prefs);\n    },\n\n    rules: {\n\n        ref: {\n            method(ref) {\n\n                assert(!this.$_terms.link, 'Cannot reinitialize schema');\n\n                ref = Compile.ref(ref);\n\n                assert(ref.type === 'value' || ref.type === 'local', 'Invalid reference type:', ref.type);\n                assert(ref.type === 'local' || ref.ancestor === 'root' || ref.ancestor > 0, 'Link cannot reference itself');\n\n                const obj = this.clone();\n                obj.$_terms.link = [{ ref }];\n                return obj;\n            }\n        },\n\n        relative: {\n            method(enabled = true) {\n\n                return this.$_setFlag('relative', enabled);\n            }\n        }\n    },\n\n    overrides: {\n\n        concat(source) {\n\n            assert(this.$_terms.link, 'Uninitialized link schema');\n            assert(Common.isSchema(source), 'Invalid schema object');\n            assert(source.type !== 'link', 'Cannot merge type link with another link');\n\n            const obj = this.clone();\n\n            if (!obj.$_terms.whens) {\n                obj.$_terms.whens = [];\n            }\n\n            obj.$_terms.whens.push({ concat: source });\n            return obj.$_mutateRebuild();\n        }\n    },\n\n    manifest: {\n\n        build(obj, desc) {\n\n            assert(desc.link, 'Invalid link description missing link');\n            return obj.ref(desc.link);\n        }\n    }\n});\n\n\n// Helpers\n\ninternals.generate = function (schema, value, state, prefs) {\n\n    let linked = state.mainstay.links.get(schema);\n    if (linked) {\n        return linked._generate(value, state, prefs).schema;\n    }\n\n    const ref = schema.$_terms.link[0].ref;\n    const { perspective, path } = internals.perspective(ref, state);\n    internals.assert(perspective, 'which is outside of schema boundaries', ref, schema, state, prefs);\n\n    try {\n        linked = path.length ? perspective.$_reach(path) : perspective;\n    }\n    catch {\n        internals.assert(false, 'to non-existing schema', ref, schema, state, prefs);\n    }\n\n    internals.assert(linked.type !== 'link', 'which is another link', ref, schema, state, prefs);\n\n    if (!schema._flags.relative) {\n        state.mainstay.links.set(schema, linked);\n    }\n\n    return linked._generate(value, state, prefs).schema;\n};\n\n\ninternals.perspective = function (ref, state) {\n\n    if (ref.type === 'local') {\n        for (const { schema, key } of state.schemas) {                              // From parent to root\n            const id = schema._flags.id || key;\n            if (id === ref.path[0]) {\n                return { perspective: schema, path: ref.path.slice(1) };\n            }\n\n            if (schema.$_terms.shared) {\n                for (const shared of schema.$_terms.shared) {\n                    if (shared._flags.id === ref.path[0]) {\n                        return { perspective: shared, path: ref.path.slice(1) };\n                    }\n                }\n            }\n        }\n\n        return { perspective: null, path: null };\n    }\n\n    if (ref.ancestor === 'root') {\n        return { perspective: state.schemas[state.schemas.length - 1].schema, path: ref.path };\n    }\n\n    return { perspective: state.schemas[ref.ancestor] && state.schemas[ref.ancestor].schema, path: ref.path };\n};\n\n\ninternals.assert = function (condition, message, ref, schema, state, prefs) {\n\n    if (condition) {                // Manual check to avoid generating error message on success\n        return;\n    }\n\n    assert(false, `\"${Errors.label(schema._flags, state, prefs)}\" contains link reference \"${ref.display}\" ${message}`);\n};\n"
  },
  {
    "path": "lib/types/number.js",
    "content": "'use strict';\n\nconst { assert } = require('@hapi/hoek');\n\nconst Any = require('./any');\nconst Common = require('../common');\n\n\nconst internals = {\n    numberRx: /^\\s*[+-]?(?:(?:\\d+(?:\\.\\d*)?)|(?:\\.\\d+))(?:e([+-]?\\d+))?\\s*$/i,\n    precisionRx: /(?:\\.(\\d+))?(?:[eE]([+-]?\\d+))?$/,\n    exponentialPartRegex: /[eE][+-]?\\d+$/,\n    leadingSignAndZerosRegex: /^[+-]?(0*)?/,\n    dotRegex: /\\./,\n    trailingZerosRegex: /0+$/,\n    decimalPlaces(value) {\n\n        const str = value.toString();\n        const dindex = str.indexOf('.');\n        const eindex = str.indexOf('e');\n        return (\n            (dindex < 0 ? 0 : (eindex < 0 ? str.length : eindex) - dindex - 1) +\n            (eindex < 0 ? 0 : Math.max(0, -parseInt(str.slice(eindex + 1))))\n        );\n    }\n};\n\n\nmodule.exports = Any.extend({\n\n    type: 'number',\n\n    flags: {\n\n        unsafe: { default: false }\n    },\n\n    coerce: {\n        from: 'string',\n        method(value, { schema, error }) {\n\n            const matches = value.match(internals.numberRx);\n            if (!matches) {\n                return;\n            }\n\n            value = value.trim();\n            const result = { value: parseFloat(value) };\n\n            if (result.value === 0) {\n                result.value = 0;           // -0\n            }\n\n            if (!schema._flags.unsafe) {\n                if (value.match(/e/i)) {\n                    if (internals.extractSignificantDigits(value) !== internals.extractSignificantDigits(String(result.value))) {\n                        result.errors = error('number.unsafe');\n                        return result;\n                    }\n                }\n                else {\n                    const string = result.value.toString();\n                    if (string.match(/e/i)) {\n                        return result;\n                    }\n\n                    if (string !== internals.normalizeDecimal(value)) {\n                        result.errors = error('number.unsafe');\n                        return result;\n                    }\n                }\n            }\n\n            return result;\n        }\n    },\n\n    validate(value, { schema, error, prefs }) {\n\n        if (value === Infinity ||\n            value === -Infinity) {\n\n            return { value, errors: error('number.infinity') };\n        }\n\n        if (!Common.isNumber(value)) {\n            return { value, errors: error('number.base') };\n        }\n\n        const result = { value };\n\n        if (prefs.convert) {\n            const rule = schema.$_getRule('precision');\n            if (rule) {\n                const precision = Math.pow(10, rule.args.limit);                    // This is conceptually equivalent to using toFixed but it should be much faster\n                result.value = Math.round(result.value * precision) / precision;\n            }\n        }\n\n        if (result.value === 0) {\n            result.value = 0;           // -0\n        }\n\n        if (!schema._flags.unsafe &&\n            (value > Number.MAX_SAFE_INTEGER || value < Number.MIN_SAFE_INTEGER)) {\n\n            result.errors = error('number.unsafe');\n        }\n\n        return result;\n    },\n\n    rules: {\n\n        compare: {\n            method: false,\n            validate(value, helpers, { limit }, { name, operator, args }) {\n\n                if (Common.compare(value, limit, operator)) {\n                    return value;\n                }\n\n                return helpers.error('number.' + name, { limit: args.limit, value });\n            },\n            args: [\n                {\n                    name: 'limit',\n                    ref: true,\n                    assert: Common.isNumber,\n                    message: 'must be a number'\n                }\n            ]\n        },\n\n        greater: {\n            method(limit) {\n\n                return this.$_addRule({ name: 'greater', method: 'compare', args: { limit }, operator: '>' });\n            }\n        },\n\n        integer: {\n            method() {\n\n                return this.$_addRule('integer');\n            },\n            validate(value, helpers) {\n\n                if (Math.trunc(value) - value === 0) {\n                    return value;\n                }\n\n                return helpers.error('number.integer');\n            }\n        },\n\n        less: {\n            method(limit) {\n\n                return this.$_addRule({ name: 'less', method: 'compare', args: { limit }, operator: '<' });\n            }\n        },\n\n        max: {\n            method(limit) {\n\n                return this.$_addRule({ name: 'max', method: 'compare', args: { limit }, operator: '<=' });\n            }\n        },\n\n        min: {\n            method(limit) {\n\n                return this.$_addRule({ name: 'min', method: 'compare', args: { limit }, operator: '>=' });\n            }\n        },\n\n        multiple: {\n            method(base) {\n\n                const baseDecimalPlace = typeof base === 'number' ? internals.decimalPlaces(base) : null;\n                const pfactor = Math.pow(10, baseDecimalPlace);\n\n                return this.$_addRule({\n                    name: 'multiple',\n                    args: {\n                        base,\n                        baseDecimalPlace,\n                        pfactor\n                    }\n                });\n            },\n            validate(value, helpers, { base, baseDecimalPlace, pfactor }, options) {\n\n                const valueDecimalPlace = internals.decimalPlaces(value);\n\n                if (valueDecimalPlace > baseDecimalPlace) {\n                    // Value with higher precision than base can never be a multiple\n                    return helpers.error('number.multiple', { multiple: options.args.base, value });\n                }\n\n                return Math.round(pfactor * value) % Math.round(pfactor * base) === 0 ?\n                    value :\n                    helpers.error('number.multiple', { multiple: options.args.base, value });\n            },\n            args: [\n                {\n                    name: 'base',\n                    ref: true,\n                    assert: (value) => typeof value === 'number' && isFinite(value) && value > 0,\n                    message: 'must be a positive number'\n                },\n                'baseDecimalPlace',\n                'pfactor'\n            ],\n            multi: true\n        },\n\n        negative: {\n            method() {\n\n                return this.sign('negative');\n            }\n        },\n\n        port: {\n            method() {\n\n                return this.$_addRule('port');\n            },\n            validate(value, helpers) {\n\n                if (Number.isSafeInteger(value) &&\n                    value >= 0 &&\n                    value <= 65535) {\n\n                    return value;\n                }\n\n                return helpers.error('number.port');\n            }\n        },\n\n        positive: {\n            method() {\n\n                return this.sign('positive');\n            }\n        },\n\n        precision: {\n            method(limit) {\n\n                assert(Number.isSafeInteger(limit), 'limit must be an integer');\n\n                return this.$_addRule({ name: 'precision', args: { limit } });\n            },\n            validate(value, helpers, { limit }) {\n\n                const places = value.toString().match(internals.precisionRx);\n                const decimals = Math.max((places[1] ? places[1].length : 0) - (places[2] ? parseInt(places[2], 10) : 0), 0);\n                if (decimals <= limit) {\n                    return value;\n                }\n\n                return helpers.error('number.precision', { limit, value });\n            },\n            convert: true\n        },\n\n        sign: {\n            method(sign) {\n\n                assert(['negative', 'positive'].includes(sign), 'Invalid sign', sign);\n\n                return this.$_addRule({ name: 'sign', args: { sign } });\n            },\n            validate(value, helpers, { sign }) {\n\n                if (sign === 'negative' && value < 0 ||\n                    sign === 'positive' && value > 0) {\n\n                    return value;\n                }\n\n                return helpers.error(`number.${sign}`);\n            }\n        },\n\n        unsafe: {\n            method(enabled = true) {\n\n                assert(typeof enabled === 'boolean', 'enabled must be a boolean');\n\n                return this.$_setFlag('unsafe', enabled);\n            }\n        }\n    },\n\n    cast: {\n        string: {\n            from: (value) => typeof value === 'number',\n            to(value, helpers) {\n\n                return value.toString();\n            }\n        }\n    },\n\n    messages: {\n        'number.base': '{{#label}} must be a number',\n        'number.greater': '{{#label}} must be greater than {{#limit}}',\n        'number.infinity': '{{#label}} cannot be infinity',\n        'number.integer': '{{#label}} must be an integer',\n        'number.less': '{{#label}} must be less than {{#limit}}',\n        'number.max': '{{#label}} must be less than or equal to {{#limit}}',\n        'number.min': '{{#label}} must be greater than or equal to {{#limit}}',\n        'number.multiple': '{{#label}} must be a multiple of {{#multiple}}',\n        'number.negative': '{{#label}} must be a negative number',\n        'number.port': '{{#label}} must be a valid port',\n        'number.positive': '{{#label}} must be a positive number',\n        'number.precision': '{{#label}} must have no more than {{#limit}} decimal places',\n        'number.unsafe': '{{#label}} must be a safe number'\n    }\n});\n\n\n// Helpers\n\ninternals.extractSignificantDigits = function (value) {\n\n    return value\n        .replace(internals.exponentialPartRegex, '')\n        .replace(internals.dotRegex, '')\n        .replace(internals.trailingZerosRegex, '')\n        .replace(internals.leadingSignAndZerosRegex, '');\n};\n\n\ninternals.normalizeDecimal = function (str) {\n\n    str = str\n        // Remove leading plus signs\n        .replace(/^\\+/, '')\n        // Remove trailing zeros if there is a decimal point and unecessary decimal points\n        .replace(/\\.0*$/, '')\n        // Add a integer 0 if the numbers starts with a decimal point\n        .replace(/^(-?)\\.([^\\.]*)$/, '$10.$2')\n        // Remove leading zeros\n        .replace(/^(-?)0+([0-9])/, '$1$2');\n\n    if (str.includes('.') &&\n        str.endsWith('0')) {\n\n        str = str.replace(/0+$/, '');\n    }\n\n    if (str === '-0') {\n        return '0';\n    }\n\n    return str;\n};\n"
  },
  {
    "path": "lib/types/object.js",
    "content": "'use strict';\n\nconst Keys = require('./keys');\n\n\nconst internals = {};\n\n\nmodule.exports = Keys.extend({\n\n    type: 'object',\n\n    cast: {\n        map: {\n            from: (value) => value && typeof value === 'object',\n            to(value, helpers) {\n\n                return new Map(Object.entries(value));\n            }\n        }\n    }\n});\n"
  },
  {
    "path": "lib/types/string.js",
    "content": "'use strict';\n\nconst { assert, escapeRegex } = require('@hapi/hoek');\nconst { isDomainValid, isEmailValid, ipRegex, uriRegex } = require('@hapi/address');\nconst Tlds = require('@hapi/tlds');\n\nconst Any = require('./any');\nconst Common = require('../common');\n\n\nconst internals = {\n    tlds: Tlds.tlds instanceof Set ? { tlds: { allow: Tlds.tlds, deny: null } } : false,              // $lab:coverage:ignore$\n    base64Regex: {\n        // paddingRequired\n        true: {\n            // urlSafe\n            true: /^(?:[\\w\\-]{2}[\\w\\-]{2})*(?:[\\w\\-]{2}==|[\\w\\-]{3}=)?$/,\n            false: /^(?:[A-Za-z0-9+\\/]{2}[A-Za-z0-9+\\/]{2})*(?:[A-Za-z0-9+\\/]{2}==|[A-Za-z0-9+\\/]{3}=)?$/\n        },\n        false: {\n            true: /^(?:[\\w\\-]{2}[\\w\\-]{2})*(?:[\\w\\-]{2}(==)?|[\\w\\-]{3}=?)?$/,\n            false: /^(?:[A-Za-z0-9+\\/]{2}[A-Za-z0-9+\\/]{2})*(?:[A-Za-z0-9+\\/]{2}(==)?|[A-Za-z0-9+\\/]{3}=?)?$/\n        }\n    },\n    dataUriRegex: /^data:[\\w+.-]+\\/[\\w+.-]+;((charset=[\\w-]+|base64),)?(.*)$/,\n    hexRegex: {\n        withPrefix: /^0x[0-9a-f]+$/i,\n        withOptionalPrefix: /^(?:0x)?[0-9a-f]+$/i,\n        withoutPrefix: /^[0-9a-f]+$/i\n    },\n    ipRegex: ipRegex({ cidr: 'forbidden' }).regex,\n    isoDurationRegex: /^P(?!$)(\\d+Y)?(\\d+M)?(\\d+W)?(\\d+D)?(T(?=\\d)(\\d+H)?(\\d+M)?(\\d+S)?)?$/,\n\n    guidBrackets: {\n        '{': '}', '[': ']', '(': ')', '': ''\n    },\n    guidVersions: {\n        uuidv1: '1',\n        uuidv2: '2',\n        uuidv3: '3',\n        uuidv4: '4',\n        uuidv5: '5',\n        uuidv6: '6',\n        uuidv7: '7',\n        uuidv8: '8'\n    },\n    guidSeparators: new Set([undefined, true, false, '-', ':']),\n\n    normalizationForms: ['NFC', 'NFD', 'NFKC', 'NFKD']\n};\n\n\nmodule.exports = Any.extend({\n\n    type: 'string',\n\n    flags: {\n\n        insensitive: { default: false },\n        truncate: { default: false }\n    },\n\n    terms: {\n\n        replacements: { init: null }\n    },\n\n    coerce: {\n        from: 'string',\n        method(value, { schema, state, prefs }) {\n\n            const normalize = schema.$_getRule('normalize');\n            if (normalize) {\n                value = value.normalize(normalize.args.form);\n            }\n\n            const casing = schema.$_getRule('case');\n            if (casing) {\n                value = casing.args.direction === 'upper' ? value.toLocaleUpperCase() : value.toLocaleLowerCase();\n            }\n\n            const trim = schema.$_getRule('trim');\n            if (trim &&\n                trim.args.enabled) {\n\n                value = value.trim();\n            }\n\n            if (schema.$_terms.replacements) {\n                for (const replacement of schema.$_terms.replacements) {\n                    value = value.replace(replacement.pattern, replacement.replacement);\n                }\n            }\n\n            const hex = schema.$_getRule('hex');\n            if (hex &&\n                hex.args.options.byteAligned &&\n                value.length % 2 !== 0) {\n\n                value = `0${value}`;\n            }\n\n            if (schema.$_getRule('isoDate')) {\n                const iso = internals.isoDate(value);\n                if (iso) {\n                    value = iso;\n                }\n            }\n\n            if (schema._flags.truncate) {\n                const rule = schema.$_getRule('max');\n                if (rule) {\n                    let limit = rule.args.limit;\n                    if (Common.isResolvable(limit)) {\n                        limit = limit.resolve(value, state, prefs);\n                        if (!Common.limit(limit)) {\n                            return { value, errors: schema.$_createError('any.ref', limit, { ref: rule.args.limit, arg: 'limit', reason: 'must be a positive integer' }, state, prefs) };\n                        }\n                    }\n\n                    value = value.slice(0, limit);\n                }\n            }\n\n            return { value };\n        }\n    },\n\n    validate(value, { schema, error }) {\n\n        if (typeof value !== 'string') {\n            return { value, errors: error('string.base') };\n        }\n\n        if (value === '') {\n            const min = schema.$_getRule('min');\n            if (min &&\n                min.args.limit === 0) {\n\n                return;\n            }\n\n            return { value, errors: error('string.empty') };\n        }\n    },\n\n    rules: {\n\n        alphanum: {\n            method() {\n\n                return this.$_addRule('alphanum');\n            },\n            validate(value, helpers) {\n\n                if (/^[a-zA-Z0-9]+$/.test(value)) {\n                    return value;\n                }\n\n                return helpers.error('string.alphanum');\n            }\n        },\n\n        base64: {\n            method(options = {}) {\n\n                Common.assertOptions(options, ['paddingRequired', 'urlSafe']);\n\n                options = { urlSafe: false, paddingRequired: true, ...options };\n                assert(typeof options.paddingRequired === 'boolean', 'paddingRequired must be boolean');\n                assert(typeof options.urlSafe === 'boolean', 'urlSafe must be boolean');\n\n                return this.$_addRule({ name: 'base64', args: { options } });\n            },\n            validate(value, helpers, { options }) {\n\n                const regex = internals.base64Regex[options.paddingRequired][options.urlSafe];\n                if (regex.test(value)) {\n                    return value;\n                }\n\n                return helpers.error('string.base64');\n            }\n        },\n\n        case: {\n            method(direction) {\n\n                assert(['lower', 'upper'].includes(direction), 'Invalid case:', direction);\n\n                return this.$_addRule({ name: 'case', args: { direction } });\n            },\n            validate(value, helpers, { direction }) {\n\n                if (direction === 'lower' && value === value.toLocaleLowerCase() ||\n                    direction === 'upper' && value === value.toLocaleUpperCase()) {\n\n                    return value;\n                }\n\n                return helpers.error(`string.${direction}case`);\n            },\n            convert: true\n        },\n\n        creditCard: {\n            method() {\n\n                return this.$_addRule('creditCard');\n            },\n            validate(value, helpers) {\n\n                let i = value.length;\n                let sum = 0;\n                let mul = 1;\n\n                while (i--) {\n                    const char = value.charAt(i) * mul;\n                    sum = sum + (char - (char > 9) * 9);\n                    mul = mul ^ 3;\n                }\n\n                if (sum > 0 &&\n                    sum % 10 === 0) {\n\n                    return value;\n                }\n\n                return helpers.error('string.creditCard');\n            }\n        },\n\n        dataUri: {\n            method(options = {}) {\n\n                Common.assertOptions(options, ['paddingRequired']);\n\n                options = { paddingRequired: true, ...options };\n                assert(typeof options.paddingRequired === 'boolean', 'paddingRequired must be boolean');\n\n                return this.$_addRule({ name: 'dataUri', args: { options } });\n            },\n            validate(value, helpers, { options }) {\n\n                const matches = value.match(internals.dataUriRegex);\n\n                if (matches) {\n                    if (!matches[2]) {\n                        return value;\n                    }\n\n                    if (matches[2] !== 'base64') {\n                        return value;\n                    }\n\n                    const base64regex = internals.base64Regex[options.paddingRequired].false;\n                    if (base64regex.test(matches[3])) {\n                        return value;\n                    }\n                }\n\n                return helpers.error('string.dataUri');\n            }\n        },\n\n        domain: {\n            method(options) {\n\n                if (options) {\n                    Common.assertOptions(options, ['allowFullyQualified', 'allowUnicode', 'allowUnderscore', 'maxDomainSegments', 'minDomainSegments', 'tlds']);\n                }\n\n                const address = internals.addressOptions(options);\n                return this.$_addRule({ name: 'domain', args: { options }, address });\n            },\n            validate(value, helpers, args, { address }) {\n\n                if (isDomainValid(value, address)) {\n                    return value;\n                }\n\n                return helpers.error('string.domain');\n            }\n        },\n\n        email: {\n            method(options = {}) {\n\n                Common.assertOptions(options, ['allowFullyQualified', 'allowUnicode', 'ignoreLength', 'maxDomainSegments', 'minDomainSegments', 'multiple', 'separator', 'tlds']);\n                assert(options.multiple === undefined || typeof options.multiple === 'boolean', 'multiple option must be an boolean');\n\n                const address = internals.addressOptions(options);\n                const regex = new RegExp(`\\\\s*[${options.separator ? escapeRegex(options.separator) : ','}]\\\\s*`);\n\n                return this.$_addRule({ name: 'email', args: { options }, regex, address });\n            },\n            validate(value, helpers, { options }, { regex, address }) {\n\n                const emails = options.multiple ? value.split(regex) : [value];\n                const invalids = [];\n                for (const email of emails) {\n                    if (!isEmailValid(email, address)) {\n                        invalids.push(email);\n                    }\n                }\n\n                if (!invalids.length) {\n                    return value;\n                }\n\n                return helpers.error('string.email', { value, invalids });\n            }\n        },\n\n        guid: {\n            alias: 'uuid',\n            method(options = {}) {\n\n                Common.assertOptions(options, ['version', 'separator', 'wrapper']);\n\n                assert(\n                    options.wrapper === undefined ||\n                    typeof options.wrapper === 'boolean' ||\n                    (typeof options.wrapper === 'string' && typeof internals.guidBrackets[options.wrapper] === 'string'),\n                    `\"wrapper\" must be true, false, or one of \"${Object.keys(internals.guidBrackets).filter(Boolean).join('\", \"')}\"`\n                );\n\n                let versionNumbers = '';\n\n                if (options.version) {\n                    const versions = [].concat(options.version);\n\n                    assert(versions.length >= 1, 'version must have at least 1 valid version specified');\n                    const set = new Set();\n\n                    for (let i = 0; i < versions.length; ++i) {\n                        const version = versions[i];\n                        assert(typeof version === 'string', 'version at position ' + i + ' must be a string');\n                        const versionNumber = internals.guidVersions[version.toLowerCase()];\n                        assert(versionNumber, 'version at position ' + i + ' must be one of ' + Object.keys(internals.guidVersions).join(', '));\n                        assert(!set.has(versionNumber), 'version at position ' + i + ' must not be a duplicate');\n\n                        versionNumbers += versionNumber;\n                        set.add(versionNumber);\n                    }\n                }\n\n                assert(internals.guidSeparators.has(options.separator), 'separator must be one of true, false, \"-\", or \":\"');\n                const separator = options.separator === undefined ? '[:-]?' :\n                    options.separator === true ? '[:-]' :\n                        options.separator === false ? '[]?' : `\\\\${options.separator}`;\n\n                let wrapperStart;\n                let wrapperEnd;\n\n                if (options.wrapper === undefined) {\n                    wrapperStart = '[\\\\[{\\\\(]?';\n                    wrapperEnd = '[\\\\]}\\\\)]?';\n                }\n                else if (options.wrapper === true) {\n                    wrapperStart = '[\\\\[{\\\\(]';\n                    wrapperEnd = '[\\\\]}\\\\)]';\n                }\n                else if (options.wrapper === false) {\n                    wrapperStart = '';\n                    wrapperEnd = '';\n                }\n                else {\n                    wrapperStart = escapeRegex(options.wrapper);\n                    wrapperEnd = escapeRegex(internals.guidBrackets[options.wrapper]);\n                }\n\n                const regex = new RegExp(\n                    `^(${wrapperStart})[0-9A-F]{8}(${separator})[0-9A-F]{4}\\\\2?[${\n                        versionNumbers || '0-9A-F'\n                    }][0-9A-F]{3}\\\\2?[${\n                        versionNumbers ? '89AB' : '0-9A-F'\n                    }][0-9A-F]{3}\\\\2?[0-9A-F]{12}(${wrapperEnd})$`,\n                    'i'\n                );\n\n                return this.$_addRule({ name: 'guid', args: { options }, regex });\n            },\n\n            validate(value, helpers, args, { regex }) {\n\n                const results = regex.exec(value);\n\n                if (!results) {\n                    return helpers.error('string.guid');\n                }\n\n                const open = results[1];\n                const close = results[results.length - 1];\n\n                if ((open || close) && internals.guidBrackets[open] !== close) {\n                    return helpers.error('string.guid');\n                }\n\n                return value;\n            }\n        },\n\n        hex: {\n            method(options = {}) {\n\n                Common.assertOptions(options, ['byteAligned', 'prefix']);\n\n                options = { byteAligned: false, prefix: false, ...options };\n                assert(typeof options.byteAligned === 'boolean', 'byteAligned must be boolean');\n                assert(typeof options.prefix === 'boolean' || options.prefix === 'optional', 'prefix must be boolean or \"optional\"');\n\n                return this.$_addRule({ name: 'hex', args: { options } });\n            },\n            validate(value, helpers, { options }) {\n\n                const re = options.prefix === 'optional' ?\n                    internals.hexRegex.withOptionalPrefix :\n                    options.prefix === true ?\n                        internals.hexRegex.withPrefix :\n                        internals.hexRegex.withoutPrefix;\n                if (!re.test(value)) {\n                    return helpers.error('string.hex');\n                }\n\n                if (options.byteAligned &&\n                    value.length % 2 !== 0) {\n\n                    return helpers.error('string.hexAlign');\n                }\n\n                return value;\n            }\n        },\n\n        hostname: {\n            method() {\n\n                return this.$_addRule('hostname');\n            },\n            validate(value, helpers) {\n\n                if (isDomainValid(value, { minDomainSegments: 1 }) ||\n                    internals.ipRegex.test(value)) {\n\n                    return value;\n                }\n\n                return helpers.error('string.hostname');\n            }\n        },\n\n        insensitive: {\n            method() {\n\n                return this.$_setFlag('insensitive', true);\n            }\n        },\n\n        ip: {\n            method(options = {}) {\n\n                Common.assertOptions(options, ['cidr', 'version']);\n\n                const { cidr, versions, regex } = ipRegex(options);\n                const version = options.version ? versions : undefined;\n                return this.$_addRule({ name: 'ip', args: { options: { cidr, version } }, regex });\n            },\n            validate(value, helpers, { options }, { regex }) {\n\n                if (regex.test(value)) {\n                    return value;\n                }\n\n                if (options.version) {\n                    return helpers.error('string.ipVersion', { value, cidr: options.cidr, version: options.version });\n                }\n\n                return helpers.error('string.ip', { value, cidr: options.cidr });\n            }\n        },\n\n        isoDate: {\n            method() {\n\n                return this.$_addRule('isoDate');\n            },\n            validate(value, { error }) {\n\n                if (internals.isoDate(value)) {\n                    return value;\n                }\n\n                return error('string.isoDate');\n            }\n        },\n\n        isoDuration: {\n            method() {\n\n                return this.$_addRule('isoDuration');\n            },\n            validate(value, helpers) {\n\n                if (internals.isoDurationRegex.test(value)) {\n                    return value;\n                }\n\n                return helpers.error('string.isoDuration');\n            }\n        },\n\n        length: {\n            method(limit, encoding) {\n\n                return internals.length(this, 'length', limit, '=', encoding);\n            },\n            validate(value, helpers, { limit, encoding }, { name, operator, args }) {\n\n                const length = encoding ? Buffer && Buffer.byteLength(value, encoding) : value.length;      // $lab:coverage:ignore$\n                if (Common.compare(length, limit, operator)) {\n                    return value;\n                }\n\n                return helpers.error('string.' + name, { limit: args.limit, value, encoding });\n            },\n            args: [\n                {\n                    name: 'limit',\n                    ref: true,\n                    assert: Common.limit,\n                    message: 'must be a positive integer'\n                },\n                'encoding'\n            ]\n        },\n\n        lowercase: {\n            method() {\n\n                return this.case('lower');\n            }\n        },\n\n        max: {\n            method(limit, encoding) {\n\n                return internals.length(this, 'max', limit, '<=', encoding);\n            },\n            args: ['limit', 'encoding']\n        },\n\n        min: {\n            method(limit, encoding) {\n\n                return internals.length(this, 'min', limit, '>=', encoding);\n            },\n            args: ['limit', 'encoding']\n        },\n\n        normalize: {\n            method(form = 'NFC') {\n\n                assert(internals.normalizationForms.includes(form), 'normalization form must be one of ' + internals.normalizationForms.join(', '));\n\n                return this.$_addRule({ name: 'normalize', args: { form } });\n            },\n            validate(value, { error }, { form }) {\n\n                if (value === value.normalize(form)) {\n                    return value;\n                }\n\n                return error('string.normalize', { value, form });\n            },\n            convert: true\n        },\n\n        pattern: {\n            alias: 'regex',\n            method(regex, options = {}) {\n\n                assert(regex instanceof RegExp, 'regex must be a RegExp');\n                assert(!regex.flags.includes('g') && !regex.flags.includes('y'), 'regex should not use global or sticky mode');\n\n                if (typeof options === 'string') {\n                    options = { name: options };\n                }\n\n                Common.assertOptions(options, ['invert', 'name']);\n\n                const errorCode = ['string.pattern', options.invert ? '.invert' : '', options.name ? '.name' : '.base'].join('');\n                return this.$_addRule({ name: 'pattern', args: { regex, options }, errorCode });\n            },\n            validate(value, helpers, { regex, options }, { errorCode }) {\n\n                const patternMatch = regex.test(value);\n\n                if (patternMatch ^ options.invert) {\n                    return value;\n                }\n\n                return helpers.error(errorCode, { name: options.name, regex, value });\n            },\n            args: ['regex', 'options'],\n            multi: true\n        },\n\n        replace: {\n            method(pattern, replacement) {\n\n                if (typeof pattern === 'string') {\n                    pattern = new RegExp(escapeRegex(pattern), 'g');\n                }\n\n                assert(pattern instanceof RegExp, 'pattern must be a RegExp');\n                assert(typeof replacement === 'string', 'replacement must be a String');\n\n                const obj = this.clone();\n\n                if (!obj.$_terms.replacements) {\n                    obj.$_terms.replacements = [];\n                }\n\n                obj.$_terms.replacements.push({ pattern, replacement });\n                return obj;\n            }\n        },\n\n        token: {\n            method() {\n\n                return this.$_addRule('token');\n            },\n            validate(value, helpers) {\n\n                if (/^\\w+$/.test(value)) {\n                    return value;\n                }\n\n                return helpers.error('string.token');\n            }\n        },\n\n        trim: {\n            method(enabled = true) {\n\n                assert(typeof enabled === 'boolean', 'enabled must be a boolean');\n\n                return this.$_addRule({ name: 'trim', args: { enabled } });\n            },\n            validate(value, helpers, { enabled }) {\n\n                if (!enabled ||\n                    value === value.trim()) {\n\n                    return value;\n                }\n\n                return helpers.error('string.trim');\n            },\n            convert: true\n        },\n\n        truncate: {\n            method(enabled = true) {\n\n                assert(typeof enabled === 'boolean', 'enabled must be a boolean');\n\n                return this.$_setFlag('truncate', enabled);\n            }\n        },\n\n        uppercase: {\n            method() {\n\n                return this.case('upper');\n            }\n        },\n\n        uri: {\n            method(options = {}) {\n\n                Common.assertOptions(options, ['allowRelative', 'allowQuerySquareBrackets', 'domain', 'relativeOnly', 'scheme', 'encodeUri']);\n\n                if (options.domain) {\n                    Common.assertOptions(options.domain, ['allowFullyQualified', 'allowUnicode', 'maxDomainSegments', 'minDomainSegments', 'tlds']);\n                }\n\n                const { regex, scheme } = uriRegex(options);\n                const domain = options.domain ? internals.addressOptions(options.domain) : null;\n                return this.$_addRule({ name: 'uri', args: { options }, regex, domain, scheme });\n            },\n            validate(value, helpers, { options }, { regex, domain, scheme }) {\n\n                if (['http:/', 'https:/'].includes(value)) {            // scheme:/ is technically valid but makes no sense\n                    return helpers.error('string.uri');\n                }\n\n                let match = regex.exec(value);\n\n                if (!match && helpers.prefs.convert && options.encodeUri) {\n                    const encoded = encodeURI(value);\n                    match = regex.exec(encoded);\n                    if (match) {\n                        value = encoded;\n                    }\n                }\n\n                if (match) {\n                    const matched = match[1] || match[2];\n                    if (domain &&\n                        (!options.allowRelative || matched) &&\n                        !isDomainValid(matched, domain)) {\n\n                        return helpers.error('string.domain', { value: matched });\n                    }\n\n                    return value;\n                }\n\n                if (options.relativeOnly) {\n                    return helpers.error('string.uriRelativeOnly');\n                }\n\n                if (options.scheme) {\n                    return helpers.error('string.uriCustomScheme', { scheme, value });\n                }\n\n                return helpers.error('string.uri');\n            }\n        }\n    },\n\n    manifest: {\n\n        build(obj, desc) {\n\n            if (desc.replacements) {\n                for (const { pattern, replacement } of desc.replacements) {\n                    obj = obj.replace(pattern, replacement);\n                }\n            }\n\n            return obj;\n        }\n    },\n\n    messages: {\n        'string.alphanum': '{{#label}} must only contain alpha-numeric characters',\n        'string.base': '{{#label}} must be a string',\n        'string.base64': '{{#label}} must be a valid base64 string',\n        'string.creditCard': '{{#label}} must be a credit card',\n        'string.dataUri': '{{#label}} must be a valid dataUri string',\n        'string.domain': '{{#label}} must contain a valid domain name',\n        'string.email': '{{#label}} must be a valid email',\n        'string.empty': '{{#label}} is not allowed to be empty',\n        'string.guid': '{{#label}} must be a valid GUID',\n        'string.hex': '{{#label}} must only contain hexadecimal characters',\n        'string.hexAlign': '{{#label}} hex decoded representation must be byte aligned',\n        'string.hostname': '{{#label}} must be a valid hostname',\n        'string.ip': '{{#label}} must be a valid ip address with a {{#cidr}} CIDR',\n        'string.ipVersion': '{{#label}} must be a valid ip address of one of the following versions {{#version}} with a {{#cidr}} CIDR',\n        'string.isoDate': '{{#label}} must be in iso format',\n        'string.isoDuration': '{{#label}} must be a valid ISO 8601 duration',\n        'string.length': '{{#label}} length must be {{#limit}} characters long',\n        'string.lowercase': '{{#label}} must only contain lowercase characters',\n        'string.max': '{{#label}} length must be less than or equal to {{#limit}} characters long',\n        'string.min': '{{#label}} length must be at least {{#limit}} characters long',\n        'string.normalize': '{{#label}} must be unicode normalized in the {{#form}} form',\n        'string.token': '{{#label}} must only contain alpha-numeric and underscore characters',\n        'string.pattern.base': '{{#label}} with value {:[.]} fails to match the required pattern: {{#regex}}',\n        'string.pattern.name': '{{#label}} with value {:[.]} fails to match the {{#name}} pattern',\n        'string.pattern.invert.base': '{{#label}} with value {:[.]} matches the inverted pattern: {{#regex}}',\n        'string.pattern.invert.name': '{{#label}} with value {:[.]} matches the inverted {{#name}} pattern',\n        'string.trim': '{{#label}} must not have leading or trailing whitespace',\n        'string.uri': '{{#label}} must be a valid uri',\n        'string.uriCustomScheme': '{{#label}} must be a valid uri with a scheme matching the {{#scheme}} pattern',\n        'string.uriRelativeOnly': '{{#label}} must be a valid relative uri',\n        'string.uppercase': '{{#label}} must only contain uppercase characters'\n    }\n});\n\n\n// Helpers\n\ninternals.addressOptions = function (options) {\n\n    if (!options) {\n        return internals.tlds || options;      // $lab:coverage:ignore$\n    }\n\n    // minDomainSegments\n\n    assert(options.minDomainSegments === undefined ||\n        Number.isSafeInteger(options.minDomainSegments) && options.minDomainSegments > 0, 'minDomainSegments must be a positive integer');\n\n    // maxDomainSegments\n\n    assert(options.maxDomainSegments === undefined ||\n        Number.isSafeInteger(options.maxDomainSegments) && options.maxDomainSegments > 0, 'maxDomainSegments must be a positive integer');\n\n    // tlds\n\n    if (options.tlds === false) {\n        return options;\n    }\n\n    if (options.tlds === true ||\n        options.tlds === undefined) {\n\n        assert(internals.tlds, 'Built-in TLD list disabled');\n        return Object.assign({}, options, internals.tlds);\n    }\n\n    assert(typeof options.tlds === 'object', 'tlds must be true, false, or an object');\n\n    const deny = options.tlds.deny;\n    if (deny) {\n        if (Array.isArray(deny)) {\n            options = Object.assign({}, options, { tlds: { deny: new Set(deny) } });\n        }\n\n        assert(options.tlds.deny instanceof Set, 'tlds.deny must be an array, Set, or boolean');\n        assert(!options.tlds.allow, 'Cannot specify both tlds.allow and tlds.deny lists');\n        internals.validateTlds(options.tlds.deny, 'tlds.deny');\n        return options;\n    }\n\n    const allow = options.tlds.allow;\n    if (!allow) {\n        return { ...options, tlds: false };\n    }\n\n    if (allow === true) {\n        assert(internals.tlds, 'Built-in TLD list disabled');\n        return Object.assign({}, options, internals.tlds);\n    }\n\n    if (Array.isArray(allow)) {\n        options = Object.assign({}, options, { tlds: { allow: new Set(allow) } });\n    }\n\n    assert(options.tlds.allow instanceof Set, 'tlds.allow must be an array, Set, or boolean');\n    internals.validateTlds(options.tlds.allow, 'tlds.allow');\n    return options;\n};\n\n\ninternals.validateTlds = function (set, source) {\n\n    for (const tld of set) {\n        assert(isDomainValid(tld, { minDomainSegments: 1, maxDomainSegments: 1 }), `${source} must contain valid top level domain names`);\n    }\n};\n\n\ninternals.isoDate = function (value) {\n\n    if (!Common.isIsoDate(value)) {\n        return null;\n    }\n\n    if (/.*T.*[+-]\\d\\d$/.test(value)) {             // Add missing trailing zeros to timeshift\n        value += '00';\n    }\n\n    const date = new Date(value);\n    if (isNaN(date.getTime())) {\n        return null;\n    }\n\n    return date.toISOString();\n};\n\n\ninternals.length = function (schema, name, limit, operator, encoding) {\n\n    assert(!encoding || Buffer && Buffer.isEncoding(encoding), 'Invalid encoding:', encoding);      // $lab:coverage:ignore$\n\n    return schema.$_addRule({ name, method: 'length', args: { limit, encoding }, operator });\n};\n"
  },
  {
    "path": "lib/types/symbol.js",
    "content": "'use strict';\n\nconst { assert } = require('@hapi/hoek');\n\nconst Any = require('./any');\n\n\nconst internals = {};\n\n\ninternals.Map = class extends Map {\n\n    slice() {\n\n        return new internals.Map(this);\n    }\n};\n\n\nmodule.exports = Any.extend({\n\n    type: 'symbol',\n\n    terms: {\n\n        map: { init: new internals.Map() }\n    },\n\n    coerce: {\n        method(value, { schema, error }) {\n\n            const lookup = schema.$_terms.map.get(value);\n            if (lookup) {\n                value = lookup;\n            }\n\n            if (!schema._flags.only ||\n                typeof value === 'symbol') {\n\n                return { value };\n            }\n\n            return { value, errors: error('symbol.map', { map: schema.$_terms.map }) };\n        }\n    },\n\n    validate(value, { error }) {\n\n        if (typeof value !== 'symbol') {\n            return { value, errors: error('symbol.base') };\n        }\n    },\n\n    rules: {\n        map: {\n            method(iterable) {\n\n                if (iterable &&\n                    !iterable[Symbol.iterator] &&\n                    typeof iterable === 'object') {\n\n                    iterable = Object.entries(iterable);\n                }\n\n                assert(iterable && iterable[Symbol.iterator], 'Iterable must be an iterable or object');\n\n                const obj = this.clone();\n\n                const symbols = [];\n                for (const entry of iterable) {\n                    assert(entry && entry[Symbol.iterator], 'Entry must be an iterable');\n                    const [key, value] = entry;\n\n                    assert(typeof key !== 'object' && typeof key !== 'function' && typeof key !== 'symbol', 'Key must not be of type object, function, or Symbol');\n                    assert(typeof value === 'symbol', 'Value must be a Symbol');\n\n                    obj.$_terms.map.set(key, value);\n                    symbols.push(value);\n                }\n\n                return obj.valid(...symbols);\n            }\n        }\n    },\n\n    manifest: {\n\n        build(obj, desc) {\n\n            if (desc.map) {\n                obj = obj.map(desc.map);\n            }\n\n            return obj;\n        }\n    },\n\n    messages: {\n        'symbol.base': '{{#label}} must be a symbol',\n        'symbol.map': '{{#label}} must be one of {{#map}}'\n    }\n});\n"
  },
  {
    "path": "lib/validator.js",
    "content": "'use strict';\n\nconst { assert, clone, ignore, reach } = require('@hapi/hoek');\n\nconst Common = require('./common');\nconst Errors = require('./errors');\nconst State = require('./state');\n\n\nconst internals = {\n    result: Symbol('result')\n};\n\n\nexports.entry = function (value, schema, prefs) {\n\n    let settings = Common.defaults;\n    if (prefs) {\n        assert(prefs.warnings === undefined, 'Cannot override warnings preference in synchronous validation');\n        assert(prefs.artifacts === undefined, 'Cannot override artifacts preference in synchronous validation');\n        settings = Common.preferences(Common.defaults, prefs);\n    }\n\n    const result = internals.entry(value, schema, settings);\n    assert(!result.mainstay.externals.length, 'Schema with external rules must use validateAsync()');\n    const outcome = { value: result.value };\n\n    if (result.error) {\n        outcome.error = result.error;\n    }\n\n    if (result.mainstay.warnings.length) {\n        outcome.warning = Errors.details(result.mainstay.warnings);\n    }\n\n    if (result.mainstay.debug) {\n        outcome.debug = result.mainstay.debug;\n    }\n\n    if (result.mainstay.artifacts) {\n        outcome.artifacts = result.mainstay.artifacts;\n    }\n\n    return outcome;\n};\n\n\nexports.entryAsync = async function (value, schema, prefs) {\n\n    let settings = Common.defaults;\n    if (prefs) {\n        settings = Common.preferences(Common.defaults, prefs);\n    }\n\n    const result = internals.entry(value, schema, settings);\n    const mainstay = result.mainstay;\n    if (result.error) {\n        if (mainstay.debug) {\n            result.error.debug = mainstay.debug;\n        }\n\n        throw result.error;\n    }\n\n    if (mainstay.externals.length) {\n        let root = result.value;\n        const errors = [];\n        for (const external of mainstay.externals) {\n            const path = external.state.path;\n            const linked = external.schema.type === 'link' ? mainstay.links.get(external.schema) : null;\n            let node = root;\n            let key;\n            let parent;\n\n            const ancestors = path.length ? [root] : [];\n            const original = path.length ? reach(value, path) : value;\n\n            if (path.length) {\n                key = path[path.length - 1];\n\n                let current = root;\n                for (const segment of path.slice(0, -1)) {\n                    current = current[segment];\n                    ancestors.unshift(current);\n                }\n\n                parent = ancestors[0];\n                node = parent[key];\n            }\n\n            try {\n                const createError = (code, local) => (linked || external.schema).$_createError(code, node, local, external.state, settings);\n                const output = await external.method(node, {\n                    schema: external.schema,\n                    linked,\n                    state: external.state,\n                    prefs,\n                    original,\n                    error: createError,\n                    errorsArray: internals.errorsArray,\n                    warn: (code, local) => mainstay.warnings.push((linked || external.schema).$_createError(code, node, local, external.state, settings)),\n                    message: (messages, local) => (linked || external.schema).$_createError('external', node, local, external.state, settings, { messages })\n                });\n\n                if (output === undefined ||\n                    output === node) {\n\n                    continue;\n                }\n\n                if (output instanceof Errors.Report) {\n                    mainstay.tracer.log(external.schema, external.state, 'rule', 'external', 'error');\n                    errors.push(output);\n\n                    if (settings.abortEarly) {\n                        break;\n                    }\n\n                    continue;\n                }\n\n                if (Array.isArray(output) &&\n                    output[Common.symbols.errors]) {\n                    mainstay.tracer.log(external.schema, external.state, 'rule', 'external', 'error');\n                    errors.push(...output);\n\n                    if (settings.abortEarly) {\n                        break;\n                    }\n\n                    continue;\n                }\n\n                if (parent) {\n                    mainstay.tracer.value(external.state, 'rule', node, output, 'external');\n                    parent[key] = output;\n                }\n                else {\n                    mainstay.tracer.value(external.state, 'rule', root, output, 'external');\n                    root = output;\n                }\n            }\n            catch (err) {\n                if (settings.errors.label) {\n                    err.message += ` (${(external.label)})`;       // Change message to include path\n                }\n\n                throw err;\n            }\n        }\n\n        result.value = root;\n\n        if (errors.length) {\n            result.error = Errors.process(errors, value, settings);\n\n            if (mainstay.debug) {\n                result.error.debug = mainstay.debug;\n            }\n\n            throw result.error;\n        }\n    }\n\n    if (!settings.warnings &&\n        !settings.debug &&\n        !settings.artifacts) {\n\n        return result.value;\n    }\n\n    const outcome = { value: result.value };\n    if (mainstay.warnings.length) {\n        outcome.warning = Errors.details(mainstay.warnings);\n    }\n\n    if (mainstay.debug) {\n        outcome.debug = mainstay.debug;\n    }\n\n    if (mainstay.artifacts) {\n        outcome.artifacts = mainstay.artifacts;\n    }\n\n    return outcome;\n};\n\n\nexports.standard = function (value, schema) {\n\n\n    if (schema.isAsync()) {\n        return exports.entryAsync(value, schema);\n    }\n\n    return exports.entry(value, schema);\n};\n\n\ninternals.Mainstay = class {\n\n    constructor(tracer, debug, links) {\n\n        this.externals = [];\n        this.warnings = [];\n        this.tracer = tracer;\n        this.debug = debug;\n        this.links = links;\n        this.shadow = null;\n        this.artifacts = null;\n\n        this._snapshots = [];\n    }\n\n    snapshot() {\n\n        this._snapshots.push({\n            externals: this.externals.slice(),\n            warnings: this.warnings.slice()\n        });\n    }\n\n    restore() {\n\n        const snapshot = this._snapshots.pop();\n        this.externals = snapshot.externals;\n        this.warnings = snapshot.warnings;\n    }\n\n    commit() {\n\n        this._snapshots.pop();\n    }\n};\n\n\ninternals.entry = function (value, schema, prefs) {\n\n    // Prepare state\n\n    const { tracer, cleanup } = internals.tracer(schema, prefs);\n    const debug = prefs.debug ? [] : null;\n    const links = schema._ids._schemaChain ? new Map() : null;\n    const mainstay = new internals.Mainstay(tracer, debug, links);\n    const schemas = schema._ids._schemaChain ? [{ schema }] : null;\n    const state = new State([], [], { mainstay, schemas });\n\n    // Validate value\n\n    const result = exports.validate(value, schema, state, prefs);\n\n    // Process value and errors\n\n    if (cleanup) {\n        schema.$_root.untrace();\n    }\n\n    const error = Errors.process(result.errors, value, prefs);\n    return { value: result.value, error, mainstay };\n};\n\n\ninternals.tracer = function (schema, prefs) {\n\n    if (schema.$_root._tracer) {\n        return { tracer: schema.$_root._tracer._register(schema) };\n    }\n\n    if (prefs.debug) {\n        assert(schema.$_root.trace, 'Debug mode not supported');\n        return { tracer: schema.$_root.trace()._register(schema), cleanup: true };\n    }\n\n    return { tracer: internals.ignore };\n};\n\n\nexports.validate = function (value, schema, state, prefs, overrides = {}) {\n\n    if (schema.$_terms.whens) {\n        schema = schema._generate(value, state, prefs).schema;\n    }\n\n    // Setup state and settings\n\n    if (schema._preferences) {\n        prefs = internals.prefs(schema, prefs);\n    }\n\n    // Cache\n\n    if (schema._cache &&\n        prefs.cache) {\n\n        const result = schema._cache.get(value);\n        state.mainstay.tracer.debug(state, 'validate', 'cached', !!result);\n        if (result) {\n            return result;\n        }\n    }\n\n    // Helpers\n\n    const createError = (code, local, localState) => schema.$_createError(code, value, local, localState || state, prefs);\n    const helpers = {\n        original: value,\n        prefs,\n        schema,\n        state,\n        error: createError,\n        errorsArray: internals.errorsArray,\n        warn: (code, local, localState) => state.mainstay.warnings.push(createError(code, local, localState)),\n        message: (messages, local) => schema.$_createError('custom', value, local, state, prefs, { messages })\n    };\n\n    // Prepare\n\n    state.mainstay.tracer.entry(schema, state);\n\n    const def = schema._definition;\n    if (def.prepare &&\n        value !== undefined &&\n        prefs.convert) {\n\n        const prepared = def.prepare(value, helpers);\n        if (prepared) {\n            state.mainstay.tracer.value(state, 'prepare', value, prepared.value);\n            if (prepared.errors) {\n                return internals.finalize(prepared.value, [].concat(prepared.errors), helpers);         // Prepare error always aborts early\n            }\n\n            value = prepared.value;\n        }\n    }\n\n    // Type coercion\n\n    if (def.coerce &&\n        value !== undefined &&\n        prefs.convert &&\n        (!def.coerce.from || def.coerce.from.includes(typeof value))) {\n\n        const coerced = def.coerce.method(value, helpers);\n        if (coerced) {\n            state.mainstay.tracer.value(state, 'coerced', value, coerced.value);\n            if (coerced.errors) {\n                return internals.finalize(coerced.value, [].concat(coerced.errors), helpers);           // Coerce error always aborts early\n            }\n\n            value = coerced.value;\n        }\n    }\n\n    // Empty value\n\n    const empty = schema._flags.empty;\n    if (empty &&\n        empty.$_match(internals.trim(value, schema), state.nest(empty), Common.defaults)) {\n\n        state.mainstay.tracer.value(state, 'empty', value, undefined);\n        value = undefined;\n    }\n\n    // Presence requirements (required, optional, forbidden)\n\n    const presence = overrides.presence || schema._flags.presence || (schema._flags._endedSwitch ? null : prefs.presence);\n    if (value === undefined) {\n        if (presence === 'forbidden') {\n            return internals.finalize(value, null, helpers);\n        }\n\n        if (presence === 'required') {\n            return internals.finalize(value, [schema.$_createError('any.required', value, null, state, prefs)], helpers);\n        }\n\n        if (presence === 'optional') {\n            if (schema._flags.default !== Common.symbols.deepDefault) {\n                return internals.finalize(value, null, helpers);\n            }\n\n            state.mainstay.tracer.value(state, 'default', value, {});\n            value = {};\n        }\n    }\n    else if (presence === 'forbidden') {\n        return internals.finalize(value, [schema.$_createError('any.unknown', value, null, state, prefs)], helpers);\n    }\n\n    // Allowed values\n\n    const errors = [];\n\n    if (schema._valids) {\n        const match = schema._valids.get(value, state, prefs, schema._flags.insensitive);\n        if (match) {\n            if (prefs.convert) {\n                state.mainstay.tracer.value(state, 'valids', value, match.value);\n                value = match.value;\n            }\n\n            state.mainstay.tracer.filter(schema, state, 'valid', match);\n            return internals.finalize(value, null, helpers);\n        }\n\n        if (schema._flags.only) {\n            const report = schema.$_createError('any.only', value, { valids: schema._valids.values({ display: true }) }, state, prefs);\n            if (prefs.abortEarly) {\n                return internals.finalize(value, [report], helpers);\n            }\n\n            errors.push(report);\n        }\n    }\n\n    // Denied values\n\n    if (schema._invalids) {\n        const match = schema._invalids.get(value, state, prefs, schema._flags.insensitive);\n        if (match) {\n            state.mainstay.tracer.filter(schema, state, 'invalid', match);\n            const report = schema.$_createError('any.invalid', value, { invalids: schema._invalids.values({ display: true }) }, state, prefs);\n            if (prefs.abortEarly) {\n                return internals.finalize(value, [report], helpers);\n            }\n\n            errors.push(report);\n        }\n    }\n\n    // Base type\n\n    if (def.validate) {\n        const base = def.validate(value, helpers);\n        if (base) {\n            state.mainstay.tracer.value(state, 'base', value, base.value);\n            value = base.value;\n\n            if (base.errors) {\n                if (!Array.isArray(base.errors)) {\n                    errors.push(base.errors);\n                    return internals.finalize(value, errors, helpers);          // Base error always aborts early\n                }\n\n                if (base.errors.length) {\n                    errors.push(...base.errors);\n                    return internals.finalize(value, errors, helpers);          // Base error always aborts early\n                }\n            }\n        }\n    }\n\n    // Validate tests\n\n    if (!schema._rules.length) {\n        return internals.finalize(value, errors, helpers);\n    }\n\n    return internals.rules(value, errors, helpers);\n};\n\n\ninternals.rules = function (value, errors, helpers) {\n\n    const { schema, state, prefs } = helpers;\n\n    for (const rule of schema._rules) {\n        const definition = schema._definition.rules[rule.method];\n\n        // Skip rules that are also applied in coerce step\n\n        if (definition.convert &&\n            prefs.convert) {\n\n            state.mainstay.tracer.log(schema, state, 'rule', rule.name, 'full');\n            continue;\n        }\n\n        // Resolve references\n\n        let ret;\n        let args = rule.args;\n        if (rule._resolve.length) {\n            args = Object.assign({}, args);                                     // Shallow copy\n            for (const key of rule._resolve) {\n                const resolver = definition.argsByName.get(key);\n\n                const resolved = args[key].resolve(value, state, prefs);\n                const normalized = resolver.normalize ? resolver.normalize(resolved) : resolved;\n\n                const invalid = Common.validateArg(normalized, null, resolver);\n                if (invalid) {\n                    ret = schema.$_createError('any.ref', resolved, { arg: key, ref: args[key], reason: invalid }, state, prefs);\n                    break;\n                }\n\n                args[key] = normalized;\n            }\n        }\n\n        // Test rule\n\n        ret = ret || definition.validate(value, helpers, args, rule);           // Use ret if already set to reference error\n\n        const result = internals.rule(ret, rule);\n        if (result.errors) {\n            state.mainstay.tracer.log(schema, state, 'rule', rule.name, 'error');\n\n            if (rule.warn) {\n                state.mainstay.warnings.push(...result.errors);\n                continue;\n            }\n\n            if (prefs.abortEarly) {\n                return internals.finalize(value, result.errors, helpers);\n            }\n\n            errors.push(...result.errors);\n        }\n        else {\n            state.mainstay.tracer.log(schema, state, 'rule', rule.name, 'pass');\n            state.mainstay.tracer.value(state, 'rule', value, result.value, rule.name);\n            value = result.value;\n        }\n    }\n\n    return internals.finalize(value, errors, helpers);\n};\n\n\ninternals.rule = function (ret, rule) {\n\n    if (ret instanceof Errors.Report) {\n        internals.error(ret, rule);\n        return { errors: [ret], value: null };\n    }\n\n    if (Array.isArray(ret) &&\n        ret[Common.symbols.errors]) {\n\n        ret.forEach((report) => internals.error(report, rule));\n        return { errors: ret, value: null };\n    }\n\n    return { errors: null, value: ret };\n};\n\n\ninternals.error = function (report, rule) {\n\n    if (rule.message) {\n        report._setTemplate(rule.message);\n    }\n\n    return report;\n};\n\n\ninternals.finalize = function (value, errors, helpers) {\n\n    errors = errors || [];\n    const { schema, state, prefs } = helpers;\n\n    // Failover value\n\n    if (errors.length) {\n        const failover = internals.default('failover', undefined, errors, helpers);\n        if (failover !== undefined) {\n            state.mainstay.tracer.value(state, 'failover', value, failover);\n            value = failover;\n            errors = [];\n        }\n    }\n\n    // Error override\n\n    if (errors.length &&\n        schema._flags.error) {\n\n        if (typeof schema._flags.error === 'function') {\n            errors = schema._flags.error(errors);\n            if (!Array.isArray(errors)) {\n                errors = [errors];\n            }\n\n            for (const error of errors) {\n                assert(error instanceof Error || error instanceof Errors.Report, 'error() must return an Error object');\n            }\n        }\n        else {\n            errors = [schema._flags.error];\n        }\n    }\n\n    // Default\n\n    if (value === undefined) {\n        const defaulted = internals.default('default', value, errors, helpers);\n        state.mainstay.tracer.value(state, 'default', value, defaulted);\n        value = defaulted;\n    }\n\n    // Cast\n\n    if (schema._flags.cast &&\n        value !== undefined) {\n\n        const caster = schema._definition.cast[schema._flags.cast];\n        if (caster.from(value)) {\n            const casted = caster.to(value, helpers);\n            state.mainstay.tracer.value(state, 'cast', value, casted, schema._flags.cast);\n            value = casted;\n        }\n    }\n\n    // Externals\n\n    if (schema.$_terms.externals &&\n        prefs.externals &&\n        prefs._externals !== false) {                       // Disabled for matching\n\n        for (const { method } of schema.$_terms.externals) {\n            state.mainstay.externals.push({ method, schema, state, label: Errors.label(schema._flags, state, prefs) });\n        }\n    }\n\n    // Result\n\n    const result = { value, errors: errors.length ? errors : null };\n\n    if (schema._flags.result) {\n        result.value = schema._flags.result === 'strip' ? undefined : /* raw */ helpers.original;\n        state.mainstay.tracer.value(state, schema._flags.result, value, result.value);\n        state.shadow(value, schema._flags.result);\n    }\n\n    // Cache\n\n    if (schema._cache &&\n        prefs.cache !== false &&\n        !schema._refs.length) {\n\n        schema._cache.set(helpers.original, result);\n    }\n\n    // Artifacts\n\n    if (value !== undefined &&\n        !result.errors &&\n        schema._flags.artifact !== undefined) {\n\n        state.mainstay.artifacts = state.mainstay.artifacts || new Map();\n        if (!state.mainstay.artifacts.has(schema._flags.artifact)) {\n            state.mainstay.artifacts.set(schema._flags.artifact, []);\n        }\n\n        state.mainstay.artifacts.get(schema._flags.artifact).push(state.path);\n    }\n\n    return result;\n};\n\n\ninternals.prefs = function (schema, prefs) {\n\n    const isDefaultOptions = prefs === Common.defaults;\n    if (isDefaultOptions &&\n        schema._preferences[Common.symbols.prefs]) {\n\n        return schema._preferences[Common.symbols.prefs];\n    }\n\n    prefs = Common.preferences(prefs, schema._preferences);\n    if (isDefaultOptions) {\n        schema._preferences[Common.symbols.prefs] = prefs;\n    }\n\n    return prefs;\n};\n\n\ninternals.default = function (flag, value, errors, helpers) {\n\n    const { schema, state, prefs } = helpers;\n    const source = schema._flags[flag];\n    if (prefs.noDefaults ||\n        source === undefined) {\n\n        return value;\n    }\n\n    state.mainstay.tracer.log(schema, state, 'rule', flag, 'full');\n\n    if (!source) {\n        return source;\n    }\n\n    if (typeof source === 'function') {\n        const args = source.length ? [clone(state.ancestors[0]), helpers] : [];\n\n        try {\n            return source(...args);\n        }\n        catch (err) {\n            errors.push(schema.$_createError(`any.${flag}`, null, { error: err }, state, prefs));\n            return;\n        }\n    }\n\n    if (typeof source !== 'object') {\n        return source;\n    }\n\n    if (source[Common.symbols.literal]) {\n        return source.literal;\n    }\n\n    if (Common.isResolvable(source)) {\n        return source.resolve(value, state, prefs);\n    }\n\n    return clone(source);\n};\n\n\ninternals.trim = function (value, schema) {\n\n    if (typeof value !== 'string') {\n        return value;\n    }\n\n    const trim = schema.$_getRule('trim');\n    if (!trim ||\n        !trim.args.enabled) {\n\n        return value;\n    }\n\n    return value.trim();\n};\n\n\ninternals.ignore = {\n    active: false,\n    debug: ignore,\n    entry: ignore,\n    filter: ignore,\n    log: ignore,\n    resolve: ignore,\n    value: ignore\n};\n\n\ninternals.errorsArray = function () {\n\n    const errors = [];\n    errors[Common.symbols.errors] = true;\n    return errors;\n};\n"
  },
  {
    "path": "lib/values.js",
    "content": "'use strict';\n\nconst { assert, deepEqual } = require('@hapi/hoek');\n\nconst Common = require('./common');\n\n\nconst internals = {};\n\n\nmodule.exports = internals.Values = class {\n\n    constructor(values, refs) {\n\n        this._values = new Set(values);\n        this._refs = new Set(refs);\n        this._lowercase = internals.lowercases(values);\n\n        this._override = false;\n    }\n\n    get length() {\n\n        return this._values.size + this._refs.size;\n    }\n\n    add(value, refs) {\n\n        // Reference\n\n        if (Common.isResolvable(value)) {\n            if (!this._refs.has(value)) {\n                this._refs.add(value);\n\n                if (refs) {                     // Skipped in a merge\n                    refs.register(value);\n                }\n            }\n\n            return;\n        }\n\n        // Value\n\n        if (!this.has(value, null, null, false)) {\n            this._values.add(value);\n\n            if (typeof value === 'string') {\n                this._lowercase.set(value.toLowerCase(), value);\n            }\n        }\n    }\n\n    static merge(target, source, remove) {\n\n        target = target || new internals.Values();\n\n        if (source) {\n            if (source._override) {\n                return source.clone();\n            }\n\n            for (const item of [...source._values, ...source._refs]) {\n                target.add(item);\n            }\n        }\n\n        if (remove) {\n            for (const item of [...remove._values, ...remove._refs]) {\n                target.remove(item);\n            }\n        }\n\n        return target.length ? target : null;\n    }\n\n    remove(value) {\n\n        // Reference\n\n        if (Common.isResolvable(value)) {\n            this._refs.delete(value);\n            return;\n        }\n\n        // Value\n\n        this._values.delete(value);\n\n        if (typeof value === 'string') {\n            this._lowercase.delete(value.toLowerCase());\n        }\n    }\n\n    has(value, state, prefs, insensitive) {\n\n        return !!this.get(value, state, prefs, insensitive);\n    }\n\n    get(value, state, prefs, insensitive) {\n\n        if (!this.length) {\n            return false;\n        }\n\n        // Simple match\n\n        if (this._values.has(value)) {\n            return { value };\n        }\n\n        // Case insensitive string match\n\n        if (typeof value === 'string' &&\n            value &&\n            insensitive) {\n\n            const found = this._lowercase.get(value.toLowerCase());\n            if (found) {\n                return { value: found };\n            }\n        }\n\n        if (!this._refs.size &&\n            typeof value !== 'object') {\n\n            return false;\n        }\n\n        // Objects\n\n        if (typeof value === 'object') {\n            for (const item of this._values) {\n                if (deepEqual(item, value)) {\n                    return { value: item };\n                }\n            }\n        }\n\n        // References\n\n        if (state) {\n            for (const ref of this._refs) {\n                const resolved = ref.resolve(value, state, prefs, null, { in: true });\n                if (resolved === undefined) {\n                    continue;\n                }\n\n                const items = !ref.in || typeof resolved !== 'object'\n                    ? [resolved]\n                    : Array.isArray(resolved) ? resolved : Object.keys(resolved);\n\n                for (const item of items) {\n                    if (typeof item !== typeof value) {\n                        continue;\n                    }\n\n                    if (insensitive &&\n                        value &&\n                        typeof value === 'string') {\n\n                        if (item.toLowerCase() === value.toLowerCase()) {\n                            return { value: item, ref };\n                        }\n                    }\n                    else {\n                        if (deepEqual(item, value)) {\n                            return { value: item, ref };\n                        }\n                    }\n                }\n            }\n        }\n\n        return false;\n    }\n\n    override() {\n\n        this._override = true;\n    }\n\n    values(options) {\n\n        if (options &&\n            options.display) {\n\n            const values = [];\n\n            for (const item of [...this._values, ...this._refs]) {\n                if (item !== undefined) {\n                    values.push(item);\n                }\n            }\n\n            return values;\n        }\n\n        return Array.from([...this._values, ...this._refs]);\n    }\n\n    clone() {\n\n        const set = new internals.Values(this._values, this._refs);\n        set._override = this._override;\n        return set;\n    }\n\n    concat(source) {\n\n        assert(!source._override, 'Cannot concat override set of values');\n\n        const set = new internals.Values([...this._values, ...source._values], [...this._refs, ...source._refs]);\n        set._override = this._override;\n        return set;\n    }\n\n    describe() {\n\n        const normalized = [];\n\n        if (this._override) {\n            normalized.push({ override: true });\n        }\n\n        for (const value of this._values.values()) {\n            normalized.push(value && typeof value === 'object' ? { value } : value);\n        }\n\n        for (const value of this._refs.values()) {\n            normalized.push(value.describe());\n        }\n\n        return normalized;\n    }\n};\n\n\ninternals.Values.prototype[Common.symbols.values] = true;\n\n\n// Aliases\n\ninternals.Values.prototype.slice = internals.Values.prototype.clone;\n\n\n// Helpers\n\ninternals.lowercases = function (from) {\n\n    const map = new Map();\n\n    if (from) {\n        for (const value of from) {\n            if (typeof value === 'string') {\n                map.set(value.toLowerCase(), value);\n            }\n        }\n    }\n\n    return map;\n};\n"
  },
  {
    "path": "package.json",
    "content": "{\n    \"name\": \"joi\",\n    \"description\": \"Object schema validation\",\n    \"version\": \"18.0.2\",\n    \"repository\": {\n        \"url\": \"git://github.com/hapijs/joi\",\n        \"type\": \"git\"\n    },\n    \"engines\": {\n        \"node\": \">= 20\"\n    },\n    \"main\": \"lib/index.js\",\n    \"types\": \"lib/index.d.ts\",\n    \"browser\": \"dist/joi-browser.min.js\",\n    \"files\": [\n        \"lib/**/*\",\n        \"dist/*\"\n    ],\n    \"keywords\": [\n        \"schema\",\n        \"validation\"\n    ],\n    \"dependencies\": {\n        \"@hapi/address\": \"^5.1.1\",\n        \"@hapi/formula\": \"^3.0.2\",\n        \"@hapi/hoek\": \"^11.0.7\",\n        \"@hapi/pinpoint\": \"^2.0.1\",\n        \"@hapi/tlds\": \"^1.1.1\",\n        \"@hapi/topo\": \"^6.0.2\",\n        \"@standard-schema/spec\": \"^1.0.0\"\n    },\n    \"devDependencies\": {\n        \"@hapi/bourne\": \"^3.0.0\",\n        \"@hapi/code\": \"^9.0.3\",\n        \"@hapi/eslint-plugin\": \"^7.0.0\",\n        \"@hapi/joi-legacy-test\": \"npm:@hapi/joi@15.x.x\",\n        \"@hapi/lab\": \"^26.0.0\",\n        \"@types/node\": \"^20.17.47\",\n        \"typescript\": \"^5.8.3\"\n    },\n    \"scripts\": {\n        \"prepublishOnly\": \"cd browser && npm install && npm run build\",\n        \"test\": \"lab -t 100 -a @hapi/code -L -Y\",\n        \"test-cov-html\": \"lab -r html -o coverage.html -a @hapi/code\"\n    },\n    \"license\": \"BSD-3-Clause\"\n}\n"
  },
  {
    "path": "test/base.js",
    "content": "'use strict';\n\nconst Code = require('@hapi/code');\nconst Joi = require('..');\nconst Lab = require('@hapi/lab');\n\nconst Helper = require('./helper');\n\n\nconst internals = {};\n\n\nconst { describe, it } = exports.lab = Lab.script();\nconst { expect } = Code;\n\n\ndescribe('any', () => {\n\n    it('should throw an exception if arguments were passed.', () => {\n\n        expect(() => Joi.any('invalid argument.')).to.throw('The any type does not allow arguments');\n    });\n\n    describe('allow()', () => {\n\n        it('allows valid values to be set', () => {\n\n            const schema = Joi.any().allow(true, 1, 'hello', new Date());\n            Helper.validate(schema, [\n                [true, true],\n                [1, true],\n                ['hello', true],\n                [new Date(), true]\n            ]);\n        });\n\n        it('overrides previous values', () => {\n\n            const schema = Joi.object().allow(1).allow(Joi.override, 2);\n            Helper.validate(schema, [\n                [1, false, '\"value\" must be of type object'],\n                [2, true]\n            ]);\n        });\n\n        it('clears previous values', () => {\n\n            const schema = Joi.object().allow(1).allow(Joi.override);\n            Helper.validate(schema, [\n                [1, false, '\"value\" must be of type object'],\n                [2, false, '\"value\" must be of type object'],\n                [{}, true]\n            ]);\n        });\n\n        it('ignores empty override', () => {\n\n            const schema = Joi.allow(Joi.override);\n            expect(schema._valids).to.be.null();\n        });\n\n        it('throws when passed undefined', () => {\n\n            expect(() => Joi.any().allow(undefined)).to.throw('Cannot call allow/valid/invalid with undefined');\n        });\n\n        it('throws when override is not first item', () => {\n\n            expect(() => Joi.any().allow(1, Joi.override)).to.throw('Override must be the first value');\n        });\n    });\n\n    describe('alter()', () => {\n\n        it('errors on invalid argument', () => {\n\n            expect(() => Joi.number().alter()).to.throw('Invalid targets argument');\n            expect(() => Joi.number().alter('x')).to.throw('Invalid targets argument');\n            expect(() => Joi.number().alter([])).to.throw('Invalid targets argument');\n\n            expect(() => Joi.number().alter({ xx: 'x' })).to.throw('Alteration adjuster for xx must be a function');\n        });\n    });\n\n    describe('artifact()', () => {\n\n        it('returns matching artifacts', async () => {\n\n            const sym = Symbol('3');\n            const schema = Joi.object({\n                a: [\n                    Joi.string().artifact(1),\n                    Joi.number().artifact('2')\n                ],\n                b: Joi.boolean().artifact(sym),\n                c: {\n                    d: {\n                        e: Joi.any().artifact(4)\n                    }\n                },\n                f: Joi.array().items(Joi.string(), Joi.number().artifact(0))\n            });\n\n            Helper.validate(schema, [\n                [{ a: '5', b: true, c: { d: { e: {} } } }, true, { a: '5', b: true, c: { d: { e: {} } } }]\n            ]);\n\n            expect(schema.validate({ a: '5', b: true, c: { d: { e: {} } } }).artifacts).to.equal(new Map([[1, [['a']]], [sym, [['b']]], [4, [['c', 'd', 'e']]]]));\n            expect(schema.validate({ a: 5, c: { d: { e: {} } } }).artifacts).to.equal(new Map([['2', [['a']]], [4, [['c', 'd', 'e']]]]));\n\n            expect((await schema.validateAsync({ a: 5 })).artifacts).to.not.exist();\n            expect((await schema.validateAsync({ a: 5 }, { artifacts: true })).artifacts).to.equal(new Map([['2', [['a']]]]));\n            expect((await schema.validateAsync({ f: [5, 'x', 6] }, { artifacts: true })).artifacts).to.equal(new Map([[0, [['f', 0], ['f', 2]]]]));\n        });\n    });\n\n    describe('cast()', () => {\n\n        it('cancels cast', () => {\n\n            const schema = Joi.number().cast('string').cast(false);\n            Helper.validate(schema, [\n                ['123', true, 123]\n            ]);\n        });\n    });\n\n    describe('concat()', () => {\n\n        it('throws when schema is not any', () => {\n\n            expect(() => Joi.string().concat(Joi.number())).to.throw('Cannot merge type string with another type: number');\n        });\n\n        it('throws when schema is missing', () => {\n\n            expect(() => Joi.string().concat()).to.throw('Invalid schema object');\n        });\n\n        it('throws when schema is invalid', () => {\n\n            expect(() => Joi.string().concat(1)).to.throw('Invalid schema object');\n        });\n\n        it('merges two schemas (settings)', () => {\n\n            const a = Joi.number().prefs({ convert: true });\n            const b = Joi.prefs({ convert: false });\n\n            Helper.validate(a, [\n                [1, true],\n                ['1', true, 1]\n            ]);\n\n            Helper.validate(a.concat(b), [\n                [1, true],\n                ['1', false, {\n                    message: '\"value\" must be a number',\n                    path: [],\n                    type: 'number.base',\n                    context: { label: 'value', value: '1' }\n                }]\n            ]);\n        });\n\n        it('merges two schemas (valid)', () => {\n\n            const a = Joi.string().valid('a');\n            const b = Joi.string().valid('b');\n\n            Helper.validate(a, [\n                ['a', true],\n                ['b', false, {\n                    message: '\"value\" must be [a]',\n                    path: [],\n                    type: 'any.only',\n                    context: { value: 'b', valids: ['a'], label: 'value' }\n                }]\n            ]);\n\n            Helper.validate(b, [\n                ['b', true],\n                ['a', false, {\n                    message: '\"value\" must be [b]',\n                    path: [],\n                    type: 'any.only',\n                    context: { value: 'a', valids: ['b'], label: 'value' }\n                }]\n            ]);\n\n            Helper.validate(a.concat(b), [\n                ['a', true],\n                ['b', true]\n            ]);\n        });\n\n        it('merges two schemas (invalid)', () => {\n\n            const a = Joi.string().invalid('a');\n            const b = Joi.invalid('b');\n\n            Helper.validate(a, [\n                ['b', true],\n                ['a', false, {\n                    message: '\"value\" contains an invalid value',\n                    path: [],\n                    type: 'any.invalid',\n                    context: { value: 'a', invalids: ['a'], label: 'value' }\n                }]\n            ]);\n\n            Helper.validate(b, [\n                ['a', true],\n                ['b', false, {\n                    message: '\"value\" contains an invalid value',\n                    path: [],\n                    type: 'any.invalid',\n                    context: { value: 'b', invalids: ['b'], label: 'value' }\n                }]\n            ]);\n\n            Helper.validate(a.concat(b), [\n                ['a', false, {\n                    message: '\"value\" contains an invalid value',\n                    path: [],\n                    type: 'any.invalid',\n                    context: { value: 'a', invalids: ['a', 'b'], label: 'value' }\n                }],\n                ['b', false, {\n                    message: '\"value\" contains an invalid value',\n                    path: [],\n                    type: 'any.invalid',\n                    context: { value: 'b', invalids: ['a', 'b'], label: 'value' }\n                }]\n            ]);\n        });\n\n        it('merges two schemas (valid/invalid)', () => {\n\n            const a = Joi.string().valid('a').invalid('b');\n            const b = Joi.string().valid('b').invalid('a');\n\n            Helper.validate(a, [\n                ['a', true],\n                ['b', false, {\n                    message: '\"value\" must be [a]',\n                    path: [],\n                    type: 'any.only',\n                    context: { value: 'b', valids: ['a'], label: 'value' }\n                }]\n            ]);\n\n            Helper.validate(b, [\n                ['b', true],\n                ['a', false, {\n                    message: '\"value\" must be [b]',\n                    path: [],\n                    type: 'any.only',\n                    context: { value: 'a', valids: ['b'], label: 'value' }\n                }]\n            ]);\n\n            Helper.validate(a.concat(b), [\n                ['b', true],\n                ['a', false, {\n                    message: '\"value\" must be [b]',\n                    path: [],\n                    type: 'any.only',\n                    context: { value: 'a', valids: ['b'], label: 'value' }\n                }]\n            ]);\n        });\n\n        it('merges two schemas (tests)', () => {\n\n            const a = Joi.number().min(5);\n            const b = Joi.number().max(10);\n\n            Helper.validate(a, [\n                [4, false, {\n                    message: '\"value\" must be greater than or equal to 5',\n                    path: [],\n                    type: 'number.min',\n                    context: { limit: 5, value: 4, label: 'value' }\n                }],\n                [11, true]\n            ]);\n\n            Helper.validate(b, [\n                [6, true],\n                [11, false, {\n                    message: '\"value\" must be less than or equal to 10',\n                    path: [],\n                    type: 'number.max',\n                    context: { limit: 10, value: 11, label: 'value' }\n                }]\n            ]);\n\n            Helper.validate(a.concat(b), [\n                [4, false, {\n                    message: '\"value\" must be greater than or equal to 5',\n                    path: [],\n                    type: 'number.min',\n                    context: { limit: 5, value: 4, label: 'value' }\n                }],\n                [6, true],\n                [11, false, {\n                    message: '\"value\" must be less than or equal to 10',\n                    path: [],\n                    type: 'number.max',\n                    context: { limit: 10, value: 11, label: 'value' }\n                }]\n            ]);\n        });\n\n        it('merges two schemas (overlap rules)', () => {\n\n            const a = Joi.number().min(5);\n            const b = Joi.number().min(10);\n\n            Helper.validate(a.concat(b), [\n                [11, true],\n                [4, false, {\n                    message: '\"value\" must be greater than or equal to 10',\n                    path: [],\n                    type: 'number.min',\n                    context: { limit: 10, value: 4, label: 'value' }\n                }],\n                [6, false, {\n                    message: '\"value\" must be greater than or equal to 10',\n                    path: [],\n                    type: 'number.min',\n                    context: { limit: 10, value: 6, label: 'value' }\n                }]\n            ]);\n        });\n\n        it('merges two schemas (flags)', () => {\n\n            const a = Joi.string().valid('a');\n            const b = Joi.string().insensitive();\n\n            Helper.validate(a, [\n                ['a', true],\n                ['A', false, {\n                    message: '\"value\" must be [a]',\n                    path: [],\n                    type: 'any.only',\n                    context: { value: 'A', valids: ['a'], label: 'value' }\n                }],\n                ['b', false, {\n                    message: '\"value\" must be [a]',\n                    path: [],\n                    type: 'any.only',\n                    context: { value: 'b', valids: ['a'], label: 'value' }\n                }]\n            ]);\n\n            Helper.validate(a.concat(b), [\n                ['a', true, 'a'],\n                ['A', true, 'a'],\n                ['b', false, {\n                    message: '\"value\" must be [a]',\n                    path: [],\n                    type: 'any.only',\n                    context: { value: 'b', valids: ['a'], label: 'value' }\n                }]\n            ]);\n        });\n\n        it('merges two schemas (flags with empty on both sides)', () => {\n\n            const a = Joi.string().valid('a').empty('');\n            const b = Joi.string().insensitive().empty(' ');\n\n            Helper.validate(a, [\n                ['a', true],\n                ['A', false, {\n                    message: '\"value\" must be [a]',\n                    path: [],\n                    type: 'any.only',\n                    context: { value: 'A', valids: ['a'], label: 'value' }\n                }],\n                ['b', false, {\n                    message: '\"value\" must be [a]',\n                    path: [],\n                    type: 'any.only',\n                    context: { value: 'b', valids: ['a'], label: 'value' }\n                }],\n                ['', true, undefined],\n                [' ', false, {\n                    message: '\"value\" must be [a]',\n                    path: [],\n                    type: 'any.only',\n                    context: { value: ' ', valids: ['a'], label: 'value' }\n                }]\n            ]);\n\n            const ab = a.concat(b);\n            Helper.validate(ab, [\n                ['a', true, 'a'],\n                ['A', true, 'a'],\n                ['b', false, {\n                    message: '\"value\" must be [a]',\n                    path: [],\n                    type: 'any.only',\n                    context: { value: 'b', valids: ['a'], label: 'value' }\n                }],\n                ['', true, undefined],\n                [' ', true, undefined]\n            ]);\n\n            expect(ab.describe()).to.equal({\n                type: 'string',\n                flags: {\n                    only: true,\n                    empty: {\n                        type: 'any',\n                        flags: { only: true },\n                        allow: ['', ' ']\n                    },\n                    insensitive: true\n                },\n                allow: ['a']\n            });\n        });\n\n        it('merges two schemas (flags with empty on one side)', () => {\n\n            const a = Joi.string().valid('a').empty('');\n            const b = Joi.string().insensitive();\n\n            Helper.validate(a, [\n                ['a', true],\n                ['A', false, {\n                    message: '\"value\" must be [a]',\n                    path: [],\n                    type: 'any.only',\n                    context: { value: 'A', valids: ['a'], label: 'value' }\n                }],\n                ['b', false, {\n                    message: '\"value\" must be [a]',\n                    path: [],\n                    type: 'any.only',\n                    context: { value: 'b', valids: ['a'], label: 'value' }\n                }],\n                ['', true, undefined],\n                [' ', false, {\n                    message: '\"value\" must be [a]',\n                    path: [],\n                    type: 'any.only',\n                    context: { value: ' ', valids: ['a'], label: 'value' }\n                }]\n            ]);\n\n            const ab = a.concat(b);\n            Helper.validate(ab, [\n                ['a', true, 'a'],\n                ['A', true, 'a'],\n                ['b', false, {\n                    message: '\"value\" must be [a]',\n                    path: [],\n                    type: 'any.only',\n                    context: { value: 'b', valids: ['a'], label: 'value' }\n                }],\n                ['', true, undefined],\n                [' ', false, {\n                    message: '\"value\" must be [a]',\n                    path: [],\n                    type: 'any.only',\n                    context: { value: ' ', valids: ['a'], label: 'value' }\n                }]\n            ]);\n\n            expect(ab.describe()).to.equal({\n                type: 'string',\n                flags: {\n                    only: true,\n                    empty: {\n                        type: 'any',\n                        flags: { only: true },\n                        allow: ['']\n                    },\n                    insensitive: true\n                },\n                allow: ['a']\n            });\n\n            const ba = b.concat(a);\n            Helper.validate(ba, [\n                ['a', true, 'a'],\n                ['A', true, 'a'],\n                ['b', false, {\n                    message: '\"value\" must be [a]',\n                    path: [],\n                    type: 'any.only',\n                    context: { value: 'b', valids: ['a'], label: 'value' }\n                }],\n                ['', true, undefined],\n                [' ', false, {\n                    message: '\"value\" must be [a]',\n                    path: [],\n                    type: 'any.only',\n                    context: { value: ' ', valids: ['a'], label: 'value' }\n                }]\n            ]);\n\n            expect(ba.describe()).to.equal({\n                type: 'string',\n                flags: {\n                    only: true,\n                    empty: {\n                        type: 'any',\n                        flags: { only: true },\n                        allow: ['']\n                    },\n                    insensitive: true\n                },\n                allow: ['a']\n            });\n        });\n\n        it('overrides and append information', () => {\n\n            const a = Joi.any().description('a').unit('a').tag('a').example('a');\n            const b = Joi.any().description('b').unit('b').tag('b').example('b');\n\n            const desc = a.concat(b).describe();\n            expect(desc).to.equal({\n                type: 'any',\n                tags: ['a', 'b'],\n                examples: ['a', 'b'],\n                flags: {\n                    description: 'b',\n                    unit: 'b'\n                }\n            });\n        });\n\n        it('merges two objects (any key + specific key)', () => {\n\n            const a = Joi.object();\n            const b = Joi.object({ b: 1 });\n\n            Helper.validate(a, [\n                [{ b: 1 }, true],\n                [{ b: 2 }, true]\n            ]);\n\n            Helper.validate(b, [\n                [{ b: 1 }, true],\n                [{ b: 2 }, false, {\n                    message: '\"b\" must be [1]',\n                    path: ['b'],\n                    type: 'any.only',\n                    context: { value: 2, valids: [1], label: 'b', key: 'b' }\n                }]\n            ]);\n\n            Helper.validate(a.concat(b), [\n                [{ b: 1 }, true],\n                [{ b: 2 }, false, {\n                    message: '\"b\" must be [1]',\n                    path: ['b'],\n                    type: 'any.only',\n                    context: { value: 2, valids: [1], label: 'b', key: 'b' }\n                }]\n            ]);\n\n            Helper.validate(b.concat(a), [\n                [{ b: 1 }, true],\n                [{ b: 2 }, false, {\n                    message: '\"b\" must be [1]',\n                    path: ['b'],\n                    type: 'any.only',\n                    context: { value: 2, valids: [1], label: 'b', key: 'b' }\n                }]\n            ]);\n        });\n\n        it('merges two objects (no key + any key)', () => {\n\n            const a = Joi.object({});\n            const b = Joi.object();\n\n            Helper.validate(a, [\n                [{}, true],\n                [{ b: 2 }, false, {\n                    message: '\"b\" is not allowed',\n                    path: ['b'],\n                    type: 'object.unknown',\n                    context: { child: 'b', label: 'b', key: 'b', value: 2 }\n                }]\n            ]);\n\n            Helper.validate(b, [\n                [{}, true],\n                [{ b: 2 }, true]\n            ]);\n\n            Helper.validate(a.concat(b), [\n                [{}, true],\n                [{ b: 2 }, false, {\n                    message: '\"b\" is not allowed',\n                    path: ['b'],\n                    type: 'object.unknown',\n                    context: { child: 'b', label: 'b', key: 'b', value: 2 }\n                }]\n            ]);\n\n            Helper.validate(b.concat(a), [\n                [{}, true],\n                [{ b: 2 }, false, {\n                    message: '\"b\" is not allowed',\n                    path: ['b'],\n                    type: 'object.unknown',\n                    context: { child: 'b', label: 'b', key: 'b', value: 2 }\n                }]\n            ]);\n        });\n\n        it('merges two objects (key + key)', () => {\n\n            const a = Joi.object({ a: 1 });\n            const b = Joi.object({ b: 2 });\n\n            Helper.validate(a, [\n                [{ a: 1 }, true],\n                [{ b: 2 }, false, {\n                    message: '\"b\" is not allowed',\n                    path: ['b'],\n                    type: 'object.unknown',\n                    context: { child: 'b', label: 'b', key: 'b', value: 2 }\n                }]\n            ]);\n\n            Helper.validate(b, [\n                [{ a: 1 }, false, {\n                    message: '\"a\" is not allowed',\n                    path: ['a'],\n                    type: 'object.unknown',\n                    context: { child: 'a', label: 'a', key: 'a', value: 1 }\n                }],\n                [{ b: 2 }, true]\n            ]);\n\n            Helper.validate(a.concat(b), [\n                [{ a: 1 }, true],\n                [{ b: 2 }, true]\n            ]);\n\n            Helper.validate(b.concat(a), [\n                [{ a: 1 }, true],\n                [{ b: 2 }, true]\n            ]);\n        });\n\n        it('merges two objects (renames)', () => {\n\n            const a = Joi.object({ a: 1 }).rename('c', 'a');\n            const b = Joi.object({ b: 2 }).rename('d', 'b');\n\n            const schema = a.concat(b);\n            Helper.validate(schema, [\n                [{ c: 1, d: 2 }, true, { a: 1, b: 2 }]\n            ]);\n        });\n\n        it('merges two objects (deps)', () => {\n\n            const a = Joi.object({ a: 1 });\n            const b = Joi.object({ b: 2 }).and('b', 'a');\n\n            const schema = a.concat(b);\n            Helper.validate(schema, [\n                [{ a: 1, b: 2 }, true]\n            ]);\n        });\n\n        it('merges two objects (same key)', () => {\n\n            const a = Joi.object({ a: Joi.valid(1), b: Joi.valid(2), c: Joi.valid(3) });\n            const b = Joi.object({ b: Joi.valid(1), c: Joi.valid(2), a: Joi.valid(3) });\n\n            const ab = a.concat(b);\n\n            Helper.validate(a, [\n                [{ a: 1, b: 2, c: 3 }, true],\n                [{ a: 3, b: 1, c: 2 }, false, {\n                    message: '\"a\" must be [1]',\n                    path: ['a'],\n                    type: 'any.only',\n                    context: { value: 3, valids: [1], label: 'a', key: 'a' }\n                }]\n            ]);\n\n            Helper.validate(b, [\n                [{ a: 1, b: 2, c: 3 }, false, {\n                    message: '\"b\" must be [1]',\n                    path: ['b'],\n                    type: 'any.only',\n                    context: { value: 2, valids: [1], label: 'b', key: 'b' }\n                }],\n                [{ a: 3, b: 1, c: 2 }, true]\n            ]);\n\n            Helper.validate(ab, [\n                [{ a: 1, b: 2, c: 3 }, true],\n                [{ a: 3, b: 1, c: 2 }, true],\n                [{ a: 1, b: 2, c: 2 }, true],\n                [{ a: 1, b: 2, c: 4 }, false, {\n                    message: '\"c\" must be one of [3, 2]',\n                    path: ['c'],\n                    type: 'any.only',\n                    context: { value: 4, valids: [3, 2], label: 'c', key: 'c' }\n                }]\n            ]);\n        });\n\n        it('merges two objects (same key, implicit override)', () => {\n\n            const a = Joi.object({ a: 1, b: 2, c: 3 });\n            const b = Joi.object({ b: 1, c: 2, a: 3 });\n\n            const ab = a.concat(b);\n\n            Helper.validate(a, [\n                [{ a: 1, b: 2, c: 3 }, true],\n                [{ a: 3, b: 1, c: 2 }, false, {\n                    message: '\"a\" must be [1]',\n                    path: ['a'],\n                    type: 'any.only',\n                    context: { value: 3, valids: [1], label: 'a', key: 'a' }\n                }]\n            ]);\n\n            Helper.validate(b, [\n                [{ a: 3, b: 1, c: 2 }, true],\n                [{ a: 1, b: 2, c: 3 }, false, {\n                    message: '\"b\" must be [1]',\n                    path: ['b'],\n                    type: 'any.only',\n                    context: { value: 2, valids: [1], label: 'b', key: 'b' }\n                }]\n            ]);\n\n            Helper.validate(ab, [\n                [{ a: 3, b: 1, c: 2 }, true],\n                [{ b: 2, c: 3 }, false, {\n                    message: '\"b\" must be [1]',\n                    path: ['b'],\n                    type: 'any.only',\n                    context: { value: 2, valids: [1], label: 'b', key: 'b' }\n                }],\n                [{ a: 1, b: 2, c: 3 }, false, {\n                    message: '\"a\" must be [3]',\n                    path: ['a'],\n                    type: 'any.only',\n                    context: { value: 1, valids: [3], label: 'a', key: 'a' }\n                }]\n            ]);\n        });\n\n        it('merges two objects (same key, explicit override)', () => {\n\n            const a = Joi.object({ a: Joi.valid(Joi.override, 1), b: Joi.valid(Joi.override, 2), c: Joi.valid(Joi.override, 3) });\n            const b = Joi.object({ b: Joi.valid(Joi.override, 1), c: Joi.valid(Joi.override, 2), a: Joi.valid(Joi.override, 3) });\n\n            const ab = a.concat(b);\n\n            Helper.validate(a, [\n                [{ a: 1, b: 2, c: 3 }, true],\n                [{ a: 3, b: 1, c: 2 }, false, {\n                    message: '\"a\" must be [1]',\n                    path: ['a'],\n                    type: 'any.only',\n                    context: { value: 3, valids: [1], label: 'a', key: 'a' }\n                }]\n            ]);\n\n            Helper.validate(b, [\n                [{ a: 3, b: 1, c: 2 }, true],\n                [{ a: 1, b: 2, c: 3 }, false, {\n                    message: '\"b\" must be [1]',\n                    path: ['b'],\n                    type: 'any.only',\n                    context: { value: 2, valids: [1], label: 'b', key: 'b' }\n                }]\n            ]);\n\n            Helper.validate(ab, [\n                [{ a: 3, b: 1, c: 2 }, true],\n                [{ b: 2, c: 3 }, false, {\n                    message: '\"b\" must be [1]',\n                    path: ['b'],\n                    type: 'any.only',\n                    context: { value: 2, valids: [1], label: 'b', key: 'b' }\n                }],\n                [{ a: 1, b: 2, c: 3 }, false, {\n                    message: '\"a\" must be [3]',\n                    path: ['a'],\n                    type: 'any.only',\n                    context: { value: 1, valids: [3], label: 'a', key: 'a' }\n                }]\n            ]);\n        });\n\n        it('throws when schema key types do not match', () => {\n\n            const a = Joi.object({ a: Joi.number() });\n            const b = Joi.object({ a: Joi.string() });\n\n            expect(() => a.concat(b)).to.throw('Cannot merge type number with another type: string');\n        });\n\n        it('merges two alternatives with references', () => {\n\n            const ref1 = Joi.ref('a.c');\n            const ref2 = Joi.ref('c');\n            const schema = Joi.object({\n                a: { c: Joi.number() },\n                b: Joi.alternatives(ref1).concat(Joi.alternatives(ref2)),\n                c: Joi.number()\n            });\n\n            Helper.validate(schema, [\n                [{ a: {} }, true],\n                [{ a: { c: '5' }, b: 5 }, true, { a: { c: 5 }, b: 5 }],\n                [{ a: { c: '5' }, b: 6, c: '6' }, true, { a: { c: 5 }, b: 6, c: 6 }],\n                [{ a: { c: '5' }, b: 7, c: '6' }, false, {\n                    message: '\"b\" must be one of [ref:a.c, ref:c]',\n                    type: 'alternatives.types',\n                    path: ['b'],\n                    context: {\n                        key: 'b',\n                        label: 'b',\n                        value: 7,\n                        types: [ref1, ref2]\n                    }\n                }]\n            ]);\n        });\n\n        it('merges meta properly', () => {\n\n            const metaA = { a: 1 };\n            const metaB = { b: 1 };\n            const a = Joi.any().meta(metaA);\n            const b = Joi.any().meta(metaB);\n            const c = Joi.any();\n            const d = Joi.any();\n\n            expect(a.concat(b).describe().metas).to.equal([{ a: 1 }, { b: 1 }]);\n            expect(a.concat(c).describe().metas).to.equal([metaA]);\n            expect(b.concat(c).describe().metas).to.equal([metaB]);\n            expect(c.concat(d).describe().metas).to.not.exist();\n        });\n\n        it('merges into an any', () => {\n\n            const a = Joi.any().required();\n            const b = Joi.number().valid(0);\n\n            expect(() => a.concat(b)).to.not.throw();\n\n            const schema = a.concat(b);\n            Helper.validate(schema, [\n                [undefined, false, {\n                    message: '\"value\" is required',\n                    path: [],\n                    type: 'any.required',\n                    context: { label: 'value' }\n                }],\n                [1, false, {\n                    message: '\"value\" must be [0]',\n                    path: [],\n                    type: 'any.only',\n                    context: { value: 1, valids: [0], label: 'value' }\n                }]\n            ]);\n        });\n    });\n\n    describe('default()', () => {\n\n        it('sets the value', () => {\n\n            const schema = Joi.object({ foo: Joi.string().default('test') });\n            Helper.validate(schema, [\n                [{}, true, { foo: 'test' }]\n            ]);\n        });\n\n        it('allows passing description as a property of a default method', () => {\n\n            const defaultFn = function () {\n\n                return 'test';\n            };\n\n            defaultFn.description = 'test';\n\n            expect(() => Joi.object({ foo: Joi.string().default(defaultFn) })).to.not.throw();\n        });\n\n        it('sets the value when passing a method', () => {\n\n            const schema = Joi.object({\n                foo: Joi.string().default(() => {\n\n                    return 'test';\n                })\n            });\n\n            Helper.validate(schema, [\n                [{}, true, { foo: 'test' }]\n            ]);\n        });\n\n        it('executes the default method each time validate is called', () => {\n\n            let count = 0;\n            const schema = Joi.object({\n                foo: Joi.number().default(() => ++count)\n            });\n\n            const input = {};\n            expect(schema.validate(input)).to.equal({ value: { foo: 1 } });\n            expect(schema.validate(input)).to.equal({ value: { foo: 2 } });\n        });\n\n        it('passes a clone of the parent if the default method accepts an argument', () => {\n\n            const schema = Joi.object({\n                foo: Joi.string().default((parent, { prefs }) => {\n\n                    expect(prefs.abortEarly).to.be.true();\n                    return parent.bar + 'ing';\n                }),\n                bar: Joi.string()\n            });\n\n            Helper.validate(schema, [\n                [{ bar: 'test' }, true, { bar: 'test', foo: 'testing' }]\n            ]);\n        });\n\n        it('does not modify the original object when modifying the clone in a default method', () => {\n\n            const defaultFn = function (parent) {\n\n                parent.bar = 'broken';\n                return 'test';\n            };\n\n            defaultFn.description = 'testing';\n\n            const schema = Joi.object({\n                foo: Joi.string().default(defaultFn),\n                bar: Joi.string()\n            });\n\n            Helper.validate(schema, [\n                [{ bar: 'test' }, true, { bar: 'test', foo: 'test' }]\n            ]);\n        });\n\n        it('passes undefined as the parent if the default method has no parent', () => {\n\n            let c;\n            let methodCalled = false;\n            const schema = Joi.string().default((parent) => {\n\n                methodCalled = true;\n                c = parent;\n                return 'test';\n            });\n\n            Helper.validate(schema, [\n                [undefined, true, 'test']\n            ]);\n\n            expect(methodCalled).to.equal(true);\n            expect(c).to.equal(undefined);\n        });\n\n        it('sets literal function default', () => {\n\n            const func = () => 'just a function';\n            const schema = Joi.function().default(func, { literal: true });\n\n            Helper.validate(schema, [\n                [undefined, true, func]\n            ]);\n        });\n\n        it('allows passing a method that generates a default method when validating a function', () => {\n\n            const defaultFn = function () {\n\n                return 'just a function';\n            };\n\n            const defaultGeneratorFn = function () {\n\n                return defaultFn;\n            };\n\n            const schema = Joi.function().default(defaultGeneratorFn);\n            Helper.validate(schema, [[undefined, true, defaultFn]]);\n        });\n\n        it('allows passing a ref as a default without a description', () => {\n\n            const schema = Joi.object({\n                a: Joi.string(),\n                b: Joi.string().default(Joi.ref('a'))\n            });\n\n            Helper.validate(schema, [[{ a: 'test' }, true, { a: 'test', b: 'test' }]]);\n        });\n\n        it('ignores description when passing a ref as a default', () => {\n\n            const schema = Joi.object({\n                a: Joi.string(),\n                b: Joi.string().default(Joi.ref('a'))\n            });\n\n            Helper.validate(schema, [[{ a: 'test' }, true, { a: 'test', b: 'test' }]]);\n        });\n\n        it('catches errors in default methods', () => {\n\n            const error = new Error('boom');\n            const defaultFn = function () {\n\n                throw error;\n            };\n\n            defaultFn.description = 'broken method';\n\n            const schema = Joi.string().default(defaultFn);\n\n            Helper.validate(schema, [\n                [undefined, false, {\n                    message: '\"value\" threw an error when running default method',\n                    path: [],\n                    type: 'any.default',\n                    context: { error, label: 'value', value: null }\n                }]\n            ]);\n        });\n\n        it('should not override a value when value is given', () => {\n\n            const schema = Joi.object({ foo: Joi.string().default('bar') });\n            const input = { foo: 'test' };\n            Helper.validate(schema, [[input, true, { foo: 'test' }]]);\n        });\n\n        it('sets value based on condition (outer)', () => {\n\n            const schema = Joi.object({\n                a: Joi.boolean(),\n                b: Joi.boolean()\n                    .default(false)\n                    .when('a', { is: true, then: Joi.required(), otherwise: Joi.forbidden() })\n            });\n\n            Helper.validate(schema, [[{ a: false }, true, { a: false, b: false }]]);\n        });\n\n        it('sets value based on condition (inner)', () => {\n\n            const schema = Joi.object({\n                a: Joi.boolean(),\n                b: Joi.boolean().when('a', { is: true, then: Joi.any().default(false), otherwise: Joi.forbidden() })\n            });\n\n            Helper.validate(schema, [[{ a: true }, true, { a: true, b: false }]]);\n        });\n\n        it('creates deep defaults', () => {\n\n            const schema = Joi.object({\n                a: Joi.number().default(42),\n                b: Joi.object({\n                    c: Joi.boolean().default(true),\n                    d: Joi.string()\n                })\n                    .default()\n            })\n                .default();\n\n            Helper.validate(schema, [\n                [undefined, true, { a: 42, b: { c: true } }],\n                [{ a: 24 }, true, { a: 24, b: { c: true } }]\n            ]);\n        });\n\n        it('errors when missing value on non-object schemas', () => {\n\n            expect(() => Joi.number().default()).to.throw('Missing default value');\n        });\n\n        it('should set default value as a clone', () => {\n\n            const defaultValue = { bar: 'val' };\n            const schema = Joi.object({ foo: Joi.object().default(defaultValue) });\n            const input = {};\n\n            const value = schema.validate(input).value;\n            expect(value.foo).to.not.shallow.equal(defaultValue);\n            expect(value.foo).to.only.include({ bar: 'val' });\n\n            value.foo.bar = 'mutated';\n\n            const value2 = schema.validate(input).value;\n            expect(value2.foo).to.not.shallow.equal(defaultValue);\n            expect(value2.foo).to.only.include({ bar: 'val' });\n        });\n\n        it('should not apply default values if the noDefaults option is enquire', () => {\n\n            const schema = Joi.object({\n                a: Joi.string().default('foo'),\n                b: Joi.number()\n            });\n\n            const input = { b: 42 };\n            Helper.validate(schema, { noDefaults: true }, [[input, true, { b: 42 }]]);\n        });\n\n        it('should not apply default values from functions if the noDefaults option is enquire', () => {\n\n            const func = function () {\n\n                return 'foo';\n            };\n\n            func.description = 'test parameter';\n\n            const schema = Joi.object({\n                a: Joi.string().default(func),\n                b: Joi.number()\n            });\n\n            const input = { b: 42 };\n            Helper.validate(schema, { noDefaults: true }, [[input, true, { b: 42 }]]);\n        });\n\n        it('should not apply default values from references if the noDefaults option is enquire', () => {\n\n            const schema = Joi.object({\n                a: Joi.string().default(Joi.ref('b')),\n                b: Joi.number()\n            });\n\n            const input = { b: 42 };\n            Helper.validate(schema, { noDefaults: true }, [[input, true, { b: 42 }]]);\n        });\n\n        it('should be able to support both empty and noDefaults', () => {\n\n            const schema = Joi.object({\n                a: Joi.string().empty('foo').default('bar'),\n                b: Joi.number()\n            });\n\n            const input = { a: 'foo', b: 42 };\n            Helper.validate(schema, { noDefaults: true }, [[input, true, { b: 42 }]]);\n        });\n    });\n\n    describe('description()', () => {\n\n        it('sets the description', () => {\n\n            const b = Joi.any().description('my description');\n            expect(b.describe().flags.description).to.equal('my description');\n        });\n\n        it('throws when description is missing', () => {\n\n            expect(() => Joi.any().description()).to.throw('Description must be a non-empty string');\n        });\n    });\n\n    describe('empty()', () => {\n\n        it('should void values when considered empty', () => {\n\n            const schema = Joi.string().empty('');\n            Helper.validate(schema, [\n                [undefined, true, undefined],\n                ['abc', true, 'abc'],\n                ['', true, undefined]\n            ]);\n        });\n\n        it('should void values with trim', () => {\n\n            const schema = Joi.string().empty('').trim();\n            Helper.validate(schema, [\n                [undefined, true, undefined],\n                ['abc', true, 'abc'],\n                ['', true, undefined],\n                [' ', true, undefined],\n                ['       ', true, undefined],\n                [42, false, {\n                    message: '\"value\" must be a string',\n                    path: [],\n                    type: 'string.base',\n                    context: { value: 42, label: 'value' }\n                }]\n            ]);\n\n            Helper.validate(schema.trim(false), [\n                [undefined, true, undefined],\n                ['abc', true, 'abc'],\n                ['', true, undefined],\n                [' ', true, ' ']\n            ]);\n        });\n\n        it('should override any previous empty', () => {\n\n            const schema = Joi.string().empty('').empty('abc');\n            Helper.validate(schema, [\n                [undefined, true, undefined],\n                ['abc', true, undefined],\n                ['', false, {\n                    message: '\"value\" is not allowed to be empty',\n                    path: [],\n                    type: 'string.empty',\n                    context: { value: '', label: 'value' }\n                }],\n                ['def', true, 'def']\n            ]);\n        });\n\n        it('should be possible to reset the empty value', () => {\n\n            const schema = Joi.string().empty('').empty();\n            Helper.validate(schema, [\n                [undefined, true, undefined],\n                ['abc', true, 'abc'],\n                ['', false, {\n                    message: '\"value\" is not allowed to be empty',\n                    path: [],\n                    type: 'string.empty',\n                    context: { value: '', label: 'value' }\n                }]\n            ]);\n        });\n\n        it('should have no effect if only reset is used', () => {\n\n            const schema = Joi.string().empty();\n            Helper.validate(schema, [\n                [undefined, true, undefined],\n                ['abc', true, 'abc'],\n                ['', false, {\n                    message: '\"value\" is not allowed to be empty',\n                    path: [],\n                    type: 'string.empty',\n                    context: { value: '', label: 'value' }\n                }]\n            ]);\n        });\n\n        it('should remove empty flag if only reset is used', () => {\n\n            const schema = Joi.string().empty('').empty();\n            expect(schema._flags.empty).to.not.exist();\n            expect(schema.describe().flags).to.not.exist();\n        });\n\n        it('should work with dependencies', () => {\n\n            const schema = Joi.object({\n                a: Joi.string().empty(''),\n                b: Joi.string().empty('')\n            }).or('a', 'b');\n\n            Helper.validate(schema, [\n                [{}, false, {\n                    message: '\"value\" must contain at least one of [a, b]',\n                    path: [],\n                    type: 'object.missing',\n                    context: {\n                        peers: ['a', 'b'],\n                        peersWithLabels: ['a', 'b'],\n                        label: 'value',\n                        value: {}\n                    }\n                }],\n                [{ a: '' }, false, {\n                    message: '\"value\" must contain at least one of [a, b]',\n                    path: [],\n                    type: 'object.missing',\n                    context: {\n                        peers: ['a', 'b'],\n                        peersWithLabels: ['a', 'b'],\n                        label: 'value',\n                        value: {}\n                    }\n                }],\n                [{ a: 'a' }, true, { a: 'a' }],\n                [{ a: '', b: 'b' }, true, { b: 'b' }]\n            ]);\n        });\n\n        it('supports references', () => {\n\n            const schema = Joi.object({\n                empty: Joi.string(),\n                a: Joi.string().empty(Joi.ref('empty'))\n            });\n\n            Helper.validate(schema, [\n                [{ empty: 'x', a: 'x' }, true, { empty: 'x' }],\n                [{ empty: 'y', a: 'x' }, true, { empty: 'y', a: 'x' }]\n            ]);\n        });\n    });\n\n    describe('equal()', () => {\n\n        it('validates valid values', () => {\n\n            Helper.validate(Joi.equal(4), [\n                [4, true],\n                [5, false, {\n                    message: '\"value\" must be [4]',\n                    path: [],\n                    type: 'any.only',\n                    context: { value: 5, valids: [4], label: 'value' }\n                }]\n            ]);\n        });\n    });\n\n    describe('error()', () => {\n\n        it('returns custom error', () => {\n\n            const schema = Joi.object({\n                a: Joi.string(),\n                b: {\n                    c: Joi.number().strict().error(new Error('Really wanted a number!'))\n                }\n            });\n\n            const err = schema.validate({ a: 'abc', b: { c: 'x' } }).error;\n            expect(err.isJoi).to.not.exist();\n            expect(err.message).to.equal('Really wanted a number!');\n            expect(err.details).to.not.exist();\n        });\n\n        it('returns first custom error with multiple errors', () => {\n\n            const schema = Joi.object({\n                a: Joi.string(),\n                b: {\n                    c: Joi.number().error(new Error('Really wanted a number!'))\n                }\n            }).prefs({ abortEarly: false });\n\n            const err = schema.validate({ a: 22, b: { c: 'x' } }).error;\n            expect(err.isJoi).to.not.exist();\n            expect(err.message).to.equal('Really wanted a number!');\n            expect(err.details).to.not.exist();\n        });\n\n        it('returns first error with multiple errors (first not custom)', () => {\n\n            const schema = Joi.object({\n                a: Joi.string(),\n                b: {\n                    c: Joi.number().error(new Error('Really wanted a number!'))\n                }\n            });\n\n            const err = schema.validate({ a: 22, b: { c: 'x' } }).error;\n            expect(err.isJoi).to.be.true();\n            expect(err.message).to.equal('\"a\" must be a string');\n            expect(err.details).to.equal([{\n                message: '\"a\" must be a string',\n                path: ['a'],\n                type: 'string.base',\n                context: { value: 22, label: 'a', key: 'a' }\n            }]);\n        });\n\n        it('errors on invalid error option', () => {\n\n            expect(() => {\n\n                Joi.object({\n                    a: Joi.string(),\n                    b: {\n                        c: Joi.number().error('Really wanted a number!')\n                    }\n                });\n            }).to.throw('Must provide a valid Error object or a function');\n        });\n\n        it('errors on missing error option', () => {\n\n            expect(() => {\n\n                Joi.object({\n                    a: Joi.string(),\n                    b: {\n                        c: Joi.number().error()\n                    }\n                });\n            }).to.throw('Missing error');\n        });\n\n        describe('with a function', () => {\n\n            it('replaces the error message with an error', () => {\n\n                const schema = Joi.object({\n                    a: Joi.string(),\n                    b: {\n                        c: Joi.number().strict().error(() => new Error('Really wanted a number!'))\n                    }\n                });\n\n                const err = schema.validate({ a: 'abc', b: { c: 'x' } }).error;\n                expect(err.isJoi).to.not.exist();\n                expect(err.message).to.equal('Really wanted a number!');\n            });\n\n            it('should be able to combine several error messages', () => {\n\n                const schema = Joi.object({\n                    a: Joi.string(),\n                    b: {\n                        c: Joi.number().min(0).integer().strict().error((errors) => {\n\n                            return new Error(errors.join(' and ')); // Automatic toString() of each error on join\n                        })\n                    }\n                });\n\n                const err = schema.validate({ a: 'abc', b: { c: -1.5 } }, { abortEarly: false }).error;\n                expect(err.isJoi).to.not.exist();\n                expect(err.message).to.equal('\"b.c\" must be greater than or equal to 0 and \"b.c\" must be an integer');\n            });\n\n            it('caches report message string', () => {\n\n                const schema = Joi.object({\n                    a: Joi.string(),\n                    b: {\n                        c: Joi.number().min(0).integer().strict().error((errors) => {\n\n                            for (const err of errors) {\n                                err.toString();\n                                err.message += '!';\n                            }\n\n                            return new Error(errors.join(' and ')); // Automatic toString() of each error on join\n                        })\n                    }\n                });\n\n                const err = schema.validate({ a: 'abc', b: { c: -1.5 } }, { abortEarly: false }).error;\n                expect(err.isJoi).to.not.exist();\n                expect(err.message).to.equal('\"b.c\" must be greater than or equal to 0! and \"b.c\" must be an integer!');\n            });\n\n            it('uses selected date format', () => {\n\n                const min = new Date('1974-05-07');\n\n                const schema = Joi.object({\n                    a: Joi.date().min(min).greater(min).error((errors) => {\n\n                        return new Error(errors.join(' and ')); // Automatic toString() of each error on join\n                    })\n                });\n\n                const err = schema.validate({ a: new Date('1973-01-01') }, { dateFormat: 'date', abortEarly: false }).error;\n                expect(err.isJoi).to.not.exist();\n                expect(err.message).to.equal(`\"a\" must be greater than or equal to \"${min.toDateString()}\" and \"a\" must be greater than \"${min.toDateString()}\"`);\n            });\n\n            it('should be able to combine several error messages using context', () => {\n\n                const schema = Joi.object({\n                    a: Joi.string(),\n                    b: {\n                        c: Joi.number().min(0).integer().strict().error((errors) => {\n\n                            const message = errors.reduce((memo, error) => {\n\n                                let text = memo ? ' && ' : '';\n                                switch (error.code) {\n                                    case 'number.base':\n                                        text += `\"${error.local.key}\" ∈ ℝ`;\n                                        break;\n                                    case 'number.min':\n                                        text += `\"${error.local.key}\" > ${error.local.limit}`;\n                                        break;\n                                    case 'number.integer':\n                                        text += `\"${error.local.key}\" ∈ ℤ`;\n                                        break;\n                                }\n\n                                return memo + text;\n                            }, '');\n\n                            return new Error(message);\n                        })\n                    }\n                });\n\n                const err = schema.validate({ a: 'abc', b: { c: -1.5 } }, { abortEarly: false }).error;\n                expect(err.isJoi).to.not.exist();\n                expect(err.message).to.equal('\"c\" > 0 && \"c\" ∈ ℤ');\n            });\n\n            it('should be able to return a javascript Error', () => {\n\n                const schema = Joi.object({\n                    a: Joi.string(),\n                    b: {\n                        c: Joi.number().min(0).integer().strict().error((errors) => new Error(`error of type ${errors[0].code}`))\n                    }\n                });\n\n                const err = schema.validate({ a: 'abc', b: { c: -1.5 } }, { abortEarly: false }).error;\n                expect(err).to.be.an.error('error of type number.min');\n                expect(err.isJoi).to.not.exist();\n                expect(err.details).to.not.exist();\n            });\n\n            it('handles multiple errors return value', () => {\n\n                const item = Joi.string()\n                    .trim()\n                    .regex(/^\\w*$/)\n                    .error((errors) => {\n\n                        const error = errors[0];\n                        if (error.code === 'string.pattern.base') {\n                            error.message = 'my new error message';\n                        }\n\n                        return errors;\n                    });\n\n                const schema = Joi.object({\n                    a: Joi.array().items(item.min(2).max(64)),\n                    b: item\n                });\n\n                expect(schema.validate({ a: [' xx', 'yy'], b: ' x' }).value).to.equal({ a: ['xx', 'yy'], b: 'x' });\n\n                const result1 = schema.validate({ a: [' xx', 'yy'], b: ' x?' });\n                expect(result1.value).to.equal({ a: ['xx', 'yy'], b: ' x?' });\n                expect(result1.error).to.be.an.error('my new error message');\n\n                const result2 = schema.validate({ a: [' xx', 'yy?'], b: ' x' });\n                expect(result2.value).to.equal({ a: [' xx', 'yy?'], b: ' x' });\n                expect(result2.error).to.be.an.error('my new error message');\n            });\n\n            it('errors on invalid error function', () => {\n\n                const schema = Joi.number().error(() => 'not an Error');\n\n                expect(() => schema.validate('x')).to.throw('error() must return an Error object');\n            });\n        });\n    });\n\n    describe('example()', () => {\n\n        it('sets an example', () => {\n\n            const schema = Joi.valid(5, 6, 7).example(5);\n            expect(schema.describe().examples).to.equal([5]);\n        });\n\n        it('appends examples', () => {\n\n            const schema = Joi.valid(5, 6, 7).example(4).example(5);\n            expect(schema.describe().examples).to.equal([4, 5]);\n        });\n\n        it('overrides example', () => {\n\n            const schema = Joi.valid(5, 6, 7).example(4).example(5, { override: true });\n            expect(schema.describe().examples).to.equal([5]);\n        });\n\n        it('does not flatten examples', () => {\n\n            const schema = Joi.array().items(5, 6, 7).example([5, 6]);\n            expect(schema.describe().examples).to.equal([[5, 6]]);\n        });\n\n        it('throws when examples are missing', () => {\n\n            expect(() => Joi.any().example()).to.throw('Missing example');\n        });\n    });\n\n    describe('exist()', () => {\n\n        it('validates required values', () => {\n\n            Helper.validate(Joi.exist(), [\n                [0, true],\n                [null, true],\n                ['', true],\n                [false, true],\n                [undefined, false, {\n                    message: '\"value\" is required',\n                    path: [],\n                    type: 'any.required',\n                    context: { label: 'value' }\n                }]\n            ]);\n        });\n    });\n\n    describe('external()', () => {\n\n        it('errors on invalid arguments', () => {\n\n            const method = () => null;\n            expect(() => Joi.any().external(method, '')).to.throw('Description must be a non-empty string');\n            expect(() => Joi.any().external(method, 0)).to.throw('Description must be a non-empty string');\n            expect(() => Joi.any().external(method, [])).to.throw('Description must be a non-empty string');\n            expect(() => Joi.any().external({ method }, 'text')).to.throw('Cannot combine options with description');\n        });\n\n        it('includes description in manifest', () => {\n\n            const method = () => null;\n            const schema = Joi.any().external(method, 'some description');\n            expect(schema.describe()).to.equal({\n                type: 'any',\n                externals: [\n                    {\n                        description: 'some description',\n                        method\n                    }\n                ]\n            });\n        });\n\n        it('does not run on invalid array items', async () => {\n\n            const schema = Joi.array().items(\n                Joi.string().min(5).external((value) => value + value),\n                Joi.string().external((value) => value + '-')\n            );\n\n            expect(await schema.validateAsync(['x'])).to.equal(['x-']);\n        });\n\n        it('does not run on invalid alternatives', async () => {\n\n            const schema = Joi.alternatives(\n                Joi.string().min(5).external((value) => value + value),\n                Joi.string().external((value) => value + '-')\n            );\n\n            expect(await schema.validateAsync('x')).to.equal('x-');\n        });\n\n        it('does not run on invalid alternatives with match mode \"one\"', async () => {\n\n            const schema = Joi.alternatives().try(\n                Joi.string().min(5).external((value) => value + value),\n                Joi.string().external((value) => value + '-')\n            ).match('one');\n\n            expect(await schema.validateAsync('x')).to.equal('x-');\n        });\n    });\n\n    describe('failover()', () => {\n\n        it('sets value on error', () => {\n\n            const schema = Joi.object({ x: Joi.number().default(1).failover(2) });\n\n            Helper.validate(schema, [\n                [{}, true, { x: 1 }],\n                [{ x: 3 }, true, { x: 3 }],\n                [{ x: [] }, true, { x: 2 }]\n            ]);\n        });\n    });\n\n    describe('forbidden()', () => {\n\n        it('validates forbidden', () => {\n\n            const schema = Joi.object({\n                a: Joi.number(),\n                b: Joi.forbidden()\n            });\n\n            Helper.validate(schema, [\n                [{ a: 5 }, true],\n                [{ a: 5, b: 6 }, false, {\n                    message: '\"b\" is not allowed',\n                    path: ['b'],\n                    type: 'any.unknown',\n                    context: { label: 'b', key: 'b', value: 6 }\n                }],\n                [{ a: 'a' }, false, {\n                    message: '\"a\" must be a number',\n                    path: ['a'],\n                    type: 'number.base',\n                    context: { label: 'a', key: 'a', value: 'a' }\n                }],\n                [{}, true],\n                [{ b: undefined }, true],\n                [{ b: null }, false, {\n                    message: '\"b\" is not allowed',\n                    path: ['b'],\n                    type: 'any.unknown',\n                    context: { label: 'b', key: 'b', value: null }\n                }]\n            ]);\n        });\n\n        it('avoids unnecessary cloning when called twice', () => {\n\n            const schema = Joi.any().forbidden();\n            expect(schema.forbidden()).to.shallow.equal(schema);\n        });\n    });\n\n    describe('invalid()', () => {\n\n        it('allows invalid values to be set', () => {\n\n            expect(() => Joi.any().valid(true, 1, 'hello', new Date(), Symbol('foo'))).not.to.throw();\n        });\n\n        it('throws when passed undefined', () => {\n\n            expect(() => {\n\n                Joi.any().invalid(undefined);\n            }).to.throw('Cannot call allow/valid/invalid with undefined');\n        });\n\n        it('preserves passed value when cloned', () => {\n\n            const o = {};\n            Helper.validate(Joi.object().invalid(o).clone(), [[o, false, '\"value\" contains an invalid value']]);\n            Helper.validate(Joi.object().invalid({}).clone(), [[{}, false, '\"value\" contains an invalid value']]);\n        });\n\n        it('errors on blocked schema', () => {\n\n            expect(() => Joi.any().valid(1).invalid(1)).to.throw('Setting invalid value 1 leaves schema rejecting all values due to previous valid rule');\n            expect(() => Joi.any().allow(1).invalid(1)).to.not.throw();\n        });\n\n        it('appends invalid values', () => {\n\n            const schema = Joi.any().invalid(1).invalid(2);\n            expect(schema.describe()).to.equal({ type: 'any', invalid: [1, 2] });\n        });\n\n        it('throws when override is not first item', () => {\n\n            expect(() => Joi.any().invalid(1, Joi.override)).to.throw('Override must be the first value');\n        });\n\n        it('overrides previous values', () => {\n\n            const schema = Joi.number().invalid(2).invalid(Joi.override, 1);\n            Helper.validate(schema, [\n                [1, false, '\"value\" contains an invalid value'],\n                [2, true, 2]\n            ]);\n        });\n\n        it('cancels previous values', () => {\n\n            const schema = Joi.number().invalid(2).invalid(Joi.override);\n            Helper.validate(schema, [\n                [1, true, 1],\n                [2, true, 2]\n            ]);\n        });\n\n        it('ignores empty override', () => {\n\n            const schema = Joi.invalid(Joi.override);\n            expect(schema._invalids).to.be.null();\n        });\n    });\n\n    describe('keep()', () => {\n\n        it('retains both unique rule instances', () => {\n\n            const schema = Joi.number()\n                .min(10).keep()\n                .min(100);\n\n            Helper.validate(schema, { abortEarly: false }, [[1, false, '\"value\" must be greater than or equal to 10. \"value\" must be greater than or equal to 100']]);\n        });\n\n        it('retains both unique rule instances in concat', () => {\n\n            const schema = Joi.number()\n                .min(10).keep()\n                .concat(Joi.number().min(100));\n\n            Helper.validate(schema, { abortEarly: false }, [[1, false, '\"value\" must be greater than or equal to 10. \"value\" must be greater than or equal to 100']]);\n        });\n    });\n\n    describe('label()', () => {\n\n        it('adds to existing options', () => {\n\n            const schema = Joi.object({ b: Joi.string().email().label('Custom label') });\n            const input = { b: 'not_a_valid_email' };\n            Helper.validate(schema, [[input, false, {\n                message: '\"Custom label\" must be a valid email',\n                path: ['b'],\n                type: 'string.email',\n                context: { value: 'not_a_valid_email', invalids: ['not_a_valid_email'], label: 'Custom label', key: 'b' }\n            }]]);\n        });\n\n        it('throws when label is missing', () => {\n\n            expect(() => {\n\n                Joi.any().label();\n            }).to.throw('Label name must be a non-empty string');\n        });\n\n        it('can describe a label', () => {\n\n            const schema = Joi.object().label('lbl').describe();\n            expect(schema).to.equal({ type: 'object', flags: { label: 'lbl' } });\n        });\n\n        it('does not leak into sub objects', () => {\n\n            const schema = Joi.object({ a: Joi.number() }).label('foo');\n            Helper.validate(schema, [[{ a: 'a' }, false, {\n                message: '\"a\" must be a number',\n                path: ['a'],\n                type: 'number.base',\n                context: { label: 'a', key: 'a', value: 'a' }\n            }]]);\n        });\n\n        it('does not leak into sub objects from an array', () => {\n\n            const schema = Joi.array().items(\n                Joi.object({ a: Joi.number() }).label('foo')\n            ).label('bar');\n\n            Helper.validate(schema, [[[{ a: 'a' }], false, {\n                message: '\"[0].a\" must be a number',\n                path: [0, 'a'],\n                type: 'number.base',\n                context: { label: '[0].a', key: 'a', value: 'a' }\n            }]]);\n        });\n\n        it('does not leak into unknown keys', () => {\n\n            const schema = Joi.object({ a: Joi.number() }).label('foo');\n            Helper.validate(schema, [[{ b: 'a' }, false, {\n                message: '\"b\" is not allowed',\n                path: ['b'],\n                type: 'object.unknown',\n                context: { child: 'b', label: 'b', key: 'b', value: 'a' }\n            }]]);\n        });\n\n        it('applies only to hierarchy edge', () => {\n\n            const schema = Joi.object({\n                a: Joi.object({\n                    b: Joi.object({\n                        c: Joi.number().label('C')\n                    }).label('B')\n                }).label('A')\n            });\n\n            Helper.validate(schema, [[{ a: { b: { c: 'x' } } }, false, {\n                message: '\"C\" must be a number',\n                path: ['a', 'b', 'c'],\n                type: 'number.base',\n                context: { label: 'C', value: 'x', key: 'c' }\n            }]]);\n        });\n    });\n\n    describe('message()', () => {\n\n        it('overrides message', () => {\n\n            const schema = Joi.number()\n                .min(10).message('way too small')\n                .max(100).message('way too big');\n\n            Helper.validate(schema, [\n                [1, false, 'way too small'],\n                [1000, false, 'way too big']\n            ]);\n        });\n\n        it('overrides message with template', () => {\n\n            const schema = Joi.number()\n                .min(10).message(Joi.x('way too small'));\n\n            Helper.validate(schema, [[1, false, 'way too small']]);\n        });\n\n        it('overrides message in multiple language', () => {\n\n            const messages = {\n                english: {\n                    root: 'value',\n                    'number.min': '{#label} too small'\n                },\n                latin: {\n                    root: 'valorem',\n                    'number.min': Joi.x('{@label} angustus', { prefix: { local: '@' } })\n                }\n            };\n\n            const schema = Joi.number().min(10).message(messages);\n\n            expect(schema.validate(1, { errors: { language: 'english' } }).error).to.be.an.error('\"value\" too small');\n            expect(schema.validate(1, { errors: { language: 'latin' } }).error).to.be.an.error('\"valorem\" angustus');\n            expect(schema.validate(1, { errors: { language: 'unknown' } }).error).to.be.an.error('\"value\" must be greater than or equal to 10');\n            expect(schema.label('special').validate(1, { errors: { language: 'english' } }).error).to.be.an.error('\"special\" too small');\n        });\n\n        it('overrides wildcard message in specific language', () => {\n\n            const messages = {\n                english: {\n                    root: 'value',\n                    'number.min': '{#label} too small',\n                    '*': '{#label} is something else'\n                }\n            };\n\n            const schema = Joi.number().min(10).max(11).messages(messages);\n\n            expect(schema.validate(1, { errors: { language: 'english' } }).error).to.be.an.error('\"value\" too small');\n            expect(schema.validate(12, { errors: { language: 'english' } }).error).to.be.an.error('\"value\" is something else');\n        });\n\n        it('overrides wildcard message', () => {\n\n            const messages = {\n                root: 'value',\n                'number.min': '{#label} too small',\n                '*': '{#label} is something else'\n            };\n\n            const schema = Joi.number().min(10).max(11).messages(messages);\n\n            expect(schema.validate(1).error).to.be.an.error('\"value\" too small');\n            expect(schema.validate(12).error).to.be.an.error('\"value\" is something else');\n        });\n\n        it('overrides message in multiple language (nested)', () => {\n\n            const messages = {\n                english: {\n                    root: 'value',\n                    'number.min': '{#label} too small'\n                },\n                latin: {\n                    root: 'valorem',\n                    'number.min': '{#label} angustus'\n                }\n            };\n\n            const schema = Joi.object({ a: Joi.number().min(10).message(messages) });\n\n            expect(schema.validate({ a: 1 }, { errors: { language: 'english' } }).error).to.be.an.error('\"a\" too small');\n            expect(schema.validate({ a: 1 }, { errors: { language: 'latin' } }).error).to.be.an.error('\"a\" angustus');\n            expect(schema.validate({ a: 1 }, { errors: { language: 'unknown' } }).error).to.be.an.error('\"a\" must be greater than or equal to 10');\n        });\n\n        it('overrides message in multiple language (flat)', () => {\n\n            const messages = {\n                root: 'valorem',\n                'number.min': '{#label} angustus'\n            };\n\n            const schema = Joi.object({ a: Joi.number().min(10).message(messages) });\n            Helper.validate(schema, [[{ a: 1 }, false, '\"a\" angustus']]);\n        });\n\n        it('overrides message in multiple language (flat template)', () => {\n\n            const messages = {\n                root: 'valorem',\n                'number.min': Joi.x('{@label} angustus', { prefix: { local: '@' } })\n            };\n\n            const schema = Joi.object({ a: Joi.number().min(10).message(messages) });\n            Helper.validate(schema, [[{ a: 1 }, false, '\"a\" angustus']]);\n        });\n\n        it('errors on invalid message value', () => {\n\n            expect(() => Joi.number().min(10).message(12)).to.throw('Invalid message options');\n            expect(() => Joi.number().min(10).message({ 'number.min': 12 })).to.throw('Invalid message for number.min');\n            expect(() => Joi.number().min(10).message({ english: { 'number.min': 12 } })).to.throw('Invalid message for number.min in english');\n        });\n    });\n\n    describe('meta()', () => {\n\n        it('sets the meta', () => {\n\n            const meta = { prop: 'val', prop2: 3 };\n            let b = Joi.any().meta(meta);\n            expect(b.describe().metas).to.equal([meta]);\n\n            b = b.meta({ other: true });\n            expect(b.describe().metas).to.equal([meta, {\n                other: true\n            }]);\n\n        });\n\n        it('throws when meta is missing', () => {\n\n            expect(() => Joi.any().meta()).to.throw('Meta cannot be undefined');\n        });\n    });\n\n    describe('not()', () => {\n\n        it('validates invalid values', () => {\n\n            Helper.validate(Joi.not(5), [\n                [4, true],\n                [5, false, {\n                    message: '\"value\" contains an invalid value',\n                    path: [],\n                    type: 'any.invalid',\n                    context: { value: 5, invalids: [5], label: 'value' }\n                }]\n            ]);\n        });\n    });\n\n    describe('note()', () => {\n\n        it('sets notes', () => {\n\n            const b = Joi.any().note('a').note('my notes');\n            expect(b.describe().notes).to.equal(['a', 'my notes']);\n        });\n\n        it('throws when notes are missing', () => {\n\n            expect(() => Joi.any().note()).to.throw('Missing notes');\n        });\n\n        it('throws when notes are invalid', () => {\n\n            expect(() => Joi.any().note(5)).to.throw('Notes must be non-empty strings');\n            expect(() => Joi.any().note('')).to.throw('Notes must be non-empty strings');\n        });\n    });\n\n    describe('only()', () => {\n\n        it('allows only allowed values', () => {\n\n            const schema = Joi.number().allow(1, 'x').only();\n\n            Helper.validate(schema, [\n                [1, true],\n                ['x', true],\n                [2, false, '\"value\" must be one of [1, x]']\n            ]);\n        });\n    });\n\n    describe('optional()', () => {\n\n        it('validates optional with default required', () => {\n\n            const schema = Joi.object({\n                a: Joi.any(),\n                b: Joi.any(),\n                c: {\n                    d: Joi.any()\n                }\n            }).prefs({ presence: 'required' });\n\n            Helper.validate(schema, [\n                [{ a: 5 }, false, {\n                    message: '\"b\" is required',\n                    path: ['b'],\n                    type: 'any.required',\n                    context: { label: 'b', key: 'b' }\n                }],\n                [{ a: 5, b: 6 }, false, {\n                    message: '\"c\" is required',\n                    path: ['c'],\n                    type: 'any.required',\n                    context: { label: 'c', key: 'c' }\n                }],\n                [{ a: 5, b: 6, c: {} }, false, {\n                    message: '\"c.d\" is required',\n                    path: ['c', 'd'],\n                    type: 'any.required',\n                    context: { label: 'c.d', key: 'd' }\n                }],\n                [{ a: 5, b: 6, c: { d: 7 } }, true],\n                [{}, false, {\n                    message: '\"a\" is required',\n                    path: ['a'],\n                    type: 'any.required',\n                    context: { label: 'a', key: 'a' }\n                }],\n                [{ b: 5 }, false, {\n                    message: '\"a\" is required',\n                    path: ['a'],\n                    type: 'any.required',\n                    context: { label: 'a', key: 'a' }\n                }]\n            ]);\n        });\n\n        it('avoids unnecessary cloning when called twice', () => {\n\n            const schema = Joi.any().optional();\n            expect(schema.optional()).to.shallow.equal(schema);\n        });\n    });\n\n    describe('options()', () => {\n\n        it('does not modify provided options', () => {\n\n            const options = { convert: true };\n            const schema = Joi.object().prefs(options);\n            schema.validate({});\n            expect(options).to.equal({ convert: true });\n\n            const options2 = { convert: false };\n            schema.validate({}, options2);\n            expect(options).to.equal({ convert: true });\n            expect(options2).to.equal({ convert: false });\n        });\n\n        it('adds to existing options', () => {\n\n            const schema = Joi.object({ b: Joi.number().strict().prefs({ convert: true }) });\n            const input = { b: '2' };\n            Helper.validate(schema, [[input, true, { b: 2 }]]);\n        });\n\n        it('throws with an invalid option', () => {\n\n            expect(() => Joi.any().prefs({ foo: 'bar' })).to.throw('\"foo\" is not allowed');\n        });\n\n        it('throws with an invalid option type', () => {\n\n            expect(() => Joi.any().prefs({ convert: 'yes' })).to.throw('\"convert\" must be a boolean');\n        });\n\n        it('throws with an invalid option value', () => {\n\n            expect(() => Joi.any().prefs({ presence: 'yes' })).to.throw('\"presence\" must be one of [required, optional, forbidden]');\n        });\n\n        it('does not throw with multiple options including presence key', () => {\n\n            expect(() => Joi.any().prefs({ presence: 'optional', noDefaults: true })).to.not.throw();\n        });\n\n        it('describes a schema with options', () => {\n\n            const schema = Joi.any().prefs({ abortEarly: false, convert: false });\n            expect(schema.describe()).to.equal({ type: 'any', preferences: { abortEarly: false, convert: false } });\n        });\n\n        it('merges options properly', () => {\n\n            const baseSchema = Joi.any();\n            expect(baseSchema.describe().preferences).to.be.undefined();\n\n            const languageSchema = baseSchema.prefs({ messages: { 'type.foo': 'foo' } });\n            expect(languageSchema.describe().preferences).to.equal({ messages: { 'type.foo': 'foo' } });\n\n            const normalOptionSchema = baseSchema.prefs({ abortEarly: true });\n            expect(normalOptionSchema.describe().preferences).to.equal({ abortEarly: true });\n\n            const normalOptionsOverLanguageSchema = languageSchema.prefs({ abortEarly: true });\n            expect(normalOptionsOverLanguageSchema.describe().preferences).to.equal({\n                abortEarly: true,\n                messages: {\n                    'type.foo': 'foo'\n                }\n            });\n\n            const languageOptionsOverNormalOptionsSchema = normalOptionSchema.prefs({ messages: { 'type.foo': 'foo' } });\n            expect(languageOptionsOverNormalOptionsSchema.describe().preferences).to.equal({\n                abortEarly: true,\n                messages: {\n                    'type.foo': 'foo'\n                }\n            });\n\n            const languageOptionsOverLanguageOptionsSchema = languageSchema.prefs({\n                messages: {\n                    'type.bar': 'bar',\n                    'type2.foo': 'foo'\n                }\n            });\n            expect(languageOptionsOverLanguageOptionsSchema.describe().preferences).to.equal({\n                messages: {\n                    'type.foo': 'foo',\n                    'type.bar': 'bar',\n                    'type2.foo': 'foo'\n                }\n            });\n        });\n    });\n\n    describe('raw()', () => {\n\n        it('gives the raw input', () => {\n\n            const tests = [\n                [Joi.binary(), 'abc'],\n                [Joi.boolean(), false],\n                [Joi.date(), '1970/01/01'],\n                [Joi.number(), '12'],\n                [Joi.any().strict(), 'abc']\n            ];\n\n            for (const test of tests) {\n                const input = test[1];\n                const schema = test[0].raw();\n                Helper.validate(schema.raw(), [[input, true, input]]);\n            }\n        });\n\n        it('cancels raw mode', () => {\n\n            const schema = Joi.number().raw().raw(false);\n            Helper.validate(schema, [['123', true, 123]]);\n        });\n\n        it('avoids unnecessary cloning when called twice', () => {\n\n            const schema = Joi.any().raw();\n            expect(schema.raw()).to.shallow.equal(schema);\n        });\n    });\n\n    describe('required', () => {\n\n        it('avoids unnecessary cloning when called twice', () => {\n\n            const schema = Joi.any().required();\n            expect(schema.required()).to.shallow.equal(schema);\n        });\n    });\n\n    describe('rule()', () => {\n\n        it('throws on empty ruleset', () => {\n\n            expect(() => Joi.number().min(10).rule({})).to.not.throw();\n            expect(() => Joi.number().ruleset.min(10).rule({})).to.not.throw();\n            expect(() => Joi.number().$.min(10).rule({})).to.not.throw();\n            expect(() => Joi.number().$.min(10).rule({}).max(11).rule({})).to.not.throw();\n            expect(() => Joi.string().trim().min(1)._ruleRemove('trim').rule({})).to.not.throw();\n            expect(() => Joi.string().trim().$.min(1)._ruleRemove('trim').rule({})).to.not.throw();\n\n            expect(() => Joi.number().ruleset.min(10).concat(Joi.any())).to.throw('Cannot concatenate onto a schema with open ruleset');\n            expect(() => Joi.number().concat(Joi.number().min(10)).rule({})).to.throw('Cannot apply rules to empty ruleset or the last rule added does not support rule properties');\n            expect(() => Joi.number().concat(Joi.number().$.min(10))).to.throw('Cannot concatenate a schema with open ruleset');\n\n            expect(() => Joi.any().ruleset.rule({})).to.throw('Cannot apply rules to empty ruleset');\n            expect(() => Joi.any().rule({})).to.throw('Cannot apply rules to empty ruleset');\n            expect(() => Joi.number().min(10).rule({}).rule({})).to.throw('Cannot apply rules to empty ruleset or the last rule added does not support rule properties');\n            expect(() => Joi.string().ruleset.trim()._ruleRemove('trim').rule({})).to.throw('Cannot apply rules to empty ruleset');\n            expect(() => Joi.string().trim()._ruleRemove('trim').rule({})).to.throw('Cannot apply rules to empty ruleset');\n\n            expect(() => Joi.number().ruleset.min(10).concat(Joi.any().ruleset).rule({})).to.throw('Cannot concatenate onto a schema with open ruleset');\n            expect(() => Joi.number().min(10).concat(Joi.number().$.max(11).rule({})).rule({})).to.throw('Cannot apply rules to empty ruleset or the last rule added does not support rule properties');\n\n            expect(() => Joi.string().insensitive().rule({})).to.throw('Cannot apply rules to empty ruleset or the last rule added does not support rule properties');\n            expect(() => Joi.string().lowercase().insensitive().rule({})).to.throw('Cannot apply rules to empty ruleset or the last rule added does not support rule properties');\n            expect(() => Joi.string().$.lowercase().insensitive()).to.throw('Cannot set flag inside a ruleset');\n        });\n\n        describe('keep', () => {\n\n            it('retains both unique rule instances', () => {\n\n                const schema = Joi.number()\n                    .min(10).rule({ message: 'way too small', keep: true })\n                    .min(100).message('still too small');\n\n                Helper.validate(schema, [\n                    [1, false, 'way too small'],\n                    [90, false, 'still too small']\n                ]);\n            });\n        });\n\n        describe('message', () => {\n\n            it('overrides message', () => {\n\n                expect(Joi.number().min(10).rule({ message: 'way too small' }).validate(1).error).to.be.an.error('way too small');\n                expect(Joi.number().min(10).rule({ message: { 'number.min': 'way too small' } }).validate(1).error).to.be.an.error('way too small');\n                expect(Joi.number().min(10).rule({ message: { 'number.max': 'way too big' } }).validate(1).error).to.be.an.error('\"value\" must be greater than or equal to 10');\n            });\n\n            it('overrides ruleset with single message', () => {\n\n                const schema = Joi.number().$.max(100).min(10).rule({ message: 'number out of bound' });\n                Helper.validate(schema, [\n                    [1, false, 'number out of bound'],\n                    [101, false, 'number out of bound']\n                ]);\n            });\n\n            it('overrides ruleset messages', () => {\n\n                const schema = Joi.number().$.max(100).min(10).rule({ message: { 'number.max': 'way too big', 'number.min': 'way too small' } });\n                Helper.validate(schema, [\n                    [1, false, 'way too small'],\n                    [101, false, 'way too big']\n                ]);\n            });\n\n            it('overrides template', () => {\n\n                expect(Joi.number().min(10).rule({ message: '{{#label}} way too small' }).validate(1).error).to.be.an.error('\"value\" way too small');\n                expect(Joi.number().min(10).rule({ message: { 'number.min': '{{#label}} way too small' } }).validate(1).error).to.be.an.error('\"value\" way too small');\n                expect(Joi.number().min(10).rule({ message: { 'number.max': '{{#label}} way too big' } }).validate(1).error).to.be.an.error('\"value\" must be greater than or equal to 10');\n            });\n\n            it('overrides ruleset with single template', () => {\n\n                const schema = Joi.number().$.max(100).min(10).rule({ message: '{{#label}} number out of bound' });\n                Helper.validate(schema, [\n                    [1, false, '\"value\" number out of bound'],\n                    [101, false, '\"value\" number out of bound']\n                ]);\n            });\n\n            it('overrides ruleset templates', () => {\n\n                const schema = Joi.number().$.max(100).min(10).rule({ message: { 'number.max': '{{#label}} way too big', 'number.min': '{{#label}} way too small' } });\n                Helper.validate(schema, [\n                    [1, false, '\"value\" way too small'],\n                    [101, false, '\"value\" way too big']\n                ]);\n            });\n\n            it('overrides ruleset with both message and template', () => {\n\n                const schema = Joi.number().$.max(100).min(10).rule({ message: { 'number.max': 'way too big', 'number.min': '{{#label}} way too small' } });\n                Helper.validate(schema, [\n                    [1, false, '\"value\" way too small'],\n                    [101, false, 'way too big']\n                ]);\n            });\n        });\n    });\n\n    describe('strict()', () => {\n\n        it('validates without converting', () => {\n\n            const schema = Joi.object({\n                array: Joi.array().items(Joi.string().min(5), Joi.number().min(3))\n            }).strict();\n\n            Helper.validate(schema, [\n                [{ array: ['12345'] }, true],\n                [{ array: ['1'] }, false, {\n                    message: '\"array[0]\" does not match any of the allowed types',\n                    path: ['array', 0],\n                    type: 'array.includes',\n                    context: { pos: 0, value: '1', label: 'array[0]', key: 0 }\n                }],\n                [{ array: [3] }, true],\n                [{ array: ['12345', 3] }, true],\n                [{ array: ['3'] }, false, {\n                    message: '\"array[0]\" does not match any of the allowed types',\n                    path: ['array', 0],\n                    type: 'array.includes',\n                    context: { pos: 0, value: '3', label: 'array[0]', key: 0 }\n                }],\n                [{ array: [1] }, false, {\n                    message: '\"array[0]\" does not match any of the allowed types',\n                    path: ['array', 0],\n                    type: 'array.includes',\n                    context: { pos: 0, value: 1, label: 'array[0]', key: 0 }\n                }]\n            ]);\n        });\n\n        it('can be disabled', () => {\n\n            const schema = Joi.object({\n                array: Joi.array().items(Joi.string().min(5), Joi.number().min(3))\n            }).strict().strict(false);\n\n            Helper.validate(schema, [\n                [{ array: ['12345'] }, true],\n                [{ array: ['1'] }, false, {\n                    message: '\"array[0]\" does not match any of the allowed types',\n                    path: ['array', 0],\n                    type: 'array.includes',\n                    context: { pos: 0, value: '1', label: 'array[0]', key: 0 }\n                }],\n                [{ array: [3] }, true],\n                [{ array: ['12345', 3] }, true],\n                [{ array: ['3'] }, true, { array: [3] }],\n                [{ array: [1] }, false, {\n                    message: '\"array[0]\" does not match any of the allowed types',\n                    path: ['array', 0],\n                    type: 'array.includes',\n                    context: { pos: 0, value: 1, label: 'array[0]', key: 0 }\n                }]\n            ]);\n        });\n\n        it('adds to existing options', () => {\n\n            const schema = Joi.object({ b: Joi.number().prefs({ convert: true }).strict() });\n            const input = { b: '2' };\n            Helper.validate(schema, [[input, false, {\n                message: '\"b\" must be a number',\n                path: ['b'],\n                type: 'number.base',\n                context: { label: 'b', key: 'b', value: '2' }\n            }]]);\n        });\n    });\n\n    describe('strip()', () => {\n\n        it('validates and returns undefined', () => {\n\n            const schema = Joi.string().strip();\n            Helper.validate(schema, [['test', true, undefined]]);\n        });\n\n        it('validates and returns an error', () => {\n\n            const schema = Joi.string().strip();\n\n            Helper.validate(schema, [[1, false, {\n                message: '\"value\" must be a string',\n                path: [],\n                type: 'string.base',\n                context: { value: 1, label: 'value' }\n            }]]);\n        });\n\n        it('avoids unnecessary cloning when called twice', () => {\n\n            const schema = Joi.strip();\n            expect(schema.strip()).to.shallow.equal(schema);\n        });\n\n        it('cancels strip', () => {\n\n            const schema = Joi.strip().strip(false);\n            expect(schema._flags.result).to.not.exist();\n        });\n\n        it('strips item inside array item', () => {\n\n            const schema = Joi.array().items(\n                Joi.array().items(\n                    Joi.number(),\n                    Joi.strip()\n                ),\n                Joi.strip()\n            );\n\n            Helper.validate(schema, [\n                [['x', ['x']], true, [[]]],\n                [[1, 2, [1, 2, 'x']], true, [[1, 2]]]\n            ]);\n        });\n\n        it('strips nested keys', () => {\n\n            const schema = Joi.object({\n                a: Joi.object({\n                    x: Joi.any(),\n                    y: Joi.strip()\n                })\n                    .strip(),\n\n                b: Joi.ref('a.y')\n            });\n\n            Helper.validate(schema, [\n                [{}, true, {}],\n                [{ a: { x: 1 } }, true, {}],\n                [{ a: { x: 1, y: 2 } }, true, {}],\n                [{ a: { x: 1, y: 2 }, b: 2 }, true, { b: 2 }],\n                [{ a: { x: 1, y: 2 }, b: 3 }, false, '\"b\" must be [ref:a.y]'],\n                [{ b: 1 }, false, '\"b\" must be [ref:a.y]']\n            ]);\n        });\n\n        it('references validated stripped value (after child already stripped)', () => {\n\n            const schema = Joi.object({\n                a: Joi.object({\n                    x: Joi.any(),\n                    y: Joi.strip()\n                })\n                    .strip(),\n\n                b: Joi.ref('a')\n            });\n\n            Helper.validate(schema, [\n                [{}, true, {}],\n                [{ a: { x: 1, y: 2 } }, true, {}],\n                [{ a: { x: 1, y: 2 }, b: { x: 1 } }, true, { b: { x: 1 } }],\n                [{ a: { x: 1, y: 2 }, b: { x: 1, y: 2 } }, false, '\"b\" must be [ref:a]']\n            ]);\n        });\n\n        it('strips key nested in alternative', () => {\n\n            const schema = Joi.object({\n                a: Joi.alternatives([\n                    Joi.object({\n                        x: Joi.number().strip(),\n                        y: Joi.any()\n                    }),\n                    Joi.object({\n                        x: Joi.any(),\n                        z: Joi.any()\n                    })\n                ]),\n\n                b: Joi.ref('a.x')\n            });\n\n            Helper.validate(schema, [\n                [{}, true, {}],\n                [{ a: { x: 1, y: 1 } }, true, { a: { y: 1 } }],\n                [{ a: { x: 1, z: 1 } }, true],\n                [{ a: { x: 1, z: 1 }, b: 1 }, true],\n                [{ a: { x: 1, z: 1 }, b: 2 }, false, '\"b\" must be [ref:a.x]'],\n                [{ a: { x: '2', z: 1 }, b: 2 }, false, '\"b\" must be [ref:a.x]'],\n                [{ a: { x: '2', z: 1 }, b: '2' }, true]\n            ]);\n        });\n\n        it('strips key in array item', () => {\n\n            const schema = Joi.array()\n                .items(\n                    Joi.object({\n                        a: Joi.number().strip(),\n                        b: Joi.ref('a')\n                    }),\n                    Joi.object({\n                        a: Joi.string(),\n                        b: Joi.ref('a')\n                    })\n                );\n\n            Helper.validate(schema, [\n                [[], true],\n                [[{ a: 'x', b: 'x' }], true],\n                [[{ a: 1, b: 1 }], true, [{ b: 1 }]],\n                [[{ a: 'x', b: 'x' }, { a: 1, b: 1 }], true, [{ a: 'x', b: 'x' }, { b: 1 }]],\n                [[{ a: '1', b: '1' }], true, [{ a: '1', b: '1' }]]\n            ]);\n        });\n\n        it('ignored in matches', () => {\n\n            const schema = Joi.array()\n                .items(Joi.object({\n                    a: {\n                        b: Joi.number().strip(),\n                        c: Joi.number()\n                    }\n                }))\n                .has(Joi.object({\n                    a: {\n                        b: Joi.number().min(10),\n                        c: Joi.boolean().truthy(30).strip()         // Does not affect result\n                    }\n                }));\n\n            Helper.validate(schema, [\n                [[{ a: { b: '20', c: '30' } }], true, [{ a: { c: 30 } }]]\n            ]);\n        });\n\n        it('retains shadow after match', () => {\n\n            const schema = Joi.object({\n                a: Joi.number().strip()\n            })\n                .assert('.a', Joi.number().cast('string').strip().required(), 'error 1')\n                .assert('.a', Joi.number().strict().required(), 'error 2');\n\n            Helper.validate(schema, [\n                [{ a: '1' }, true, {}]\n            ]);\n        });\n\n        it('revers match changes when shadow exists', () => {\n\n            const schema = Joi.object({\n                x: Joi.strip(),\n                y: Joi.object({\n                    z: Joi.when('$x', { otherwise: Joi.strip() })\n                })\n            });\n\n            Helper.validate(schema, [\n                [{ x: 1, y: { z: 'x' } }, true, { y: {} }]\n            ]);\n        });\n    });\n\n    describe('tag()', () => {\n\n        it('sets the tags', () => {\n\n            const b = Joi.any().tag('tag1', 'tag2').tag('tag3');\n            expect(b.describe().tags).to.include('tag1');\n            expect(b.describe().tags).to.include('tag2');\n            expect(b.describe().tags).to.include('tag3');\n        });\n\n        it('throws when tags are missing', () => {\n\n            expect(() => Joi.any().tag()).to.throw('Missing tags');\n        });\n\n        it('throws when tags are invalid', () => {\n\n            expect(() => Joi.any().tag(5)).to.throw('Tags must be non-empty strings');\n            expect(() => Joi.any().tag('')).to.throw('Tags must be non-empty strings');\n        });\n    });\n\n    describe('tailor()', () => {\n\n        it('customizes root schema', () => {\n\n            const alterations = {\n                x: (s) => s.min(10),\n                y: (s) => s.max(50),\n                z: (s) => s.integer()\n            };\n\n            const before = Joi.number().alter(alterations);\n\n            const first = before.tailor('x');\n            const after1 = Joi.number().min(10).alter(alterations);\n\n            Helper.equal(first, after1);\n            expect(first.describe()).to.equal(after1.describe());\n\n            const second = first.tailor(['y', 'z']);\n            const after2 = Joi.number().min(10).max(50).integer().alter(alterations);\n\n            Helper.equal(second, after2);\n        });\n\n        it('customizes root schema (multiple alter calls)', () => {\n\n            const alter1 = {\n                x: (s) => s.min(10)\n            };\n\n            const alter2 = {\n                y: (s) => s.max(50),\n                z: (s) => s.integer()\n            };\n\n            const before = Joi.number().alter(alter1).alter(alter2);\n\n            const first = before.tailor('x');\n            const after1 = Joi.number().min(10).alter(alter1).alter(alter2);\n\n            Helper.equal(first, after1);\n            expect(first.describe()).to.equal(after1.describe());\n\n            const second = first.tailor(['y', 'z']);\n            const after2 = Joi.number().min(10).max(50).integer().alter(alter1).alter(alter2);\n\n            Helper.equal(second, after2);\n        });\n\n        it('customizes nested schema', () => {\n\n            const alterations = {\n                x: (s) => s.min(10),\n                y: (s) => s.max(50)\n            };\n\n            const number = Joi.number().alter(alterations);\n\n            const before = Joi.object({\n                a: number,\n                b: {\n                    c: number\n                },\n                d: Joi.object()\n                    .pattern(/.*/, number)\n            });\n\n            const tailored = before.tailor(['x', 'y']);\n\n            const numberxy = number.min(10).max(50);\n            const after = Joi.object({\n                a: numberxy,\n                b: {\n                    c: numberxy\n                },\n                d: Joi.object()\n                    .pattern(/.*/, numberxy)\n            });\n\n            Helper.equal(tailored, after);\n            expect(tailored.describe()).to.equal(after.describe());\n        });\n    });\n\n    describe('unit()', () => {\n\n        it('sets the unit', () => {\n\n            const b = Joi.any().unit('milliseconds');\n            expect(b.describe().flags.unit).to.equal('milliseconds');\n        });\n\n        it('throws when unit is missing', () => {\n\n            expect(() => Joi.any().unit()).to.throw('Unit name must be a non-empty string');\n        });\n    });\n\n    describe('valid()', () => {\n\n        it('allows valid values to be set', () => {\n\n            expect(() => {\n\n                Joi.any().valid(true, 1, 'hello', new Date(), Symbol('foo'), () => { }, {});\n            }).not.to.throw();\n        });\n\n        it('throws when passed undefined', () => {\n\n            expect(() => {\n\n                Joi.any().valid(undefined);\n            }).to.throw(Error, 'Cannot call allow/valid/invalid with undefined');\n        });\n\n        it('validates different types of values', () => {\n\n            Helper.validate(Joi.valid(1), [[1, true]]);\n            Helper.validate(Joi.valid(1), [[2, false, '\"value\" must be [1]']]);\n\n            const d = new Date();\n            Helper.validate(Joi.valid(d), [\n                [new Date(d.getTime()), true],\n                [new Date(d.getTime() + 1), false, {\n                    message: `\"value\" must be [${d.toISOString()}]`,\n                    path: [],\n                    type: 'any.only',\n                    context: { value: new Date(d.getTime() + 1), valids: [d], label: 'value' }\n                }]\n            ]);\n            Helper.validate(Joi.valid(Joi.ref('$a')), { context: { a: new Date(d.getTime()) } }, [[d, true]]);\n            Helper.validate(Joi.object({ a: Joi.date(), b: Joi.valid(Joi.ref('a')) }), [[{ a: d, b: d }, true]]);\n            Helper.validate(Joi.object({ a: Joi.array().items(Joi.date()).single(), b: Joi.valid(Joi.in('a')) }), [[{ a: [d], b: d }, true, { a: [d], b: d }]]);\n            Helper.validate(Joi.object({ a: Joi.array().items(Joi.date()).single(), b: Joi.valid(Joi.in('a')) }), [[{ a: [new Date(0)], b: d }, false, '\"b\" must be [ref:a]']]);\n\n            const str = 'foo';\n            Helper.validate(Joi.valid(str), [\n                [str, true],\n                ['foobar', false, {\n                    message: '\"value\" must be [foo]',\n                    path: [],\n                    type: 'any.only',\n                    context: { value: 'foobar', valids: [str], label: 'value' }\n                }]\n            ]);\n\n            const s = Symbol('foo');\n            const otherSymbol = Symbol('foo');\n            Helper.validate(Joi.valid(s), [\n                [s, true],\n                [otherSymbol, false, {\n                    message: '\"value\" must be [Symbol(foo)]',\n                    path: [],\n                    type: 'any.only',\n                    context: { value: otherSymbol, valids: [s], label: 'value' }\n                }]\n            ]);\n\n            const o = {};\n            Helper.validate(Joi.valid(o), [\n                [o, true],\n                [{}, true],\n                [{ x: 1 }, false, {\n                    message: '\"value\" must be [[object Object]]',\n                    path: [],\n                    type: 'any.only',\n                    context: { value: { x: 1 }, valids: [o], label: 'value' }\n                }]\n            ]);\n\n            const f = () => { };\n            const otherFunction = () => { };\n            Helper.validate(Joi.valid(f), [\n                [f, true],\n                [otherFunction, false, {\n                    message: '\"value\" must be [() => { }]',\n                    path: [],\n                    type: 'any.only',\n                    context: { value: otherFunction, valids: [f], label: 'value' }\n                }]\n            ]);\n\n            const b = Buffer.from('foo');\n            Helper.validate(Joi.valid(b), [\n                [b, true],\n                [Buffer.from('foobar'), false, {\n                    message: '\"value\" must be [foo]',\n                    path: [],\n                    type: 'any.only',\n                    context: { value: Buffer.from('foobar'), valids: [b], label: 'value' }\n                }]\n            ]);\n        });\n\n        it('supports templates', () => {\n\n            const schema = Joi.object({\n                a: Joi.number(),\n                b: Joi.valid(Joi.x('{a + 1}'))\n            });\n\n            Helper.validate(schema, [\n                [{ a: 5, b: 6 }, true],\n                [{ a: 5, b: 5 }, false, '\"b\" must be [{a + 1}]']\n            ]);\n        });\n\n        it('supports templates with literals', () => {\n\n            const schema = Joi.object({\n                a: Joi.number(),\n                b: Joi.valid(Joi.x('x{a + 1}'))\n            });\n\n            Helper.validate(schema, [\n                [{ a: 5, b: 'x6' }, true],\n                [{ a: 5, b: 'x5' }, false, '\"b\" must be [x{a + 1}]']\n            ]);\n        });\n\n        it('supports pure literal templates', () => {\n\n            const schema = Joi.object({\n                a: Joi.number(),\n                b: Joi.valid(Joi.x('x'))\n            });\n\n            Helper.validate(schema, [\n                [{ a: 5, b: 'x' }, true],\n                [{ a: 5, b: 'y' }, false, '\"b\" must be [x]']\n            ]);\n        });\n\n        it('supports templates with functions', () => {\n\n            const schema = Joi.object({\n                a: Joi.number(),\n                b: Joi.boolean(),\n                c: Joi.valid(Joi.x('{if(a == 5 && b == true, a * 2, null)}'))\n            });\n\n            Helper.validate(schema, [\n                [{ a: 5, b: true, c: 10 }, true],\n                [{ a: 5, b: false, c: null }, true]\n            ]);\n        });\n\n        it('looks up values in array (from single value)', () => {\n\n            const schema = Joi.object({\n                a: Joi.array().items(Joi.string()),\n                b: Joi.in('a')\n            });\n\n            Helper.validate(schema, [\n                [{ a: ['1', '2'], b: '2' }, true],\n                [{ a: ['1', '2'], b: '3' }, false, '\"b\" must be [ref:a]']\n            ]);\n        });\n\n        it('looks up values in array (from array value)', () => {\n\n            const schema = Joi.object({\n                a: Joi.array().items(Joi.string()),\n                b: Joi.array().valid(Joi.ref('a'))\n            });\n\n            Helper.validate(schema, [\n                [{ a: ['1', '2'], b: ['1', '2'] }, true],\n                [{ a: ['1', '2'], b: ['1'] }, false, '\"b\" must be [ref:a]']\n            ]);\n        });\n    });\n\n    describe('$_validate()', () => {\n\n        it('checks value after conversion', () => {\n\n            const schema = Joi.number().invalid(2);\n            Helper.validate(schema, { abortEarly: false }, [['2', false, {\n                message: '\"value\" contains an invalid value',\n                details: [{\n                    message: '\"value\" contains an invalid value',\n                    path: [],\n                    type: 'any.invalid',\n                    context: { value: 2, invalids: [2], label: 'value' }\n                }]\n            }]]);\n        });\n    });\n\n    describe('validate()', () => {\n\n        it('accepts only value (sync way)', () => {\n\n            const schema = Joi.number();\n            Helper.validate(schema, [['2', true, 2]]);\n        });\n\n        it('accepts value and options', () => {\n\n            const schema = Joi.number();\n            Helper.validate(schema, { convert: false }, [['2', false, {\n                message: '\"value\" must be a number',\n                path: [],\n                type: 'number.base',\n                context: { label: 'value', value: '2' }\n            }]]);\n        });\n\n        it('accepts value, options and callback', () => {\n\n            const schema = Joi.number();\n            Helper.validate(schema, { convert: false }, [['2', false, {\n                message: '\"value\" must be a number',\n                path: [],\n                type: 'number.base',\n                context: { label: 'value', value: '2' }\n            }]]);\n        });\n    });\n\n    describe('warn', () => {\n\n        it('turns error into warning', () => {\n\n            const schema = Joi.string().min(10).warn();\n            const { value, error, warning } = schema.validate('abc');\n            expect(value).to.equal('abc');\n            expect(error).to.not.exist();\n            expect(warning).to.equal({\n                message: '\"value\" length must be at least 10 characters long',\n                details: [\n                    {\n                        message: '\"value\" length must be at least 10 characters long',\n                        path: [],\n                        type: 'string.min',\n                        context: {\n                            encoding: undefined,\n                            label: 'value',\n                            limit: 10,\n                            value: 'abc'\n                        }\n                    }\n                ]\n            });\n        });\n    });\n\n    describe('when()', () => {\n\n        it('throws when options are invalid', () => {\n\n            expect(() => Joi.when('a', 4)).to.throw('Options must be of type object');\n            expect(() => Joi.when('a')).to.throw('Missing options');\n            expect(() => Joi.when(0)).to.throw('Missing options');\n        });\n\n        it('throws when break used with then and otherwise', () => {\n\n            expect(() => Joi.when('a', { then: 1, otherwise: 2, break: true })).to.throw('Cannot specify then, otherwise, and break all together');\n            expect(() => Joi.when('a', { switch: [{ is: true, then: 1, otherwise: 2 }], break: true })).to.throw('Cannot specify both otherwise and break');\n        });\n\n        it('validates multiple conditions', () => {\n\n            const schema = Joi.object({\n                a: Joi.boolean(),\n                b: Joi.boolean(),\n                c: Joi.number()\n                    .when('a', { is: true, then: Joi.number().max(100) })\n                    .when('b', { is: true, then: Joi.number().min(10) })\n            });\n\n            Helper.validate(schema, [\n                [{ c: 0 }, true],\n                [{ c: 10 }, true],\n                [{ c: 100 }, true],\n                [{ c: 50 }, true],\n                [{ c: 101 }, true],\n                [{ a: true, c: 0 }, true],\n                [{ a: true, c: 100 }, true],\n                [{ a: true, c: 101 }, false, {\n                    message: '\"c\" must be less than or equal to 100',\n                    path: ['c'],\n                    type: 'number.max',\n                    context: { value: 101, label: 'c', key: 'c', limit: 100 }\n                }],\n                [{ b: true, c: 10 }, true],\n                [{ b: true, c: 50 }, true],\n                [{ b: true, c: 101 }, true],\n                [{ b: true, c: 0 }, false, {\n                    message: '\"c\" must be greater than or equal to 10',\n                    path: ['c'],\n                    type: 'number.min',\n                    context: { value: 0, label: 'c', key: 'c', limit: 10 }\n                }],\n                [{ a: true, b: true, c: 10 }, true],\n                [{ a: true, b: true, c: 100 }, true],\n                [{ a: true, b: true, c: 50 }, true],\n                [{ a: true, b: true, c: 0 }, false, {\n                    message: '\"c\" must be greater than or equal to 10',\n                    path: ['c'],\n                    type: 'number.min',\n                    context: { value: 0, label: 'c', key: 'c', limit: 10 }\n                }],\n                [{ a: true, b: true, c: 101 }, false, {\n                    message: '\"c\" must be less than or equal to 100',\n                    path: ['c'],\n                    type: 'number.max',\n                    context: { value: 101, label: 'c', key: 'c', limit: 100 }\n                }]\n            ]);\n        });\n\n        it('validates multiple conditions (implicit valids)', () => {\n\n            const schema = Joi.object({\n                a: Joi.boolean(),\n                b: Joi.boolean(),\n                c: Joi.number()\n                    .when('a', { is: true, then: 1 })\n                    .when('b', { is: true, then: 2 })\n            });\n\n            Helper.validate(schema, [\n                [{ c: 0 }, true],\n                [{ c: 1 }, true],\n                [{ c: 2 }, true],\n                [{ a: true, c: 1 }, true],\n                [{ a: true, c: 2 }, false, {\n                    message: '\"c\" must be [1]',\n                    path: ['c'],\n                    type: 'any.only',\n                    context: { value: 2, label: 'c', key: 'c', valids: [1] }\n                }],\n                [{ b: true, c: 2 }, true],\n                [{ b: true, c: 1 }, false, {\n                    message: '\"c\" must be [2]',\n                    path: ['c'],\n                    type: 'any.only',\n                    context: { value: 1, label: 'c', key: 'c', valids: [2] }\n                }],\n                [{ a: true, b: true, c: 2 }, true],\n                [{ a: true, b: true, c: 1 }, false, {\n                    message: '\"c\" must be [2]',\n                    path: ['c'],\n                    type: 'any.only',\n                    context: { value: 1, label: 'c', key: 'c', valids: [2] }\n                }]\n            ]);\n        });\n\n        it('supports implicit this', () => {\n\n            const schema = Joi.object({\n                c: Joi.number()\n                    .when({ is: 1, then: Joi.strip() })\n            });\n\n            Helper.validate(schema, [\n                [{ c: 0 }, true],\n                [{ c: 1 }, true, {}]\n            ]);\n        });\n\n        it('supports not', () => {\n\n            const schema = Joi.object({\n                a: Joi.boolean(),\n                b: Joi.boolean(),\n                c: Joi.number()\n                    .when('a', { not: false, then: 1 })\n                    .when('b', { not: false, then: 2 })\n            });\n\n            Helper.validate(schema, [\n                [{ c: 0 }, true],\n                [{ c: 1 }, true],\n                [{ c: 2 }, true],\n                [{ a: true, c: 1 }, true],\n                [{ a: true, c: 2 }, false, {\n                    message: '\"c\" must be [1]',\n                    path: ['c'],\n                    type: 'any.only',\n                    context: { value: 2, label: 'c', key: 'c', valids: [1] }\n                }],\n                [{ b: true, c: 2 }, true],\n                [{ b: true, c: 1 }, false, {\n                    message: '\"c\" must be [2]',\n                    path: ['c'],\n                    type: 'any.only',\n                    context: { value: 1, label: 'c', key: 'c', valids: [2] }\n                }],\n                [{ a: true, b: true, c: 2 }, true],\n                [{ a: true, b: true, c: 1 }, false, {\n                    message: '\"c\" must be [2]',\n                    path: ['c'],\n                    type: 'any.only',\n                    context: { value: 1, label: 'c', key: 'c', valids: [2] }\n                }]\n            ]);\n        });\n\n        it('defaults is to truthy', () => {\n\n            const schema = Joi.object({\n                a: Joi.any(),\n                b: Joi.number()\n                    .when('a', { then: 1, otherwise: 2 })\n            });\n\n            Helper.validate(schema, [\n                [{ b: 2 }, true],\n                [{ a: 1, b: 1 }, true],\n                [{ b: 1 }, false, {\n                    message: '\"b\" must be [2]',\n                    path: ['b'],\n                    type: 'any.only',\n                    context: { value: 1, label: 'b', key: 'b', valids: [2] }\n                }],\n                [{ a: 0, b: 1 }, false, {\n                    message: '\"b\" must be [2]',\n                    path: ['b'],\n                    type: 'any.only',\n                    context: { value: 1, label: 'b', key: 'b', valids: [2] }\n                }],\n                [{ a: '', b: 1 }, false, {\n                    message: '\"b\" must be [2]',\n                    path: ['b'],\n                    type: 'any.only',\n                    context: { value: 1, label: 'b', key: 'b', valids: [2] }\n                }],\n                [{ a: false, b: 1 }, false, {\n                    message: '\"b\" must be [2]',\n                    path: ['b'],\n                    type: 'any.only',\n                    context: { value: 1, label: 'b', key: 'b', valids: [2] }\n                }],\n                [{ a: null, b: 1 }, false, {\n                    message: '\"b\" must be [2]',\n                    path: ['b'],\n                    type: 'any.only',\n                    context: { value: 1, label: 'b', key: 'b', valids: [2] }\n                }]\n            ]);\n        });\n\n        it('sets type whens', () => {\n\n            const schema = Joi.object({\n                a: Joi.any(),\n                b: Joi.string().valid('x').when('a', { is: 5, then: Joi.valid('y'), otherwise: Joi.valid('z') })\n            });\n\n            Helper.validate(schema, [\n                [{ a: 5, b: 'x' }, true],\n                [{ a: 5, b: 'y' }, true],\n                [{ a: 5, b: 'z' }, false, {\n                    message: '\"b\" must be one of [x, y]',\n                    path: ['b'],\n                    type: 'any.only',\n                    context: { value: 'z', valids: ['x', 'y'], label: 'b', key: 'b' }\n                }],\n                [{ a: 1, b: 'x' }, true],\n                [{ a: 1, b: 'y' }, false, {\n                    message: '\"b\" must be one of [x, z]',\n                    path: ['b'],\n                    type: 'any.only',\n                    context: { value: 'y', valids: ['x', 'z'], label: 'b', key: 'b' }\n                }],\n                [{ a: 1, b: 'z' }, true],\n                [{ a: 5, b: 'a' }, false, {\n                    message: '\"b\" must be one of [x, y]',\n                    path: ['b'],\n                    type: 'any.only',\n                    context: { value: 'a', valids: ['x', 'y'], label: 'b', key: 'b' }\n                }],\n                [{ b: 'a' }, false, {\n                    message: '\"b\" must be one of [x, z]',\n                    path: ['b'],\n                    type: 'any.only',\n                    context: { value: 'a', valids: ['x', 'z'], label: 'b', key: 'b' }\n                }]\n            ]);\n        });\n\n        it('sets type whens (only then)', () => {\n\n            const schema = Joi.object({\n                a: Joi.any(),\n                b: Joi.string().valid('x').when('a', { is: 5, then: Joi.valid('y') })\n            });\n\n            Helper.validate(schema, [\n                [{ a: 5, b: 'x' }, true],\n                [{ a: 5, b: 'y' }, true],\n                [{ a: 5, b: 'z' }, false, {\n                    message: '\"b\" must be one of [x, y]',\n                    path: ['b'],\n                    type: 'any.only',\n                    context: { value: 'z', valids: ['x', 'y'], label: 'b', key: 'b' }\n                }],\n                [{ a: 1, b: 'x' }, true],\n                [{ a: 1, b: 'y' }, false, {\n                    message: '\"b\" must be [x]',\n                    path: ['b'],\n                    type: 'any.only',\n                    context: { value: 'y', valids: ['x'], label: 'b', key: 'b' }\n                }],\n                [{ a: 1, b: 'z' }, false, {\n                    message: '\"b\" must be [x]',\n                    path: ['b'],\n                    type: 'any.only',\n                    context: { value: 'z', valids: ['x'], label: 'b', key: 'b' }\n                }],\n                [{ a: 5, b: 'a' }, false, {\n                    message: '\"b\" must be one of [x, y]',\n                    path: ['b'],\n                    type: 'any.only',\n                    context: { value: 'a', valids: ['x', 'y'], label: 'b', key: 'b' }\n                }],\n                [{ b: 'a' }, false, {\n                    message: '\"b\" must be [x]',\n                    path: ['b'],\n                    type: 'any.only',\n                    context: { value: 'a', valids: ['x'], label: 'b', key: 'b' }\n                }]\n            ]);\n        });\n\n        it('sets type whens (only otherwise)', () => {\n\n            const schema = Joi.object({\n                a: Joi.any(),\n                b: Joi.string().valid('x').when('a', { is: 5, otherwise: Joi.valid('z') })\n            });\n\n            Helper.validate(schema, [\n                [{ a: 5, b: 'x' }, true],\n                [{ a: 5, b: 'y' }, false, {\n                    message: '\"b\" must be [x]',\n                    path: ['b'],\n                    type: 'any.only',\n                    context: { value: 'y', valids: ['x'], label: 'b', key: 'b' }\n                }],\n                [{ a: 5, b: 'z' }, false, {\n                    message: '\"b\" must be [x]',\n                    path: ['b'],\n                    type: 'any.only',\n                    context: { value: 'z', valids: ['x'], label: 'b', key: 'b' }\n                }],\n                [{ a: 1, b: 'x' }, true],\n                [{ a: 1, b: 'y' }, false, {\n                    message: '\"b\" must be one of [x, z]',\n                    path: ['b'],\n                    type: 'any.only',\n                    context: { value: 'y', valids: ['x', 'z'], label: 'b', key: 'b' }\n                }],\n                [{ a: 1, b: 'z' }, true],\n                [{ a: 5, b: 'a' }, false, {\n                    message: '\"b\" must be [x]',\n                    path: ['b'],\n                    type: 'any.only',\n                    context: { value: 'a', valids: ['x'], label: 'b', key: 'b' }\n                }],\n                [{ b: 'a' }, false, {\n                    message: '\"b\" must be one of [x, z]',\n                    path: ['b'],\n                    type: 'any.only',\n                    context: { value: 'a', valids: ['x', 'z'], label: 'b', key: 'b' }\n                }]\n            ]);\n        });\n\n        it('sets type whens (with is as a schema)', () => {\n\n            const schema = Joi.object({\n                a: Joi.any(),\n                b: Joi.string().valid('x').when('a', { is: Joi.number().valid(5).required(), then: Joi.valid('y') })\n            });\n\n            Helper.validate(schema, [\n                [{ a: 5, b: 'x' }, true],\n                [{ a: 5, b: 'y' }, true],\n                [{ a: 5, b: 'z' }, false, {\n                    message: '\"b\" must be one of [x, y]',\n                    path: ['b'],\n                    type: 'any.only',\n                    context: { value: 'z', valids: ['x', 'y'], label: 'b', key: 'b' }\n                }],\n                [{ a: 1, b: 'x' }, true],\n                [{ a: 1, b: 'y' }, false, {\n                    message: '\"b\" must be [x]',\n                    path: ['b'],\n                    type: 'any.only',\n                    context: { value: 'y', valids: ['x'], label: 'b', key: 'b' }\n                }],\n                [{ a: 1, b: 'z' }, false, {\n                    message: '\"b\" must be [x]',\n                    path: ['b'],\n                    type: 'any.only',\n                    context: { value: 'z', valids: ['x'], label: 'b', key: 'b' }\n                }],\n                [{ a: 5, b: 'a' }, false, {\n                    message: '\"b\" must be one of [x, y]',\n                    path: ['b'],\n                    type: 'any.only',\n                    context: { value: 'a', valids: ['x', 'y'], label: 'b', key: 'b' }\n                }],\n                [{ b: 'a' }, false, {\n                    message: '\"b\" must be [x]',\n                    path: ['b'],\n                    type: 'any.only',\n                    context: { value: 'a', valids: ['x'], label: 'b', key: 'b' }\n                }]\n            ]);\n        });\n\n        it('makes peer required', () => {\n\n            const schema = Joi.object({\n                a: Joi.when('b', { is: 5, then: Joi.required() }),\n                b: Joi.any()\n            });\n\n            Helper.validate(schema, [\n                [{ b: 5 }, false, {\n                    message: '\"a\" is required',\n                    path: ['a'],\n                    type: 'any.required',\n                    context: { label: 'a', key: 'a' }\n                }],\n                [{ b: 6 }, true],\n                [{ a: 'b' }, true],\n                [{ b: 5, a: 'x' }, true]\n            ]);\n        });\n\n        it('makes peer required (is ref)', () => {\n\n            const schema = Joi.object({\n                a: Joi.when('b', { is: Joi.ref('c'), then: Joi.required() }),\n                b: Joi.any(),\n                c: Joi.any()\n            });\n\n            Helper.validate(schema, [\n                [{ b: 5, c: 5 }, false, {\n                    message: '\"a\" is required',\n                    path: ['a'],\n                    type: 'any.required',\n                    context: { label: 'a', key: 'a' }\n                }],\n                [{ b: 6, c: 5 }, true],\n                [{ a: 'b' }, true],\n                [{ b: 5, a: 'x' }, true]\n            ]);\n        });\n\n        it('makes peer required (switch is ref)', () => {\n\n            const schema = Joi.object({\n                a: Joi.when('b', [\n                    { is: Joi.ref('c'), then: Joi.required() },\n                    { is: 10, then: Joi.forbidden() }\n                ]),\n                b: Joi.any(),\n                c: Joi.any()\n            });\n\n            Helper.validate(schema, [\n                [{ b: 5, c: 5 }, false, {\n                    message: '\"a\" is required',\n                    path: ['a'],\n                    type: 'any.required',\n                    context: { label: 'a', key: 'a' }\n                }],\n                [{ b: 6, c: 5 }, true],\n                [{ a: 'b' }, true],\n                [{ b: 5, a: 'x' }, true]\n            ]);\n        });\n\n        it('breaks early', () => {\n\n            const schema = Joi.object({\n                a: Joi.boolean(),\n                b: Joi.boolean(),\n                c: Joi.number()\n                    .when('d', { then: 0, break: true })\n                    .when('a', { then: 1, break: true })\n                    .when('b', { then: 2 })\n            });\n\n            Helper.validate(schema, [\n                [{ a: true, b: true, c: 1 }, true],\n                [{ a: false, b: true, c: 2 }, true],\n                [{ a: false, b: true, c: 1 }, false, {\n                    message: '\"c\" must be [2]',\n                    path: ['c'],\n                    type: 'any.only',\n                    context: { value: 1, label: 'c', key: 'c', valids: [2] }\n                }]\n            ]);\n        });\n\n        it('breaks early (otherwise)', () => {\n\n            const schema = Joi.object({\n                a: Joi.boolean(),\n                b: Joi.boolean(),\n                c: Joi.number()\n                    .when('a', { otherwise: 2, break: true })\n                    .when('b', { then: 1 })\n            });\n\n            Helper.validate(schema, [\n                [{ a: true, b: true, c: 1 }, true],\n                [{ a: false, b: true, c: 2 }, true],\n                [{ a: false, b: true, c: 1 }, false, {\n                    message: '\"c\" must be [2]',\n                    path: ['c'],\n                    type: 'any.only',\n                    context: { value: 1, label: 'c', key: 'c', valids: [2] }\n                }]\n            ]);\n        });\n\n        it('works with prefs()', () => {\n\n            const schema = Joi.number()\n                .prefs({ errors: { label: 'key' } })\n                .when('$x', { then: Joi.number().min(1) });\n\n            Helper.validate(schema, { context: { x: true } }, [\n                [1, true],\n                [0, false, '\"value\" must be greater than or equal to 1']\n            ]);\n        });\n\n        it('describes the base schema', () => {\n\n            const schema = Joi.number()\n                .min(10)\n                .when('a', {\n                    is: 5,\n                    then: Joi.number().max(20).required()\n                });\n\n            expect(schema.describe()).to.equal({\n                type: 'number',\n                rules: [\n                    { args: { limit: 10 }, name: 'min' }\n                ],\n                whens: [{\n                    ref: { path: ['a'] },\n                    is: {\n                        type: 'any',\n                        flags: {\n                            only: true,\n                            presence: 'required'\n                        },\n                        allow: [{ override: true }, 5]\n                    },\n                    then: {\n                        type: 'number',\n                        flags: {\n                            presence: 'required'\n                        },\n                        rules: [{ name: 'max', args: { limit: 20 } }]\n                    }\n                }]\n            });\n        });\n\n        it('can describe as the original object (with a schema as a condition)', () => {\n\n            const schema = Joi.number()\n                .min(10)\n                .when(Joi.number().min(5), { then: Joi.number().max(20).required() });\n\n            expect(schema.describe()).to.equal({\n                type: 'number',\n                rules: [{ args: { limit: 10 }, name: 'min' }],\n                whens: [{\n                    is: {\n                        type: 'number',\n                        rules: [{ name: 'min', args: { limit: 5 } }]\n                    },\n                    then: {\n                        type: 'number',\n                        flags: { presence: 'required' },\n                        rules: [{ name: 'max', args: { limit: 20 } }]\n                    }\n                }]\n            });\n        });\n\n        it('sets value based on multiple conditions', () => {\n\n            const schema = Joi.object({\n                a: Joi.number().required(),\n                b: Joi.number()\n                    .when('a', [\n                        { is: 0, then: Joi.valid(1) },\n                        { is: 1, then: Joi.valid(2) },\n                        { is: 2, then: Joi.valid(3) }\n                    ])\n            });\n\n            Helper.validate(schema, [\n                [{ a: 0, b: 1 }, true],\n                [{ a: 0, b: 2 }, false, {\n                    message: '\"b\" must be [1]',\n                    path: ['b'],\n                    type: 'any.only',\n                    context: { value: 2, valids: [1], label: 'b', key: 'b' }\n                }],\n                [{ a: 1, b: 2 }, true],\n                [{ a: 1, b: 3 }, false, {\n                    message: '\"b\" must be [2]',\n                    path: ['b'],\n                    type: 'any.only',\n                    context: { value: 3, valids: [2], label: 'b', key: 'b' }\n                }],\n                [{ a: 2, b: 3 }, true],\n                [{ a: 2, b: 4 }, false, {\n                    message: '\"b\" must be [3]',\n                    path: ['b'],\n                    type: 'any.only',\n                    context: { value: 4, valids: [3], label: 'b', key: 'b' }\n                }],\n                [{ a: 42, b: 128 }, true]\n            ]);\n        });\n\n        it('sets value based on multiple conditions with otherwise', () => {\n\n            const schema = Joi.object({\n                a: Joi.number().required(),\n                b: Joi.number()\n                    .when('a', {\n                        switch: [\n                            { is: 0, then: Joi.valid(1) },\n                            { is: 1, then: Joi.valid(2) },\n                            { is: 2, then: Joi.valid(3) }\n                        ],\n                        otherwise: Joi.valid(4)\n                    })\n            });\n\n            Helper.validate(schema, [\n                [{ a: 0, b: 1 }, true],\n                [{ a: 0, b: 2 }, false, {\n                    message: '\"b\" must be [1]',\n                    path: ['b'],\n                    type: 'any.only',\n                    context: { value: 2, valids: [1], label: 'b', key: 'b' }\n                }],\n                [{ a: 1, b: 2 }, true],\n                [{ a: 1, b: 3 }, false, {\n                    message: '\"b\" must be [2]',\n                    path: ['b'],\n                    type: 'any.only',\n                    context: { value: 3, valids: [2], label: 'b', key: 'b' }\n                }],\n                [{ a: 2, b: 3 }, true],\n                [{ a: 2, b: 2 }, false, {\n                    message: '\"b\" must be [3]',\n                    path: ['b'],\n                    type: 'any.only',\n                    context: { value: 2, valids: [3], label: 'b', key: 'b' }\n                }],\n                [{ a: 42, b: 4 }, true],\n                [{ a: 42, b: 128 }, false, {\n                    message: '\"b\" must be [4]',\n                    path: ['b'],\n                    type: 'any.only',\n                    context: { value: 128, valids: [4], label: 'b', key: 'b' }\n                }]\n            ]);\n        });\n\n        it('sets value based on multiple conditions with otherwise (short)', () => {\n\n            const schema = Joi.object({\n                a: Joi.number().required(),\n                b: Joi.number()\n                    .when('a', [\n                        { is: 0, then: 1 },\n                        { is: 1, then: 2 },\n                        { is: 2, then: 3, otherwise: 4 }\n                    ])\n            });\n\n            Helper.validate(schema, [\n                [{ a: 0, b: 1 }, true],\n                [{ a: 0, b: 2 }, false, {\n                    message: '\"b\" must be [1]',\n                    path: ['b'],\n                    type: 'any.only',\n                    context: { value: 2, valids: [1], label: 'b', key: 'b' }\n                }],\n                [{ a: 1, b: 2 }, true],\n                [{ a: 1, b: 3 }, false, {\n                    message: '\"b\" must be [2]',\n                    path: ['b'],\n                    type: 'any.only',\n                    context: { value: 3, valids: [2], label: 'b', key: 'b' }\n                }],\n                [{ a: 2, b: 3 }, true],\n                [{ a: 2, b: 2 }, false, {\n                    message: '\"b\" must be [3]',\n                    path: ['b'],\n                    type: 'any.only',\n                    context: { value: 2, valids: [3], label: 'b', key: 'b' }\n                }],\n                [{ a: 42, b: 4 }, true],\n                [{ a: 42, b: 128 }, false, {\n                    message: '\"b\" must be [4]',\n                    path: ['b'],\n                    type: 'any.only',\n                    context: { value: 128, valids: [4], label: 'b', key: 'b' }\n                }]\n            ]);\n        });\n\n        it('sets value based on multiple conditions (with base rules)', () => {\n\n            const schema = Joi.object({\n                a: Joi.number().required(),\n                b: Joi.number().valid(10)\n                    .when('a', [\n                        { is: 0, then: Joi.valid(1) },\n                        { is: 1, then: Joi.valid(2) },\n                        { is: 2, then: Joi.valid(3) }\n                    ])\n            });\n\n            Helper.validate(schema, [\n                [{ a: 0, b: 1 }, true],\n                [{ a: 0, b: 2 }, false, {\n                    message: '\"b\" must be one of [10, 1]',\n                    path: ['b'],\n                    type: 'any.only',\n                    context: { value: 2, valids: [10, 1], label: 'b', key: 'b' }\n                }],\n                [{ a: 0, b: 10 }, true],\n                [{ a: 1, b: 2 }, true],\n                [{ a: 1, b: 3 }, false, {\n                    message: '\"b\" must be one of [10, 2]',\n                    path: ['b'],\n                    type: 'any.only',\n                    context: { value: 3, valids: [10, 2], label: 'b', key: 'b' }\n                }],\n                [{ a: 1, b: 10 }, true],\n                [{ a: 2, b: 3 }, true],\n                [{ a: 2, b: 4 }, false, {\n                    message: '\"b\" must be one of [10, 3]',\n                    path: ['b'],\n                    type: 'any.only',\n                    context: { value: 4, valids: [10, 3], label: 'b', key: 'b' }\n                }],\n                [{ a: 2, b: 10 }, true],\n                [{ a: 42, b: 128 }, false, {\n                    message: '\"b\" must be [10]',\n                    path: ['b'],\n                    type: 'any.only',\n                    context: { value: 128, valids: [10], label: 'b', key: 'b' }\n                }],\n                [{ a: 42, b: 10 }, true]\n            ]);\n        });\n\n        it('caches partials using variations in sub when conditions', () => {\n\n            const sub = Joi.when('b', { is: true, then: 2, otherwise: 3 });\n            const schema = Joi.object({\n                a: Joi.boolean().required(),\n                b: Joi.boolean().required(),\n                c: Joi.number()\n                    .when('a', { is: true, then: 1, otherwise: sub })\n            });\n\n            Helper.validate(schema, [\n                [{ a: true, b: true, c: 1 }, true],\n                [{ a: false, b: true, c: 2 }, true],\n                [{ a: false, b: false, c: 3 }, true]\n            ]);\n        });\n\n        it('validates conditional whens (self reference, explicit)', () => {\n\n            const schema = Joi.object({\n                a: Joi.boolean().required()\n            })\n                .when(Joi.ref('a', { ancestor: 0 }), {\n                    is: true,\n                    then: {\n                        b: Joi.string().required()\n                    },\n                    otherwise: {\n                        c: Joi.string().required()\n                    }\n                });\n\n            Helper.validate(schema, [\n                [{ a: true, b: 'x' }, true],\n                [{ a: true, b: 5 }, false, {\n                    message: '\"b\" must be a string',\n                    path: ['b'],\n                    type: 'string.base',\n                    context: { value: 5, key: 'b', label: 'b' }\n                }],\n                [{ a: true }, false, {\n                    message: '\"b\" is required',\n                    path: ['b'],\n                    type: 'any.required',\n                    context: { key: 'b', label: 'b' }\n                }],\n                [{ a: true, c: 5 }, false, {\n                    message: '\"b\" is required',\n                    path: ['b'],\n                    type: 'any.required',\n                    context: { key: 'b', label: 'b' }\n                }],\n                [{ a: true, c: 'x' }, false, {\n                    message: '\"b\" is required',\n                    path: ['b'],\n                    type: 'any.required',\n                    context: { key: 'b', label: 'b' }\n                }],\n\n                [{ a: false, b: 'x' }, false, {\n                    message: '\"c\" is required',\n                    path: ['c'],\n                    type: 'any.required',\n                    context: { key: 'c', label: 'c' }\n                }],\n                [{ a: false, b: 5 }, false, {\n                    message: '\"c\" is required',\n                    path: ['c'],\n                    type: 'any.required',\n                    context: { key: 'c', label: 'c' }\n                }],\n                [{ a: false }, false, {\n                    message: '\"c\" is required',\n                    path: ['c'],\n                    type: 'any.required',\n                    context: { key: 'c', label: 'c' }\n                }],\n                [{ a: false, c: 5 }, false, {\n                    message: '\"c\" must be a string',\n                    path: ['c'],\n                    type: 'string.base',\n                    context: { value: 5, key: 'c', label: 'c' }\n                }],\n                [{ a: false, c: 'x' }, true]\n            ]);\n        });\n\n        it('validates conditional whens (self reference, implicit)', () => {\n\n            const schema = Joi.object({\n                a: Joi.boolean().required()\n            })\n                .when('.a', {\n                    is: true,\n                    then: {\n                        b: Joi.string().required()\n                    },\n                    otherwise: {\n                        c: Joi.string().required()\n                    }\n                });\n\n            Helper.validate(schema, [\n                [{ a: true, b: 'x' }, true],\n                [{ a: true, b: 5 }, false, {\n                    message: '\"b\" must be a string',\n                    path: ['b'],\n                    type: 'string.base',\n                    context: { value: 5, key: 'b', label: 'b' }\n                }],\n                [{ a: true }, false, {\n                    message: '\"b\" is required',\n                    path: ['b'],\n                    type: 'any.required',\n                    context: { key: 'b', label: 'b' }\n                }],\n                [{ a: true, c: 5 }, false, {\n                    message: '\"b\" is required',\n                    path: ['b'],\n                    type: 'any.required',\n                    context: { key: 'b', label: 'b' }\n                }],\n                [{ a: true, c: 'x' }, false, {\n                    message: '\"b\" is required',\n                    path: ['b'],\n                    type: 'any.required',\n                    context: { key: 'b', label: 'b' }\n                }],\n                [{ a: false, b: 'x' }, false, {\n                    message: '\"c\" is required',\n                    path: ['c'],\n                    type: 'any.required',\n                    context: { key: 'c', label: 'c' }\n                }],\n                [{ a: false, b: 5 }, false, {\n                    message: '\"c\" is required',\n                    path: ['c'],\n                    type: 'any.required',\n                    context: { key: 'c', label: 'c' }\n                }],\n                [{ a: false }, false, {\n                    message: '\"c\" is required',\n                    path: ['c'],\n                    type: 'any.required',\n                    context: { key: 'c', label: 'c' }\n                }],\n                [{ a: false, c: 5 }, false, {\n                    message: '\"c\" must be a string',\n                    path: ['c'],\n                    type: 'string.base',\n                    context: { value: 5, key: 'c', label: 'c' }\n                }],\n                [{ a: false, c: 'x' }, true]\n            ]);\n        });\n\n        it('validates with nested whens', () => {\n\n            // If ((b === 0 && a === 123) ||\n            //     (b !== 0 && a === anything))\n            // then c === 456\n            // else c === 789\n\n            const schema = Joi.object({\n                a: Joi.number().required(),\n                b: Joi.number().required(),\n                c: Joi.when('a', {\n                    is: Joi.when('b', {\n                        is: Joi.valid(0),\n                        then: Joi.valid(123)\n                    }),\n                    then: Joi.valid(456),\n                    otherwise: Joi.valid(789)\n                })\n            });\n\n            Helper.validate(schema, [\n                [{ a: 123, b: 0, c: 456 }, true],\n                [{ a: 0, b: 1, c: 456 }, true],\n                [{ a: 0, b: 0, c: 789 }, true],\n                [{ a: 123, b: 456, c: 456 }, true],\n                [{ a: 0, b: 0, c: 456 }, false, {\n                    message: '\"c\" must be [789]',\n                    path: ['c'],\n                    type: 'any.only',\n                    context: { value: 456, valids: [789], label: 'c', key: 'c' }\n                }],\n                [{ a: 123, b: 456, c: 789 }, false, {\n                    message: '\"c\" must be [456]',\n                    path: ['c'],\n                    type: 'any.only',\n                    context: { value: 789, valids: [456], label: 'c', key: 'c' }\n                }]\n            ]);\n        });\n\n        it('throws when conditions conflict', () => {\n\n            const schema = Joi.object({\n                a: Joi.boolean(),\n                b: Joi.boolean(),\n                c: Joi.when('a', { is: true, then: Joi.number() }).when('b', { is: true, then: Joi.string() })\n            });\n\n            expect(() => schema.validate({ a: true, b: true, c: 'x' })).to.throw('Cannot merge type number with another type: string');\n        });\n\n        describe('with schema', () => {\n\n            it('should peek inside a simple value', () => {\n\n                const schema = Joi.number().when(Joi.number().min(0), { then: Joi.number().min(10) });\n                Helper.validate(schema, [\n                    [-1, true, -1],\n                    [1, false, {\n                        message: '\"value\" must be greater than or equal to 10',\n                        path: [],\n                        type: 'number.min',\n                        context: { limit: 10, value: 1, label: 'value' }\n                    }],\n                    [10, true, 10]\n                ]);\n            });\n\n            it('should peek inside an object', () => {\n\n                const schema = Joi.object().keys({\n                    foo: Joi.string(),\n                    bar: Joi.number()\n                })\n                    .when(Joi.object().keys({ foo: Joi.valid('hasBar').required() }).unknown(), {\n                        then: Joi.object().keys({ bar: Joi.required() })\n                    });\n\n                Helper.validate(schema, [\n                    [{ foo: 'whatever' }, true, { foo: 'whatever' }],\n                    [{ foo: 'hasBar' }, false, {\n                        message: '\"bar\" is required',\n                        path: ['bar'],\n                        type: 'any.required',\n                        context: { key: 'bar', label: 'bar' }\n                    }],\n                    [{ foo: 'hasBar', bar: 42 }, true, { foo: 'hasBar', bar: 42 }],\n                    [{}, true, {}]\n                ]);\n            });\n        });\n    });\n\n    describe('_rule()', () => {\n\n        it('errors on invalid options', () => {\n\n            expect(() => Joi.any().$_addRule()).to.throw('Invalid options');\n            expect(() => Joi.any().$_addRule(5)).to.throw('Invalid options');\n            expect(() => Joi.any().$_addRule({})).to.throw('Invalid rule name');\n            expect(() => Joi.any().$_addRule('')).to.throw('Invalid rule name');\n            expect(() => Joi.any().$_addRule({ name: '' })).to.throw('Invalid rule name');\n            expect(() => Joi.any().$_addRule({ name: 5 })).to.throw('Invalid rule name');\n        });\n    });\n\n    describe('~standard', () => {\n\n        it('returns the version number of the standard', () => {\n\n            const schema = Joi.number();\n            expect(schema['~standard'].version).to.equal(1);\n        });\n\n        it('returns the vendor name of the schema library', () => {\n\n            const schema = Joi.number();\n            expect(schema['~standard'].vendor).to.equal('joi');\n        });\n\n        describe('validate', () => {\n\n            it('return only value when passing', () => {\n\n                const schema = Joi.string();\n                expect(schema['~standard'].validate('3')).to.equal({\n                    value: '3'\n                });\n            });\n\n            it('return only issues when not passing', () => {\n\n                const schema = Joi.string();\n                expect(schema['~standard'].validate(3)).to.equal({\n                    issues: [{ message: '\"value\" must be a string', path: [] }]\n                });\n            });\n\n            it('return only issues when not passing (custom error is Error)', () => {\n\n                const schema = Joi.string().error(new Error('Was REALLY expecting a string'));\n                expect(schema['~standard'].validate(3)).to.equal({\n                    issues: [{ message: 'Was REALLY expecting a string' }]\n                });\n            });\n\n            it('return only issues when not passing (custom error is validation error function that return string)', () => {\n\n                const schema = Joi.object({\n                    foo: Joi.number().min(0).error((errors) => new Error('\"foo\" requires a positive number'))\n                });\n                expect(schema['~standard'].validate({ foo: -2 })).to.equal({\n                    issues: [{ message: '\"foo\" requires a positive number' }]\n                });\n            });\n        });\n    });\n});\n"
  },
  {
    "path": "test/cache.js",
    "content": "'use strict';\n\nconst Code = require('@hapi/code');\nconst Lab = require('@hapi/lab');\nconst Joi = require('..');\n\n\nconst internals = {};\n\n\nconst { describe, it } = exports.lab = Lab.script();\nconst { expect } = Code;\n\n\ndescribe('Cache', () => {\n\n    describe('schema', () => {\n\n        it('caches values', () => {\n\n            const schema = Joi.string().pattern(/abc/).cache();\n\n            const validate = schema._definition.rules.pattern.validate;\n            let count = 0;\n            schema._definition.rules.pattern.validate = function (...args) {\n\n                ++count;\n                return validate(...args);\n            };\n\n            expect(schema.validate('xabcd').error).to.not.exist();\n            expect(schema.validate('xabcd').error).to.not.exist();\n            expect(schema.validate('xabcd').error).to.not.exist();\n\n            expect(count).to.equal(1);\n\n            schema._definition.rules.pattern.validate = validate;\n        });\n\n        it('caches values (object key)', () => {\n\n            const a = Joi.string().pattern(/abc/).cache();\n            const schema = Joi.object({ a });\n\n            const validate = a._definition.rules.pattern.validate;\n            let count = 0;\n            a._definition.rules.pattern.validate = function (...args) {\n\n                ++count;\n                return validate(...args);\n            };\n\n            expect(schema.validate({ a: 'xabcd' }).error).to.not.exist();\n            expect(schema.validate({ a: 'xabcd' }).error).to.not.exist();\n            expect(schema.validate({ a: 'xabcd' }).error).to.not.exist();\n\n            expect(count).to.equal(1);\n\n            a._definition.rules.pattern.validate = validate;\n        });\n\n        it('caches errors', () => {\n\n            const schema = Joi.string().pattern(/abc/).cache();\n\n            const validate = schema._definition.rules.pattern.validate;\n            let count = 0;\n            schema._definition.rules.pattern.validate = function (...args) {\n\n                ++count;\n                return validate(...args);\n            };\n\n            const err = schema.validate('xbcd').error;\n            expect(schema.validate('xbcd').error).to.equal(err);\n            expect(schema.validate('xbcd').error).to.equal(err);\n            expect(schema.validate('xbcd').error).to.equal(err);\n\n            expect(count).to.equal(1);\n\n            schema._definition.rules.pattern.validate = validate;\n        });\n\n        it('skips caching when prefs disabled', () => {\n\n            const cache = Joi.cache.provision();\n            const schema = Joi.string().pattern(/abc/).cache(cache);\n\n            const validate = schema._definition.rules.pattern.validate;\n            let count = 0;\n            schema._definition.rules.pattern.validate = function (...args) {\n\n                ++count;\n                return validate(...args);\n            };\n\n            expect(schema.validate('xabcd', { cache: false }).error).to.not.exist();\n            expect(schema.validate('xabcd', { cache: false }).error).to.not.exist();\n            expect(schema.validate('xabcd', { cache: false }).error).to.not.exist();\n\n            expect(count).to.equal(3);\n\n            schema._definition.rules.pattern.validate = validate;\n        });\n\n        it('skips caching when schema contains refs', () => {\n\n            const a = Joi.string().allow(Joi.ref('b')).pattern(/abc/).cache();\n            const schema = Joi.object({\n                a,\n                b: Joi.any()\n            });\n\n            const validate = a._definition.rules.pattern.validate;\n            let count = 0;\n            a._definition.rules.pattern.validate = function (...args) {\n\n                ++count;\n                return validate(...args);\n            };\n\n            expect(schema.validate({ a: 'xabcd' }).error).to.not.exist();\n            expect(schema.validate({ a: 'xabcd' }).error).to.not.exist();\n            expect(schema.validate({ a: 'xabcd' }).error).to.not.exist();\n\n            expect(count).to.equal(3);\n\n            a._definition.rules.pattern.validate = validate;\n        });\n    });\n\n    describe('provider', () => {\n\n        describe('provision()', () => {\n\n            it('generates cache', () => {\n\n                const cache = Joi.cache.provision({ max: 5 });\n\n                cache.set(1, 'x');\n                expect(cache.get(1)).to.equal('x');\n\n                cache.set(2, 'y');\n                expect(cache.get(2)).to.equal('y');\n\n                cache.set(3, 'z');\n                expect(cache.get(3)).to.equal('z');\n\n                cache.set('a', 'b');\n                expect(cache.get('a')).to.equal('b');\n\n                cache.set('b', 'c');\n                expect(cache.get('b')).to.equal('c');\n\n                cache.set({}, 'ignore');\n                expect(cache.get({})).to.not.exist();\n\n                cache.set(1, 'v');\n                expect(cache.get(1)).to.equal('v');\n\n                cache.set(null, 'x');\n                expect(cache.get(null)).to.equal('x');\n\n                expect(cache).to.have.length(5);\n\n                expect(cache.get(2)).to.not.exist();\n                expect(cache.get(1)).to.equal('v');\n            });\n        });\n\n        describe('Joi.cache', () => {\n\n            it('generates cache with default max', () => {\n\n                const cache = Joi.cache.provision();\n                for (let i = 0; i < 1020; ++i) {\n                    cache.set(i, i + 10);\n                    expect(cache.get(i)).to.equal(i + 10);\n                }\n\n                expect(cache).to.have.length(1000);\n            });\n\n            it('errors on invalid max option', () => {\n\n                expect(() => Joi.cache.provision({ max: null })).to.throw('Invalid max cache size');\n                expect(() => Joi.cache.provision({ max: Infinity })).to.throw('Invalid max cache size');\n                expect(() => Joi.cache.provision({ max: -1 })).to.throw('Invalid max cache size');\n                expect(() => Joi.cache.provision({ max: 0 })).to.throw('Invalid max cache size');\n            });\n        });\n    });\n});\n"
  },
  {
    "path": "test/common.js",
    "content": "'use strict';\n\nconst Code = require('@hapi/code');\nconst Lab = require('@hapi/lab');\n\nconst Common = require('../lib/common');\n\n\nconst internals = {};\n\n\nconst { describe, it } = exports.lab = Lab.script();\nconst { expect } = Code;\n\n\ndescribe('Common', () => {\n\n    describe('assertOptions', () => {\n\n        it('validates null', () => {\n\n            expect(() => Common.assertOptions()).to.throw('Options must be of type object');\n        });\n    });\n});\n"
  },
  {
    "path": "test/compile.js",
    "content": "'use strict';\n\nconst Code = require('@hapi/code');\nconst Joi = require('..');\nconst Lab = require('@hapi/lab');\nconst Legacy = require('@hapi/joi-legacy-test');\n\nconst Helper = require('./helper');\n\n\nconst internals = {};\n\n\nconst { describe, it } = exports.lab = Lab.script();\nconst { expect } = Code;\n\n\ndescribe('cast', () => {\n\n    describe('schema()', () => {\n\n        it('casts templates', () => {\n\n            const schema = Joi.object({\n                a: Joi.number(),\n                b: Joi.x('{a + 1}')\n            });\n\n            Helper.validate(schema, [[{ a: 5, b: 6 }, true]]);\n        });\n\n        it('compiles null schema', () => {\n\n            Helper.validate(Joi.compile(null), [\n                ['a', false, {\n                    message: '\"value\" must be [null]',\n                    path: [],\n                    type: 'any.only',\n                    context: { value: 'a', valids: [null], label: 'value' }\n                }],\n                [null, true]\n            ]);\n        });\n\n        it('compiles number literal', () => {\n\n            Helper.validate(Joi.compile(5), [\n                [6, false, {\n                    message: '\"value\" must be [5]',\n                    path: [],\n                    type: 'any.only',\n                    context: { value: 6, valids: [5], label: 'value' }\n                }],\n                [5, true]\n            ]);\n        });\n\n        it('compiles string literal', () => {\n\n            Helper.validate(Joi.compile('5'), [\n                ['6', false, {\n                    message: '\"value\" must be [5]',\n                    path: [],\n                    type: 'any.only',\n                    context: { value: '6', valids: ['5'], label: 'value' }\n                }],\n                ['5', true]\n            ]);\n        });\n\n        it('compiles boolean literal', () => {\n\n            Helper.validate(Joi.compile(true), [\n                [false, false, {\n                    message: '\"value\" must be [true]',\n                    path: [],\n                    type: 'any.only',\n                    context: { value: false, valids: [true], label: 'value' }\n                }],\n                [true, true]\n            ]);\n        });\n\n        it('compiles date literal', () => {\n\n            const now = Date.now();\n            const dnow = new Date(now);\n            Helper.validate(Joi.compile(dnow), [\n                [new Date(now), true],\n                [now, true, new Date(now)],\n                [now * 2, false, {\n                    message: `\"value\" must be [${dnow.toISOString()}]`,\n                    path: [],\n                    type: 'any.only',\n                    context: { value: new Date(now * 2), valids: [dnow], label: 'value' }\n                }]\n            ]);\n        });\n\n        it('compile [null]', () => {\n\n            const schema = Joi.compile([null]);\n            Helper.equal(schema, Joi.valid(Joi.override, null));\n        });\n\n        it('compile [1]', () => {\n\n            const schema = Joi.compile([1]);\n            Helper.equal(schema, Joi.valid(Joi.override, 1));\n        });\n\n        it('compile [\"a\"]', () => {\n\n            const schema = Joi.compile(['a']);\n            Helper.equal(schema, Joi.valid(Joi.override, 'a'));\n        });\n\n        it('compile [null, null, null]', () => {\n\n            const schema = Joi.compile([null]);\n            Helper.equal(schema, Joi.valid(Joi.override, null));\n        });\n\n        it('compile [1, 2, 3]', () => {\n\n            const schema = Joi.compile([1, 2, 3]);\n            Helper.equal(schema, Joi.valid(Joi.override, 1, 2, 3));\n        });\n\n        it('compile [\"a\", \"b\", \"c\"]', () => {\n\n            const schema = Joi.compile(['a', 'b', 'c']);\n            Helper.equal(schema, Joi.valid(Joi.override, 'a', 'b', 'c'));\n        });\n\n        it('compile [null, \"a\", 1, true]', () => {\n\n            const schema = Joi.compile([null, 'a', 1, true]);\n            Helper.equal(schema, Joi.valid(Joi.override, null, 'a', 1, true));\n        });\n    });\n\n    describe('compile()', () => {\n\n        it('compiles object with plain keys', () => {\n\n            const schema = {\n                a: 1\n            };\n\n            expect(Joi.isSchema(schema)).to.be.false();\n\n            const compiled = Joi.compile(schema);\n            expect(Joi.isSchema(compiled)).to.be.true();\n        });\n\n        it('compiles object with schema keys', () => {\n\n            const schema = {\n                a: Joi.number()\n            };\n\n            expect(Joi.isSchema(schema)).to.be.false();\n\n            const compiled = Joi.compile(schema);\n            expect(Joi.isSchema(compiled)).to.be.true();\n        });\n\n        it('errors on legacy schema', () => {\n\n            const schema = Legacy.number();\n            expect(() => Joi.compile(schema)).to.throw(`Cannot mix different versions of joi schemas: ${require('@hapi/joi-legacy-test/package.json').version} ${require('../package.json').version}`);\n            expect(() => Joi.compile(schema, { legacy: true })).to.not.throw();\n        });\n\n        it('errors on legacy keys', () => {\n\n            const schema = {\n                a: Legacy.number()\n            };\n\n            expect(() => Joi.compile(schema)).to.throw('Cannot mix different versions of joi schemas (a)');\n        });\n\n        describe('legacy', () => {\n\n            it('compiles object with plain keys', () => {\n\n                const schema = {\n                    a: 1,\n                    b: [2, 3]\n                };\n\n                expect(Joi.isSchema(schema)).to.be.false();\n\n                const compiled = Joi.compile(schema, { legacy: true });\n                expect(Joi.isSchema(compiled)).to.be.true();\n            });\n\n            it('compiles object with schema keys (v16)', () => {\n\n                const schema = {\n                    a: Joi.number()\n                };\n\n                expect(Joi.isSchema(schema)).to.be.false();\n\n                const compiled = Joi.compile(schema, { legacy: true });\n                expect(Joi.isSchema(compiled)).to.be.true();\n            });\n\n            it('compiles object with schema array items (v16)', () => {\n\n                const schema = {\n                    a: [Joi.number()]\n                };\n\n                expect(Joi.isSchema(schema)).to.be.false();\n\n                const compiled = Joi.compile(schema, { legacy: true });\n                expect(Joi.isSchema(compiled)).to.be.true();\n            });\n\n            it('compiles object with schema keys (v15)', () => {\n\n                const schema = {\n                    a: Legacy.number()\n                };\n\n                expect(Joi.isSchema(schema)).to.be.false();\n\n                const compiled = Joi.compile(schema, { legacy: true });\n                expect(Joi.isSchema(compiled, { legacy: true })).to.be.true();\n                expect(() => Joi.isSchema(compiled)).to.throw('Cannot mix different versions of joi schemas');\n            });\n\n            it('compiles object with schema keys (v15)', () => {\n\n                const schema = {\n                    a: [Legacy.number()]\n                };\n\n                expect(Joi.isSchema(schema)).to.be.false();\n\n                const compiled = Joi.compile(schema, { legacy: true });\n                expect(Joi.isSchema(compiled, { legacy: true })).to.be.true();\n                expect(() => Joi.isSchema(compiled)).to.throw('Cannot mix different versions of joi schemas');\n            });\n        });\n    });\n});\n"
  },
  {
    "path": "test/errors.js",
    "content": "'use strict';\n\nconst Code = require('@hapi/code');\nconst Joi = require('..');\nconst Lab = require('@hapi/lab');\n\nconst Helper = require('./helper');\n\n\nconst internals = {};\n\n\nconst { describe, it } = exports.lab = Lab.script();\nconst { expect } = Code;\n\n\ndescribe('errors', () => {\n\n    it('has an isJoi property', () => {\n\n        const err = Joi.valid('foo').validate('bar').error;\n        expect(err).to.be.an.error();\n        expect(err.isJoi).to.be.true();\n        expect(Joi.isError(err)).to.be.true();\n    });\n\n\n    it('has no stack by default', () => {\n\n        const err = Joi.valid('foo').validate('bar').error;\n        expect(err).to.be.an.error();\n        expect(err.isJoi).to.be.true();\n        expect(err.stack).to.not.contain('\\n');\n    });\n\n    it('has a stack when enabled', () => {\n\n        const err = Joi.valid('foo').validate('bar', { errors: { stack: true } }).error;\n        expect(err).to.be.an.error();\n        expect(err.isJoi).to.be.true();\n        expect(err.stack).to.contain('    at ');\n        expect(err.stack).to.contain('exports.process');\n    });\n\n    it('supports custom errors when validating types', () => {\n\n        const schema = Joi.object({\n            email: Joi.string().email(),\n            date: Joi.date(),\n            alphanum: Joi.string().alphanum(),\n            min: Joi.string().min(3),\n            max: Joi.string().max(3),\n            required: Joi.string().required(),\n            xor: Joi.string(),\n            renamed: Joi.string().valid('456'),\n            notEmpty: Joi.string().required()\n        })\n            .rename('renamed', 'required')\n            .without('required', 'xor')\n            .without('xor', 'required');\n\n        const value = {\n            email: 'invalid-email',\n            date: 'invalid-date',\n            alphanum: '\\b\\n\\f\\r\\t',\n            min: 'ab',\n            max: 'abcd',\n            required: 'hello',\n            xor: '123',\n            renamed: '456',\n            notEmpty: ''\n        };\n\n        const messages = {\n            'string.empty': '{#label} 3',\n            'date.base': '{#label} 18',\n            'string.base': '{#label} 13',\n            'string.min': '{#label} 14',\n            'string.max': '{#label} 15',\n            'string.alphanum': '{#label} 16',\n            'string.email': '{#label} 19',\n            'object.without': '{#label} 7',\n            'object.rename.override': '{#label} 11'\n        };\n\n        const error = schema.validate(value, { abortEarly: false, messages }).error;\n\n        value.required = value.renamed;\n        delete value.renamed;\n\n        expect(error).to.be.an.error('\"value\" 11. \"email\" 19. \"date\" 18. \"alphanum\" 16. \"min\" 14. \"max\" 15. \"notEmpty\" 3. \"value\" 7');\n        expect(error.name).to.equal('ValidationError');\n        expect(error.details).to.equal([\n            {\n                message: '\"value\" 11',\n                path: [],\n                type: 'object.rename.override',\n                context: { from: 'renamed', to: 'required', label: 'value', pattern: false, value }\n            },\n            {\n                message: '\"email\" 19',\n                path: ['email'],\n                type: 'string.email',\n                context: { value: 'invalid-email', invalids: ['invalid-email'], label: 'email', key: 'email' }\n            },\n            {\n                message: '\"date\" 18',\n                path: ['date'],\n                type: 'date.base',\n                context: { label: 'date', key: 'date', value: 'invalid-date' }\n            },\n            {\n                message: '\"alphanum\" 16',\n                path: ['alphanum'],\n                type: 'string.alphanum',\n                context: { value: '\\b\\n\\f\\r\\t', label: 'alphanum', key: 'alphanum' }\n            },\n            {\n                message: '\"min\" 14',\n                path: ['min'],\n                type: 'string.min',\n                context: {\n                    limit: 3,\n                    value: 'ab',\n                    encoding: undefined,\n                    label: 'min',\n                    key: 'min'\n                }\n            },\n            {\n                message: '\"max\" 15',\n                path: ['max'],\n                type: 'string.max',\n                context: {\n                    limit: 3,\n                    value: 'abcd',\n                    encoding: undefined,\n                    label: 'max',\n                    key: 'max'\n                }\n            },\n            {\n                message: '\"notEmpty\" 3',\n                path: ['notEmpty'],\n                type: 'string.empty',\n                context: { value: '', label: 'notEmpty', key: 'notEmpty' }\n            },\n            {\n                message: '\"value\" 7',\n                path: [],\n                type: 'object.without',\n                context: {\n                    main: 'required',\n                    mainWithLabel: 'required',\n                    peer: 'xor',\n                    peerWithLabel: 'xor',\n                    label: 'value',\n                    value\n                }\n            },\n            {\n                message: '\"value\" 7',\n                path: [],\n                type: 'object.without',\n                context: {\n                    main: 'xor',\n                    mainWithLabel: 'xor',\n                    peer: 'required',\n                    peerWithLabel: 'required',\n                    label: 'value',\n                    value\n                }\n            }\n        ]);\n    });\n\n    it('supports language preference', () => {\n\n        const schema = Joi.number().min(10);\n\n        const messages = {\n            english: {\n                root: 'value',\n                'number.min': '{#label} too small'\n            },\n            latin: {\n                root: 'valorem',\n                'number.min': Joi.x('{@label} angustus', { prefix: { local: '@' } })\n            },\n            empty: {}\n        };\n\n        expect(schema.validate(1, { messages, errors: { language: 'english' } }).error).to.be.an.error('\"value\" too small');\n        expect(schema.validate(1, { messages, errors: { language: 'latin' } }).error).to.be.an.error('\"valorem\" angustus');\n        expect(schema.validate(1, { messages, errors: { language: 'unknown' } }).error).to.be.an.error('\"value\" must be greater than or equal to 10');\n        expect(schema.validate(1, { messages, errors: { language: 'empty' } }).error).to.be.an.error('\"value\" must be greater than or equal to 10');\n    });\n\n    it('supports language preference (fallthrough)', () => {\n\n        const messages = {\n            english: {\n                root: 'value',\n                'number.min': '{#label} too small'\n            },\n            root: 'valorem',\n            'number.min': '{#label} angustus'\n        };\n\n        const schema = Joi.number().min(10).prefs({ messages });\n\n        expect(schema.validate(1, { errors: { language: 'english' } }).error).to.be.an.error('\"value\" too small');\n        expect(schema.validate(1, { errors: { language: 'latin' } }).error).to.be.an.error('\"valorem\" angustus');\n\n        expect(schema.describe().preferences.messages).to.equal(messages);\n    });\n\n    it('supports language preference combination', () => {\n\n        const code = {\n            english: {\n                'number.min': '{#label} too small'\n            },\n            latin: {\n                'number.min': Joi.x('{@label} angustus', { prefix: { local: '@' } })\n            },\n            empty: {}\n        };\n\n        const root = {\n            english: {\n                root: 'value'\n            },\n            latin: {\n                root: 'valorem'\n            }\n        };\n\n        const schema = Joi.number().min(10).prefs({ messages: code }).prefs({ messages: root });\n\n        expect(schema.validate(1, { errors: { language: 'english' } }).error).to.be.an.error('\"value\" too small');\n        expect(schema.validate(1, { errors: { language: 'latin' } }).error).to.be.an.error('\"valorem\" angustus');\n        expect(schema.validate(1, { errors: { language: 'unknown' } }).error).to.be.an.error('\"value\" must be greater than or equal to 10');\n        expect(schema.validate(1, { errors: { language: 'empty' } }).error).to.be.an.error('\"value\" must be greater than or equal to 10');\n    });\n\n    it('supports language ref preference', () => {\n\n        const messages = {\n            english: {\n                'number.min': '{#label} too small'\n            },\n            latin: {\n                'number.min': Joi.x('{@label} angustus', { prefix: { local: '@' } })\n            },\n            empty: {}\n        };\n\n        const schema = Joi.object({\n            a: Joi.number().min(10),\n            lang: Joi.string().required()\n        })\n            .prefs({\n                messages,\n                errors: {\n                    language: Joi.ref('/lang'),\n                    wrap: {\n                        label: false\n                    }\n                }\n            });\n\n        expect(schema.validate({ a: 1, lang: 'english' }).error).to.be.an.error('a too small');\n        expect(schema.validate({ a: 1, lang: 'latin' }).error).to.be.an.error('a angustus');\n        expect(schema.validate({ a: 1, lang: 'unknown' }).error).to.be.an.error('a must be greater than or equal to 10');\n        expect(schema.validate({ a: 1, lang: 'empty' }).error).to.be.an.error('a must be greater than or equal to 10');\n    });\n\n    it('supports custom wrap characters', () => {\n\n        const messages = {\n            english: {\n                'number.min': '{#label} too small'\n            },\n            latin: {\n                'number.min': Joi.x('{@label} angustus', { prefix: { local: '@' } })\n            },\n            empty: {}\n        };\n\n        const schema = Joi.object({\n            a: Joi.number().min(10),\n            lang: Joi.string().required(),\n            select: ['x']\n        })\n            .prefs({\n                messages,\n                errors: {\n                    language: Joi.ref('/lang'),\n                    wrap: {\n                        label: '{}',\n                        string: '`\\''\n                    }\n                }\n            });\n\n        expect(schema.validate({ a: 1, lang: 'english' }).error).to.be.an.error('{a} too small');\n        expect(schema.validate({ a: 1, lang: 'latin' }).error).to.be.an.error('{a} angustus');\n        expect(schema.validate({ a: 1, lang: 'unknown' }).error).to.be.an.error('{a} must be greater than or equal to 10');\n        expect(schema.validate({ a: 1, lang: 'empty' }).error).to.be.an.error('{a} must be greater than or equal to 10');\n        expect(schema.validate({ select: 'y', a: 20, lang: 'empty' }).error).to.be.an.error('{select} must be [`x\\']');\n    });\n\n    it('supports render preference', () => {\n\n        expect(Joi.number().min(10).validate(1, { errors: { render: false } }).error).to.be.an.error('number.min');\n    });\n\n    it('does not prefix with key when messages uses context.key', () => {\n\n        const schema = Joi.valid('sad').prefs({ messages: { 'any.only': 'my hero {{#label}} is not {{#valids}}' } });\n        const err = schema.validate(5).error;\n        expect(err).to.be.an.error('my hero \"value\" is not [sad]');\n        expect(err.details).to.equal([{\n            message: 'my hero \"value\" is not [sad]',\n            path: [],\n            type: 'any.only',\n            context: { value: 5, valids: ['sad'], label: 'value' }\n        }]);\n    });\n\n    it('escapes unsafe keys', () => {\n\n        const schema = Joi.object({\n            'a()': Joi.number()\n        });\n\n        const err = schema.validate({ 'a()': 'x' }, { errors: { escapeHtml: true } }).error;\n        expect(err).to.be.an.error('\"a&#x28;&#x29;\" must be a number');\n        expect(err.details).to.equal([{\n            message: '\"a&#x28;&#x29;\" must be a number',\n            path: ['a()'],\n            type: 'number.base',\n            context: { label: 'a()', key: 'a()', value: 'x' }\n        }]);\n\n        const err2 = schema.validate({ 'b()': 'x' }, { errors: { escapeHtml: true } }).error;\n        expect(err2).to.be.an.error('\"b&#x28;&#x29;\" is not allowed');\n        expect(err2.details).to.equal([{\n            message: '\"b&#x28;&#x29;\" is not allowed',\n            path: ['b()'],\n            type: 'object.unknown',\n            context: { child: 'b()', label: 'b()', key: 'b()', value: 'x' }\n        }]);\n    });\n\n    it('does not escape unsafe keys by default', () => {\n\n        const schema = Joi.object({\n            'a()': Joi.number()\n        });\n\n        const err = schema.validate({ 'a()': 'x' }).error;\n        expect(err).to.be.an.error('\"a()\" must be a number');\n        expect(err.details).to.equal([{\n            message: '\"a()\" must be a number',\n            path: ['a()'],\n            type: 'number.base',\n            context: { label: 'a()', key: 'a()', value: 'x' }\n        }]);\n\n        const err2 = schema.validate({ 'b()': 'x' }).error;\n        expect(err2).to.be.an.error('\"b()\" is not allowed');\n        expect(err2.details).to.equal([{\n            message: '\"b()\" is not allowed',\n            path: ['b()'],\n            type: 'object.unknown',\n            context: { child: 'b()', label: 'b()', key: 'b()', value: 'x' }\n        }]);\n    });\n\n    it('returns error type in validation error', () => {\n\n        const input = {\n            notNumber: '',\n            notString: true,\n            notBoolean: 9\n        };\n\n        const schema = Joi.object({\n            notNumber: Joi.number().required(),\n            notString: Joi.string().required(),\n            notBoolean: Joi.boolean().required()\n        });\n\n        const err = schema.validate(input, { abortEarly: false }).error;\n        expect(err).to.be.an.error('\"notNumber\" must be a number. \"notString\" must be a string. \"notBoolean\" must be a boolean');\n        expect(err.details).to.equal([\n            {\n                message: '\"notNumber\" must be a number',\n                path: ['notNumber'],\n                type: 'number.base',\n                context: { label: 'notNumber', key: 'notNumber', value: '' }\n            },\n            {\n                message: '\"notString\" must be a string',\n                path: ['notString'],\n                type: 'string.base',\n                context: { value: true, label: 'notString', key: 'notString' }\n            },\n            {\n                message: '\"notBoolean\" must be a boolean',\n                path: ['notBoolean'],\n                type: 'boolean.base',\n                context: { label: 'notBoolean', key: 'notBoolean', value: 9 }\n            }\n        ]);\n    });\n\n    it('returns a full path to an error value on an array (items)', () => {\n\n        const schema = Joi.array().items(Joi.array().items({ x: Joi.number() }));\n        const input = [\n            [{ x: 1 }],\n            [{ x: 1 }, { x: 'a' }]\n        ];\n\n        const err = schema.validate(input).error;\n        expect(err).to.be.an.error('\"[1][1].x\" must be a number');\n        expect(err.details).to.equal([{\n            message: '\"[1][1].x\" must be a number',\n            path: [1, 1, 'x'],\n            type: 'number.base',\n            context: { label: '[1][1].x', key: 'x', value: 'a' }\n        }]);\n    });\n\n    it('returns a full path to an error value on an array (items forbidden)', () => {\n\n        const schema = Joi.array().items(Joi.array().items(Joi.object({ x: Joi.string() }).forbidden()));\n        const input = [\n            [{ x: 1 }],\n            [{ x: 1 }, { x: 'a' }]\n        ];\n\n        const err = schema.validate(input).error;\n        expect(err).to.be.an.error('\"[1][1]\" contains an excluded value');\n        expect(err.details).to.equal([{\n            message: '\"[1][1]\" contains an excluded value',\n            path: [1, 1],\n            type: 'array.excludes',\n            context: { pos: 1, value: { x: 'a' }, label: '[1][1]', key: 1 }\n        }]);\n    });\n\n    it('returns a full path to an error value on an object', () => {\n\n        const schema = Joi.object({\n            x: Joi.array().items({ x: Joi.number() })\n        });\n\n        const input = {\n            x: [{ x: 1 }, { x: 'a' }]\n        };\n\n        const err = schema.validate(input).error;\n        expect(err).to.be.an.error('\"x[1].x\" must be a number');\n        expect(err.details).to.equal([{\n            message: '\"x[1].x\" must be a number',\n            path: ['x', 1, 'x'],\n            type: 'number.base',\n            context: { label: 'x[1].x', key: 'x', value: 'a' }\n        }]);\n    });\n\n    it('overrides root key messages', () => {\n\n        const schema = Joi.string().prefs({ messages: { root: 'blah' } });\n        const err = schema.validate(4).error;\n        expect(err).to.be.an.error('\"blah\" must be a string');\n        expect(err.details).to.equal([{\n            message: '\"blah\" must be a string',\n            path: [],\n            type: 'string.base',\n            context: { value: 4, label: 'blah' }\n        }]);\n    });\n\n    it('disables wrap.array', () => {\n\n        const schema = Joi.alternatives(Joi.number(), Joi.string()).prefs({ errors: { wrap: { array: false } } });\n\n        Helper.validate(schema, [\n            [1, true],\n            ['x', true],\n            [true, false, '\"value\" must be one of number, string']\n        ]);\n    });\n\n    it('overrides wrap.array', () => {\n\n        const schema = Joi.alternatives(Joi.number(), Joi.string()).prefs({ errors: { wrap: { array: '{}' } } });\n\n        Helper.validate(schema, [\n            [1, true],\n            ['x', true],\n            [true, false, '\"value\" must be one of {number, string}']\n        ]);\n    });\n\n    it('allows html escaping', () => {\n\n        const schema = Joi.string().prefs({ messages: { root: 'blah' } }).label('bleh');\n        const err = schema.validate(4).error;\n        expect(err).to.be.an.error('\"bleh\" must be a string');\n        expect(err.details).to.equal([{\n            message: '\"bleh\" must be a string',\n            path: [],\n            type: 'string.base',\n            context: { value: 4, label: 'bleh' }\n        }]);\n    });\n\n    it('provides context with the error', () => {\n\n        const schema = Joi.object({ length: Joi.number().min(3).required() });\n        const err = schema.validate({ length: 1 }).error;\n        expect(err.details).to.equal([{\n            message: '\"length\" must be greater than or equal to 3',\n            path: ['length'],\n            type: 'number.min',\n            context: {\n                limit: 3,\n                key: 'length',\n                label: 'length',\n                value: 1\n            }\n        }]);\n    });\n\n    it('has a name that is ValidationError', () => {\n\n        const schema = Joi.number();\n        const validateErr = schema.validate('a').error;\n        expect(validateErr.name).to.be.equal('ValidationError');\n\n        try {\n            Joi.assert('a', schema);\n            throw new Error('should not reach that');\n        }\n        catch (assertErr) {\n            expect(assertErr.name).to.be.equal('ValidationError');\n        }\n\n        try {\n            Joi.assert('a', schema, 'foo');\n            throw new Error('should not reach that');\n        }\n        catch (assertErr) {\n            expect(assertErr.name).to.be.equal('ValidationError');\n        }\n\n        try {\n            Joi.assert('a', schema, new Error('foo'));\n            throw new Error('should not reach that');\n        }\n        catch (assertErr) {\n            expect(assertErr.name).to.equal('Error');\n        }\n    });\n\n    it('changes label value', () => {\n\n        const schema = Joi.object({\n            x: Joi.object({\n                y: Joi.object({\n                    z: Joi.valid('z'),\n                    a: Joi.array().items(Joi.string())\n                })\n            })\n        });\n\n        expect(schema.validate({ x: { y: { z: 'o' } } }).error).to.be.an.error('\"x.y.z\" must be [z]');\n        expect(schema.validate({ x: { y: { z: 'o' } } }, { errors: { label: false } }).error).to.be.an.error('must be [z]');\n        expect(schema.validate({ x: { y: { z: 'o' } } }, { errors: { label: 'key' } }).error).to.be.an.error('\"z\" must be [z]');\n        expect(schema.validate(1, { errors: { label: 'key' } }).error).to.be.an.error('\"value\" must be of type object');\n        expect(schema.validate({ x: { y: { a: [1] } } }, { errors: { label: 'key' } }).error).to.be.an.error('\"[0]\" must be a string');\n    });\n\n    it('removes labels when label is false', () => {\n\n        const schema = Joi.object({\n            x: Joi.object({\n                y: Joi.object({\n                    z: Joi.valid('z').label('z'),\n                    a: Joi.array().items(Joi.string().label('item'))\n                })\n            })\n        }).options({ errors: { label: false } });\n\n        expect(schema.validate({ x: { y: { z: 'o' } } }).error).to.be.an.error('must be [z]');\n        expect(schema.validate({ x: { y: { a: [1] } } }).error).to.be.an.error('must be a string');\n    });\n\n    describe('annotate()', () => {\n\n        it('annotates error', () => {\n\n            const object = {\n                a: 'm',\n                y: {\n                    b: {\n                        c: 10\n                    }\n                }\n            };\n\n            const schema = Joi.object({\n                a: Joi.string().valid('a', 'b', 'c', 'd'),\n                y: Joi.object({\n                    u: Joi.string().valid('e', 'f', 'g', 'h').required(),\n                    b: Joi.string().valid('i', 'j').allow(false),\n                    d: Joi.object({\n                        x: Joi.string().valid('k', 'l').required(),\n                        c: Joi.number()\n                    })\n                })\n            });\n\n            const err = schema.validate(object, { abortEarly: false }).error;\n            expect(err).to.be.an.error('\"a\" must be one of [a, b, c, d]. \"y.u\" is required. \"y.b\" must be one of [i, j, false]. \"y.b\" must be a string');\n            expect(err.details).to.equal([\n                {\n                    message: '\"a\" must be one of [a, b, c, d]',\n                    path: ['a'],\n                    type: 'any.only',\n                    context: { value: 'm', valids: ['a', 'b', 'c', 'd'], label: 'a', key: 'a' }\n                },\n                {\n                    message: '\"y.u\" is required',\n                    path: ['y', 'u'],\n                    type: 'any.required',\n                    context: { label: 'y.u', key: 'u' }\n                },\n                {\n                    message: '\"y.b\" must be one of [i, j, false]',\n                    path: ['y', 'b'],\n                    type: 'any.only',\n                    context: { value: { c: 10 }, label: 'y.b', key: 'b', valids: ['i', 'j', false] }\n                },\n                {\n                    message: '\"y.b\" must be a string',\n                    path: ['y', 'b'],\n                    type: 'string.base',\n                    context: { value: { c: 10 }, label: 'y.b', key: 'b' }\n                }\n            ]);\n\n            expect(err.annotate()).to.equal('{\\n  \"y\": {\\n    \"b\" \\u001b[31m[3, 4]\\u001b[0m: {\\n      \"c\": 10\\n    },\\n    \\u001b[41m\"u\"\\u001b[0m\\u001b[31m [2]: -- missing --\\u001b[0m\\n  },\\n  \"a\" \\u001b[31m[1]\\u001b[0m: \"m\"\\n}\\n\\u001b[31m\\n[1] \"a\" must be one of [a, b, c, d]\\n[2] \"y.u\" is required\\n[3] \"y.b\" must be one of [i, j, false]\\n[4] \"y.b\" must be a string\\u001b[0m');\n        });\n\n        it('annotates error without colors if requested', () => {\n\n            const object = {\n                a: 'm'\n            };\n\n            const schema = Joi.object({\n                a: Joi.string().valid('a', 'b', 'c', 'd')\n            });\n\n            const err = schema.validate(object, { abortEarly: false }).error;\n            expect(err).to.be.an.error('\"a\" must be one of [a, b, c, d]');\n            expect(err.details).to.equal([{\n                message: '\"a\" must be one of [a, b, c, d]',\n                path: ['a'],\n                type: 'any.only',\n                context: { value: 'm', valids: ['a', 'b', 'c', 'd'], label: 'a', key: 'a' }\n            }]);\n            expect(err.annotate(true)).to.equal('{\\n  \"a\" [1]: \"m\"\\n}\\n\\n[1] \"a\" must be one of [a, b, c, d]');\n        });\n\n        it('annotates error within array', () => {\n\n            const object = {\n                a: [1, 2, 3, 4, 2, 5]\n            };\n\n            const schema = Joi.object({\n                a: Joi.array().items(Joi.valid(1, 2))\n            });\n\n            const err = schema.validate(object, { abortEarly: false }).error;\n            expect(err).to.be.an.error('\"a[2]\" must be one of [1, 2]. \"a[3]\" must be one of [1, 2]. \"a[5]\" must be one of [1, 2]');\n            expect(err.details).to.equal([\n                {\n                    message: '\"a[2]\" must be one of [1, 2]',\n                    path: ['a', 2],\n                    type: 'any.only',\n                    context: { value: 3, valids: [1, 2], label: 'a[2]', key: 2 }\n                },\n                {\n                    message: '\"a[3]\" must be one of [1, 2]',\n                    path: ['a', 3],\n                    type: 'any.only',\n                    context: { value: 4, valids: [1, 2], label: 'a[3]', key: 3 }\n                },\n                {\n                    message: '\"a[5]\" must be one of [1, 2]',\n                    path: ['a', 5],\n                    type: 'any.only',\n                    context: { value: 5, valids: [1, 2], label: 'a[5]', key: 5 }\n                }\n            ]);\n            expect(err.annotate()).to.equal('{\\n  \"a\": [\\n    1,\\n    2,\\n    3, \\u001b[31m[1]\\u001b[0m\\n    4, \\u001b[31m[2]\\u001b[0m\\n    2,\\n    5 \\u001b[31m[3]\\u001b[0m\\n  ]\\n}\\n\\u001b[31m\\n[1] \"a[2]\" must be one of [1, 2]\\n[2] \"a[3]\" must be one of [1, 2]\\n[3] \"a[5]\" must be one of [1, 2]\\u001b[0m');\n        });\n\n        it('annotates error within array multiple times on the same element', () => {\n\n            const object = {\n                a: [2, 3, 4]\n            };\n\n            const schema = Joi.object({\n                a: Joi.array().items(Joi.number().min(4).max(2))\n            });\n\n            const err = schema.validate(object, { abortEarly: false }).error;\n            expect(err).to.be.an.error('\"a[0]\" must be greater than or equal to 4. \"a[1]\" must be greater than or equal to 4. \"a[1]\" must be less than or equal to 2. \"a[2]\" must be less than or equal to 2');\n            expect(err.details).to.equal([\n                {\n                    message: '\"a[0]\" must be greater than or equal to 4',\n                    path: ['a', 0],\n                    type: 'number.min',\n                    context: { limit: 4, value: 2, label: 'a[0]', key: 0 }\n                },\n                {\n                    message: '\"a[1]\" must be greater than or equal to 4',\n                    path: ['a', 1],\n                    type: 'number.min',\n                    context: { limit: 4, value: 3, label: 'a[1]', key: 1 }\n                },\n                {\n                    message: '\"a[1]\" must be less than or equal to 2',\n                    path: ['a', 1],\n                    type: 'number.max',\n                    context: { limit: 2, value: 3, label: 'a[1]', key: 1 }\n                },\n                {\n                    message: '\"a[2]\" must be less than or equal to 2',\n                    path: ['a', 2],\n                    type: 'number.max',\n                    context: { limit: 2, value: 4, label: 'a[2]', key: 2 }\n                }\n            ]);\n\n            expect(err.annotate()).to.equal('{\\n  \"a\": [\\n    2, \\u001b[31m[1]\\u001b[0m\\n    3, \\u001b[31m[2, 3]\\u001b[0m\\n    4 \\u001b[31m[4]\\u001b[0m\\n  ]\\n}\\n\\u001b[31m\\n[1] \"a[0]\" must be greater than or equal to 4\\n[2] \"a[1]\" must be greater than or equal to 4\\n[3] \"a[1]\" must be less than or equal to 2\\n[4] \"a[2]\" must be less than or equal to 2\\u001b[0m');\n        });\n\n        it('annotates error within array when it is an object', () => {\n\n            const object = {\n                a: [{ b: 2 }]\n            };\n\n            const schema = Joi.object({\n                a: Joi.array().items(Joi.number())\n            });\n\n            const err = schema.validate(object, { abortEarly: false }).error;\n            expect(err).to.be.an.error('\"a[0]\" must be a number');\n            expect(err.details).to.equal([{\n                message: '\"a[0]\" must be a number',\n                path: ['a', 0],\n                type: 'number.base',\n                context: { label: 'a[0]', key: 0, value: { b: 2 } }\n            }]);\n            expect(err.annotate()).to.equal('{\\n  \"a\": [\\n    { \\u001b[31m[1]\\u001b[0m\\n      \"b\": 2\\n    }\\n  ]\\n}\\n\\u001b[31m\\n[1] \"a[0]\" must be a number\\u001b[0m');\n        });\n\n        it('annotates error within multiple arrays and multiple times on the same element', () => {\n\n            const object = {\n                a: [2, 3, 4],\n                b: [2, 3, 4]\n            };\n\n            const schema = Joi.object({\n                a: Joi.array().items(Joi.number().min(4).max(2)),\n                b: Joi.array().items(Joi.number().min(4).max(2))\n            });\n\n            const err = schema.validate(object, { abortEarly: false }).error;\n            expect(err).to.be.an.error('\"a[0]\" must be greater than or equal to 4. \"a[1]\" must be greater than or equal to 4. \"a[1]\" must be less than or equal to 2. \"a[2]\" must be less than or equal to 2. \"b[0]\" must be greater than or equal to 4. \"b[1]\" must be greater than or equal to 4. \"b[1]\" must be less than or equal to 2. \"b[2]\" must be less than or equal to 2');\n            expect(err.details).to.equal([\n                {\n                    message: '\"a[0]\" must be greater than or equal to 4',\n                    path: ['a', 0],\n                    type: 'number.min',\n                    context: { limit: 4, value: 2, label: 'a[0]', key: 0 }\n                },\n                {\n                    message: '\"a[1]\" must be greater than or equal to 4',\n                    path: ['a', 1],\n                    type: 'number.min',\n                    context: { limit: 4, value: 3, label: 'a[1]', key: 1 }\n                },\n                {\n                    message: '\"a[1]\" must be less than or equal to 2',\n                    path: ['a', 1],\n                    type: 'number.max',\n                    context: { limit: 2, value: 3, label: 'a[1]', key: 1 }\n                },\n                {\n                    message: '\"a[2]\" must be less than or equal to 2',\n                    path: ['a', 2],\n                    type: 'number.max',\n                    context: { limit: 2, value: 4, label: 'a[2]', key: 2 }\n                },\n                {\n                    message: '\"b[0]\" must be greater than or equal to 4',\n                    path: ['b', 0],\n                    type: 'number.min',\n                    context: { limit: 4, value: 2, label: 'b[0]', key: 0 }\n                },\n                {\n                    message: '\"b[1]\" must be greater than or equal to 4',\n                    path: ['b', 1],\n                    type: 'number.min',\n                    context: { limit: 4, value: 3, label: 'b[1]', key: 1 }\n                },\n                {\n                    message: '\"b[1]\" must be less than or equal to 2',\n                    path: ['b', 1],\n                    type: 'number.max',\n                    context: { limit: 2, value: 3, label: 'b[1]', key: 1 }\n                },\n                {\n                    message: '\"b[2]\" must be less than or equal to 2',\n                    path: ['b', 2],\n                    type: 'number.max',\n                    context: { limit: 2, value: 4, label: 'b[2]', key: 2 }\n                }\n            ]);\n            expect(err.annotate()).to.equal('{\\n  \"a\": [\\n    2, \\u001b[31m[1]\\u001b[0m\\n    3, \\u001b[31m[2, 3]\\u001b[0m\\n    4 \\u001b[31m[4]\\u001b[0m\\n  ],\\n  \"b\": [\\n    2, \\u001b[31m[5]\\u001b[0m\\n    3, \\u001b[31m[6, 7]\\u001b[0m\\n    4 \\u001b[31m[8]\\u001b[0m\\n  ]\\n}\\n\\u001b[31m\\n[1] \"a[0]\" must be greater than or equal to 4\\n[2] \"a[1]\" must be greater than or equal to 4\\n[3] \"a[1]\" must be less than or equal to 2\\n[4] \"a[2]\" must be less than or equal to 2\\n[5] \"b[0]\" must be greater than or equal to 4\\n[6] \"b[1]\" must be greater than or equal to 4\\n[7] \"b[1]\" must be less than or equal to 2\\n[8] \"b[2]\" must be less than or equal to 2\\u001b[0m');\n        });\n\n        it('displays alternatives fail as a single line', () => {\n\n            const schema = Joi.object({\n                x: [\n                    Joi.string(),\n                    Joi.number(),\n                    Joi.date()\n                ]\n            });\n\n            const err = schema.validate({ x: true }).error;\n            expect(err).to.be.an.error('\"x\" must be one of [string, number, date]');\n            expect(err.details).to.equal([\n                {\n                    message: '\"x\" must be one of [string, number, date]',\n                    path: ['x'],\n                    type: 'alternatives.types',\n                    context: { types: ['string', 'number', 'date'], label: 'x', key: 'x', value: true }\n                }\n            ]);\n\n            expect(err.annotate()).to.equal('{\\n  \"x\" \\u001b[31m[1]\\u001b[0m: true\\n}\\n\\u001b[31m\\n[1] \"x\" must be one of [string, number, date]\\u001b[0m');\n        });\n\n        it('annotates circular input', () => {\n\n            const schema = Joi.object({\n                x: Joi.object({\n                    y: Joi.object({\n                        z: Joi.number()\n                    })\n                })\n            });\n\n            const input = {};\n            input.x = input;\n\n            const err = schema.validate(input).error;\n            expect(err).to.be.an.error('\"x.x\" is not allowed');\n            expect(err.details).to.equal([{\n                message: '\"x.x\" is not allowed',\n                path: ['x', 'x'],\n                type: 'object.unknown',\n                context: { child: 'x', label: 'x.x', key: 'x', value: input.x }\n            }]);\n            expect(err.annotate()).to.equal('{\\n  \"x\" \\u001b[31m[1]\\u001b[0m: \"[Circular ~]\"\\n}\\n\\u001b[31m\\n[1] \"x.x\" is not allowed\\u001b[0m');\n        });\n\n        it('annotates deep circular input', () => {\n\n            const schema = Joi.object({\n                x: Joi.object({\n                    y: Joi.object({\n                        z: Joi.number()\n                    })\n                })\n            });\n\n            const input = { x: { y: {} } };\n            input.x.y.z = input.x.y;\n\n            const err = schema.validate(input).error;\n            expect(err).to.be.an.error('\"x.y.z\" must be a number');\n            expect(err.details).to.equal([{\n                message: '\"x.y.z\" must be a number',\n                path: ['x', 'y', 'z'],\n                type: 'number.base',\n                context: { label: 'x.y.z', key: 'z', value: input.x.y }\n            }]);\n            expect(err.annotate()).to.equal('{\\n  \"x\": {\\n    \"y\": {\\n      \"z\" \\u001b[31m[1]\\u001b[0m: \"[Circular ~.x.y]\"\\n    }\\n  }\\n}\\n\\u001b[31m\\n[1] \"x.y.z\" must be a number\\u001b[0m');\n        });\n\n        it('annotates deep circular input with extra keys', () => {\n\n            const schema = Joi.object({\n                x: Joi.object({\n                    y: Joi.object({\n                        z: Joi.number()\n                    })\n                })\n            });\n\n            const input = { x: { y: { z: 1, foo: {} } } };\n            input.x.y.foo = input.x.y;\n\n            const err = schema.validate(input).error;\n            expect(err).to.be.an.error('\"x.y.foo\" is not allowed');\n            expect(err.details).to.equal([{\n                message: '\"x.y.foo\" is not allowed',\n                path: ['x', 'y', 'foo'],\n                type: 'object.unknown',\n                context: { child: 'foo', label: 'x.y.foo', key: 'foo', value: input.x.y.foo }\n            }]);\n            expect(err.annotate()).to.equal('{\\n  \"x\": {\\n    \"y\": {\\n      \"z\": 1,\\n      \"foo\" \\u001b[31m[1]\\u001b[0m: \"[Circular ~.x.y]\"\\n    }\\n  }\\n}\\n\\u001b[31m\\n[1] \"x.y.foo\" is not allowed\\u001b[0m');\n        });\n\n        it('prints NaN, Infinity and -Infinity correctly in errors', () => {\n\n            const schema = Joi.object({\n                x: Joi.object({\n                    y: Joi.date().allow(null),\n                    z: Joi.date().allow(null),\n                    u: Joi.date().allow(null),\n                    g: Joi.date().allow(null),\n                    h: Joi.date().allow(null),\n                    i: Joi.date().allow(null),\n                    k: Joi.date().allow(null),\n                    p: Joi.date().allow(null),\n                    f: Joi.date().allow(null)\n                })\n            });\n\n            const input = {\n                x: {\n                    y: NaN,\n                    z: Infinity,\n                    u: -Infinity,\n                    g: Symbol('foo'),\n                    h: -Infinity,\n                    i: Infinity,\n                    k: (a) => a,\n                    p: Symbol('bar'),\n                    f: function (x) {\n\n                        return [{ y: 2 }];\n                    }\n                }\n            };\n\n            const err = schema.validate(input).error;\n            expect(err).to.be.an.error('\"x.y\" must be a valid date');\n            expect(err.details).to.equal([{\n                message: '\"x.y\" must be a valid date',\n                path: ['x', 'y'],\n                type: 'date.base',\n                context: { label: 'x.y', key: 'y', value: NaN }\n            }]);\n            expect(err.annotate().replace(/\\\\r/g, '')).to.equal('{\\n  \"x\": {\\n    \"z\": Infinity,\\n    \"u\": -Infinity,\\n    \"g\": Symbol(foo),\\n    \"h\": -Infinity,\\n    \"i\": Infinity,\\n    \"k\": (a) => a,\\n    \"p\": Symbol(bar),\\n    \"f\": function (x) {\\\\n\\\\n                        return [{ y: 2 }];\\\\n                    },\\n    \"y\" \\u001b[31m[1]\\u001b[0m: NaN\\n  }\\n}\\n\\u001b[31m\\n[1] \"x.y\" must be a valid date\\u001b[0m');\n        });\n\n        it('handles child to uncle relationship inside a child', () => {\n\n            const object = {\n                response: {\n                    options: {\n                        stripUnknown: true\n                    }\n                }\n            };\n\n            const ref = Joi.ref('.options.stripUnknown');\n            const schema = Joi.object({\n                response: Joi.object({\n                    modify: Joi.boolean(),\n                    options: Joi.object()\n                })\n                    .assert(ref, Joi.ref('modify'), 'meet requirement of having peer modify set to true')\n            });\n\n            const err = schema.validate(object, { abortEarly: false }).error;\n            expect(err).to.be.an.error('\"response\" is invalid because \"options.stripUnknown\" failed to meet requirement of having peer modify set to true');\n            expect(err.details).to.equal([{\n                message: '\"response\" is invalid because \"options.stripUnknown\" failed to meet requirement of having peer modify set to true',\n                path: ['response'],\n                type: 'object.assert',\n                context: {\n                    subject: ref,\n                    message: 'meet requirement of having peer modify set to true',\n                    label: 'response',\n                    key: 'response',\n                    value: object.response\n                }\n            }]);\n\n            expect(() => err.annotate(true)).to.not.throw();\n        });\n\n        it('annotates joi schema error', () => {\n\n            const schema = Joi.object({\n                type: 'string'\n            })\n                .unknown();\n\n            const value = Joi.number().min(1);\n            const err = schema.validate(value).error;\n            expect(err.message).equal('\"type\" must be [string]');\n            expect(err.annotate()).to.contain('\"type\" must be [string]');\n            Helper.equal(value, Joi.number().min(1));\n        });\n    });\n});\n"
  },
  {
    "path": "test/extend.js",
    "content": "'use strict';\n\nconst Bourne = require('@hapi/bourne');\nconst Code = require('@hapi/code');\nconst Joi = require('..');\nconst Lab = require('@hapi/lab');\n\nconst Helper = require('./helper');\n\n\nconst internals = {};\n\n\nconst { describe, it } = exports.lab = Lab.script();\nconst { expect } = Code;\n\n\ndescribe('extension', () => {\n\n    it('extends string locally', () => {\n\n        const special = Joi.string().extend({\n            type: 'special',\n            rules: {\n                hello: {\n                    validate(value, helpers, args, options) {\n\n                        if (!/hello/.test(value)) {\n                            return helpers.error('special.hello');\n                        }\n\n                        if (!value.includes('!')) {\n                            helpers.warn('special.excited');\n                        }\n\n                        return value;\n                    }\n                }\n            },\n            messages: {\n                'special.hello': '{{#label}} must say hello',\n                'special.excited': 'You do not seem excited enough'\n            }\n        });\n\n        expect(special.type).to.equal('special');\n        expect(special.hello().validate('HELLO').error).to.be.an.error('\"value\" must say hello');\n        expect(special.lowercase().hello().validate('HELLO')).to.equal({\n            value: 'hello',\n            warning: {\n                message: 'You do not seem excited enough',\n                details: [\n                    {\n                        context: {\n                            label: 'value',\n                            value: 'hello'\n                        },\n                        message: 'You do not seem excited enough',\n                        path: [],\n                        type: 'special.excited'\n                    }\n                ]\n            }\n        });\n    });\n\n    it('extends string globally', () => {\n\n        const custom = Joi.extend({\n            type: 'special',\n            base: Joi.string(),\n            rules: {\n                hello: {\n                    validate(value, helpers, args, options) {\n\n                        if (value === 'hello') {\n                            return value;\n                        }\n\n                        return helpers.error('special.hello');\n                    }\n                }\n            },\n            messages: {\n                'special.hello': '{{#label}} must say hello'\n            }\n        });\n\n        const special = custom.special();\n        expect(special.type).to.equal('special');\n        expect(special.hello().validate('HELLO').error).to.be.an.error('\"value\" must say hello');\n        expect(special.lowercase().hello().validate('HELLO').error).to.not.exist();\n    });\n\n    it('extends multiple types', () => {\n\n        const custom = Joi.extend({\n            type: /^s/,\n            rules: {\n                hello: {\n                    validate(value, helpers, args, options) {\n\n                        return 'hello';\n                    }\n                }\n            }\n        });\n\n        const string = custom.string().hello();\n        expect(string.type).to.equal('string');\n        expect(string.hello().validate('goodbye').value).to.equal('hello');\n\n        const symbol = custom.symbol().hello();\n        expect(symbol.type).to.equal('symbol');\n        expect(symbol.hello().validate(Symbol('x')).value).to.equal('hello');\n\n        expect(() => custom.number().hello()).to.throw('custom.number(...).hello is not a function');\n    });\n\n    it('aliases a type', () => {\n\n        const custom = Joi.extend({\n            type: 'special',\n            base: Joi.string()\n        });\n\n        const special = custom.special();\n        expect(special.type).to.equal('special');\n        expect(special.validate(1).error).to.be.an.error('\"value\" must be a string');\n    });\n\n    it('errors on missing error code', () => {\n\n        const special = Joi.string().extend({\n            type: 'special',\n            rules: {\n                fail: {\n                    method() {\n\n                        return this.$_addRule('fail');\n                    },\n                    validate(value, helpers) {\n\n                        return helpers.error('special.fail');\n                    }\n                }\n            }\n        });\n\n        expect(special.fail().validate('anything').error).to.be.an.error('Error code \"special.fail\" is not defined, your custom type is missing the correct messages definition');\n    });\n\n    it('extends with a custom base', () => {\n\n        const custom = Joi.extend({\n            type: 'special',\n            base: Joi.string().min(2)\n        });\n\n        expect(Joi.special).to.not.exist();\n        expect(custom.special).to.be.a.function();\n\n        const schema = custom.special();\n        Helper.validate(schema, [\n            [123, false, {\n                message: '\"value\" must be a string',\n                path: [],\n                type: 'string.base',\n                context: { value: 123, label: 'value' }\n            }],\n            ['a', false, {\n                message: '\"value\" length must be at least 2 characters long',\n                path: [],\n                type: 'string.min',\n                context: {\n                    limit: 2,\n                    value: 'a',\n                    encoding: undefined,\n                    label: 'value'\n                }\n            }],\n            ['abc', true]\n        ]);\n    });\n\n    it('extends with constructor arguments', () => {\n\n        const custom = Joi.extend({\n            type: 'special',\n            base: Joi.string(),\n            args(schema, limit) {\n\n                return schema.min(limit);\n            }\n        });\n\n        expect(Joi.special).to.not.exist();\n        expect(custom.special).to.be.a.function();\n\n        const schema = custom.special(2);\n        Helper.validate(schema, [\n            [123, false, {\n                message: '\"value\" must be a string',\n                path: [],\n                type: 'string.base',\n                context: { value: 123, label: 'value' }\n            }],\n            ['a', false, {\n                message: '\"value\" length must be at least 2 characters long',\n                path: [],\n                type: 'string.min',\n                context: {\n                    limit: 2,\n                    value: 'a',\n                    encoding: undefined,\n                    label: 'value'\n                }\n            }],\n            ['abc', true]\n        ]);\n    });\n\n    it('extends with a custom base while preserving its original constructor arguments', () => {\n\n        const custom = Joi.extend({\n            type: 'special',\n            base: Joi.object()\n        });\n\n        expect(Joi.special).to.not.exist();\n        expect(custom.special).to.be.a.function();\n\n        const schema = custom.special({ a: custom.number() });\n        Helper.validate(schema, [\n            [undefined, true],\n            [{}, true],\n            [{ a: 1 }, true],\n            [{ a: 'a' }, false, {\n                message: '\"a\" must be a number',\n                path: ['a'],\n                type: 'number.base',\n                context: { key: 'a', label: 'a', value: 'a' }\n            }]\n        ]);\n    });\n\n    it('extends with a custom base and validate (object)', () => {\n\n        const custom = Joi.extend({\n            type: 'special',\n            base: Joi.object(),\n            validate(value, helpers) {\n\n                return { value };\n            }\n        });\n\n        expect(Joi.special).to.not.exist();\n        expect(custom.special).to.be.a.function();\n\n        const schema = custom.special({ a: custom.number() });\n        Helper.validate(schema, [\n            [undefined, true],\n            [{}, true],\n            [{ a: 1 }, true],\n            [{ a: 'a' }, false, {\n                message: '\"a\" must be a number',\n                path: ['a'],\n                type: 'number.base',\n                context: { key: 'a', label: 'a', value: 'a' }\n            }]\n        ]);\n    });\n\n    it('extends with a custom base and validate (number)', () => {\n\n        const custom = Joi.extend({\n            type: 'special',\n            base: Joi.number(),\n            validate(value, helpers) {\n\n                return { value };\n            }\n        });\n\n        expect(Joi.special).to.not.exist();\n        expect(custom.special).to.be.a.function();\n\n        const schema = custom.special({ a: custom.number() });\n        Helper.validate(schema, [\n            [undefined, true],\n            [1, true],\n            ['1', true, 1],\n            [{}, false, {\n                message: '\"value\" must be a number',\n                path: [],\n                type: 'number.base',\n                context: { label: 'value', value: {} }\n            }]\n        ]);\n    });\n\n    it('extends with a custom base and validate (boolean)', () => {\n\n        const custom = Joi.extend({\n            type: 'special',\n            base: Joi.boolean(),\n            validate(value, helpers) {\n\n                return { value };\n            }\n        });\n\n        expect(Joi.special).to.not.exist();\n        expect(custom.special).to.be.a.function();\n\n        const schema = custom.special();\n        Helper.validate(schema, [\n            [undefined, true],\n            [true, true],\n            [false, true],\n            [{}, false, {\n                message: '\"value\" must be a boolean',\n                path: [],\n                type: 'boolean.base',\n                context: { label: 'value', value: {} }\n            }]\n        ]);\n    });\n\n    it('extends with new rules', () => {\n\n        const custom = Joi.extend({\n            type: 'special',\n            rules: {\n                foo: {\n                    validate(value, helpers, args, options) {\n\n                        return null;\n                    }\n                },\n                bar: {\n                    validate(value, helpers, args, options) {\n\n                        return helpers.error('special.bar');\n                    }\n                },\n                foo_bar: {\n                    validate(value, helpers, args, options) {\n\n                        return null;\n                    }\n                }\n            },\n            messages: {\n                'special.bar': '{#label} oh no bar !'\n            }\n        });\n\n        const original = Joi.any();\n        expect(original.foo).to.not.exist();\n        expect(original.bar).to.not.exist();\n        expect(original.foo_bar).to.not.exist();\n\n        const schema = custom.special();\n        expect(schema.foo().validate({})).to.equal({ value: null });\n        expect(schema.bar().validate({}).error).to.be.an.error('\"value\" oh no bar !');\n        expect(schema.foo_bar().validate({})).to.equal({ value: null });\n    });\n\n    it('concats custom type', () => {\n\n        const custom = Joi.extend({\n            type: 'special',\n            rules: {\n                test: {\n                    validate(value, helpers, args, options) {\n\n                        return value;\n                    },\n                    multi: true\n                }\n            }\n        });\n\n        const schema = custom.special();\n        const base = schema.test();\n        const merged = base.concat(base);\n\n        expect(merged.describe()).to.equal({\n            type: 'special',\n            rules: [{ name: 'test' }, { name: 'test' }]\n        });\n    });\n\n    it('adds a new rule with the correct this', () => {\n\n        const custom = Joi.extend({\n            type: 'special',\n            rules: {\n                foo: {\n                    validate(value, { error }) {\n\n                        return error('special.bar');\n                    }\n                }\n            },\n            messages: {\n                'special.bar': '{#label} oh no bar !'\n            }\n        });\n\n        const schema = custom.special().foo().label('baz');\n        expect(schema.validate({}).error).to.be.an.error('\"baz\" oh no bar !');\n    });\n\n    it('extends with flag rule', () => {\n\n        const custom = Joi.extend({\n            type: 'special',\n            validate(value, { schema }) {\n\n                return { value: schema.$_getFlag('foo') };\n            },\n            rules: {\n                foo: {\n                    method(first, second) {\n\n                        Joi.assert(first, Joi.string());\n                        Joi.assert(second, Joi.object().ref());\n\n                        return this.$_setFlag('foo', { first, second });\n                    }\n                }\n            }\n        });\n\n        const schema = custom.special();\n        expect(schema.foo('bar').validate(null)).to.equal({ value: { first: 'bar', second: undefined } });\n        expect(schema.foo('bar', Joi.ref('a.b')).validate(null).value.first).to.equal('bar');\n        expect(Joi.isRef(schema.foo('bar', Joi.ref('a.b')).validate(null).value.second)).to.be.true();\n    });\n\n    it('extends with flag rule (customer setter)', () => {\n\n        const custom = Joi.extend({\n            type: 'special',\n            flags: {\n                xfoo: {\n                    setter: 'foo'\n                }\n            },\n            validate(value, { schema }) {\n\n                return { value: schema.$_getFlag('xfoo') };\n            },\n            rules: {\n                foo: {\n                    method(first, second) {\n\n                        Joi.assert(first, Joi.string());\n                        Joi.assert(second, Joi.object().ref());\n\n                        return this.$_setFlag('xfoo', { first, second });\n                    }\n                }\n            }\n        });\n\n        const schema = custom.special();\n        expect(schema.foo('bar').validate(null)).to.equal({ value: { first: 'bar', second: undefined } });\n        expect(schema.foo('bar', Joi.ref('a.b')).validate(null).value.first).to.equal('bar');\n        expect(Joi.isRef(schema.foo('bar', Joi.ref('a.b')).validate(null).value.second)).to.be.true();\n    });\n\n    it('extends with flag rule (schema)', () => {\n\n        const custom = Joi.extend({\n            type: 'special',\n            coerce(value, helpers) {\n\n                const swap = helpers.schema.$_getFlag('swap');\n                if (swap &&\n                    swap.$_match(value, helpers.state.nest(swap), helpers.prefs)) {\n\n                    return { value: 'swapped' };\n                }\n            },\n            rules: {\n                swap: {\n                    method(schema) {\n\n                        return this.$_setFlag('swap', this.$_compile(schema));\n                    }\n                }\n            }\n        });\n\n        const schema = custom.special().swap(Joi.number().id('x')).empty(Joi.object());\n        expect(schema.validate(3)).to.equal({ value: 'swapped' });\n        expect(schema.validate({})).to.equal({ value: undefined });\n\n        expect(schema.fork('x', (s) => s.min(10)).validate(3)).to.equal({ value: 3 });\n    });\n\n    it('defines a rule that can change the value', () => {\n\n        const custom = Joi.extend({\n            type: 'number',\n            base: Joi.number(),\n            rules: {\n                double: {\n                    validate(value, helpers, args, options) {\n\n                        return value * 2;\n                    }\n                }\n            }\n        });\n\n        const original = Joi.number();\n        expect(original.double).to.not.exist();\n\n        const schema = custom.number().double();\n        expect(schema.validate(3)).to.contain({ value: 6 });\n    });\n\n    it('overrides all error messages with string', () => {\n\n        const custom = Joi.extend({\n            type: 'special',\n            base: Joi.string(),\n            messages: 'shit happens'\n        });\n\n        const special = custom.special();\n        expect(special.type).to.equal('special');\n        expect(special.validate(1).error).to.be.an.error('shit happens');\n    });\n\n    it('overrides all error messages with a template', () => {\n\n        const custom = Joi.extend({\n            type: 'special',\n            base: Joi.string(),\n            messages: Joi.x('shit happens')\n        });\n\n        const special = custom.special();\n        expect(special.type).to.equal('special');\n        expect(special.validate(1).error).to.be.an.error('shit happens');\n    });\n\n    it('overrides specific error messages with string', () => {\n\n        const custom = Joi.extend({\n            type: 'special',\n            base: Joi.string(),\n            messages: {\n                'string.base': 'shit happens'\n            }\n        });\n\n        const special = custom.special();\n        expect(special.type).to.equal('special');\n        expect(special.validate(1).error).to.be.an.error('shit happens');\n    });\n\n    it('overrides specific error messages with template', () => {\n\n        const custom = Joi.extend({\n            type: 'special',\n            base: Joi.string(),\n            messages: {\n                'string.base': Joi.x('shit happens')\n            }\n        });\n\n        const special = custom.special();\n        expect(special.type).to.equal('special');\n        expect(special.validate(1).error).to.be.an.error('shit happens');\n    });\n\n    it('overrides specific error messages with string (language)', () => {\n\n        const custom = Joi.extend(\n            {\n                type: 'special',\n                base: Joi.string(),\n                messages: {\n                    root: 'thing',\n                    en: {\n                        root: 'stuff',\n                        'string.base': '{{#label}} shit happens'\n                    }\n                }\n            },\n            (joi) => {\n\n                return {\n                    type: 'special',\n                    base: joi.special(),\n                    messages: {\n                        en: {\n                            'string.base': '{{#label}} bad shit happens'\n                        }\n                    }\n                };\n            }\n        );\n\n        const special = custom.special();\n        expect(special.validate(1, { errors: { language: 'en' } }).error).to.be.an.error('\"stuff\" bad shit happens');\n    });\n\n    it('overrides specific error messages with template (language)', () => {\n\n        const custom = Joi.extend({\n            type: 'special',\n            base: Joi.string(),\n            messages: {\n                root: 'thing',\n                en: {\n                    root: 'stuff',\n                    'string.base': Joi.x('{{#label}} shit happens')\n                }\n            }\n        });\n\n        const special = custom.special();\n        expect(special.type).to.equal('special');\n        expect(special.validate(1, { errors: { language: 'en' } }).error).to.be.an.error('\"stuff\" shit happens');\n        expect(special.validate(1).error).to.be.an.error('\"thing\" must be a string');\n    });\n\n    it('errors on invalid message overrides (language)', () => {\n\n        expect(() => {\n\n            Joi.extend({\n                type: 'special',\n                base: Joi.string(),\n                messages: {\n                    root: 'thing',\n                    en: 10\n                }\n            });\n        }).to.throw('Invalid message for en');\n    });\n\n    it('retains base predefined options', () => {\n\n        const custom = Joi.extend({\n            type: 'special',\n            base: Joi.number().prefs({ abortEarly: false }),\n            rules: {\n                foo: {\n                    validate(value, { error }, args, options) {\n\n                        return error('special.foo');\n                    }\n                }\n            },\n            messages: {\n                'special.foo': '{#label} foo'\n            }\n        });\n\n        const schema = custom.special().min(10).max(0).foo();\n        expect(schema.validate(5).error).to.be.an.error('\"value\" must be greater than or equal to 10. \"value\" must be less than or equal to 0. \"value\" foo');\n    });\n\n    it('extends coerce', () => {\n\n        const custom = Joi.extend({\n            type: 'special',\n            base: Joi.string(),\n            coerce(value, helpers) {\n\n                return { value: 'foobar' };\n            }\n        });\n\n        const schema = custom.special();\n        expect(schema.validate(true)).to.equal({ value: 'foobar' });\n    });\n\n    it('extends coerce (from different types)', () => {\n\n        const type1 = Joi.any().extend({\n            type: 'type1',\n            coerce: {\n                from: 'string',\n                method(value, { error }) {\n\n                    if (value === '1') {\n                        return { errors: error('type1.error') };\n                    }\n\n                    if (value === '2') {\n                        return { value: 2 };\n                    }\n                }\n            },\n            messages: {\n                'type1.error': 'failed1'\n            }\n        });\n\n        const type2 = type1.extend({\n            type: 'type2',\n            coerce: {\n                from: 'number',\n                method(value, { error }) {\n\n                    if (value === 4) {\n                        return { errors: error('type2.error') };\n                    }\n\n                    if (value === 2) {\n                        return { value: 'x' };\n                    }\n                }\n            },\n            messages: {\n                'type2.error': 'failed2'\n            }\n        });\n\n        expect(type2.validate('2')).to.equal({ value: 'x' });\n        expect(type2.validate(2)).to.equal({ value: 'x' });\n        expect(type2.validate('3')).to.equal({ value: '3' });\n        expect(type2.validate(5)).to.equal({ value: 5 });\n        expect(type2.validate('1').error).to.be.an.error('failed1');\n        expect(type2.validate(4).error).to.be.an.error('failed2');\n    });\n\n    it('extends coerce (no from on parent)', () => {\n\n        const type1 = Joi.any().extend({\n            type: 'type1',\n            coerce: {\n                method(value, { error }) {\n\n                    if (value === '1') {\n                        return { errors: error('type1.error') };\n                    }\n\n                    if (value === '2') {\n                        return { value: 2 };\n                    }\n                }\n            },\n            messages: {\n                'type1.error': 'failed1'\n            }\n        });\n\n        const type2 = type1.extend({\n            type: 'type2',\n            coerce: {\n                from: 'number',\n                method(value, { error }) {\n\n                    if (value === 4) {\n                        return { errors: error('type2.error') };\n                    }\n\n                    if (value === 2) {\n                        return { value: 'x' };\n                    }\n                }\n            },\n            messages: {\n                'type2.error': 'failed2'\n            }\n        });\n\n        expect(type2.validate('2')).to.equal({ value: 'x' });\n        expect(type2.validate(2)).to.equal({ value: 'x' });\n        expect(type2.validate('3')).to.equal({ value: '3' });\n        expect(type2.validate(5)).to.equal({ value: 5 });\n        expect(type2.validate('1').error).to.be.an.error('failed1');\n        expect(type2.validate(4).error).to.be.an.error('failed2');\n    });\n\n    it('extends coerce (undefined)', () => {\n\n        const type1 = Joi.any().extend({\n            type: 'type1',\n            coerce(value, { error }) {\n\n                if (value === '1') {\n                    return { value: undefined };\n\n                }\n            }\n        });\n\n        const type2 = type1.extend({\n            type: 'type2',\n            coerce(value, { error }) {\n\n                if (value === '2') {\n                    return { value: undefined };\n                }\n            }\n        });\n\n        const type3 = type2.extend({\n            type: 'type3',\n            coerce(value, { error }) {\n\n                if (value === '3') {\n                    return { value: undefined };\n                }\n            }\n        });\n\n        expect(type3.validate('1')).to.equal({ value: undefined });\n        expect(type3.validate('2')).to.equal({ value: undefined });\n        expect(type3.validate('3')).to.equal({ value: undefined });\n    });\n\n    it('extends with a failing validate', () => {\n\n        const custom = Joi.extend({\n            type: 'special',\n            validate(value, { error }) {\n\n                return { errors: error('any.invalid') };\n            }\n        });\n\n        const schema = custom.special();\n        expect(schema.validate('foo').error).to.be.an.error('\"value\" contains an invalid value');\n    });\n\n    it('returns a custom Joi with types not inheriting root properties', () => {\n\n        const custom = Joi.extend({\n            type: 'special'\n        });\n\n        const schema = custom.valid(true);\n        expect(schema.isRef).to.not.exist();\n    });\n\n    it('uses types defined in the same extend call', () => {\n\n        const custom = Joi.extend(\n            {\n                type: 'special'\n            },\n            (joi) => ({\n                type: 'second',\n                base: joi.special()\n            })\n        );\n\n        expect(() => custom.second()).to.not.throw();\n    });\n\n    it('merges rules when type is defined several times in the same extend call', () => {\n\n        const custom = Joi.extend(\n            (joi) => ({\n                type: 'special',\n                base: joi.number(),\n                rules: {\n                    foo: {\n                        validate(value, helpers) {\n\n                            return 1;\n                        }\n                    }\n                }\n            }),\n            (joi) => ({\n                type: 'special',\n                base: joi.special(),\n                rules: {\n                    bar: {\n                        validate(value, helpers) {\n\n                            return 2;\n                        }\n                    }\n                }\n            })\n        );\n\n        expect(() => custom.special().foo().bar()).to.not.throw();\n        expect(custom.attempt({ a: 123, b: 456 }, Joi.object({ a: custom.special().foo(), b: custom.special().bar() }))).to.equal({ a: 1, b: 2 });\n    });\n\n    it('uses last definition when type is defined several times with different bases', () => {\n\n        const custom = Joi.extend(\n            (joi) => ({\n                type: 'special',\n                base: Joi.number(),\n                rules: {\n                    foo: {\n                        validate(value, { error }) {\n\n                            return 1;\n                        }\n                    }\n                }\n            }),\n            (joi) => ({\n                type: 'special',\n                base: Joi.string(),\n                rules: {\n                    bar: {\n                        validate(value, { error }) {\n\n                            return 2;\n                        }\n                    }\n                }\n            })\n        );\n\n        expect(() => custom.special().foo()).to.throw();\n        expect(() => custom.special().bar()).to.not.throw();\n    });\n\n    it('merges languages when multiple extensions extend the same type', () => {\n\n        const customJoiWithBoth = Joi.extend(\n            (joi) => ({\n                type: 'number',\n                base: joi.number(),\n                messages: { 'number.foo': '{#label} foo' },\n                rules: {\n                    foo: {\n                        validate(value, { error }) {\n\n                            return error('number.foo');\n                        }\n                    }\n                }\n            }),\n            (joi) => ({\n                type: 'number',\n                base: joi.number(),\n                messages: { 'number.bar': '{#label} bar' },\n                rules: {\n                    bar: {\n                        validate(value, { error }) {\n\n                            return error('number.bar');\n                        }\n                    }\n                }\n            })\n        );\n\n        expect(customJoiWithBoth.number().foo().validate(0).error).to.be.an.error('\"value\" foo');\n        expect(customJoiWithBoth.number().bar().validate(0).error).to.be.an.error('\"value\" bar');\n\n        const customJoiWithFirst = Joi.extend(\n            (joi) => ({\n                type: 'number',\n                base: joi.number(),\n                messages: { 'number.foo': '{#label} foo' },\n                rules: {\n                    foo: {\n                        validate(value, { error }) {\n\n                            return error('number.foo');\n                        }\n                    }\n                }\n            }),\n            (joi) => ({\n                type: 'number',\n                base: joi.number(),\n                rules: {\n                    bar: {\n                        validate(value, { error }) {\n\n                            return error('number.base');\n                        }\n                    }\n                }\n            })\n        );\n\n        expect(customJoiWithFirst.number().foo().validate(0).error).to.be.an.error('\"value\" foo');\n        expect(customJoiWithFirst.number().bar().validate(0).error).to.be.an.error('\"value\" must be a number');\n\n        const customJoiWithSecond = Joi.extend(\n            (joi) => ({\n                type: 'number',\n                base: joi.number(),\n                rules: {\n                    foo: {\n                        validate(value, { error }) {\n\n                            return error('number.base');\n                        }\n                    }\n                }\n            }),\n            (joi) => ({\n                type: 'number',\n                base: joi.number(),\n                messages: { 'number.bar': '{#label} bar' },\n                rules: {\n                    bar: {\n                        validate(value, { error }) {\n\n                            return error('number.bar');\n                        }\n                    }\n                }\n            })\n        );\n\n        expect(customJoiWithSecond.number().foo().validate(0).error).to.be.an.error('\"value\" must be a number');\n        expect(customJoiWithSecond.number().bar().validate(0).error).to.be.an.error('\"value\" bar');\n    });\n\n    it('returns extended shortcuts', () => {\n\n        const custom = Joi.extend({\n            base: Joi.string(),\n            type: 'special'\n        });\n\n        expect(() => {\n\n            const string = custom.string;\n            string();\n        }).to.throw('Must be invoked on a Joi instance.');\n\n        const { string, special } = custom.types();\n        expect(() => string.allow('x')).to.not.throw();\n        expect(string.validate(0).error).to.be.an.error('\"value\" must be a string');\n\n        expect(() => special.allow('x')).to.not.throw();\n        expect(special.validate(0).error).to.be.an.error('\"value\" must be a string');\n\n        expect(custom._types.size).to.equal(Joi._types.size + 1);\n    });\n\n    it('supports rule()', () => {\n\n        const custom = Joi.extend({\n            type: 'ext',\n            base: Joi.number(),\n            messages: {\n                'ext.big': 'Not big enough'\n            },\n            rules: {\n                big: {\n                    validate(value, { error }) {\n\n                        if (value > 100) {\n                            return value;\n                        }\n\n                        return error('ext.big');\n                    }\n                }\n            }\n        });\n\n        expect(() => custom.ext().big().rule({})).to.not.throw();\n        expect(() => custom.ext().big().rule({}).big()).to.not.throw();\n    });\n\n    it('extends rule with complex example', () => {\n\n        const custom = Joi.extend((joi) => {\n\n            return {\n                type: 'million',\n                base: joi.number(),\n                messages: {\n                    'million.base': '{{#label}} must be at least a million',\n                    'million.big': '{{#label}} must be at least five millions',\n                    'million.round': '{{#label}} must be a round number',\n                    'million.dividable': '{{#label}} must be dividable by {{#q}}'\n                },\n                coerce(value, { schema }) {\n\n                    // Only called when prefs.convert is true\n\n                    if (schema.$_getRule('round')) {\n                        return { value: Math.round(value) };\n                    }\n                },\n                validate(value, { schema, error }) {\n\n                    // Base validation regardless of the rules applied\n\n                    if (value < 1000000) {\n                        return { value, errors: error('million.base') };\n                    }\n\n                    // Check flags for global state\n\n                    if (schema.$_getFlag('big') &&\n                        value < 5000000) {\n\n                        return { value, errors: error('million.big') };\n                    }\n                },\n                rules: {\n                    big: {\n                        alias: 'large',\n                        method() {\n\n                            return this.$_setFlag('big', true);\n                        }\n                    },\n                    round: {\n                        convert: true,              // Dual rule: converts or validates\n                        method() {\n\n                            return this.$_addRule('round');\n                        },\n                        validate(value, helpers, args, options) {\n\n                            // Only called when prefs.convert is false (due to rule convert option)\n\n                            if (value % 1 !== 0) {\n                                return helpers.error('million.round');\n                            }\n                        }\n                    },\n                    dividable: {\n                        multi: true,                // Rule supports multiple invocations\n                        method(q) {\n\n                            return this.$_addRule({ name: 'dividable', args: { q } });\n                        },\n                        args: [\n                            {\n                                name: 'q',\n                                ref: true,\n                                assert: (value) => typeof value === 'number' && !isNaN(value),\n                                message: 'must be a number'\n                            }\n                        ],\n                        validate(value, helpers, args, options) {\n\n                            if (value % args.q === 0) {\n                                return value;       // Value is valid\n                            }\n\n                            return helpers.error('million.dividable', { q: args.q });\n                        }\n                    },\n                    even: {\n                        method() {\n\n                            // Rule with only method used to alias another rule\n\n                            return this.dividable(2);\n                        }\n                    }\n                }\n            };\n        });\n\n        const schema = custom.object({\n            a: custom.million().round().dividable(Joi.ref('b')),\n            b: custom.number(),\n            c: custom.million().even().dividable(7),\n            d: custom.million().round().prefs({ convert: false }),\n            e: custom.million().large()\n        });\n\n        Helper.validate(schema, [\n            [{ a: 3000000, b: 3 }, true],\n            [{ a: 1000000.1, b: 10 }, true, { a: 1000000, b: 10 }],\n            [{ a: 3000, b: 3 }, false, {\n                message: '\"a\" must be at least a million',\n                path: ['a'],\n                type: 'million.base',\n                context: { value: 3000, label: 'a', key: 'a' }\n            }],\n            [{ c: 14000000 }, true],\n            [{ c: 1000000 }, false, {\n                message: '\"c\" must be dividable by 7',\n                path: ['c'],\n                type: 'million.dividable',\n                context: { value: 1000000, label: 'c', key: 'c', q: 7 }\n            }],\n            [{ d: 1000000.1 }, false, {\n                message: '\"d\" must be a round number',\n                path: ['d'],\n                type: 'million.round',\n                context: { value: 1000000.1, label: 'd', key: 'd' }\n            }],\n            [{ e: 6000000 }, true],\n            [{ e: 1000000 }, false, {\n                message: '\"e\" must be at least five millions',\n                path: ['e'],\n                type: 'million.big',\n                context: { value: 1000000, label: 'e', key: 'e' }\n            }]\n        ]);\n    });\n\n    it('extends rule with schema ref validation', () => {\n\n        const custom = Joi.extend((joi) => {\n\n            return {\n                type: 'number',\n                base: joi.number(),\n                messages: {\n                    'number.dividable': '{{#label}} must be dividable by {{#q}}'\n                },\n                rules: {\n                    dividable: {\n                        method(q) {\n\n                            return this.$_addRule({ name: 'dividable', args: { q } });\n                        },\n                        args: [\n                            {\n                                name: 'q',\n                                ref: true,\n                                assert: Joi.number().required()\n                            }\n                        ],\n                        validate(value, helpers, args, options) {\n\n                            if (value % args.q === 0) {\n                                return value;       // Value is valid\n                            }\n\n                            return helpers.error('number.dividable', { q: args.q });\n                        }\n                    }\n                }\n            };\n        });\n\n        const ref = Joi.ref('b');\n        const schema = custom.object({\n            a: custom.number().dividable(ref),\n            b: custom.number()\n        });\n\n        Helper.validate(schema, [\n            [{ a: 30, b: 3 }, true],\n            [{ a: 30 }, false, {\n                message: '\"a\" q references \"ref:b\" which \"q\" is required',\n                path: ['a'],\n                type: 'any.ref',\n                context: { label: 'a', key: 'a', ref, arg: 'q', reason: '\"q\" is required' }\n            }]\n        ]);\n    });\n\n    it('extends rebuild', () => {\n\n        const custom = Joi.extend((joi) => {\n\n            return {\n                type: 'special',\n                base: joi.object(),\n                terms: {\n                    tests: { init: [] }\n                },\n                rules: {\n                    test: {\n                        method(schema) {\n\n                            const obj = this.clone();\n                            obj.$_terms.tests.push(schema);\n                            obj.$_mutateRegister(schema);\n                            return obj.$_mutateRebuild();\n                        }\n                    }\n                },\n                rebuild(schema) {\n\n                    for (const test of schema.$_terms.tests) {\n                        if (test.type === 'number') {\n                            schema.$_setFlag('_hasNumbers', true, { clone: false });\n                            break;\n                        }\n                    }\n                }\n            };\n        });\n\n        const schema = custom.special().test(Joi.number());\n        expect(schema.$_getFlag('_hasNumbers')).to.be.true();\n    });\n\n    it('supports fork', () => {\n\n        const custom = Joi.extend((joi) => {\n\n            return {\n                type: 'special',\n                base: joi.object(),\n                terms: {\n                    tests: { init: [] }\n                },\n                rules: {\n                    test: {\n                        method(schema) {\n\n                            const obj = this.clone();\n                            obj.$_terms.tests.push(schema);\n                            obj.$_mutateRegister(schema);\n                            return obj;\n                        }\n                    }\n                }\n            };\n        });\n\n        const schema = custom.special().keys({ y: Joi.number() }).test(Joi.number().id('x'));\n\n        const modified1 = schema.fork('x', (s) => s.min(10));\n        expect(modified1.describe()).to.equal({\n            type: 'special',\n            keys: {\n                y: { type: 'number' }\n            },\n            tests: [\n                {\n                    type: 'number',\n                    flags: { id: 'x' },\n                    rules: [\n                        {\n                            name: 'min',\n                            args: { limit: 10 }\n                        }\n                    ]\n                }\n            ]\n        });\n\n        const modified2 = schema.fork('y', (s) => s.min(10));\n        expect(modified2.describe()).to.equal({\n            type: 'special',\n            keys: {\n                y: {\n                    type: 'number',\n                    rules: [\n                        {\n                            name: 'min',\n                            args: { limit: 10 }\n                        }\n                    ]\n                }\n            },\n            tests: [\n                {\n                    type: 'number',\n                    flags: { id: 'x' }\n                }\n            ]\n        });\n    });\n\n    it('extends modifiers', () => {\n\n        const custom = Joi.extend({\n            type: 'special',\n            base: Joi.number(),\n            modifiers: {\n                factor(rule, n) {\n\n                    if (rule.args &&\n                        rule.args.limit) {\n\n                        rule.args.limit *= n;\n                    }\n                }\n            }\n        });\n\n        const schema = custom.special().min(1).keep().min(10).factor(2);\n        expect(schema.validate(20)).to.equal({ value: 20 });\n        expect(schema.validate(19).error).to.be.an.error('\"value\" must be greater than or equal to 20');\n    });\n\n    it('validates rule arguments', () => {\n\n        const custom = Joi.extend({\n            type: 'special',\n            rules: {\n                foo: {\n                    method(b) {\n\n                        return this.$_addRule({ name: 'foo', args: { b } });\n                    },\n                    args: [\n                        {\n                            name: 'b',\n                            assert: Joi.number()\n                        }\n                    ]\n                }\n            }\n        });\n\n        expect(() => custom.special().foo(2)).to.not.throw();\n        expect(() => custom.special().foo('x')).to.throw('\"b\" must be a number or reference');\n        expect(() => custom.special().foo('2')).to.throw('\"b\" must be a number or reference');\n    });\n\n    it('extends number to support comma delimiter', () => {\n\n        const custom = Joi.extend({\n            type: 'number',\n            base: Joi.number(),\n            prepare(value, helpers) {\n\n                if (typeof value !== 'string') {\n                    return;\n                }\n\n                return { value: value.replace(',', '.') };\n            }\n        });\n\n        expect(custom.number().validate(2.0)).to.equal({ value: 2.0 });\n        expect(custom.number().validate('2.0')).to.equal({ value: 2.0 });\n        expect(custom.number().validate('2,0')).to.equal({ value: 2.0 });\n        expect(custom.number().validate('2,0', { convert: false }).error).to.be.an.error('\"value\" must be a number');\n        expect(custom.number().validate(undefined).error).to.not.exist();\n    });\n\n    it('extends number to support only comma delimiter', () => {\n\n        const custom = Joi.extend({\n            type: 'number',\n            base: Joi.number(),\n            prepare(value, { error }) {\n\n                if (typeof value !== 'string') {\n                    return;\n                }\n\n                if (value.includes('.')) {\n                    return { errors: error('number.period') };\n                }\n\n                return { value: value.replace(',', '.') };\n            },\n            messages: {\n                'number.period': 'Number cannot use period delimiter'\n            }\n        });\n\n        expect(custom.number().validate('2.0').error).to.be.an.error('Number cannot use period delimiter');\n        expect(custom.number().validate('2,0')).to.equal({ value: 2.0 });\n    });\n\n    it('extends number to support comma delimiter and then \" delimiter', () => {\n\n        const custom = Joi.extend(\n            {\n                type: 'number',\n                base: Joi.number(),\n                prepare(value, helpers) {\n\n                    if (typeof value !== 'string') {\n                        return;\n                    }\n\n                    return { value: value.replace(',', '.') };\n                }\n            },\n            (joi) => {\n\n                return {\n                    type: 'number',\n                    base: joi.number(),\n                    prepare(value, { error }) {\n\n                        if (value === 0) {\n                            return { value: undefined };\n                        }\n\n                        if (typeof value !== 'string') {\n                            return;\n                        }\n\n                        if (value.includes('.')) {\n                            return { errors: error('number.period') };\n                        }\n\n                        return { value: value.replace('\"', '.') };\n                    },\n                    messages: {\n                        'number.period': 'Number cannot use period delimiter'\n                    }\n                };\n            }\n        );\n\n        expect(custom.number().validate(2.0)).to.equal({ value: 2.0 });\n        expect(custom.number().validate('2.0').error).to.be.an.error('Number cannot use period delimiter');\n        expect(custom.number().validate('2,0')).to.equal({ value: 2.0 });\n        expect(custom.number().validate('2\"0')).to.equal({ value: 2.0 });\n        expect(custom.number().validate(0)).to.equal({ value: undefined });\n    });\n\n    it('extends object to support string coerce', () => {\n\n        const custom = Joi.extend({\n            type: 'object',\n            base: Joi.object(),\n            coerce: {\n                from: 'string',\n                method(value, helpers) {\n\n                    if (value[0] !== '{' &&\n                        !/^\\s*\\{/.test(value)) {\n\n                        return;\n                    }\n\n                    try {\n                        return { value: Bourne.parse(value) };\n                    }\n                    catch { }\n                }\n            }\n        });\n\n        expect(custom.object().validate('{\"hi\":true}')).to.equal({ value: { hi: true } });\n        expect(custom.object().validate(' \\n\\r\\t{ \\n\\r\\t\"hi\" \\n\\r\\t: \\n\\r\\ttrue \\n\\r\\t} \\n\\r\\t')).to.equal({ value: { hi: true } });\n        expect(custom.object().strict().validate('{\"hi\":true}').error).to.be.an.error('\"value\" must be of type object');\n        expect(() => custom.attempt({ a: '{\"c\":\"string\"}' }, custom.object({ a: custom.object({ b: custom.string() }) }))).to.throw(/\\\"a.c\\\" is not allowed/);\n\n        const err1 = custom.object().validate('a string').error;\n        expect(err1).to.be.an.error('\"value\" must be of type object');\n        expect(err1.details).to.equal([{\n            message: '\"value\" must be of type object',\n            path: [],\n            type: 'object.base',\n            context: { label: 'value', value: 'a string', type: 'object' }\n        }]);\n\n        const err2 = custom.object({ a: custom.object({ b: custom.object({ c: { d: custom.string() } }) }) }).validate({ a: '{\"b\":{\"c\":{\"d\":1}}}' }, { abortEarly: false }).error;\n        expect(err2).to.be.an.error('\"a.b.c.d\" must be a string');\n        expect(err2.details).to.equal([{\n            message: '\"a.b.c.d\" must be a string',\n            path: ['a', 'b', 'c', 'd'],\n            type: 'string.base',\n            context: { value: 1, label: 'a.b.c.d', key: 'd' }\n        }]);\n\n        expect(err2.annotate(true)).to.equal('{\\n  \"a\" [1]: \"{\\\\\"b\\\\\":{\\\\\"c\\\\\":{\\\\\"d\\\\\":1}}}\"\\n}\\n\\n[1] \"a.b.c.d\" must be a string');\n    });\n\n    it('extends array to support string coerce', () => {\n\n        const custom = Joi.extend({\n            type: 'array',\n            base: Joi.array(),\n            coerce: {\n                from: 'string',\n                method(value, helpers) {\n\n                    if (typeof value !== 'string' ||\n                        value[0] !== '[' && !/^\\s*\\[/.test(value)) {\n\n                        return;\n                    }\n\n                    try {\n                        return { value: Bourne.parse(value) };\n                    }\n                    catch { }\n                }\n            }\n        });\n\n        expect(custom.array().validate('[1,2,3]')).to.equal({ value: [1, 2, 3] });\n        expect(custom.array().validate(' \\n\\r\\t[ \\n\\r\\t1 \\n\\r\\t, \\n\\r\\t2,3] \\n\\r\\t')).to.equal({ value: [1, 2, 3] });\n        expect(custom.object({ a: custom.array() }).validate({ a: '[1,2]' }).error).to.not.exist();\n\n        const err1 = custom.array().validate('{ \"something\": false }').error;\n        expect(err1).to.be.an.error('\"value\" must be an array');\n        expect(err1.details).to.equal([{\n            message: '\"value\" must be an array',\n            path: [],\n            type: 'array.base',\n            context: { label: 'value', value: '{ \"something\": false }' }\n        }]);\n\n        const err2 = custom.array().validate(' \\n\\r\\t[ \\n\\r\\t1 \\n\\r\\t, \\n\\r\\t2,3 \\n\\r\\t').error;\n        expect(err2).to.be.an.error('\"value\" must be an array');\n        expect(err2.details).to.equal([{\n            message: '\"value\" must be an array',\n            path: [],\n            type: 'array.base',\n            context: { label: 'value', value: ' \\n\\r\\t[ \\n\\r\\t1 \\n\\r\\t, \\n\\r\\t2,3 \\n\\r\\t' }\n        }]);\n    });\n\n    it('extends with a cast', () => {\n\n        const custom = Joi.extend({\n            type: 'special',\n            base: Joi.string(),\n            cast: {\n                object: {\n                    from: (value) => !!value,\n                    to: (value) => ({ value })\n                }\n            }\n        });\n\n        const special = custom.special().cast('object');\n        expect(special.type).to.equal('special');\n        expect(special.validate('abc').value).to.equal({ value: 'abc' });\n    });\n\n    it('retains base overrides', () => {\n\n        const custom = Joi.extend({\n            type: 'test',\n            base: Joi.object(),\n            overrides: {\n                label(...args) {\n\n                    this.$_parent('label', ...args);\n                }\n            }\n        });\n\n        const schema = custom.test({\n            a: custom.number().default(1)\n        })\n            .default();\n\n        expect(schema.validate({}).value).to.equal({ a: 1 });\n    });\n\n    it('errors on non-type override', () => {\n\n        expect(() => Joi.extend({ type: 'x' })).to.throw('Cannot override name x');\n    });\n});\n"
  },
  {
    "path": "test/helper.js",
    "content": "'use strict';\n\nconst Code = require('@hapi/code');\n\n\nconst internals = {};\n\n\nconst { expect } = Code;\n\n\nexports.skip = Symbol('skip');\n\n\nexports.equal = function (a, b) {\n\n    try {\n        expect(a).to.equal(b, { deepFunction: true, skip: ['$_temp', '$_root'] });\n    }\n    catch (err) {\n        console.error(err.stack);\n        err.at = internals.thrownAt();      // Adjust error location to test\n        throw err;\n    }\n};\n\n\nexports.validate = function (schema, prefs, tests) {\n\n    if (!tests) {\n        tests = prefs;\n        prefs = null;\n    }\n\n    try {\n        expect(schema.$_root.build(schema.describe())).to.equal(schema, { deepFunction: true, skip: ['$_temp'] });\n\n        for (const test of tests) {\n            const [input, pass, expected] = test;\n            if (!pass) {\n                expect(expected, 'Failing tests messages must be tested').to.exist();\n            }\n\n            const { error: errord, value: valued } = schema.validate(input, Object.assign({ debug: true }, prefs));\n\n            if (prefs === null) {\n                internals.standardValidate(schema, test, { errord, valued });\n            }\n\n            const { error, value } = schema.validate(input, prefs);\n\n            expect(error).to.equal(errord);\n            expect(value).to.equal(valued);\n\n            if (error &&\n                pass) {\n\n                console.log(error);\n            }\n\n            if (!error &&\n                !pass) {\n\n                console.log(input);\n            }\n\n            expect(!error).to.equal(pass);\n\n            if (test.length === 2) {\n                if (pass) {\n                    expect(input).to.equal(value);\n                }\n\n                continue;\n            }\n\n            if (pass) {\n                if (expected !== exports.skip) {\n                    expect(value).to.equal(expected);\n                }\n\n                continue;\n            }\n\n            if (typeof expected === 'string') {\n                expect(error.message).to.equal(expected);\n                continue;\n            }\n\n            if (schema._preferences && schema._preferences.abortEarly === false ||\n                prefs && prefs.abortEarly === false) {\n\n                expect(error.message).to.equal(expected.message);\n                expect(error.details).to.equal(expected.details);\n            }\n            else {\n                expect(error.details).to.have.length(1);\n                expect(error.message).to.equal(error.details[0].message);\n                expect(error.details[0]).to.equal(expected);\n            }\n        }\n    }\n    catch (err) {\n        console.error(err.stack);\n        err.at = internals.thrownAt();      // Adjust error location to test\n        throw err;\n    }\n};\n\n\ninternals.thrownAt = function () {\n\n    const error = new Error();\n    const frame = error.stack.replace(error.toString(), '').split('\\n').slice(1).filter((line) => !line.includes(__filename))[0];\n    const at = frame.match(/^\\s*at \\(?(.+)\\:(\\d+)\\:(\\d+)\\)?$/);\n    return {\n        filename: at[1],\n        line: at[2],\n        column: at[3]\n    };\n};\n\n\ninternals.standardValidate = function (schema, test, { errord, valued }) {\n\n    const [input, pass] = test;\n    const { issues, value } = schema['~standard'].validate(input);\n\n    if (pass) {\n        expect(issues).to.equal(undefined);\n        expect(value).to.equal(valued);\n    }\n\n    if (!pass) {\n        expect(value).to.equal(undefined);\n    }\n\n    if (!pass && !errord.details) {\n        expect(issues.length).to.equal(1);\n    }\n\n    if (!pass && errord.details) {\n        expect(issues.length).to.equal(errord.details.length);\n    }\n};\n"
  },
  {
    "path": "test/index.js",
    "content": "'use strict';\n\nconst Code = require('@hapi/code');\nconst Lab = require('@hapi/lab');\nconst Joi = require('..');\n\nconst Helper = require('./helper');\n\n\nconst internals = {};\n\n\nconst { describe, it } = exports.lab = Lab.script();\nconst { expect } = Code;\n\n\ndescribe('Joi', () => {\n\n    it('keeps schema immutable', () => {\n\n        const a = Joi.string();\n        const b = a.valid('b');\n\n        Helper.validate(a, [\n            ['a', true],\n            ['b', true],\n            [5, false, {\n                message: '\"value\" must be a string',\n                path: [],\n                type: 'string.base',\n                context: { value: 5, label: 'value' }\n            }]\n        ]);\n\n        Helper.validate(b, [\n            ['a', false, {\n                message: '\"value\" must be [b]',\n                path: [],\n                type: 'any.only',\n                context: { value: 'a', valids: ['b'], label: 'value' }\n            }],\n            ['b', true],\n            [5, false, {\n                message: '\"value\" must be [b]',\n                path: [],\n                type: 'any.only',\n                context: { value: 5, valids: ['b'], label: 'value' }\n            }]\n        ]);\n    });\n\n    it('validates a compiled complex literal', () => {\n\n        const schema = Joi.compile(['key', 5, { a: true, b: [/^a/, 'boom'] }]);\n        Helper.validate(schema, [\n            ['key', true],\n            [5, true],\n            ['other', false, {\n                message: '\"value\" must be one of [key, 5, object]',\n                path: [],\n                type: 'alternatives.types',\n                context: {\n                    label: 'value',\n                    types: ['key', 5, 'object'],\n                    value: 'other'\n                }\n            }],\n            [6, false, {\n                message: '\"value\" must be one of [key, 5, object]',\n                path: [],\n                type: 'alternatives.types',\n                context: {\n                    label: 'value',\n                    types: ['key', 5, 'object'],\n                    value: 6\n                }\n            }],\n            [{ c: 5 }, false, {\n                message: '\"c\" is not allowed',\n                path: ['c'],\n                type: 'object.unknown',\n                context: { child: 'c', label: 'c', key: 'c', value: 5 }\n            }],\n            [{}, true],\n            [{ b: 'abc' }, true],\n            [{ a: true, b: 'boom' }, true],\n            [{ a: 5, b: 'a' }, false, {\n                message: '\"a\" must be [true]',\n                path: ['a'],\n                type: 'any.only',\n                context: { label: 'a', key: 'a', value: 5, valids: [true] }\n            }]\n        ]);\n    });\n\n    it('validates regex directly', () => {\n\n        Helper.validate(Joi.compile(/^5$/), [['5', true]]);\n        Helper.validate(Joi.compile(/.{2}/), [['6', false, {\n            message: '\"value\" with value \"6\" fails to match the required pattern: /.{2}/',\n            path: [],\n            type: 'string.pattern.base',\n            context: {\n                name: undefined,\n                regex: /.{2}/,\n                value: '6',\n                label: 'value'\n            }\n        }]]);\n    });\n\n    it('validates an array of valid types', () => {\n\n        const schema = Joi.object({\n            auth: [\n                Joi.object({\n                    mode: Joi.string().valid('required', 'optional', 'try').allow(null)\n                }).allow(null),\n                Joi.string(),\n                Joi.boolean()\n            ]\n        });\n\n        Helper.validate(schema, [\n            [{ auth: { mode: 'none' } }, false, '\"auth.mode\" must be one of [required, optional, try, null]'],\n            [{ auth: { mode: 'try' } }, true],\n            [{ something: undefined }, false, {\n                message: '\"something\" is not allowed',\n                path: ['something'],\n                type: 'object.unknown',\n                context: { child: 'something', label: 'something', key: 'something' }\n            }],\n            [{ auth: { something: undefined } }, false, {\n                message: '\"auth.something\" is not allowed',\n                path: ['auth', 'something'],\n                type: 'object.unknown',\n                context: { child: 'something', label: 'auth.something', key: 'something' }\n            }],\n            [{ auth: null }, true],\n            [{ auth: undefined }, true],\n            [{}, true],\n            [{ auth: true }, true],\n            [{ auth: 123 }, false, {\n                message: '\"auth\" must be one of [object, string, boolean]',\n                path: ['auth'],\n                type: 'alternatives.types',\n                context: { types: ['object', 'string', 'boolean'], label: 'auth', key: 'auth', value: 123 }\n            }]\n        ]);\n    });\n\n    it('validates alternatives', () => {\n\n        const schema = Joi.object({\n            auth: Joi.alternatives([\n                Joi.object({\n                    mode: Joi.string().valid('required', 'optional', 'try').allow(null)\n                }).allow(null),\n                Joi.string(),\n                Joi.boolean()\n            ])\n        });\n\n        const err = schema.validate({ auth: { mode: 'none' } }).error;\n        expect(err).to.be.an.error('\"auth.mode\" must be one of [required, optional, try, null]');\n\n        Helper.validate(schema, [\n            [{ auth: { mode: 'try' } }, true],\n            [{ something: undefined }, false, {\n                message: '\"something\" is not allowed',\n                path: ['something'],\n                type: 'object.unknown',\n                context: { child: 'something', label: 'something', key: 'something' }\n            }],\n            [{ auth: { something: undefined } }, false, {\n                message: '\"auth.something\" is not allowed',\n                path: ['auth', 'something'],\n                type: 'object.unknown',\n                context: { child: 'something', label: 'auth.something', key: 'something' }\n            }],\n            [{ auth: null }, true],\n            [{ auth: undefined }, true],\n            [{}, true],\n            [{ auth: true }, true],\n            [{ auth: 123 }, false, {\n                message: '\"auth\" must be one of [object, string, boolean]',\n                path: ['auth'],\n                type: 'alternatives.types',\n                context: { types: ['object', 'string', 'boolean'], label: 'auth', key: 'auth', value: 123 }\n            }]\n        ]);\n    });\n\n    it('validates required alternatives', () => {\n\n        const schema = Joi.object({\n            a: Joi.alternatives([\n                Joi.string().required(),\n                Joi.boolean().required()\n            ])\n        });\n\n        Helper.validate(schema, [\n            [{ a: null }, false, {\n                message: '\"a\" must be one of [string, boolean]',\n                path: ['a'],\n                type: 'alternatives.types',\n                context: { types: ['string', 'boolean'], label: 'a', key: 'a', value: null }\n            }],\n            [{ a: undefined }, true],\n            [{}, true],\n            [{ a: true }, true],\n            [{ a: 'true' }, true],\n            [{ a: 123 }, false, {\n                message: '\"a\" must be one of [string, boolean]',\n                path: ['a'],\n                type: 'alternatives.types',\n                context: { types: ['string', 'boolean'], label: 'a', key: 'a', value: 123 }\n            }],\n            [{ a: { c: 1 } }, false, {\n                message: '\"a\" must be one of [string, boolean]',\n                path: ['a'],\n                type: 'alternatives.types',\n                context: { types: ['string', 'boolean'], label: 'a', key: 'a', value: { c: 1 } }\n            }],\n            [{ b: undefined }, false, {\n                message: '\"b\" is not allowed',\n                path: ['b'],\n                type: 'object.unknown',\n                context: { child: 'b', label: 'b', key: 'b' }\n            }]\n        ]);\n    });\n\n    it('validates required [] alternatives', () => {\n\n        const schema = Joi.object({\n            a: [\n                Joi.string().required(),\n                Joi.boolean().required()\n            ]\n        });\n\n        Helper.validate(schema, [\n            [{ a: null }, false, {\n                message: '\"a\" must be one of [string, boolean]',\n                path: ['a'],\n                type: 'alternatives.types',\n                context: { types: ['string', 'boolean'], label: 'a', key: 'a', value: null }\n            }],\n            [{ a: undefined }, true],\n            [{}, true],\n            [{ a: true }, true],\n            [{ a: 'true' }, true],\n            [{ a: 123 }, false, {\n                message: '\"a\" must be one of [string, boolean]',\n                path: ['a'],\n                type: 'alternatives.types',\n                context: { types: ['string', 'boolean'], label: 'a', key: 'a', value: 123 }\n            }],\n            [{ a: { c: 1 } }, false, {\n                message: '\"a\" must be one of [string, boolean]',\n                path: ['a'],\n                type: 'alternatives.types',\n                context: { types: ['string', 'boolean'], label: 'a', key: 'a', value: { c: 1 } }\n            }],\n            [{ b: undefined }, false, {\n                message: '\"b\" is not allowed',\n                path: ['b'],\n                type: 'object.unknown',\n                context: { child: 'b', label: 'b', key: 'b' }\n            }]\n        ]);\n    });\n\n    it('validates an array of string with valid', () => {\n\n        const schema = Joi.object({\n            brand: Joi.array().items(Joi.string().valid('amex', 'visa'))\n        });\n\n        Helper.validate(schema, [\n            [{ brand: ['amex'] }, true],\n            [{ brand: ['visa', 'mc'] }, false, {\n                message: '\"brand[1]\" must be one of [amex, visa]',\n                path: ['brand', 1],\n                type: 'any.only',\n                context: { value: 'mc', valids: ['amex', 'visa'], label: 'brand[1]', key: 1 }\n            }]\n        ]);\n    });\n\n    it('validates pre and post convert value', () => {\n\n        const schema = Joi.number().valid(5);\n\n        Helper.validate(schema, [\n            [5, true],\n            ['5', true, 5]\n        ]);\n    });\n\n    it('does not change object when validation fails', () => {\n\n        const schema = Joi.object({\n            a: Joi.number().valid(2)\n        });\n\n        const obj = {\n            a: '5'\n        };\n\n        const { error, value } = schema.validate(obj);\n        expect(error).to.exist();\n        expect(value.a).to.equal('5');\n    });\n\n    it('does not set optional keys when missing', () => {\n\n        const schema = Joi.object({\n            a: Joi.number()\n        });\n\n        const obj = {};\n\n        const value = schema.validate(obj).value;\n        expect(value.hasOwnProperty('a')).to.equal(false);\n    });\n\n    it('invalidates pre and post convert value', () => {\n\n        const schema = Joi.number().invalid(5);\n\n        Helper.validate(schema, [\n            [5, false, {\n                message: '\"value\" contains an invalid value',\n                path: [],\n                type: 'any.invalid',\n                context: { value: 5, invalids: [5], label: 'value' }\n            }],\n            ['5', false, {\n                message: '\"value\" contains an invalid value',\n                path: [],\n                type: 'any.invalid',\n                context: { value: 5, invalids: [5], label: 'value' }\n            }]\n        ]);\n    });\n\n    it('invalidates missing peers', () => {\n\n        const schema = Joi.object({\n            username: Joi.string(),\n            password: Joi.string()\n        }).with('username', 'password').without('password', 'access_token');\n\n        Helper.validate(schema, [[{ username: 'bob' }, false, '\"username\" missing required peer \"password\"']]);\n    });\n\n    it('validates config where the root item is a joi type', () => {\n\n        Helper.validate(Joi.boolean().allow(null), [[true, true]]);\n        Helper.validate(Joi.object(), [[{ auth: { mode: 'try' } }, true]]);\n        Helper.validate(Joi.object(), [[true, false, '\"value\" must be of type object']]);\n        Helper.validate(Joi.string(), [[true, false, '\"value\" must be a string']]);\n        Helper.validate(Joi.string().email(), [['test@test.com', true]]);\n        Helper.validate(Joi.object({ param: Joi.string().required() }), [[{ param: 'item' }, true]]);\n    });\n\n    it('converts string to number', () => {\n\n        const schema = Joi.object({\n            a: Joi.number()\n        });\n\n        const input = { a: '5' };\n        Helper.validate(schema, [[input, true, { a: 5 }]]);\n        expect(input.a).to.equal('5');\n    });\n\n    it('allows unknown keys in objects if no schema was given', () => {\n\n        Helper.validate(Joi.object(), [[{ foo: 'bar' }, true]]);\n    });\n\n    it('fails on unknown keys in objects if a schema was given', () => {\n\n        Helper.validate(Joi.object({}), [[{ foo: 'bar' }, false, {\n            message: '\"foo\" is not allowed',\n            path: ['foo'],\n            type: 'object.unknown',\n            context: { child: 'foo', label: 'foo', key: 'foo', value: 'bar' }\n        }]]);\n\n        Helper.validate(Joi.compile({}), [[{ foo: 'bar' }, false, '\"foo\" is not allowed']]);\n\n        Helper.validate(Joi.compile({ other: Joi.number() }), [[{ foo: 'bar' }, false, '\"foo\" is not allowed']]);\n    });\n\n    it('validates required key with multiple options', () => {\n\n        const config = {\n            module: Joi.alternatives([\n                Joi.object({\n                    compile: Joi.function().required(),\n                    execute: Joi.function()\n                }),\n                Joi.string()\n            ]).required()\n        };\n\n        Helper.validate(Joi.compile(config), [\n            [{}, false, '\"module\" is required'],\n            [{ module: 'test' }, true],\n            [{ module: {} }, false, '\"module.compile\" is required'],\n            [{ module: { compile() { } } }, true]\n        ]);\n    });\n\n    it('validates key with required alternatives', () => {\n\n        const config = {\n            module: Joi.alt().try(\n                Joi.object({\n                    compile: Joi.function().required(),\n                    execute: Joi.function()\n                }).required(),\n                Joi.string().required()\n            )\n        };\n\n        expect(Joi.compile(config).validate({}).error).to.not.exist();\n    });\n\n    it('validates required key with alternatives', () => {\n\n        const config = {\n            module: Joi.alt().try(\n                Joi.object({\n                    compile: Joi.function().required(),\n                    execute: Joi.function()\n                }),\n                Joi.string()\n            ).required()\n        };\n\n        Helper.validate(Joi.compile(config), [[{}, false, '\"module\" is required']]);\n    });\n\n    it('validates object successfully when config has an array of types', () => {\n\n        const schema = {\n            f: [Joi.number(), Joi.boolean()],\n            g: [Joi.string(), Joi.object()]\n        };\n\n        const obj = {\n            f: true,\n            g: 'test'\n        };\n\n        Helper.validate(Joi.compile(schema), [[obj, true]]);\n    });\n\n    it('validates object successfully when config allows for optional key and key is missing', () => {\n\n        const schema = {\n            h: Joi.number(),\n            i: Joi.string(),\n            j: Joi.object()\n        };\n\n        const obj = {\n            h: 12,\n            i: 'test'\n        };\n\n        Helper.validate(Joi.compile(schema), [[obj, true]]);\n    });\n\n    it('fails validation', () => {\n\n        const schema = {\n            a: Joi.number().min(0).max(3),\n            b: Joi.string().valid('a', 'b', 'c'),\n            c: Joi.string().email().optional()\n        };\n\n        const obj = {\n            a: 10,\n            b: 'a',\n            c: 'joe@example.com'\n        };\n\n        Helper.validate(Joi.compile(schema), [[obj, false, '\"a\" must be less than or equal to 3']]);\n    });\n\n    it('fails validation when the wrong types are supplied', () => {\n\n        const schema = {\n            a: Joi.number().min(0).max(3),\n            b: Joi.string().valid('a', 'b', 'c'),\n            c: Joi.string().email().optional()\n        };\n\n        const obj = {\n            a: 'a',\n            b: 'a',\n            c: 'joe@example.com'\n        };\n\n        Helper.validate(Joi.compile(schema), [[obj, false, '\"a\" must be a number']]);\n    });\n\n    it('fails validation when missing a required parameter', () => {\n\n        const obj = {\n            c: 10\n        };\n\n        Helper.validate(Joi.compile({ a: Joi.string().required() }), [[obj, false, '\"a\" is required']]);\n    });\n\n    it('fails validation when missing a required parameter within an object config', () => {\n\n        const obj = {\n            a: {}\n        };\n\n        Helper.validate(Joi.compile({ a: Joi.object({ b: Joi.string().required() }) }), [[obj, false, '\"a.b\" is required']]);\n    });\n\n    it('fails validation when parameter is required to be an Array but is given as string', () => {\n\n        const obj = {\n            a: 'an array'\n        };\n\n        Helper.validate(Joi.object({ a: Joi.array() }), [[obj, false, '\"a\" must be an array']]);\n    });\n\n    it('fails validation when parameter is required to be an Array but is given as a json that is incorrect (object instead of array)', () => {\n\n        const obj = {\n            a: '{\"b\":2}'\n        };\n\n        Helper.validate(Joi.object({ a: Joi.object({ b: Joi.string().required() }) }), [[obj, false, '\"a\" must be of type object']]);\n    });\n\n    it('fails validation when config is an array and fails', () => {\n\n        const schema = {\n            d: [Joi.string(), Joi.boolean()],\n            e: [Joi.number(), Joi.object()]\n        };\n\n        const obj = {\n            d: 10,\n            e: 'a'\n        };\n\n        Helper.validate(Joi.compile(schema), [[obj, false, '\"d\" must be one of [string, boolean]']]);\n    });\n\n    it('fails validation when config is an array and fails with extra keys', () => {\n\n        const schema = {\n            d: [Joi.string(), Joi.boolean()],\n            e: [Joi.number(), Joi.object()]\n        };\n\n        const obj = {\n            a: 10,\n            b: 'a'\n        };\n\n        Helper.validate(Joi.compile(schema), [[obj, false, '\"a\" is not allowed']]);\n    });\n\n    it('fails validation with extra keys', () => {\n\n        const schema = {\n            a: Joi.number()\n        };\n\n        const obj = {\n            a: 1,\n            b: 'a'\n        };\n\n        Helper.validate(Joi.compile(schema), [[obj, false, '\"b\" is not allowed']]);\n    });\n\n    it('validates missing optional key with string condition', () => {\n\n        const schema = {\n            key: Joi.string().alphanum(false).min(8)\n        };\n\n        Helper.validate(Joi.compile(schema), [[{}, true]]);\n    });\n\n    it('validates with extra keys and remove them when stripUnknown is set', () => {\n\n        const schema = Joi.object({\n            a: Joi.number().min(0).max(3),\n            b: Joi.string().valid('a', 'b', 'c'),\n            c: Joi.string().email().optional()\n        });\n\n        const obj = {\n            a: 1,\n            b: 'a',\n            d: 'c'\n        };\n\n        Helper.validate(schema, { stripUnknown: true, allowUnknown: true }, [[obj, true, { a: 1, b: 'a' }]]);\n    });\n\n    it('validates with extra keys and remove them when stripUnknown (as an object) is set', () => {\n\n        const schema = Joi.object({\n            a: Joi.number().min(0).max(3),\n            b: Joi.string().valid('a', 'b', 'c'),\n            c: Joi.string().email().optional()\n        });\n\n        const obj = {\n            a: 1,\n            b: 'a',\n            d: 'c'\n        };\n\n        Helper.validate(schema, { stripUnknown: { arrays: false, objects: true }, allowUnknown: true }, [[obj, true, { a: 1, b: 'a' }]]);\n    });\n\n    it('validates enforces local behavior of unknown(true) when stripUnknown is set', () => {\n\n        const schema = Joi.object({\n            a: Joi.number().min(0).max(3),\n            loosyObject: Joi.object({\n                b1: Joi.string()\n            }).unknown(true), // allows extra keys\n            anotherLoosyObject: Joi.object({\n                c1: Joi.string()\n            }).unknown() // also allows extra keys\n        });\n\n        const obj = {\n            a: 1,\n            loosyObject: {\n                b1: 'c1',\n                b2: 'c2'\n            },\n            anotherLoosyObject: {\n                c1: 'c1',\n                c2: 'c2'\n            }\n        };\n\n        Helper.validate(schema, { stripUnknown: true }, [[obj, true, {\n            a: 1,\n            loosyObject: {\n                b1: 'c1',\n                b2: 'c2'\n            },\n            anotherLoosyObject: {\n                c1: 'c1',\n                c2: 'c2'\n            }\n        }]]);\n    });\n\n    it('validates enforces local behavior of unknown(false) when stripUnknown is set', () => {\n\n        const schema = Joi.object({\n            a: Joi.number().min(0).max(3),\n            strictObject: Joi.object({\n                b1: Joi.string()\n            }).unknown(false) // it shouldn't allow extra keys\n        });\n\n        const obj = {\n            a: 1,\n            strictObject: {\n                b1: 'b1',\n                b2: 'b2'\n            }\n        };\n\n        Helper.validate(schema, { stripUnknown: true }, [[obj, false, {\n            message: '\"strictObject.b2\" is not allowed',\n            path: ['strictObject', 'b2'],\n            type: 'object.unknown',\n            context: {\n                child: 'b2',\n                label: 'strictObject.b2',\n                key: 'b2',\n                value: 'b2'\n            }\n        }]]);\n    });\n\n    it('validates dependencies when stripUnknown is set', () => {\n\n        const schema = Joi.object({\n            a: Joi.number(),\n            b: Joi.string()\n        }).and('a', 'b');\n\n        const obj = {\n            a: 1,\n            foo: 'bar'\n        };\n\n        Helper.validate(schema, { stripUnknown: true }, [[obj, false, {\n            message: '\"value\" contains [a] without its required peers [b]',\n            path: [],\n            type: 'object.and',\n            context: {\n                present: ['a'],\n                presentWithLabels: ['a'],\n                missing: ['b'],\n                missingWithLabels: ['b'],\n                label: 'value',\n                value: { a: 1 }\n            }\n        }]]);\n    });\n\n    it('validates dependencies when stripUnknown (as an object) is set', () => {\n\n        const schema = Joi.object({\n            a: Joi.number(),\n            b: Joi.string()\n        })\n            .and('a', 'b');\n\n        const obj = {\n            a: 1,\n            foo: 'bar'\n        };\n\n        Helper.validate(schema, { stripUnknown: { arrays: false, objects: true } }, [[obj, false, {\n            message: '\"value\" contains [a] without its required peers [b]',\n            path: [],\n            type: 'object.and',\n            context: {\n                present: ['a'],\n                presentWithLabels: ['a'],\n                missing: ['b'],\n                missingWithLabels: ['b'],\n                label: 'value',\n                value: { a: 1 }\n            }\n        }]]);\n    });\n\n    it('fails to validate with incorrect property when asked to strip unknown keys without aborting early', () => {\n\n        const schema = Joi.object({\n            a: Joi.number().min(0).max(3),\n            b: Joi.string().valid('a', 'b', 'c'),\n            c: Joi.string().email().optional()\n        });\n\n        const obj = {\n            a: 1,\n            b: 'f',\n            d: 'c'\n        };\n\n        Helper.validate(schema, { stripUnknown: true, abortEarly: false }, [[obj, false, '\"b\" must be one of [a, b, c]']]);\n    });\n\n    it('fails to validate with incorrect property when asked to strip unknown keys (as an object) without aborting early', () => {\n\n        const schema = Joi.object({\n            a: Joi.number().min(0).max(3),\n            b: Joi.string().valid('a', 'b', 'c'),\n            c: Joi.string().email().optional()\n        });\n\n        const obj = {\n            a: 1,\n            b: 'f',\n            d: 'c'\n        };\n\n        Helper.validate(schema, { stripUnknown: { arrays: false, objects: true }, abortEarly: false }, [[obj, false, '\"b\" must be one of [a, b, c]']]);\n    });\n\n    it('should pass validation with extra keys when allowUnknown is set', () => {\n\n        const schema = Joi.object({\n            a: Joi.number().min(0).max(3),\n            b: Joi.string().valid('a', 'b', 'c'),\n            c: Joi.string().email().optional()\n        });\n\n        const obj = {\n            a: 1,\n            b: 'a',\n            d: 'c'\n        };\n\n        Helper.validate(schema, { allowUnknown: true }, [[obj, true, { a: 1, b: 'a', d: 'c' }]]);\n    });\n\n    it('should pass validation with extra keys set', () => {\n\n        const localConfig = Joi.object({\n            a: Joi.number().min(0).max(3),\n            b: Joi.string().valid('a', 'b', 'c')\n        }).prefs({ allowUnknown: true });\n\n        const obj = {\n            a: 1,\n            b: 'a',\n            d: 'c'\n        };\n\n        Helper.validate(localConfig, [[obj, true, { a: 1, b: 'a', d: 'c' }]]);\n    });\n\n    it('should pass validation with extra keys and remove them when stripUnknown is set locally', () => {\n\n        const localConfig = Joi.object({\n            a: Joi.number().min(0).max(3),\n            b: Joi.string().valid('a', 'b', 'c')\n        }).prefs({ stripUnknown: true, allowUnknown: true });\n\n        const obj = {\n            a: 1,\n            b: 'a',\n            d: 'c'\n        };\n\n        Helper.validate(localConfig, [\n            [obj, true, { a: 1, b: 'a' }],\n            [{ a: 1, b: 'a' }, true, { a: 1, b: 'a' }]\n        ]);\n    });\n\n    it('should pass validation with extra keys and remove them when stripUnknown (as an object) is set locally', () => {\n\n        const localConfig = Joi.object({\n            a: Joi.number().min(0).max(3),\n            b: Joi.string().valid('a', 'b', 'c')\n        }).prefs({ stripUnknown: { arrays: false, objects: true }, allowUnknown: true });\n\n        const obj = {\n            a: 1,\n            b: 'a',\n            d: 'c'\n        };\n\n        Helper.validate(localConfig, [\n            [obj, true, { a: 1, b: 'a' }],\n            [{ a: 1, b: 'a' }, true, { a: 1, b: 'a' }]\n        ]);\n    });\n\n    it('should work when the skipFunctions setting is enabled', () => {\n\n        const schema = Joi.object({ username: Joi.string() }).prefs({ skipFunctions: true });\n        const input = { username: 'test', func() { } };\n        Helper.validate(schema, [[input, true]]);\n    });\n\n    it('should work when the skipFunctions setting is disabled', () => {\n\n        const schema = Joi.object({ username: Joi.string() });\n        const input = { username: 'test', func() { } };\n\n        Helper.validate(schema, { skipFunctions: false }, [[input, false, '\"func\" is not allowed']]);\n    });\n\n    it('should not convert values when convert is false', () => {\n\n        const schema = Joi.object({\n            arr: Joi.array().items(Joi.string())\n        });\n\n        const input = { arr: 'foo' };\n        Helper.validate(schema, { convert: false }, [[input, false, '\"arr\" must be an array']]);\n    });\n\n    it('full errors when abortEarly is false', () => {\n\n        const schema = Joi.object({\n            a: Joi.string(),\n            b: Joi.string()\n        });\n\n        const input = { a: 1, b: 2 };\n\n        const errOne = schema.validate(input).error;\n        const errFull = schema.validate(input, { abortEarly: false }).error;\n        expect(errFull.details.length).to.be.greaterThan(errOne.details.length);\n    });\n\n    it('errors multiple times when abortEarly is false in a complex object', () => {\n\n        const schema = Joi.object({\n            test: Joi.array().items(Joi.object().keys({\n                foo: Joi.string().required().max(3),\n                bar: Joi.string().max(5)\n            })),\n            test2: Joi.object({\n                test3: Joi.array().items(Joi.object().keys({\n                    foo: Joi.string().required().max(3),\n                    bar: Joi.string().max(5),\n                    baz: Joi.object({\n                        test4: Joi.array().items(Joi.object().keys({\n                            foo: Joi.string().required().max(3),\n                            bar: Joi.string().max(5)\n                        }))\n                    })\n                }))\n            })\n        });\n\n        const input = {\n            test: [{\n                foo: 'test1',\n                bar: 'testfailed'\n            }],\n            test2: {\n                test3: [{\n                    foo: '123'\n                }, {\n                    foo: 'test1',\n                    bar: 'testfailed'\n                }, {\n                    foo: '123',\n                    baz: {\n                        test4: [{\n                            foo: 'test1',\n                            baz: '123'\n                        }]\n                    }\n                }]\n            }\n        };\n\n        Helper.validate(schema, { abortEarly: false }, [[input, false, {\n            message: '\"test[0].foo\" length must be less than or equal to 3 characters long. \"test[0].bar\" length must be less than or equal to 5 characters long. \"test2.test3[1].foo\" length must be less than or equal to 3 characters long. \"test2.test3[1].bar\" length must be less than or equal to 5 characters long. \"test2.test3[2].baz.test4[0].foo\" length must be less than or equal to 3 characters long. \"test2.test3[2].baz.test4[0].baz\" is not allowed',\n            details: [{\n                message: '\"test[0].foo\" length must be less than or equal to 3 characters long',\n                path: ['test', 0, 'foo'],\n                type: 'string.max',\n                context: { limit: 3, value: 'test1', key: 'foo', label: 'test[0].foo', encoding: undefined }\n            }, {\n                message: '\"test[0].bar\" length must be less than or equal to 5 characters long',\n                path: ['test', 0, 'bar'],\n                type: 'string.max',\n                context: { limit: 5, value: 'testfailed', key: 'bar', label: 'test[0].bar', encoding: undefined }\n            }, {\n                message: '\"test2.test3[1].foo\" length must be less than or equal to 3 characters long',\n                path: ['test2', 'test3', 1, 'foo'],\n                type: 'string.max',\n                context: { limit: 3, value: 'test1', key: 'foo', label: 'test2.test3[1].foo', encoding: undefined }\n            }, {\n                message: '\"test2.test3[1].bar\" length must be less than or equal to 5 characters long',\n                path: ['test2', 'test3', 1, 'bar'],\n                type: 'string.max',\n                context: { limit: 5, value: 'testfailed', key: 'bar', label: 'test2.test3[1].bar', encoding: undefined }\n            }, {\n                message: '\"test2.test3[2].baz.test4[0].foo\" length must be less than or equal to 3 characters long',\n                path: ['test2', 'test3', 2, 'baz', 'test4', 0, 'foo'],\n                type: 'string.max',\n                context: {\n                    limit: 3,\n                    value: 'test1',\n                    key: 'foo',\n                    label: 'test2.test3[2].baz.test4[0].foo',\n                    encoding: undefined\n                }\n            }, {\n                message: '\"test2.test3[2].baz.test4[0].baz\" is not allowed',\n                path: ['test2', 'test3', 2, 'baz', 'test4', 0, 'baz'],\n                type: 'object.unknown',\n                context: { key: 'baz', label: 'test2.test3[2].baz.test4[0].baz', child: 'baz', value: '123' }\n            }]\n        }]]);\n    });\n\n    it('accepts no options', () => {\n\n        Helper.validate(Joi.string(), [['test', true]]);\n        Helper.validate(Joi.number(), { convert: false }, [['5', false, '\"value\" must be a number']]);\n    });\n\n    it('accepts null options', () => {\n\n        Helper.validate(Joi.string(), null, [['test', true]]);\n    });\n\n    it('accepts undefined options', () => {\n\n        Helper.validate(Joi.string(), undefined, [['test', true]]);\n    });\n\n    describe('assert()', () => {\n\n        it('respects abortEarly option', () => {\n\n            try {\n                Joi.assert({}, Joi.object().keys({ a: Joi.required(), b: Joi.required() }), { abortEarly: false });\n                throw new Error('should not reach that');\n            }\n            catch (err) {\n                expect(err.details.length).to.equal(2);\n            }\n        });\n    });\n\n    describe('attempt()', () => {\n\n        it('throws on invalid value', () => {\n\n            expect(() => {\n\n                Joi.attempt('x', Joi.number());\n            }).to.throw('\"value\" must be a number');\n        });\n\n        it('throws a ValidationError on invalid value', () => {\n\n            expect(() => {\n\n                Joi.attempt('x', Joi.number());\n            }).to.throw(Joi.ValidationError);\n        });\n\n        it('does not throw on valid value', () => {\n\n            expect(() => {\n\n                Joi.attempt('4', Joi.number());\n            }).to.not.throw();\n        });\n\n        it('returns validated structure', () => {\n\n            let valid;\n            expect(() => {\n\n                valid = Joi.attempt('4', Joi.number());\n            }).to.not.throw();\n            expect(valid).to.equal(4);\n        });\n\n        it('throws on invalid value with message', () => {\n\n            expect(() => {\n\n                Joi.attempt('x', Joi.number(), 'the reason is');\n            }).to.throw('the reason is \"value\" must be a number');\n        });\n\n        it('throws on invalid value with message and abortEarly: false', () => {\n\n            const schema = Joi.object({ a: Joi.required(), b: Joi.required() });\n            expect(() => Joi.attempt({}, schema, 'the reasons are', { abortEarly: false })).to.throw('the reasons are \"a\" is required. \"b\" is required');\n        });\n\n        it('throws on invalid value with message as error even with abortEarly: false', () => {\n\n            expect(() => {\n\n                Joi.attempt({}, Joi.object().keys({ a: Joi.required(), b: Joi.required() }), new Error('invalid value'), { abortEarly: false });\n            }).to.throw('invalid value');\n        });\n\n        it('throws a custom error from the schema if provided', () => {\n\n            expect(() => Joi.attempt('x', Joi.number().error(new Error('Oh noes !')))).to.throw('Oh noes !');\n        });\n\n        it('throws an error with combined messages', () => {\n\n            const schema = Joi.number().error(new Error('Oh noes !'));\n            expect(() => Joi.attempt('x', schema, 'invalid value')).to.throw('invalid value Oh noes !');\n            expect(() => Joi.attempt('x', schema, 'invalid value')).to.throw('invalid value Oh noes !');\n        });\n\n        it('should not convert to iso date', () => {\n\n            let value;\n            expect(() => {\n\n                value = Joi.attempt('2022-02-21T21:28:00Z', Joi.string().isoDate(), '', { convert: false });\n            }).to.not.throw();\n            expect(value).to.equal('2022-02-21T21:28:00Z');\n        });\n\n        it('should convert to iso date', () => {\n\n            let value;\n            expect(() => {\n\n                value = Joi.attempt('2022-02-21T21:28:00Z', Joi.string().isoDate(), '', { convert: true });\n            }).to.not.throw();\n            expect(value).to.equal('2022-02-21T21:28:00.000Z');\n        });\n    });\n\n    describe('checkPreferences()', () => {\n\n        it('validates preferences', () => {\n\n            expect(() => Joi.checkPreferences({ abortEarly: false })).to.not.throw();\n            expect(() => Joi.checkPreferences({ x: 1 })).to.throw();\n        });\n    });\n\n    describe('compile()', () => {\n\n        it('throws an error on invalid value', () => {\n\n            expect(() => {\n\n                Joi.compile(undefined);\n            }).to.throw('Invalid undefined schema');\n        });\n\n        it('shows path to errors in object', () => {\n\n            const schema = {\n                a: {\n                    b: {\n                        c: {\n                            d: undefined\n                        }\n                    }\n                }\n            };\n\n            expect(() => {\n\n                Joi.compile(schema);\n            }).to.throw(Error, 'Invalid undefined schema (a.b.c.d)');\n        });\n    });\n\n    describe('defaults()', () => {\n\n        it('applies defaults to root', () => {\n\n            const custom = Joi.defaults((schema) => schema.required().description('defaulted'));\n            const schema = custom.optional();\n            expect(schema.describe()).to.equal({\n                type: 'any',\n                flags: {\n                    description: 'defaulted',\n                    presence: 'optional'\n                }\n            });\n        });\n\n        it('applies defaults to standard types', () => {\n\n            const custom = Joi.defaults((schema) => schema.required().description('defaulted'));\n            const schema = custom.string();\n            expect(schema.describe()).to.equal({\n                type: 'string',\n                flags: {\n                    description: 'defaulted',\n                    presence: 'required'\n                }\n            });\n        });\n\n        it('applies defaults to types with arguments', () => {\n\n            const custom = Joi.defaults((schema) => schema.required().description('defaulted'));\n            const schema = custom.object({ foo: 'bar' });\n            expect(schema.describe()).to.equal({\n                type: 'object',\n                flags: {\n                    description: 'defaulted',\n                    presence: 'required'\n                },\n                keys: {\n                    foo: {\n                        type: 'any',\n                        flags: {\n                            description: 'defaulted',\n                            presence: 'required',\n                            only: true\n                        },\n                        allow: [{ override: true }, 'bar']\n                    }\n                }\n            });\n        });\n\n        it('keeps several defaults separated', () => {\n\n            const custom1 = Joi.defaults((schema) => schema.required().description('defaulted'));\n            const custom2 = Joi.defaults((schema) => schema.required().description('defaulted2'));\n\n            const schema = custom1.object({\n                foo: 'bar',\n                baz: custom2.object().keys({\n                    qux: 'zorg'\n                })\n            });\n\n            expect(schema.describe()).to.equal({\n                type: 'object',\n                flags: {\n                    description: 'defaulted',\n                    presence: 'required'\n                },\n                keys: {\n                    foo: {\n                        type: 'any',\n                        flags: {\n                            presence: 'required',\n                            description: 'defaulted',\n                            only: true\n                        },\n                        allow: [{ override: true }, 'bar']\n                    },\n                    baz: {\n                        keys: {\n                            qux: {\n                                flags: {\n                                    only: true,\n                                    description: 'defaulted2',\n                                    presence: 'required'\n                                },\n                                type: 'any',\n                                allow: [{ override: true }, 'zorg']\n                            }\n                        },\n                        flags: {\n                            description: 'defaulted2',\n                            presence: 'required'\n                        },\n                        type: 'object'\n                    }\n                }\n            });\n        });\n\n        it('inherits defaults', () => {\n\n            const custom = Joi\n                .defaults((schema) => schema.required().description('defaulted'))\n                .defaults((schema) => schema.raw());\n\n            const schema = custom.object({\n                foo: 'bar'\n            });\n\n            expect(schema.describe()).to.equal({\n                type: 'object',\n                flags: {\n                    description: 'defaulted',\n                    presence: 'required',\n                    result: 'raw'\n                },\n                keys: {\n                    foo: {\n                        type: 'any',\n                        flags: {\n                            description: 'defaulted',\n                            presence: 'required',\n                            only: true,\n                            result: 'raw'\n                        },\n                        allow: [{ override: true }, 'bar']\n                    }\n                }\n            });\n        });\n\n        it('keeps defaults on extensions', () => {\n\n            const custom = Joi.defaults((schema) => schema.required().description('defaulted'));\n\n            const extended = custom.extend({ type: 'foobar' });\n            const schema = extended.foobar();\n            expect(schema.describe()).to.equal({\n                type: 'foobar',\n                flags: {\n                    description: 'defaulted',\n                    presence: 'required'\n                }\n            });\n        });\n\n        it('applies defaults on extensions', () => {\n\n            const extended = Joi.extend({ type: 'foobar' });\n            const custom = extended.defaults((schema) => schema.required().description('defaulted'));\n            const schema = custom.foobar();\n            expect(schema.describe()).to.equal({\n                type: 'foobar',\n                flags: {\n                    description: 'defaulted',\n                    presence: 'required'\n                }\n            });\n        });\n\n        it('errors on missing return value (root)', () => {\n\n            expect(() => {\n\n                Joi.defaults((schema) => {\n\n                    switch (schema.type) {\n                        case 'bool':\n                            return schema.required();\n                    }\n                });\n            }).to.throw('modifier must return a valid schema object');\n        });\n\n        it('errors on missing return for a standard type', () => {\n\n            expect(() => {\n\n                Joi.defaults((schema) => {\n\n                    switch (schema.type) {\n                        case 'any':\n                            return schema.required();\n                    }\n                });\n            }).to.throw('modifier must return a valid schema object');\n        });\n    });\n\n    describe('ValidationError', () => {\n\n        it('should be Joi', () => {\n\n            const error = new Joi.ValidationError();\n            expect(error.isJoi).to.equal(true);\n        });\n\n        it('should be named ValidationError', () => {\n\n            const error = new Joi.ValidationError();\n            expect(error.name).to.equal('ValidationError');\n        });\n    });\n\n    describe('types()', () => {\n\n        it('returns type shortcut methods', () => {\n\n            expect(() => {\n\n                const string = Joi.string;\n                string();\n            }).to.throw('Must be invoked on a Joi instance.');\n\n            const { string } = Joi.types();\n            expect(() => string.allow('x')).to.not.throw();\n\n            Helper.validate(string, [[0, false, '\"value\" must be a string']]);\n        });\n\n        it('returns alias shortcut methods', () => {\n\n            expect(() => {\n\n                const func = Joi.func;\n                func();\n            }).to.throw('Must be invoked on a Joi instance.');\n\n            const { func } = Joi.types();\n            expect(() => func.allow('x')).to.not.throw();\n\n            Helper.validate(func, [[0, false, '\"value\" must be of type function']]);\n        });\n    });\n});\n"
  },
  {
    "path": "test/index.ts",
    "content": "import * as Lab from '@hapi/lab';\nimport * as Joi from '..';\nimport { StandardSchemaV1 } from \"@standard-schema/spec\";\n\nconst { expect } = Lab.types;\n\n// The following was copied (almost) as-is from:\n// https://github.com/DefinitelyTyped/DefinitelyTyped/tree/master/types/hapi__joi\n\n// --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- ---\n\nconst x: any = null;\ndeclare const value: any;\nconst num = 0;\nconst str = 'test';\nconst bool: boolean = true;\nconst buf: Buffer = Buffer.alloc(0);\nconst exp: RegExp = /./;\nconst obj: object = {};\nconst date: Date = new Date();\nconst err: Error = new Error('test');\nconst func: () => void = () => {\n};\nconst symbol = Symbol('test');\n\nconst numArr: number[] = [1, 2, 3];\nconst strArr: string[] = ['a', 'b', 'c'];\nconst expArr: RegExp[] = [/a/, /b/];\n\n// --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- ---\n\nlet schema: Joi.Schema = Joi.any();\nconst schemaLike: Joi.SchemaLike = true;\n\nlet anySchema: Joi.AnySchema = Joi.any();\nlet numSchema: Joi.NumberSchema = Joi.number();\nlet strSchema: Joi.StringSchema = Joi.string();\nlet arrSchema: Joi.ArraySchema = Joi.array();\nlet boolSchema: Joi.BooleanSchema = Joi.boolean();\nlet binSchema: Joi.BinarySchema = Joi.binary();\nlet dateSchema: Joi.DateSchema = Joi.date();\nlet funcSchema: Joi.FunctionSchema = Joi.func();\nlet objSchema: Joi.ObjectSchema = Joi.object();\n\nconst schemaArr: Joi.Schema[] = [Joi.string(), Joi.number()];\n\nlet ref: Joi.Reference = Joi.ref('test');\nlet description: Joi.Description = {};\n\n// --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- ---\n\nlet validOpts: Joi.ValidationOptions = {};\n\nvalidOpts = { abortEarly: bool };\nvalidOpts = { convert: bool };\nvalidOpts = { allowUnknown: bool };\nvalidOpts = { skipFunctions: bool };\nvalidOpts = { stripUnknown: bool };\nvalidOpts = { stripUnknown: { arrays: bool } };\nvalidOpts = { stripUnknown: { objects: bool } };\nvalidOpts = { stripUnknown: { arrays: bool, objects: bool } };\nvalidOpts = { presence: 'optional' };\nvalidOpts = { presence: 'required' };\nvalidOpts = { presence: 'forbidden' };\nvalidOpts = { context: obj };\nvalidOpts = { noDefaults: bool };\nvalidOpts = {\n  abortEarly: true,\n  messages: {\n    'any.ref': str,\n    'string.email': str,\n  },\n  dateFormat: 'iso',\n};\n// Test various permutations of string, `false`, or `undefined` for both parameters:\nvalidOpts = { errors: { wrap: { label: str, array: '[]' } } };\nvalidOpts = { errors: { wrap: { label: false, array: false } } };\nvalidOpts = { errors: { wrap: { label: str } } };\nvalidOpts = { errors: { wrap: { array: '[]' } } };\n\n// --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- ---\n\nlet renOpts: Joi.RenameOptions = {};\n\nrenOpts = { alias: bool };\nrenOpts = { multiple: bool };\nrenOpts = { override: bool };\nrenOpts = { ignoreUndefined: bool };\n\n// --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- ---\n\nlet emailOpts: Joi.EmailOptions = {};\n\nemailOpts = { allowFullyQualified: bool };\nemailOpts = { allowUnicode: bool };\nemailOpts = { tlds: { allow: strArr } };\nemailOpts = { minDomainSegments: 2 };\nemailOpts = { tlds: false };\n\n// --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- ---\n\nlet domainOpts: Joi.DomainOptions = {};\n\ndomainOpts = { allowFullyQualified: bool };\ndomainOpts = { allowUnicode: bool };\ndomainOpts = { tlds: { allow: strArr } };\ndomainOpts = { minDomainSegments: 2 };\n\n// --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- ---\n\nlet hexOpts: Joi.HexOptions = {};\n\nhexOpts = { byteAligned: bool };\n\n// --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- ---\n\nlet ipOpts: Joi.IpOptions = {};\n\nipOpts = { version: str };\nipOpts = { version: strArr };\nipOpts = { cidr: 'forbidden' };\n\n// --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- ---\n\nlet uriOpts: Joi.UriOptions = {};\n\nuriOpts = { scheme: str };\nuriOpts = { scheme: exp };\nuriOpts = { scheme: strArr };\nuriOpts = { scheme: expArr };\nuriOpts = { domain: domainOpts };\n\n// --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- ---\n\nlet base64Opts: Joi.Base64Options = {};\n\nbase64Opts = { paddingRequired: bool };\n\n// --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- ---\n\nlet dataUriOpts: Joi.DataUriOptions = {};\n\ndataUriOpts = { paddingRequired: bool };\n\n// --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- ---\n\nlet whenOpts: Joi.WhenOptions = {\n  is: Joi.any(),\n};\n\nwhenOpts = { is: x };\nwhenOpts = { is: schema, then: schema };\nwhenOpts = { is: schema, otherwise: schema };\nwhenOpts = { is: schemaLike, then: schemaLike, otherwise: schemaLike };\nwhenOpts = { not: schema, then: schema };\nwhenOpts = { not: schema, otherwise: schema };\nwhenOpts = { not: schemaLike, then: schemaLike, otherwise: schemaLike };\n\n// --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- ---\n\nlet whenSchemaOpts: Joi.WhenSchemaOptions = {};\n\nwhenSchemaOpts = { then: schema };\nwhenSchemaOpts = { otherwise: schema };\nwhenSchemaOpts = { then: schemaLike, otherwise: schemaLike };\n\n// --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- ---\n\nlet refOpts: Joi.ReferenceOptions = {};\n\nrefOpts = { separator: str };\nrefOpts = { prefix: { local: str } };\n\n// --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- ---\n\nlet stringRegexOpts: Joi.StringRegexOptions = {};\n\nstringRegexOpts = { name: str };\nstringRegexOpts = { invert: bool };\n\n// --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- ---\n\nconst validErr = new Joi.ValidationError('message', [], 'original');\nlet validErrItem: Joi.ValidationErrorItem;\nlet validErrFunc: Joi.ValidationErrorFunction;\n\nvalidErrItem = {\n  message: str,\n  type: str,\n  path: [str],\n};\n\nvalidErrItem = {\n  message: str,\n  type: str,\n  path: [str],\n  context: obj,\n};\n\nvalidErrItem = {\n  message: str,\n  type: str,\n  path: [str, num, str],\n  context: obj,\n};\n\nvalidErrFunc = (errs) => errs[0] ? errs[0] : [];\nvalidErrFunc = (errs) => 'Some error';\nvalidErrFunc = (errs) => err;\n\n// error() can take function with ErrorReport argument\nvalidErrFunc = (errors) => {\n  const path: string | undefined = errors[0]?.path[0];\n  const code: string | undefined = errors[0]?.code;\n  const messages = errors[0]?.prefs.messages;\n\n  const message: string = messages && code && messages[code]?.rendered || 'Error';\n\n  const validationErr = new Error();\n  validationErr.message = `[${path}]: ${message}`;\n  return validationErr;\n};\n\nJoi.any().error(validErrFunc);\n\nJoi.isError(validErr);\n\nconst maybeValidErr: any = new Joi.ValidationError('message', [], 'original');\n\nif (Joi.isError(maybeValidErr)) {\n  // isError is a type guard that allows accessing these properties:\n  maybeValidErr.isJoi;\n}\n\n// --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- ---\n\nschema = anySchema;\nschema = numSchema;\nschema = strSchema;\nschema = arrSchema;\nschema = boolSchema;\nschema = binSchema;\nschema = dateSchema;\nschema = funcSchema;\nschema = objSchema;\n\nanySchema = anySchema;\nanySchema = numSchema;\nanySchema = strSchema;\nanySchema = arrSchema;\nanySchema = boolSchema;\nanySchema = binSchema;\nanySchema = dateSchema;\nanySchema = funcSchema;\nanySchema = objSchema;\n\n// --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- ---\n\nlet schemaMap: Joi.SchemaMap = {};\n\nschemaMap = {\n  a: numSchema,\n  b: strSchema,\n};\nschemaMap = {\n  a: numSchema,\n  b: {\n    b1: strSchema,\n    b2: anySchema,\n  },\n};\nschemaMap = {\n  a: numSchema,\n  b: [{ b1: strSchema }, { b2: anySchema }],\n  c: arrSchema,\n  d: schemaLike,\n};\nschemaMap = {\n  a: 1,\n  b: {\n    b1: '1',\n    b2: 2,\n  },\n  c: [{ c1: true }, { c2: null }],\n};\n\n// --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- ---\n\nanySchema = Joi.any();\n\n{\n  // common\n  anySchema = anySchema.allow(x);\n  anySchema = anySchema.allow(x, x);\n  anySchema = anySchema.allow(...[x, x, x]);\n  anySchema = anySchema.valid(x, 'x');\n  anySchema = anySchema.valid(x, x);\n  anySchema = anySchema.valid(...[x, x, x]);\n  anySchema = anySchema.only();\n  anySchema = anySchema.equal(x);\n  anySchema = anySchema.equal(x, x);\n  anySchema = anySchema.equal(...[x, x, x]);\n  anySchema = anySchema.invalid(x);\n  anySchema = anySchema.invalid(x, x);\n  anySchema = anySchema.invalid(...[x, x, x]);\n  anySchema = anySchema.disallow(x);\n  anySchema = anySchema.disallow(x, x);\n  anySchema = anySchema.disallow(...[x, x, x]);\n  anySchema = anySchema.not(x);\n  anySchema = anySchema.not(x, x);\n  anySchema = anySchema.not(...[x, x, x]);\n\n  anySchema = Joi.object().default();\n  anySchema = anySchema.default(x);\n  anySchema = anySchema.default('string');\n  anySchema = anySchema.default(3.14);\n  anySchema = anySchema.default(true);\n  anySchema = anySchema.default({ foo: 'bar' });\n  anySchema = anySchema.default((parent, helpers) => {\n    return helpers.state;\n  });\n\n  anySchema = anySchema.required();\n  anySchema = anySchema.optional();\n  anySchema = anySchema.forbidden();\n  anySchema = anySchema.strip();\n\n  anySchema = anySchema.description(str);\n  anySchema = anySchema.note(str);\n  anySchema = anySchema.note(str).note(str);\n  anySchema = anySchema.tag(str);\n  anySchema = anySchema.tag(str).tag(str);\n\n  anySchema = anySchema.meta(obj);\n  anySchema = anySchema.example(obj);\n  anySchema = anySchema.unit(str);\n\n  anySchema = anySchema.preferences(validOpts);\n  anySchema = anySchema.strict();\n  anySchema = anySchema.strict(bool);\n  anySchema = anySchema.concat(Joi.object({ x: Joi.any() }));\n\n  anySchema = anySchema.when(str, whenOpts);\n  anySchema = anySchema.when(ref, whenOpts);\n  anySchema = anySchema.when(schema, whenSchemaOpts);\n\n  anySchema = anySchema.label(str);\n  anySchema = anySchema.raw();\n  anySchema = anySchema.raw(bool);\n  anySchema = anySchema.empty();\n  anySchema = anySchema.empty(str);\n  anySchema = anySchema.empty(anySchema);\n\n  anySchema = anySchema.error(err);\n  anySchema = anySchema.error(validErrFunc);\n}\n\n// --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- ---\n\narrSchema = Joi.array();\n\narrSchema = arrSchema.has(Joi.any());\narrSchema = arrSchema.sparse();\narrSchema = arrSchema.sparse(bool);\narrSchema = arrSchema.single();\narrSchema = arrSchema.single(bool);\narrSchema = Joi.array().sort();\narrSchema = arrSchema.sort({ order: 'descending' });\narrSchema = arrSchema.sort({ by: 'n' });\narrSchema = arrSchema.sort({ by: Joi.ref('.x') });\narrSchema = arrSchema.sort();\narrSchema = arrSchema.ordered(anySchema);\narrSchema = arrSchema.ordered(\n  anySchema,\n  numSchema,\n  strSchema,\n  arrSchema,\n  boolSchema,\n  binSchema,\n  dateSchema,\n  funcSchema,\n  objSchema,\n  schemaLike\n);\narrSchema = arrSchema.ordered(schemaMap);\nexpect.error(arrSchema.ordered([schemaMap, schemaMap, schemaLike]));\narrSchema = arrSchema.min(num);\narrSchema = arrSchema.max(num);\narrSchema = arrSchema.length(num);\narrSchema = arrSchema.length(ref);\narrSchema = arrSchema.unique();\narrSchema = arrSchema.unique((a, b) => a.test === b.test);\narrSchema = arrSchema.unique('customer.id');\n\narrSchema = arrSchema.items(numSchema);\narrSchema = arrSchema.items(numSchema, strSchema, schemaLike);\nexpect.error(arrSchema.items([numSchema, strSchema, schemaLike]));\narrSchema = arrSchema.items(schemaMap);\narrSchema = arrSchema.items(schemaMap, schemaMap, schemaLike);\nexpect.error(arrSchema.items([schemaMap, schemaMap, schemaLike]));\nlet value1 = Joi.array().items(Joi.string(), Joi.boolean(), Joi.number(), Joi.object({key: Joi.string()}));\nexpect.type<Joi.ArraySchema<(string | number | boolean | {key: string})[]>>(value1)\n\nconst arr1 = Joi.array().items(Joi.boolean());\nexpect.type<Joi.ArraySchema<boolean[]>>(arr1);\nconst arr2 = Joi.array().items<boolean>(Joi.boolean());\nexpect.type<Joi.ArraySchema<boolean[]>>(arr2);\nconst arr3 = Joi.array().items<number>(Joi.number());\nexpect.type<Joi.ArraySchema<number[]>>(arr3);\nconst arr4 = Joi.array().items(Joi.array().items(Joi.number()));\nexpect.type<Joi.ArraySchema<number[][]>>(arr4);\nconst arr5 = Joi.array().items(Joi.number(), Joi.string());\nexpect.type<Joi.ArraySchema<(number | string)[]>>(arr5);\nconst arr6 = Joi.array().items(Joi.number(), Joi.string(), Joi.boolean());\nexpect.type<Joi.ArraySchema<(number | string | boolean)[]>>(arr6);\nconst arr7 = Joi.array().items(process.env.NODE_ENV ? Joi.string() : Joi.number());\nexpect.type<Joi.ArraySchema<(string | number)[]>>(arr7);\nconst arr8 = Joi.array().items(process.env.NODE_ENV ? Joi.string() : Joi.number(), process.env.NODE_ENV ? Joi.boolean() : Joi.date());\nexpect.type<Joi.ArraySchema<(string | number | boolean | Date)[]>>(arr8);\nconst arr9 = Joi.array().items(\n  Joi.binary(),\n  Joi.boolean(),\n  Joi.date(),\n  Joi.function(),\n  Joi.number(),\n  Joi.object<Record<string,string>>(),\n  Joi.string(),\n);\nexpect.type<Joi.ArraySchema<(Buffer | boolean | Date | Function | number | Record<string, string> | string)[]>>(arr9);\n\n// - - - - - - - -\n\n{\n  // common copy paste\n  // use search & replace from any\n  arrSchema = arrSchema.allow(x);\n  arrSchema = arrSchema.allow(x, x);\n  arrSchema = arrSchema.allow(...[x, x, x]);\n  arrSchema = arrSchema.valid(x);\n  arrSchema = arrSchema.valid(x, x);\n  arrSchema = arrSchema.valid(...[x, x, x]);\n  arrSchema = arrSchema.only();\n  arrSchema = arrSchema.equal(x);\n  arrSchema = arrSchema.equal(x, x);\n  arrSchema = arrSchema.equal(...[x, x, x]);\n  arrSchema = Joi.array().invalid(x);\n  arrSchema = arrSchema.invalid(x, x);\n  arrSchema = arrSchema.invalid(...[x, x, x]);\n  arrSchema = arrSchema.disallow(x);\n  arrSchema = arrSchema.disallow(x, x);\n  arrSchema = arrSchema.disallow(...[x, x, x]);\n  arrSchema = arrSchema.not(x);\n  arrSchema = arrSchema.not(x, x);\n  arrSchema = arrSchema.not(...[x, x, x]);\n\n  arrSchema = arrSchema.default(x);\n\n  arrSchema = arrSchema.required();\n  arrSchema = arrSchema.optional();\n  arrSchema = arrSchema.forbidden();\n\n  arrSchema = arrSchema.description(str);\n  arrSchema = arrSchema.note(str);\n  arrSchema = arrSchema.note(str).note(str);\n  arrSchema = arrSchema.tag(str);\n  arrSchema = arrSchema.tag(str).tag(str);\n\n  arrSchema = arrSchema.meta(obj);\n  arrSchema = arrSchema.example(obj);\n  arrSchema = arrSchema.unit(str);\n\n  arrSchema = arrSchema.preferences(validOpts);\n  arrSchema = arrSchema.strict();\n  arrSchema = arrSchema.concat(Joi.array());\n\n  arrSchema = arrSchema.when(str, whenOpts);\n  arrSchema = arrSchema.when(ref, whenOpts);\n  arrSchema = arrSchema.when(schema, whenSchemaOpts);\n}\n\n// --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- ---\n\nboolSchema = Joi.bool();\nboolSchema = Joi.boolean();\n\n{\n  // common copy paste\n  boolSchema = boolSchema.allow(x);\n  boolSchema = boolSchema.allow(x, x);\n  boolSchema = boolSchema.allow(...[x, x, x]);\n  boolSchema = boolSchema.valid(x);\n  boolSchema = boolSchema.valid(x, x);\n  boolSchema = boolSchema.valid(...[x, x, x]);\n  boolSchema = boolSchema.only();\n  boolSchema = boolSchema.equal(x);\n  boolSchema = boolSchema.equal(x, x);\n  boolSchema = boolSchema.equal(...[x, x, x]);\n  boolSchema = Joi.boolean().invalid(x);\n  boolSchema = boolSchema.invalid(x, x);\n  boolSchema = boolSchema.invalid(...[x, x, x]);\n  boolSchema = boolSchema.disallow(x);\n  boolSchema = boolSchema.disallow(x, x);\n  boolSchema = boolSchema.disallow(...[x, x, x]);\n  boolSchema = boolSchema.not(x);\n  boolSchema = boolSchema.not(x, x);\n  boolSchema = boolSchema.not(...[x, x, x]);\n\n  boolSchema = boolSchema.default(x);\n\n  boolSchema = boolSchema.required();\n  boolSchema = boolSchema.optional();\n  boolSchema = boolSchema.forbidden();\n\n  boolSchema = boolSchema.description(str);\n  boolSchema = boolSchema.note(str);\n  boolSchema = boolSchema.note(str).note(str);\n  boolSchema = boolSchema.tag(str);\n  boolSchema = boolSchema.tag(str).tag(str);\n\n  boolSchema = boolSchema.meta(obj);\n  boolSchema = boolSchema.example(obj);\n  boolSchema = boolSchema.unit(str);\n\n  boolSchema = boolSchema.preferences(validOpts);\n  boolSchema = boolSchema.strict();\n  boolSchema = boolSchema.concat(Joi.boolean());\n\n  boolSchema = boolSchema.truthy(str);\n  boolSchema = boolSchema.truthy(num);\n  boolSchema = boolSchema.truthy(str, str);\n  boolSchema = boolSchema.truthy(num, num);\n  boolSchema = boolSchema.falsy(str);\n  boolSchema = boolSchema.falsy(num);\n  boolSchema = boolSchema.falsy(str, str);\n  boolSchema = boolSchema.falsy(num, num);\n  boolSchema = boolSchema.sensitive(bool);\n\n  boolSchema = boolSchema.when(str, whenOpts);\n  boolSchema = boolSchema.when(ref, whenOpts);\n  boolSchema = boolSchema.when(schema, whenSchemaOpts);\n}\n\n// --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- ---\n\nbinSchema = Joi.binary();\n\nbinSchema = binSchema.encoding('hex');\nbinSchema = binSchema.min(num);\nbinSchema = binSchema.max(num);\nbinSchema = binSchema.length(num);\n\n{\n  // common\n  binSchema = binSchema.allow(x);\n  binSchema = binSchema.allow(x, x);\n  binSchema = binSchema.allow(...[x, x, x]);\n  binSchema = binSchema.valid(x);\n  binSchema = binSchema.valid(x, x);\n  binSchema = binSchema.valid(...[x, x, x]);\n  binSchema = binSchema.only();\n  binSchema = binSchema.equal(x);\n  binSchema = binSchema.equal(x, x);\n  binSchema = binSchema.equal(...[x, x, x]);\n  binSchema = Joi.binary().invalid(x);\n  binSchema = binSchema.invalid(x, x);\n  binSchema = binSchema.invalid(...[x, x, x]);\n  binSchema = binSchema.disallow(x);\n  binSchema = binSchema.disallow(x, x);\n  binSchema = binSchema.disallow(...[x, x, x]);\n  binSchema = binSchema.not(x);\n  binSchema = binSchema.not(x, x);\n  binSchema = binSchema.not(...[x, x, x]);\n\n  binSchema = binSchema.default(x);\n\n  binSchema = binSchema.required();\n  binSchema = binSchema.optional();\n  binSchema = binSchema.forbidden();\n\n  binSchema = binSchema.description(str);\n  binSchema = binSchema.note(str);\n  binSchema = binSchema.note(str).note(str);\n  binSchema = binSchema.tag(str);\n  binSchema = binSchema.tag(str).tag(str);\n\n  binSchema = binSchema.meta(obj);\n  binSchema = binSchema.example(obj);\n  binSchema = binSchema.unit(str);\n\n  binSchema = binSchema.preferences(validOpts);\n  binSchema = binSchema.strict();\n  binSchema = binSchema.concat(Joi.binary());\n\n  binSchema = binSchema.when(str, whenOpts);\n  binSchema = binSchema.when(ref, whenOpts);\n  binSchema = binSchema.when(schema, whenSchemaOpts);\n}\n\n// --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- ---\n\ndateSchema = Joi.date();\n\ndateSchema = dateSchema.greater('now');\ndateSchema = dateSchema.less('now');\ndateSchema = dateSchema.min('now');\ndateSchema = dateSchema.max('now');\n\ndateSchema = dateSchema.greater(date);\ndateSchema = dateSchema.less(date);\ndateSchema = dateSchema.min(date);\ndateSchema = dateSchema.max(date);\n\nconst dateString: string = date.toDateString();\ndateSchema = dateSchema.greater(dateString);\ndateSchema = dateSchema.less(dateString);\ndateSchema = dateSchema.min(dateString);\ndateSchema = dateSchema.max(dateString);\n\ndateSchema = dateSchema.greater(num);\ndateSchema = dateSchema.less(num);\ndateSchema = dateSchema.min(num);\ndateSchema = dateSchema.max(num);\n\ndateSchema = dateSchema.greater(ref);\ndateSchema = dateSchema.less(ref);\ndateSchema = dateSchema.min(ref);\ndateSchema = dateSchema.max(ref);\n\ndateSchema = dateSchema.iso();\n\ndateSchema = dateSchema.timestamp();\ndateSchema = dateSchema.timestamp('javascript');\ndateSchema = dateSchema.timestamp('unix');\n\n{\n  // common\n  dateSchema = dateSchema.allow(x);\n  dateSchema = dateSchema.allow(x, x);\n  dateSchema = dateSchema.allow(...[x, x, x]);\n  dateSchema = dateSchema.valid(x);\n  dateSchema = dateSchema.valid(x, x);\n  dateSchema = dateSchema.valid(...[x, x, x]);\n  dateSchema = dateSchema.only();\n  dateSchema = dateSchema.equal(x);\n  dateSchema = dateSchema.equal(x, x);\n  dateSchema = dateSchema.equal(...[x, x, x]);\n  dateSchema = Joi.date().invalid(x);\n  dateSchema = dateSchema.invalid(x, x);\n  dateSchema = dateSchema.invalid(...[x, x, x]);\n  dateSchema = dateSchema.disallow(x);\n  dateSchema = dateSchema.disallow(x, x);\n  dateSchema = dateSchema.disallow(...[x, x, x]);\n  dateSchema = dateSchema.not(x);\n  dateSchema = dateSchema.not(x, x);\n  dateSchema = dateSchema.not(...[x, x, x]);\n\n  dateSchema = dateSchema.default(x);\n\n  dateSchema = dateSchema.required();\n  dateSchema = dateSchema.optional();\n  dateSchema = dateSchema.forbidden();\n\n  dateSchema = dateSchema.description(str);\n  dateSchema = dateSchema.note(str);\n  dateSchema = dateSchema.note(str).note(str);\n  dateSchema = dateSchema.tag(str);\n  dateSchema = dateSchema.tag(str).tag(str);\n\n  dateSchema = dateSchema.meta(obj);\n  dateSchema = dateSchema.example(obj);\n  dateSchema = dateSchema.unit(str);\n\n  dateSchema = dateSchema.preferences(validOpts);\n  dateSchema = dateSchema.strict();\n  dateSchema = dateSchema.concat(Joi.date());\n\n  dateSchema = dateSchema.when(str, whenOpts);\n  dateSchema = dateSchema.when(ref, whenOpts);\n  dateSchema = dateSchema.when(schema, whenSchemaOpts);\n}\n\n// --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- ---\n\nfuncSchema = Joi.func();\nfuncSchema = Joi.function();\n\nfuncSchema = funcSchema.arity(5);\nfuncSchema = funcSchema.minArity(6);\nfuncSchema = funcSchema.maxArity(7);\n\n// --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- ---\n\nnumSchema = Joi.number();\n\nnumSchema = numSchema.min(num);\nnumSchema = numSchema.min(ref);\nnumSchema = numSchema.max(num);\nnumSchema = numSchema.max(ref);\nnumSchema = numSchema.greater(num);\nnumSchema = numSchema.greater(ref);\nnumSchema = numSchema.less(num);\nnumSchema = numSchema.less(ref);\nnumSchema = numSchema.integer();\nnumSchema = numSchema.unsafe();\nnumSchema = numSchema.precision(num);\nnumSchema = numSchema.multiple(4);\nnumSchema = numSchema.positive();\nnumSchema = numSchema.negative();\nnumSchema = numSchema.port();\n\n{\n  // common\n  numSchema = numSchema.allow(x);\n  numSchema = numSchema.allow(x, x);\n  numSchema = numSchema.allow(...[x, x, x]);\n  numSchema = numSchema.valid(x);\n  numSchema = numSchema.valid(x, x);\n  numSchema = numSchema.valid(...[x, x, x]);\n  numSchema = numSchema.only();\n  numSchema = numSchema.equal(x);\n  numSchema = numSchema.equal(x, x);\n  numSchema = numSchema.equal(...[x, x, x]);\n  numSchema = Joi.number().invalid(x);\n  numSchema = numSchema.invalid(x, x);\n  numSchema = numSchema.invalid(...[x, x, x]);\n  numSchema = numSchema.disallow(x);\n  numSchema = numSchema.disallow(x, x);\n  numSchema = numSchema.disallow(...[x, x, x]);\n  numSchema = numSchema.not(x);\n  numSchema = numSchema.not(x, x);\n  numSchema = numSchema.not(...[x, x, x]);\n\n  numSchema = numSchema.default(x);\n\n  numSchema = numSchema.required();\n  numSchema = numSchema.optional();\n  numSchema = numSchema.forbidden();\n\n  numSchema = numSchema.description(str);\n  numSchema = numSchema.note(str);\n  numSchema = numSchema.note(str).note(str);\n  numSchema = numSchema.tag(str);\n  numSchema = numSchema.tag(str).tag(str);\n\n  numSchema = numSchema.meta(obj);\n  numSchema = numSchema.example(obj);\n  numSchema = numSchema.unit(str);\n\n  numSchema = numSchema.preferences(validOpts);\n  numSchema = numSchema.strict();\n  numSchema = numSchema.concat(Joi.number());\n\n  numSchema = numSchema.when(str, whenOpts);\n  numSchema = numSchema.when(ref, whenOpts);\n  numSchema = numSchema.when(schema, whenSchemaOpts);\n}\n\n// --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- ---\n\nobjSchema = Joi.object();\nobjSchema = Joi.object(schemaMap);\n\nobjSchema = objSchema.keys();\nobjSchema = objSchema.keys(schemaMap);\n\nobjSchema = objSchema.append();\nobjSchema = objSchema.append(schemaMap);\n\nobjSchema = objSchema.min(num);\nobjSchema = objSchema.max(num);\nobjSchema = objSchema.length(num);\n\nobjSchema = objSchema.pattern(exp, schema);\nobjSchema = objSchema.pattern(exp, schemaLike);\n\nobjSchema = objSchema.and(str);\nobjSchema = objSchema.and(str, str);\nobjSchema = objSchema.and(str, str, { separator: ',' });\n\nobjSchema = objSchema.nand(str);\nobjSchema = objSchema.nand(str, str);\nobjSchema = objSchema.nand(str, str, { separator: ',' });\n\nobjSchema = objSchema.schema();\n\nobjSchema = objSchema.or(str);\nobjSchema = objSchema.or(str, str);\nobjSchema = objSchema.or(str, str, { separator: ',' });\n\nobjSchema = objSchema.oxor(str);\nobjSchema = objSchema.oxor(str, str);\nobjSchema = objSchema.oxor(str, str, { separator: ',' });\n\nobjSchema = objSchema.xor(str);\nobjSchema = objSchema.xor(str, str);\nobjSchema = objSchema.xor(str, str, { separator: ',' });\n\nobjSchema = objSchema.with(str, str);\nobjSchema = objSchema.with(str, strArr);\n\nobjSchema = objSchema.without(str, str);\nobjSchema = objSchema.without(str, strArr);\n\nobjSchema = objSchema.rename(str, 'test2');\nobjSchema = objSchema.rename(exp, str);\nobjSchema = objSchema.rename('test1', 'test2', renOpts);\n\nobjSchema = objSchema.assert(str, schema);\nobjSchema = objSchema.assert(str, schema, str);\nobjSchema = objSchema.assert(ref, schema);\nobjSchema = objSchema.assert(ref, schema, str);\n\nobjSchema = objSchema.unknown();\nobjSchema = objSchema.unknown(bool);\n\nobjSchema = objSchema.instance(func);\nobjSchema = objSchema.instance(func, str);\n\nobjSchema = objSchema.ref();\n\nobjSchema = objSchema.regex();\n\n{\n  // common\n  objSchema = objSchema.allow(x);\n  objSchema = objSchema.allow(x, x);\n  objSchema = objSchema.allow(...[x, x, x]);\n  objSchema = objSchema.valid(x);\n  objSchema = objSchema.valid(x, x);\n  objSchema = objSchema.valid(...[x, x, x]);\n  objSchema = objSchema.only();\n  objSchema = objSchema.equal(x);\n  objSchema = objSchema.equal(x, x);\n  objSchema = objSchema.equal(...[x, x, x]);\n  objSchema = Joi.object().invalid(x);\n  objSchema = objSchema.invalid(x, x);\n  objSchema = objSchema.invalid(...[x, x, x]);\n  objSchema = objSchema.disallow(x);\n  objSchema = objSchema.disallow(x, x);\n  objSchema = objSchema.disallow(...[x, x, x]);\n  objSchema = objSchema.not(x);\n  objSchema = objSchema.not(x, x);\n  objSchema = objSchema.not(...[x, x, x]);\n\n  objSchema = objSchema.default(x);\n\n  objSchema = objSchema.required();\n  objSchema = objSchema.optional();\n  objSchema = objSchema.forbidden();\n\n  objSchema = objSchema.description(str);\n  objSchema = objSchema.note(str);\n  objSchema = objSchema.note(str).note(str);\n  objSchema = objSchema.tag(str);\n  objSchema = objSchema.tag(str).tag(str);\n\n  objSchema = objSchema.meta(obj);\n  objSchema = objSchema.example(obj);\n  objSchema = objSchema.unit(str);\n\n  objSchema = objSchema.preferences(validOpts);\n  objSchema = objSchema.strict();\n  objSchema = objSchema.concat(Joi.object());\n\n  objSchema = objSchema.when(str, whenOpts);\n  objSchema = objSchema.when(ref, whenOpts);\n  objSchema = objSchema.when(schema, whenSchemaOpts);\n}\n\n// --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- ---\n\nstrSchema = Joi.string();\n\nstrSchema = strSchema.insensitive();\nstrSchema = strSchema.min(num);\nstrSchema = strSchema.min(num, 'base64');\nstrSchema = strSchema.min(ref);\nstrSchema = strSchema.min(ref, 'base64');\nstrSchema = strSchema.max(num);\nstrSchema = strSchema.max(num, 'base64');\nstrSchema = strSchema.max(ref);\nstrSchema = strSchema.max(ref, 'base64');\nstrSchema = strSchema.creditCard();\nstrSchema = strSchema.length(num);\nstrSchema = strSchema.length(num, 'base64');\nstrSchema = strSchema.length(ref);\nstrSchema = strSchema.length(ref, 'base64');\nstrSchema = strSchema.pattern(exp);\nstrSchema = strSchema.pattern(exp, str);\nstrSchema = strSchema.pattern(exp, stringRegexOpts);\nstrSchema = strSchema.regex(exp);\nstrSchema = strSchema.regex(exp, str);\nstrSchema = strSchema.regex(exp, stringRegexOpts);\nstrSchema = strSchema.replace(exp, str);\nstrSchema = strSchema.replace(str, str);\nstrSchema = strSchema.alphanum();\nstrSchema = strSchema.token();\nstrSchema = strSchema.email();\nstrSchema = strSchema.email(emailOpts);\nstrSchema = strSchema.domain();\nstrSchema = strSchema.domain(domainOpts);\nstrSchema = strSchema.ip();\nstrSchema = strSchema.ip(ipOpts);\nstrSchema = strSchema.uri();\nstrSchema = strSchema.uri(uriOpts);\nstrSchema = strSchema.guid();\nstrSchema = strSchema.guid({\n  version: ['uuidv1', 'uuidv2', 'uuidv3', 'uuidv4', 'uuidv5', 'uuidv6', 'uuidv7', 'uuidv8'],\n});\nstrSchema = strSchema.guid({ version: 'uuidv4' });\nstrSchema = strSchema.uuid();\nstrSchema = strSchema.uuid({\n  version: ['uuidv1', 'uuidv2', 'uuidv3', 'uuidv4', 'uuidv5', 'uuidv6', 'uuidv7', 'uuidv8'],\n});\nstrSchema = strSchema.uuid({ version: 'uuidv4' });\nstrSchema = strSchema.hex();\nstrSchema = strSchema.hex(hexOpts);\nstrSchema = strSchema.hostname();\nstrSchema = strSchema.isoDate();\nstrSchema = strSchema.lowercase();\nstrSchema = strSchema.uppercase();\nstrSchema = strSchema.trim();\nstrSchema = strSchema.truncate();\nstrSchema = strSchema.truncate(false);\nstrSchema = strSchema.normalize();\nstrSchema = strSchema.normalize('NFKC');\nstrSchema = strSchema.base64();\nstrSchema = strSchema.base64(base64Opts);\nstrSchema = strSchema.dataUri();\nstrSchema = strSchema.dataUri(dataUriOpts);\n\n{\n  // common\n  strSchema = strSchema.allow(x);\n  strSchema = strSchema.allow(x, x);\n  strSchema = strSchema.allow(...[x, x, x]);\n  strSchema = strSchema.valid(x);\n  strSchema = strSchema.valid(x, x);\n  strSchema = strSchema.valid(...[x, x, x]);\n  strSchema = strSchema.only();\n  strSchema = strSchema.equal(x);\n  strSchema = strSchema.equal(x, x);\n  strSchema = strSchema.equal(...[x, x, x]);\n  strSchema = Joi.string().invalid(x);\n  strSchema = strSchema.invalid(x, x);\n  strSchema = strSchema.invalid(...[x, x, x]);\n  strSchema = strSchema.disallow(x);\n  strSchema = strSchema.disallow(x, x);\n  strSchema = strSchema.disallow(...[x, x, x]);\n  strSchema = strSchema.not(x);\n  strSchema = strSchema.not(x, x);\n  strSchema = strSchema.not(...[x, x, x]);\n\n  strSchema = strSchema.default(x);\n\n  strSchema = strSchema.required();\n  strSchema = strSchema.optional();\n  strSchema = strSchema.forbidden();\n\n  strSchema = strSchema.description(str);\n  strSchema = strSchema.note(str);\n  strSchema = strSchema.note(str).note(str);\n  strSchema = strSchema.tag(str);\n  strSchema = strSchema.tag(str).tag(str);\n\n  strSchema = strSchema.meta(obj);\n  strSchema = strSchema.example(obj);\n  strSchema = strSchema.unit(str);\n\n  strSchema = strSchema.preferences(validOpts);\n  strSchema = strSchema.strict();\n  strSchema = strSchema.concat(Joi.string());\n\n  strSchema = strSchema.when(str, whenOpts);\n  strSchema = strSchema.when(ref, whenOpts);\n  strSchema = strSchema.when(schema, whenSchemaOpts);\n}\n\n// --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- ---\n\n{\n  const custom: Joi.CustomValidator<number> = (value, helpers) => {\n    expect.type<number>(value);\n    expect.type<Joi.Schema>(helpers.schema);\n    expect.type<Joi.State>(helpers.state);\n    expect.type<Joi.ValidationOptions>(helpers.prefs);\n    expect.type<number>(helpers.original);\n    expect.type<Function>(helpers.warn);\n    expect.type<Function>(helpers.error);\n    expect.type<Function>(helpers.message);\n    return 1;\n  };\n}\n\n{\n  const external: Joi.ExternalValidationFunction<number> = (value, helpers) => {\n    expect.type<number>(value);\n    expect.type<Joi.Schema>(helpers.schema);\n    expect.type<Joi.Schema | null>(helpers.linked);\n    expect.type<Joi.State>(helpers.state);\n    expect.type<Joi.ValidationOptions>(helpers.prefs);\n    expect.type<number>(helpers.original);\n    expect.type<Function>(helpers.warn);\n    expect.type<Function>(helpers.error);\n    expect.type<Function>(helpers.message);\n    return 1;\n  };\n}\n\n// --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- ---\n\nJoi.checkPreferences(validOpts);\n\n// --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- ---\n\nlet expr;\n\nexpr = Joi.expression('{{foo}}');\nexpr = Joi.expression('{{foo}}', { adjust: (value) => value });\nexpr = Joi.expression('{{foo}}', { ancestor: 3 });\nexpr = Joi.expression('{{foo}}', { in: true });\nexpr = Joi.expression('{{foo}}', { iterables: true });\nexpr = Joi.expression('{{foo}}', { map: [['key', 'value']] });\nexpr = Joi.expression('{{foo}}', { prefix: { local: '%' } });\nexpr = Joi.expression('{{foo}}', { separator: '_' });\n\nexpr = Joi.x('{{foo}}');\nexpr = Joi.x('{{foo}}', { adjust: (value) => value });\nexpr = Joi.x('{{foo}}', { ancestor: 3 });\nexpr = Joi.x('{{foo}}', { in: true });\nexpr = Joi.x('{{foo}}', { iterables: true });\nexpr = Joi.x('{{foo}}', { map: [['key', 'value']] });\nexpr = Joi.x('{{foo}}', { prefix: { local: '%' } });\nexpr = Joi.x('{{foo}}', { separator: '_' });\n\n// --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- ---\n\nconst { string, object } = Joi.types();\n\n// --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- ---\n\nschema = Joi.alternatives();\nexpect.error(Joi.alternatives().try(schemaArr));\nschema = Joi.alternatives().try(schema, schema);\n\nexpect.type<Joi.AlternativesSchema<string | number | boolean>>(Joi.alternatives([Joi.string(), Joi.number(), Joi.boolean()]));\n\nschema = Joi.alternatives(schemaArr);\nschema = Joi.alternatives(schema, anySchema, boolSchema);\n\nschema = Joi.alt();\nexpect.error(Joi.alt().try(schemaArr));\nschema = Joi.alt().try(schema, schema);\n\nexpect.type<Joi.AlternativesSchema<string | number | boolean>>(Joi.alt([Joi.string(), Joi.number(), Joi.boolean()]));\n\nschema = Joi.alt(schemaArr);\nschema = Joi.alt(schema, anySchema, boolSchema);\n\nexpect.type<Joi.AlternativesSchema<string | number | boolean>>(Joi.alternatives().try(Joi.string(), Joi.number(), Joi.boolean()));\n\n\n// --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- ---\n\nschema = Joi.link(str);\n\n// --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- ---\n\n{\n  // validate tests\n  {\n    let value = { username: 'example', password: 'example' };\n    type TResult = { username: string; password: string };\n    const schema = Joi.object<TResult>().keys({\n      username: Joi.string().max(255).required(),\n      password: Joi.string()\n        .pattern(/^[a-zA-Z0-9]{3,255}$/)\n        .required(),\n    });\n    let result: Joi.ValidationResult;\n    let asyncResult: Promise<TResult>;\n\n    result = schema.validate(value);\n    if (result.error) {\n      throw Error('error should not be set');\n    } else {\n      expect.type<TResult>(result.value);\n    }\n    result = schema.validate(value, validOpts);\n    asyncResult = schema.validateAsync(value);\n    asyncResult = schema.validateAsync(value, validOpts);\n\n    asyncResult\n      .then((val) => JSON.stringify(val, null, 2))\n      .then((val) => {\n        throw new Error('one error');\n      })\n      .catch((e) => {\n      });\n\n    expect.type<Promise<TResult>>(schema.validateAsync(value));\n    expect.type<Promise<{\n      value: TResult,\n      artifacts: Map<any, string[][]>\n    }>>(schema.validateAsync(value, { artifacts: true }));\n    expect.type<Promise<{\n      value: TResult,\n      warning: Joi.ValidationWarning\n    }>>(schema.validateAsync(value, { warnings: true }));\n    expect.type<Promise<{\n      value: TResult,\n      artifacts: Map<any, string[][]>;\n      warning: Joi.ValidationWarning\n    }>>(schema.validateAsync(value, { artifacts: true, warnings: true }));\n    expect.error<Promise<{\n      value: TResult,\n      warning: Joi.ValidationWarning\n    }>>(schema.validateAsync(value, { artifacts: true }));\n    expect.error<Promise<{\n      value: TResult,\n      artifacts: Map<any, string[][]>\n    }>>(schema.validateAsync(value, { warnings: true }));\n    expect.error<Promise<TResult>>(schema.validateAsync(value, {\n      artifacts: true,\n      warnings: true\n    }));\n    expect.type<Promise<TResult>>(schema.validateAsync(value, { artifacts: false }));\n    expect.type<Promise<TResult>>(schema.validateAsync(value, { warnings: false }));\n    expect.type<Promise<TResult>>(schema.validateAsync(value, {\n      artifacts: false,\n      warnings: false\n    }));\n\n    const falsyValue = { username: 'example' };\n    result = schema.validate(falsyValue);\n    if (!result.error) {\n      throw Error('error should be set');\n    }\n  }\n}\n\n// --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- ---\n\nschema = Joi.compile(obj);\nschema = Joi.compile(schemaMap);\n\nJoi.assert(obj, schema);\nJoi.assert(obj, schema, str);\nJoi.assert(obj, schema, str, validOpts);\nJoi.assert(obj, schema, err);\nJoi.assert(obj, schema, err, validOpts);\nJoi.assert(obj, schema, validOpts);\nexpect.error(Joi.assert(obj, schemaLike));\n\n{\n  let value = { username: 'example', password: 'example' };\n  type TResult = { username: string; password: string };\n  let typedSchema = schema as Joi.ObjectSchema<TResult>;\n  value = Joi.attempt(obj, typedSchema);\n  value = Joi.attempt(obj, typedSchema, str);\n  value = Joi.attempt(obj, typedSchema, str, validOpts);\n  value = Joi.attempt(obj, typedSchema, err);\n  value = Joi.attempt(obj, typedSchema, err, validOpts);\n  value = Joi.attempt(obj, typedSchema, validOpts);\n  expect.type<TResult>(Joi.attempt(obj, typedSchema));\n  expect.error<string>(Joi.attempt(obj, typedSchema));\n}\n\nexpect.type<number[]>(Joi.attempt(numArr, Joi.array()));\nexpect.type<boolean>(Joi.attempt(bool, Joi.bool()));\nexpect.type<Buffer>(Joi.attempt(buf, Joi.binary()));\nexpect.type<Date>(Joi.attempt(date, Joi.date()));\nexpect.type<Function>(Joi.attempt(func, Joi.func()));\nexpect.type<number>(Joi.attempt(num, Joi.number()));\nexpect.type<string>(Joi.attempt(str, Joi.string()));\nexpect.type<Symbol>(Joi.attempt(symbol, Joi.symbol()));\n\nexpect.error(Joi.attempt(obj, schemaLike));\n\nref = Joi.ref(str, refOpts);\nref = Joi.ref(str);\n\nJoi.isExpression(expr);\nJoi.isRef(ref);\nJoi.isSchema(schema);\n\ndescription = schema.describe();\n\nconst Joi2 = Joi.extend({ type: 'test1', base: schema });\n\nconst Joi3 = Joi.extend({\n  type: 'string',\n  base: Joi.string(),\n  messages: {\n    asd: 'must be exactly asd(f)',\n  },\n  coerce(schema, value) {\n    return { value };\n  },\n  rules: {\n    asd: {\n      args: [\n        {\n          name: 'allowFalse',\n          ref: true,\n          assert: Joi.boolean(),\n        },\n      ],\n      method(allowFalse: boolean) {\n        return this.$_addRule({\n          name: 'asd',\n          args: {\n            allowFalse,\n          },\n        });\n      },\n      validate(value: boolean, helpers, params, options) {\n        if (value || (params.allowFalse && !value)) {\n          return value;\n        }\n\n        return helpers.error('asd', { v: value }, options);\n      },\n    },\n  },\n});\n\nconst Joi4 = Joi.extend(\n  { type: 'test4', base: schema },\n  { type: 'test4a', base: schema }\n);\n\nconst Joi5 = Joi.extend(\n  { type: 'test5', base: schema },\n  { type: 'test5a', base: schema },\n  { type: 'test5b', base: schema }\n);\n\n// --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- ---\n\nconst defaultsJoi = Joi.defaults((schema) => {\n  switch (schema.type) {\n    case 'string':\n      return schema.allow('');\n    case 'object':\n      return (schema as Joi.ObjectSchema).min(1);\n    default:\n      return schema;\n  }\n});\n\nschema = Joi.allow(x, x);\nschema = Joi.allow(...[x, x, x]);\nschema = Joi.valid(x);\nschema = Joi.valid(x, x);\nschema = Joi.valid(...[x, x, x]);\nschema = Joi.equal(x);\nschema = Joi.equal(x, x);\nschema = Joi.equal(...[x, x, x]);\nschema = Joi.invalid(x);\nschema = Joi.invalid(x, x);\nschema = Joi.invalid(...[x, x, x]);\nschema = Joi.disallow(x);\nschema = Joi.disallow(x, x);\nschema = Joi.disallow(...[x, x, x]);\nschema = Joi.not(x);\nschema = Joi.not(x, x);\nschema = Joi.not(...[x, x, x]);\n\nschema = Joi.required();\nschema = Joi.optional();\nschema = Joi.forbidden();\n\nschema = Joi.preferences(validOpts);\n\n// --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- ---\n\nschema = Joi.allow(x, x);\nschema = Joi.allow(...[x, x, x]);\nschema = Joi.valid(x);\nschema = Joi.valid(x, x);\nschema = Joi.valid(...[x, x, x]);\nschema = Joi.equal(x);\nschema = Joi.equal(x, x);\nschema = Joi.equal(...[x, x, x]);\nschema = Joi.invalid(x);\nschema = Joi.invalid(x, x);\nschema = Joi.invalid(...[x, x, x]);\nschema = Joi.disallow(x);\nschema = Joi.disallow(x, x);\nschema = Joi.disallow(...[x, x, x]);\nschema = Joi.not(x);\nschema = Joi.not(x, x);\nschema = Joi.not(...[x, x, x]);\n\nschema = Joi.required();\nschema = Joi.exist();\nschema = Joi.optional();\nschema = Joi.forbidden();\n\nschema = Joi.preferences(validOpts);\n\nschema = Joi.when(str, whenOpts);\nschema = Joi.when(ref, whenOpts);\nschema = Joi.when(schema, whenSchemaOpts);\n\nref = Joi.in(str);\nref = Joi.in(str, refOpts);\n\nschema = Joi.symbol();\nschema = Joi.symbol().map(new Map<string, symbol>());\nschema = Joi.symbol().map({\n  key: Symbol('asd'),\n});\n\n// --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- ---\n\nconst rule = Joi.string().case('upper').$_getRule('case');\nif (rule && rule.args) {\n  const direction = rule.args.direction;\n}\n\n// --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- ---\n\nschema = Joi.any();\nconst terms = schema.$_terms;\n\n// --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- ---\n// Test Joi.object, Joi.append and Joi.extends (with `any` type)\n\n// should be able to append any new properties\nlet anyObject = Joi.object({\n  name: Joi.string().required(),\n  family: Joi.string(),\n});\n\nanyObject = anyObject\n  .append({\n    age: Joi.number(),\n  })\n  .append({\n    height: Joi.number(),\n  });\n\nanyObject = anyObject.keys({\n  length: Joi.string(),\n});\n\n// test with keys\nJoi.object()\n  .keys({\n    name: Joi.string().required(),\n    family: Joi.string(),\n  })\n  .append({\n    age: Joi.number(),\n  })\n  .append({\n    height: Joi.number(),\n  })\n  .keys({\n    length: Joi.string(),\n  });\n\n// --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- ---\n// Test generic types\n\ninterface User {\n  name: string;\n  family?: string;\n  age: number;\n}\n\nconst userSchemaObject = Joi.object<User>({\n  name: Joi.string().required(),\n  family: Joi.string(),\n});\n\nlet userSchema = Joi.object<User>().keys({\n  name: Joi.string().required(),\n  family: Joi.string(),\n});\n\nuserSchema = userSchema.append({\n  age: Joi.number(),\n});\n\nuserSchema.append({ height: Joi.number() });\n\nconst userSchema2 = Joi.object<User>()\n  .keys({\n    name: Joi.string().required(),\n  })\n  .keys({\n    family: Joi.string(),\n  });\n\ninterface Comment {\n  text: string;\n  user: User;\n  isNew: boolean;\n}\n\nconst commentSchemaObject = Joi.object<Comment, true>({\n  text: Joi.string().required(),\n  user: userSchemaObject,\n  isNew: Joi.boolean().required(),\n});\n\ninterface Comment2 {\n  text: string;\n  user?: {\n    name: string;\n  };\n}\n\nconst commentSchemaObject2 = Joi.object<Comment2, true>({\n  text: Joi.string().required(),\n  user: {\n    name: Joi.string().required(),\n  },\n});\n\ninterface CommentWithAlternatives {\n  text: string;\n  user: string | User;\n  type: 'topLevel' | 'pingback' | 'reply';\n  reported: boolean | number;\n}\n\nconst commentWithAlternativesSchemaObject = Joi.object<\n  CommentWithAlternatives,\n  true\n>({\n  text: Joi.string().required(),\n  user: Joi.alternatives(Joi.string(), userSchemaObject),\n  type: Joi.string().required().valid('topLevel', 'pingback', 'reply'),\n  reported: Joi.alternatives(Joi.boolean(), Joi.number()),\n});\n\nexpect.error(userSchema2.keys({ height: Joi.number() }));\n\nexpect.error(Joi.string('x'));\n\n// --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- ---\n// Test Standard Schema Types\n{\n  Joi.any()['~standard'].version\n  Joi.any()['~standard'].vendor\n\n  {\n    // Standard Validate\n    let value = { username: 'example', password: 'example' };\n    type TResult = { username: string; password: string };\n    const schema = Joi.object<TResult>().keys({\n      username: Joi.string().max(255).required(),\n      password: Joi.string()\n          .pattern(/^[a-zA-Z0-9]{3,255}$/)\n          .required(),\n    });\n    let result: StandardSchemaV1.Result<TResult> | Promise<StandardSchemaV1.Result<TResult>>;\n\n    result = schema['~standard'].validate(value);\n    if (result instanceof Promise) {\n      throw Error(\"Expected sync result\");\n    }\n\n    if (result.issues) {\n      throw Error('issues should not be set')\n    }\n    expect.type<TResult>(result.value)\n\n    const falsyValue = { username: 'example' };\n    result = schema['~standard'].validate(falsyValue);\n    if (result instanceof Promise) {\n      throw new Error(\"Expected sync result\");\n    }\n\n    if (!result.issues) {\n      throw Error('issues should be set')\n    }\n    expect.error(result.value)\n  }\n}\n"
  },
  {
    "path": "test/isAsync.js",
    "content": "'use strict';\n\nconst Code = require('@hapi/code');\nconst Lab = require('@hapi/lab');\nconst Joi = require('..');\n\nconst { describe, it } = exports.lab = Lab.script();\nconst { expect } = Code;\n\ndescribe('isAsync()', () => {\n\n    it('returns false for schemas without async rules', () => {\n\n        expect(Joi.alternatives().isAsync()).to.be.false();\n        expect(Joi.array().isAsync()).to.be.false();\n        expect(Joi.boolean().isAsync()).to.be.false();\n        expect(Joi.date().isAsync()).to.be.false();\n        expect(Joi.number().isAsync()).to.be.false();\n        expect(Joi.object().isAsync()).to.be.false();\n        expect(Joi.string().isAsync()).to.be.false();\n    });\n\n    it('returns true for schemas with external rules', () => {\n\n        expect(Joi.string().external(() => {}).isAsync()).to.be.true();\n        expect(Joi.number().external(() => {}).isAsync()).to.be.true();\n        expect(Joi.boolean().external(() => {}).isAsync()).to.be.true();\n        expect(Joi.date().external(() => {}).isAsync()).to.be.true();\n        expect(Joi.object().external(() => {}).isAsync()).to.be.true();\n        expect(Joi.array().external(() => {}).isAsync()).to.be.true();\n        expect(Joi.alternatives().external(() => {}).isAsync()).to.be.true();\n    });\n\n    it('returns true for objects with async child schemas', () => {\n\n        expect(Joi.object({ a: Joi.string().external(() => {}) }).isAsync()).to.be.true();\n        expect(Joi.object({ a: Joi.string() }).isAsync()).to.be.false();\n        expect(Joi.object().pattern(/a/, Joi.string().external(() => {})).isAsync()).to.be.true();\n        expect(Joi.object().pattern(/a/, Joi.string()).isAsync()).to.be.false();\n        expect(Joi.object().pattern(Joi.string(), { a: Joi.string().external(() => {}) }).isAsync()).to.be.true();\n        expect(Joi.object().pattern(Joi.string(), { a: Joi.string() }).isAsync()).to.be.false();\n    });\n\n    it('returns true for arrays with async items', () => {\n\n        expect(Joi.array().items(Joi.string().external(() => {})).isAsync()).to.be.true();\n        expect(Joi.array().items(Joi.string()).isAsync()).to.be.false();\n        expect(Joi.array().ordered(Joi.string().external(() => {})).isAsync()).to.be.true();\n        expect(Joi.array().ordered(Joi.string()).isAsync()).to.be.false();\n    });\n\n    it('returns true for alternatives with async schemas', () => {\n\n        expect(Joi.alternatives().try(Joi.string().external(() => {})).isAsync()).to.be.true();\n        expect(Joi.alternatives().conditional('a', { is: 'b', then: Joi.string().external(() => {}) }).isAsync()).to.be.true();\n        expect(Joi.alternatives().conditional('a', { is: 'b', otherwise: Joi.string().external(() => {}) }).isAsync()).to.be.true();\n        expect(Joi.alternatives().conditional('a', {\n            is: 'b',\n            otherwise: Joi.string().external(() => {})\n        }).isAsync()).to.be.true();\n        expect(Joi.alternatives().conditional('a', {\n            is: 'b',\n            then: Joi.string(),\n            otherwise: Joi.number()\n        }).isAsync()).to.be.false();\n        expect(Joi.any().when('a', {\n            is: 'b',\n            then: Joi.string().external(() => {})\n        }).isAsync()).to.be.true();\n        expect(Joi.any().when('a', {\n            is: 'b',\n            otherwise: Joi.string().external(() => {})\n        }).isAsync()).to.be.true();\n        expect(Joi.any().when('a', {\n            is: 'b',\n            otherwise: Joi.string()\n        }).isAsync()).to.be.false();\n        expect(Joi.any().when('a', {\n            switch: [\n                { is: 'b', then: Joi.string() },\n                { is: 'c', then: Joi.number().external(() => {}), otherwise: Joi.string() }\n            ]\n        }).isAsync()).to.be.true();\n        expect(Joi.any().when('a', {\n            switch: [\n                { is: 'b', then: Joi.string() },\n                { is: 'c', then: Joi.number(), otherwise: Joi.string() }\n            ]\n        }).isAsync()).to.be.false();\n        expect(Joi.any().when('a', {\n            switch: [\n                { is: 'b', then: Joi.string() },\n                { is: 'c', then: Joi.string(), otherwise: Joi.number().external(() => {}) }\n            ]\n        }).isAsync()).to.be.true();\n        expect(Joi.any().when('a', {\n            switch: [\n                { is: 'b', then: Joi.string() },\n                { is: 'c', then: Joi.string(), otherwise: Joi.number() }\n            ]\n        }).isAsync()).to.be.false();\n    });\n});\n"
  },
  {
    "path": "test/manifest.js",
    "content": "'use strict';\n\nconst Code = require('@hapi/code');\nconst Lab = require('@hapi/lab');\nconst Joi = require('..');\n\nconst Helper = require('./helper');\n\nconst internals = {};\n\n\nconst { describe, it } = exports.lab = Lab.script();\nconst { expect } = Code;\n\n\ndescribe('Manifest', () => {\n\n    describe('describe()', () => {\n\n        it('describes schema (direct)', () => {\n\n            const defaultFn = function () {\n\n                return 'test';\n            };\n\n            defaultFn.description = 'testing';\n\n            const defaultDescribedFn = function () {\n\n                return 'test';\n            };\n\n            const defaultRef = Joi.ref('xor');\n\n            const schema = Joi.object({\n                sub: {\n                    email: Joi.string().email(),\n                    domain: Joi.string().domain(),\n                    date: Joi.date(),\n                    child: Joi.object({\n                        alphanum: Joi.string().alphanum()\n                    })\n                },\n                min: [Joi.number(), Joi.string().min(3)],\n                max: Joi.string().max(3).default(0).failover(1),\n                required: Joi.string().required(),\n                xor: Joi.string(),\n                renamed: Joi.string().valid('456'),\n                notEmpty: Joi.string().required().description('a').note('b').tag('c'),\n                empty: Joi.string().empty('').strip(),\n                defaultRef: Joi.string().default(defaultRef),\n                defaultFn: Joi.string().default(defaultFn),\n                defaultDescribedFn: Joi.string().default(defaultDescribedFn),\n                defaultArray: Joi.array().default(['x'])\n            })\n                .prefs({ abortEarly: false, convert: false })\n                .rename('renamed', 'required')\n                .without('required', 'xor')\n                .without('xor', 'required')\n                .allow({ a: 'x' })\n                .meta({ x: 1 });\n\n            const result = {\n                type: 'object',\n                allow: [{ value: { a: 'x' } }],\n                keys: {\n                    sub: {\n                        type: 'object',\n                        keys: {\n                            email: {\n                                type: 'string',\n                                rules: [{ name: 'email' }]\n                            },\n                            domain: {\n                                type: 'string',\n                                rules: [{ name: 'domain' }]\n                            },\n                            date: {\n                                type: 'date'\n                            },\n                            child: {\n                                type: 'object',\n                                keys: {\n                                    alphanum: {\n                                        type: 'string',\n                                        rules: [{ name: 'alphanum' }]\n                                    }\n                                }\n                            }\n                        }\n                    },\n                    min: {\n                        type: 'alternatives',\n                        matches: [\n                            {\n                                schema: {\n                                    type: 'number'\n                                }\n                            },\n                            {\n                                schema: {\n                                    type: 'string',\n                                    rules: [{ name: 'min', args: { limit: 3 } }]\n                                }\n                            }\n                        ]\n                    },\n                    max: {\n                        type: 'string',\n                        flags: {\n                            default: 0,\n                            failover: 1\n                        },\n                        rules: [{ name: 'max', args: { limit: 3 } }]\n                    },\n                    required: {\n                        type: 'string',\n                        flags: {\n                            presence: 'required'\n                        }\n                    },\n                    xor: {\n                        type: 'string'\n                    },\n                    renamed: {\n                        type: 'string',\n                        flags: {\n                            only: true\n                        },\n                        allow: ['456']\n                    },\n                    notEmpty: {\n                        type: 'string',\n                        flags: {\n                            description: 'a',\n                            presence: 'required'\n                        },\n                        notes: ['b'],\n                        tags: ['c']\n                    },\n                    empty: {\n                        type: 'string',\n                        flags: {\n                            empty: {\n                                type: 'any',\n                                flags: {\n                                    only: true\n                                },\n                                allow: ['']\n                            },\n                            result: 'strip'\n                        }\n                    },\n                    defaultRef: {\n                        type: 'string',\n                        flags: {\n                            default: {\n                                ref: { path: ['xor'] }\n                            }\n                        }\n                    },\n                    defaultFn: {\n                        type: 'string',\n                        flags: {\n                            default: defaultFn\n                        }\n                    },\n                    defaultDescribedFn: {\n                        type: 'string',\n                        flags: {\n                            default: defaultDescribedFn\n                        }\n                    },\n                    defaultArray: {\n                        type: 'array',\n                        flags: {\n                            default: ['x']\n                        }\n                    }\n                },\n                dependencies: [\n                    {\n                        rel: 'without',\n                        key: 'required',\n                        peers: ['xor']\n                    },\n                    {\n                        rel: 'without',\n                        key: 'xor',\n                        peers: ['required']\n                    }\n                ],\n                renames: [\n                    {\n                        from: 'renamed',\n                        to: 'required',\n                        options: {\n                            alias: false,\n                            multiple: false,\n                            override: false\n                        }\n                    }\n                ],\n                preferences: {\n                    abortEarly: false,\n                    convert: false\n                },\n                metas: [{ x: 1 }]\n            };\n\n            const description = schema.describe();\n            expect(description).to.equal(result);\n            expect(description.keys.defaultRef.flags.default).to.equal({ ref: { path: ['xor'] } });\n        });\n\n        it('describes schema without invalids', () => {\n\n            const description = Joi.allow(null).describe();\n            expect(description.invalids).to.not.exist();\n        });\n\n        it('describes value map', () => {\n\n            const symbols = [Symbol(1), Symbol(2)];\n            const map = new Map([[1, symbols[0]], ['two', symbols[1]]]);\n            const schema = Joi.symbol().map(map).describe();\n            expect(schema).to.equal({\n                type: 'symbol',\n                flags: {\n                    only: true\n                },\n                map: [...map.entries()],\n                allow: symbols\n            });\n        });\n\n        it('describes symbol without map', () => {\n\n            const symbols = [Symbol(1), Symbol(2)];\n            const schema = Joi.symbol().valid(...symbols).describe();\n            expect(schema).to.equal({\n                type: 'symbol',\n                flags: {\n                    only: true\n                },\n                allow: symbols\n            });\n        });\n\n        it('handles empty values', () => {\n\n            expect(Joi.allow(1).invalid(1).describe()).to.equal({ type: 'any', invalid: [1] });\n            expect(Joi.invalid(1).allow(1).describe()).to.equal({ type: 'any', allow: [1] });\n        });\n\n        it('describes ruleset changes', () => {\n\n            const schema = Joi.string().min(1).keep();\n            expect(schema.describe()).to.equal({\n                type: 'string',\n                rules: [\n                    {\n                        name: 'min',\n                        keep: true,\n                        args: { limit: 1 }\n                    }\n                ]\n            });\n        });\n\n        it('describes defaults', () => {\n\n            const schema = Joi.object({\n                foo: Joi.string().default('bar')\n            })\n                .default({ foo: 'bar' });\n\n            expect(schema.describe()).to.equal({\n                type: 'object',\n                flags: { default: { foo: 'bar' } },\n                keys: { foo: { type: 'string', flags: { default: 'bar' } } }\n            });\n        });\n\n        it('describes null defaults', () => {\n\n            const description = Joi.any().allow(null).default(null).describe();\n            expect(description.invalids).to.not.exist();\n        });\n    });\n\n    describe('build()', () => {\n\n        it('builds basic schemas', () => {\n\n            internals.test([\n                Joi.any(),\n                Joi.array(),\n                Joi.binary(),\n                Joi.boolean(),\n                Joi.date(),\n                Joi.function(),\n                Joi.number(),\n                Joi.object(),\n                Joi.string(),\n                Joi.symbol()\n            ]);\n        });\n\n        it('sets flags', () => {\n\n            internals.test([\n                Joi.string().required(),\n                Joi.function().default(() => null, { literal: true }),\n                Joi.object().default(),\n                Joi.array().default(['x']),\n                Joi.boolean().optional(),\n                Joi.string().empty(''),\n                Joi.binary().strip(),\n                Joi.alternatives().raw(),\n                Joi.any().result('raw')\n            ]);\n        });\n\n        it('sets preferences', () => {\n\n            internals.test([\n                Joi.object().prefs({ abortEarly: true }),\n                Joi.string().min(10).prefs({ messages: { 'string.min': Joi.x('{$x}') } }),\n                Joi.string().min(10).prefs({ messages: { 'string.min': Joi.x('{@x}', { prefix: { context: '@' } }) } })\n            ]);\n        });\n\n        it('sets allow and invalid', () => {\n\n            internals.test([\n                Joi.string().allow(1, 2, 3),\n                Joi.string().valid(Joi.ref('$x')),\n                Joi.number().invalid(1),\n                Joi.object().allow({ x: 1 }),\n                Joi.allow(null),\n                Joi.string().empty('').allow(null)\n            ]);\n        });\n\n        it('sets rules', () => {\n\n            internals.test([\n                Joi.string().lowercase(),\n                Joi.string().alphanum(),\n                Joi.string().min(10),\n                Joi.string().length(10, 'binary')\n            ]);\n        });\n\n        it('sets ruleset options', () => {\n\n            internals.test([\n                Joi.string().min(1).keep(),\n                Joi.string().$.min(1).max(2).rule({ message: 'override' }),\n                Joi.string().$.min(1).max(2).rule({ message: Joi.x('{$x}') })\n            ]);\n        });\n\n        it('sets any terms', () => {\n\n            internals.test([\n                Joi.string().example('text').tag('a').note('ok then').meta(123),\n                Joi.binary().external((v) => v, 'custom'),\n                Joi.number().alter({ x: (s) => s.min(1) })\n            ]);\n        });\n\n        it('builds alternatives', () => {\n\n            internals.test([\n                Joi.alternatives().try(Joi.boolean()),\n                Joi.alternatives(Joi.boolean(), Joi.object({ p: Joi.number() })),\n                Joi.alternatives(Joi.object()).error(new Error())\n            ]);\n        });\n\n        it('builds whens', () => {\n\n            internals.test([\n                Joi.number().when('$x', { is: true, then: Joi.required(), otherwise: Joi.forbidden() }),\n                Joi.number().when(Joi.valid('x'), { then: Joi.required(), otherwise: Joi.forbidden() }),\n                Joi.when('$x', { is: true, then: Joi.string() }),\n                Joi.number().when('a', { switch: [{ is: 0, then: Joi.valid(1) }], otherwise: Joi.valid(4) })\n            ]);\n        });\n\n        it('builds arrays', () => {\n\n            internals.test([\n                Joi.array().min(1).items(Joi.number()),\n                Joi.array().has(Joi.string()),\n                Joi.array().ordered(Joi.number(), Joi.boolean(), Joi.binary()),\n                Joi.array().items(Joi.number()).has(Joi.number()),\n                Joi.array().sparse().unique()\n            ]);\n        });\n\n        it('builds binaries', () => {\n\n            internals.test([\n                Joi.binary().default(Buffer.from('abcde')).allow(Buffer.from('123'))\n            ]);\n        });\n\n        it('builds booleans', () => {\n\n            internals.test([\n                Joi.boolean().truthy('x'),\n                Joi.boolean().falsy(Joi.ref('$x')),\n                Joi.boolean().truthy(3).falsy(4),\n                Joi.boolean().sensitive(),\n                Joi.boolean().sensitive(false)\n            ]);\n        });\n\n        it('builds dates', () => {\n\n            internals.test([\n                Joi.date().min('1-1-2000 UTC')\n            ]);\n        });\n\n        it('builds links', () => {\n\n            expect(() => Joi.build(Joi.link().describe())).to.throw('Invalid link description missing link');\n            internals.test([\n                Joi.link('....'),\n                Joi.link(Joi.ref('xxx....', { separator: 'x' })),\n                Joi.link('/'),\n                Joi.link('..a').relative()\n            ]);\n        });\n\n        it('builds objects', () => {\n\n            internals.test([\n                Joi.object({}),\n                Joi.object({ p: Joi.number() }),\n                Joi.object({ a: Joi.string(), b: Joi.number() }),\n                Joi.object().rename('a', 'b'),\n                Joi.object().rename(/a/, 'b'),\n                Joi.object().rename(/(a)/, Joi.x('x{#1}')),\n                Joi.object().and('a', 'b').or('c', 'd').without('e', 'f').xor('g.h', 'i', { separator: false }),\n                Joi.object().pattern(/x/, Joi.number()),\n                Joi.object().pattern(Joi.string(), Joi.number()),\n                Joi.object().pattern(/x/, Joi.number(), { matches: Joi.array().length(Joi.ref('$x')), fallthrough: true }),\n                Joi.object({ a: 1 }).concat(Joi.object({ a: 3 })),\n                Joi.object().instance(RegExp).default(/x/).allow({}).allow({ x: 1 }),\n                Joi.object({ regex: 'b' }),\n                Joi.object({ buffer: 'c' }),\n                Joi.object({ function: 'd' }),\n                Joi.object({ override: 'e' }),\n                Joi.object({ ref: 'f' }),\n                Joi.object({ special: 'g' }),\n                Joi.object({ value: 'h' }),\n                Joi.object({ type: 'a' }),\n                Joi.object({ type: 'a', regex: 'b', buffer: 'c', function: 'd', override: 'e', ref: 'f', special: 'g', value: 'f' })\n            ]);\n        });\n\n        it('builds strings', () => {\n\n            internals.test([\n                Joi.string().min(1).max(10).pattern(/\\d*/),\n                Joi.string().replace(/x/, 'X')\n            ]);\n        });\n\n        it('builds strings', () => {\n\n            internals.test([\n                Joi.string().min(1).max(10).pattern(/\\d*/),\n                Joi.string().replace(/x/, 'X')\n            ]);\n        });\n\n        it('builds symbols', () => {\n\n            internals.test([\n                Joi.symbol().map([['a', Symbol('a')]])\n            ]);\n        });\n\n        it('builds references', () => {\n\n            internals.test([\n                Joi.allow(Joi.ref('a'))\n            ]);\n        });\n\n        it('builds extended schema (nested builds)', () => {\n\n            const custom = Joi.extend({\n                type: 'fancy',\n                base: Joi.object({ a: Joi.number() }),\n                flags: {\n                    presence: {}                                // For coverage\n                },\n                terms: {\n                    fancy: { init: [] }\n                },\n                rules: {\n                    pants: {\n                        method(button) {\n\n                            this.$_terms.fancy.push(button);\n                            return this;\n                        }\n                    }\n                },\n                manifest: {\n\n                    build(obj, desc) {\n\n                        if (desc.fancy) {\n                            obj = obj.clone();\n                            obj.$_terms.fancy = desc.fancy.slice();\n                        }\n\n                        return obj;\n                    }\n                }\n            });\n\n            const schema = custom.fancy().pants('green').required();\n            const desc = schema.describe();\n\n            expect(desc).to.equal({\n                type: 'fancy',\n                flags: {\n                    presence: 'required'\n                },\n                keys: {\n                    a: { type: 'number' }\n                },\n                fancy: ['green']\n            });\n\n            const built = custom.build(desc);\n            Helper.equal(built, schema);\n        });\n\n        it('builds extended schema (complex)', () => {\n\n            const custom = Joi.extend({\n                type: 'million',\n                base: Joi.number(),\n                flags: {\n                    sizable: { setter: 'big' }\n                },\n                messages: {\n                    'million.base': '{{#label}} must be at least a million',\n                    'million.big': '{{#label}} must be at least five millions',\n                    'million.round': '{{#label}} must be a round number',\n                    'million.dividable': '{{#label}} must be dividable by {{#q}}'\n                },\n                coerce(value, { schema }) {\n\n                    // Only called when prefs.convert is true\n\n                    if (schema.$_getRule('round')) {\n                        return { value: Math.round(value) };\n                    }\n                },\n                validate(value, { schema, error }) {\n\n                    // Base validation regardless of the rules applied\n\n                    if (value < 1000000) {\n                        return { value, errors: error('million.base') };\n                    }\n\n                    // Check flags for global state\n\n                    if (schema.$_getFlag('sizable') &&\n                        value < 5000000) {\n\n                        return { value, errors: error('million.big') };\n                    }\n                },\n                rules: {\n                    big: {\n                        alias: 'large',\n                        method() {\n\n                            return this.$_setFlag('sizable', true);\n                        }\n                    },\n                    round: {\n                        convert: true,              // Dual rule: converts or validates\n                        method() {\n\n                            return this.$_addRule('round');\n                        },\n                        validate(value, helpers, args, options) {\n\n                            // Only called when prefs.convert is false (due to rule convert option)\n\n                            if (value % 1 !== 0) {\n                                return helpers.error('million.round');\n                            }\n                        }\n                    },\n                    dividable: {\n                        multi: true,                // Rule supports multiple invocations\n                        method(q) {\n\n                            return this.$_addRule({ name: 'dividable', args: { q } });\n                        },\n                        args: [\n                            {\n                                name: 'q',\n                                ref: true,\n                                assert: (value) => typeof value === 'number' && !isNaN(value),\n                                message: 'q must be a number or reference'\n                            }\n                        ],\n                        validate(value, helpers, args, options) {\n\n                            if (value % args.q === 0) {\n                                return value;       // Value is valid\n                            }\n\n                            return helpers.error('million.dividable', { q: args.q });\n                        }\n                    },\n                    even: {\n                        method() {\n\n                            // Rule with only method used to alias another rule\n\n                            return this.dividable(2);\n                        }\n                    }\n                }\n            });\n\n            const schema = custom.object({\n                a: custom.million().round().dividable(Joi.ref('b')),\n                b: custom.number(),\n                c: custom.million().even().dividable(7),\n                d: custom.million().round().prefs({ convert: false }),\n                e: custom.million().large()\n            });\n\n            const desc = schema.describe();\n            expect(desc).to.equal({\n                type: 'object',\n                keys: {\n                    b: {\n                        type: 'number'\n                    },\n                    a: {\n                        type: 'million',\n                        rules: [\n                            { name: 'round' },\n                            {\n                                name: 'dividable',\n                                args: { q: { ref: { path: ['b'] } } }\n                            }\n                        ]\n                    },\n                    c: {\n                        type: 'million',\n                        rules: [\n                            { name: 'dividable', args: { q: 2 } },\n                            { name: 'dividable', args: { q: 7 } }\n                        ]\n                    },\n                    d: {\n                        type: 'million',\n                        rules: [\n                            { name: 'round' }\n                        ],\n                        preferences: {\n                            convert: false\n                        }\n                    },\n                    e: {\n                        type: 'million',\n                        flags: {\n                            sizable: true\n                        }\n                    }\n                }\n            });\n\n            const built = custom.build(desc);\n            Helper.equal(built, schema);\n        });\n\n        it('builds extended schema (base with terms)', () => {\n\n            const custom = Joi.extend({\n                type: 'fancy',\n                base: Joi.array().items(Joi.string().required())\n            });\n\n            const schema = custom.fancy();\n            const desc = schema.describe();\n\n            expect(desc).to.equal({\n                type: 'fancy',\n                items: [\n                    {\n                        flags: { presence: 'required' },\n                        type: 'string'\n                    }\n                ]\n            });\n\n            const built = custom.build(desc);\n            Helper.equal(built, schema);\n\n            Helper.validate(built, [\n                [[1], false, '\"[0]\" must be a string'],\n                [['x'], true]\n            ]);\n        });\n    });\n});\n\n\ninternals.test = function (schemas) {\n\n    for (const schema of schemas) {\n        const built = Joi.build(schema.describe());\n        Helper.equal(built, schema);\n    }\n};\n"
  },
  {
    "path": "test/modify.js",
    "content": "'use strict';\n\nconst Code = require('@hapi/code');\nconst Lab = require('@hapi/lab');\nconst Joi = require('..');\n\nconst Helper = require('./helper');\n\n\nconst internals = {};\n\n\nconst { describe, it } = exports.lab = Lab.script();\nconst { expect } = Code;\n\n\ndescribe('Modify', () => {\n\n    describe('extract()', () => {\n\n        it('extracts nested schema with keys', () => {\n\n            const d = Joi.number();\n            const c = Joi.object({ d });\n            const b = Joi.object({ c });\n            const a = Joi.object({ b });\n\n            expect(a.extract('b')).to.shallow.equal(b);\n            expect(a.extract('b.c.d')).to.shallow.equal(d);\n            expect(a.extract(['b', 'c', 'd'])).to.shallow.equal(d);\n        });\n\n        it('extracts nested schema with ids', () => {\n\n            const d = Joi.number().id('D');\n            const c = Joi.object({ d }).id('C');\n            const b = Joi.object({ c }).id('B');\n            const a = Joi.object({ b });\n\n            expect(a.extract('B')).to.shallow.equal(b);\n            expect(a.extract('B.C.D')).to.shallow.equal(d);\n        });\n\n        it('extracts nested schema with ids by keys', () => {\n\n            const d = Joi.number().id('D');\n            const c = Joi.object({ d }).id('C');\n            const b = Joi.object({ c }).id('B');\n            const a = Joi.object({ b });\n\n            expect(a.extract('b')).to.shallow.equal(b);\n            expect(a.extract('b.c.d')).to.shallow.equal(d);\n        });\n\n        it('extracts nested schema with duplicate ids', () => {\n\n            const d = Joi.number().id('D');\n            const c = Joi.object({ d }).id('C');\n            const b = Joi.object({ c }).id('B');\n            const a = Joi.object({ b, x: b });\n\n            expect(a.extract('b')).to.shallow.equal(b);\n            expect(a.extract('x')).to.shallow.equal(b);\n            expect(a.extract('b.c.d')).to.shallow.equal(d);\n        });\n\n        it('extracts nested schema from array', () => {\n\n            const d = Joi.number().id('D');\n            const c = Joi.object({ d }).id('C');\n            const b = Joi.object({ c }).id('b');\n            const a = Joi.array().items(b);\n\n            expect(a.extract('b')).to.shallow.equal(b);\n            expect(a.extract('b.c.d')).to.shallow.equal(d);\n        });\n\n        it('extracts nested schema from alternatives', () => {\n\n            const d = Joi.number().id('D');\n            const c = Joi.object({ d }).id('C');\n            const b = Joi.object({ c }).id('b');\n            const a = Joi.alternatives(b);\n\n            expect(a.extract('b')).to.shallow.equal(b);\n            expect(a.extract('b.c.d')).to.shallow.equal(d);\n        });\n\n        it('extracts nested schema after object key override', () => {\n\n            const d = Joi.number();\n            const c = Joi.object({ d });\n            const b = Joi.object({ c });\n            const a = Joi.object({ b });\n            const x = a.keys({ b: c });\n\n            expect(x.extract('b')).to.shallow.equal(c);\n            expect(x.extract('b.d')).to.shallow.equal(d);\n        });\n\n        it('extracts nested schema after object key override and custom ids', () => {\n\n            const d = Joi.number().id('D');\n            const c = Joi.object({ d }).id('C');\n            const b = Joi.object({ c }).id('B');\n            const a = Joi.object({ b }).id('A');\n            const x = a.keys({ b: c });\n\n            expect(x.extract('C')).to.shallow.equal(c);\n            expect(x.extract('C.D')).to.shallow.equal(d);\n        });\n\n        it('extracts nested schema after object concat (keys)', () => {\n\n            const d = Joi.number();\n            const c = Joi.object({ d });\n            const b = Joi.object({ c });\n            const a = Joi.object({ b });\n            const x = a.concat(b);\n\n            expect(x.extract('b')).to.shallow.equal(b);\n            expect(x.extract('c')).to.shallow.equal(c);\n        });\n\n        it('extracts nested schema after object concat (ids)', () => {\n\n            const d = Joi.number().id('D');\n            const c = Joi.object({ d }).id('C');\n            const b = Joi.object({ c }).id('B');\n            const a = Joi.object({ b }).id('A');\n            const x = a.concat(b);\n\n            expect(x.extract('B')).to.shallow.equal(b);\n            expect(x.extract('C')).to.shallow.equal(c);\n        });\n\n        it('errors on missing schema', () => {\n\n            const d = Joi.number();\n            const c = Joi.object({ d });\n            const b = Joi.object({ c });\n            const a = Joi.object({ b });\n\n            expect(() => a.extract(['b', 'c', 'x'])).to.throw('Schema does not contain path b.c.x');\n        });\n\n        it('errors on conflicting schema ids', () => {\n\n            const a = Joi.number().id('A');\n            const b = Joi.string().id('A');\n\n            expect(() => Joi.object({ a, b })).to.throw('Cannot add different schemas with the same id: A');\n        });\n    });\n\n    describe('fork()', () => {\n\n        it('adjusts empty', () => {\n\n            const before = Joi.string().empty(Joi.number().id('x'));\n            const after = Joi.string().empty(Joi.number().min(10).id('x'));\n\n            Helper.equal(before.fork('x', (schema) => schema.min(10)), after);\n        });\n\n        it('preserves unchanged schemas', () => {\n\n            const before = Joi.object({ x: Joi.string().optional() });\n            const after = Joi.object({ x: Joi.string().optional() });\n\n            Helper.equal(before.fork('x', (schema) => schema.optional()), after);\n        });\n\n        it('adjusts reused schema in multiple places', () => {\n\n            const shared = Joi.number().id('x');\n\n            const before = Joi.object({\n                a: shared,\n                b: shared\n            });\n\n            expect(before.extract('a')).to.shallow.equal(before.extract('b'));\n\n            const sharedAfter = shared.min(10);\n            const after = Joi.object({\n                a: sharedAfter,\n                b: sharedAfter\n            });\n\n            expect(after.extract('a')).to.shallow.equal(after.extract('b'));\n\n            const modified = before.fork('x', (schema) => schema.min(10));\n            Helper.equal(modified, after);\n            expect(modified.extract('a')).to.shallow.equal(modified.extract('b'));\n        });\n\n        describe('alternatives', () => {\n\n            it('adjusts nested schema', () => {\n\n                const before = Joi.alternatives([\n                    Joi.number().positive().id('numbers'),\n                    Joi.object({\n                        c: Joi.object({\n                            d: Joi.number().positive()\n                        })\n                    }).id('objects')\n                ]);\n\n                const first = before.fork('objects.c.d', (schema) => schema.max(5));\n\n                const after1 = Joi.alternatives([\n                    Joi.number().positive().id('numbers'),\n                    Joi.object({\n                        c: Joi.object({\n                            d: Joi.number().positive().max(5)\n                        })\n                    }).id('objects')\n                ]);\n\n                Helper.equal(first, after1);\n\n                const second = first.fork('numbers', (schema) => schema.min(10));\n\n                const after2 = Joi.alternatives([\n                    Joi.number().positive().id('numbers').min(10),\n                    Joi.object({\n                        c: Joi.object({\n                            d: Joi.number().positive().max(5)\n                        })\n                    }).id('objects')\n                ]);\n\n                Helper.equal(second, after2);\n            });\n\n            it('adjusts when schema', () => {\n\n                const before = Joi.object({\n                    a: Joi.number(),\n                    b: Joi.boolean()\n                        .when('a', [\n                            { is: 0, then: Joi.valid(0).id('zero') },\n                            { is: 1, then: Joi.valid(1).id('one') }\n                        ])\n                });\n\n                const forked = before.fork('b.one', (schema) => schema.allow(2));\n\n                const after = Joi.object({\n                    a: Joi.number(),\n                    b: Joi.boolean()\n                        .when('a', [\n                            { is: 0, then: Joi.valid(0).id('zero') },\n                            { is: 1, then: Joi.valid(1, 2).id('one') }\n                        ])\n                });\n\n                Helper.equal(forked, after);\n            });\n\n            it('adjusts alternatives schema', () => {\n\n                const before = Joi.object({\n                    a: Joi.number(),\n                    b: Joi.alternatives()\n                        .conditional('a', {\n                            switch: [\n                                { is: 0, then: Joi.valid(0).id('zero') },\n                                { is: 1, then: Joi.valid(1).id('one'), otherwise: Joi.boolean() }\n                            ]\n                        })\n                });\n\n                const forked = before.fork('b.one', (schema) => schema.allow(2));\n\n                const after = Joi.object({\n                    a: Joi.number(),\n                    b: Joi.alternatives()\n                        .conditional('a', {\n                            switch: [\n                                { is: 0, then: Joi.valid(0).id('zero') },\n                                { is: 1, then: Joi.valid(1, 2).id('one'), otherwise: Joi.boolean() }\n                            ]\n                        })\n                });\n\n                Helper.equal(forked, after);\n            });\n        });\n\n        describe('array', () => {\n\n            it('adjusts nested schema', () => {\n\n                const before = Joi.array().items(\n                    Joi.number().positive().id('numbers'),\n                    Joi.object({\n                        c: Joi.object({\n                            d: Joi.number().positive()\n                        })\n                    })\n                        .id('objects')\n                )\n                    .has(Joi.object())\n                    .has(Joi.valid(5).id('five'));\n\n                const first = before.fork('objects.c.d', (schema) => schema.max(5));\n\n                const after1 = Joi.array().items(\n                    Joi.number().positive().id('numbers'),\n                    Joi.object({\n                        c: Joi.object({\n                            d: Joi.number().positive().max(5)\n                        })\n                    })\n                        .id('objects')\n                )\n                    .has(Joi.object())\n                    .has(Joi.valid(5).id('five'));\n\n                Helper.equal(first, after1);\n\n                const second = first.fork('numbers', (schema) => schema.min(10));\n\n                const after2 = Joi.array().items(\n                    Joi.number().positive().id('numbers').min(10),\n                    Joi.object({\n                        c: Joi.object({\n                            d: Joi.number().positive().max(5)\n                        })\n                    })\n                        .id('objects')\n                )\n                    .has(Joi.object())\n                    .has(Joi.valid(5).id('five'));\n\n                Helper.equal(second, after2);\n\n                const third = second.fork('five', (schema) => schema.allow(-5));\n\n                const after3 = Joi.array().items(\n                    Joi.number().positive().id('numbers').min(10),\n                    Joi.object({\n                        c: Joi.object({\n                            d: Joi.number().positive().max(5)\n                        })\n                    })\n                        .id('objects')\n                )\n                    .has(Joi.object())\n                    .has(Joi.valid(5, -5).id('five'));\n\n                Helper.equal(third, after3);\n            });\n        });\n\n        describe('object', () => {\n\n            it('adjusts nested schema', () => {\n\n                const before = Joi.object({\n                    a: Joi.number(),\n                    b: Joi.object({\n                        c: Joi.object({\n                            d: Joi.number()\n                        })\n                    })\n                });\n\n                const after = Joi.object({\n                    a: Joi.number(),\n                    b: Joi.object({\n                        c: Joi.object({\n                            d: Joi.number().min(10)\n                        })\n                    })\n                });\n\n                Helper.equal(before.fork('b.c.d', (schema) => schema.min(10)), after);\n                Helper.equal(before.fork([['b', 'c', 'd']], (schema) => schema.min(10)), after);\n            });\n\n            it('forks multiple times', () => {\n\n                const before = Joi.object({\n                    b: Joi.object({\n                        c: Joi.object({\n                            d: Joi.number()\n                        })\n                    }),\n                    x: Joi.number()\n                });\n\n                const bd = before.describe();\n\n                const first = before.fork('b.c.d', (schema) => schema.min(10));\n                const fd = first.describe();\n\n                const second = first.fork('b.c.d', (schema) => schema.max(20));\n                const sd = second.describe();\n\n                const third = second.fork('b.c.d', (schema) => schema.min(5));\n                const td = third.describe();\n\n                const fourth = third.fork('x', (schema) => schema.required());\n\n                const after = Joi.object({\n                    b: Joi.object({\n                        c: Joi.object({\n                            d: Joi.number().max(20).min(5)\n                        })\n                    }),\n                    x: Joi.number().required()\n                });\n\n                Helper.equal(fourth, after);\n\n                expect(before.describe()).to.equal(bd);\n                expect(first.describe()).to.equal(fd);\n                expect(second.describe()).to.equal(sd);\n                expect(third.describe()).to.equal(td);\n            });\n\n            it('forks same schema multiple times', () => {\n\n                const before = Joi.object({\n                    x: Joi.number(),\n                    b: Joi.object({\n                        c: Joi.object({\n                            d: Joi.number()\n                        })\n                    })\n                });\n\n                const bd = before.describe();\n\n                const first = before.fork('b.c.d', (schema) => schema.min(10));\n                const fd = first.describe();\n\n                const second = before.fork('b.c.d', (schema) => schema.max(20));\n                const sd = second.describe();\n\n                const third = before.fork('b.c.d', (schema) => schema.min(5));\n                const td = third.describe();\n\n                const fourth = before.fork('x', (schema) => schema.required());\n\n                const a1 = Joi.object({\n                    x: Joi.number(),\n                    b: Joi.object({\n                        c: Joi.object({\n                            d: Joi.number().min(10)\n                        })\n                    })\n                });\n\n                Helper.equal(first, a1);\n\n                const a2 = Joi.object({\n                    x: Joi.number(),\n                    b: Joi.object({\n                        c: Joi.object({\n                            d: Joi.number().max(20)\n                        })\n                    })\n                });\n\n                Helper.equal(second, a2);\n\n                const a3 = Joi.object({\n                    x: Joi.number(),\n                    b: Joi.object({\n                        c: Joi.object({\n                            d: Joi.number().min(5)\n                        })\n                    })\n                });\n\n                Helper.equal(third, a3);\n\n                const a4 = Joi.object({\n                    x: Joi.number().required(),\n                    b: Joi.object({\n                        c: Joi.object({\n                            d: Joi.number()\n                        })\n                    })\n                });\n\n                Helper.equal(fourth, a4);\n\n                expect(before.describe()).to.equal(bd);\n                expect(first.describe()).to.equal(fd);\n                expect(second.describe()).to.equal(sd);\n                expect(third.describe()).to.equal(td);\n            });\n\n            it('adjusts nested schema with ids', () => {\n\n                const before = Joi.object({\n                    a: Joi.number().id('A'),\n                    b: Joi.object({\n                        c: Joi.object({\n                            d: Joi.number().id('D')\n                        })\n                    })\n                });\n\n                const after = Joi.object({\n                    a: Joi.number().id('A'),\n                    b: Joi.object({\n                        c: Joi.object({\n                            d: Joi.number().id('D').min(10)\n                        })\n                    })\n                });\n\n                Helper.equal(before.fork('b.c.D', (schema) => schema.min(10)), after);\n                Helper.equal(before.fork([['b', 'c', 'D']], (schema) => schema.min(10)), after);\n            });\n\n            it('sets keys as required', () => {\n\n                const orig = Joi.object({ a: 0, b: 0, c: { d: 0, e: { f: 0 } }, g: { h: 0 } });\n                const schema = orig.fork(['a', 'b', 'c.d', 'c.e.f', 'g'], (x) => x.required());\n\n                Helper.validate(orig, [\n                    [{}, true]\n                ]);\n\n                Helper.validate(schema, [\n                    [{}, false, {\n                        message: '\"a\" is required',\n                        path: ['a'],\n                        type: 'any.required',\n                        context: { label: 'a', key: 'a' }\n                    }],\n                    [{ a: 0 }, false, {\n                        message: '\"b\" is required',\n                        path: ['b'],\n                        type: 'any.required',\n                        context: { label: 'b', key: 'b' }\n                    }],\n                    [{ a: 0, b: 0 }, false, {\n                        message: '\"g\" is required',\n                        path: ['g'],\n                        type: 'any.required',\n                        context: { label: 'g', key: 'g' }\n                    }],\n                    [{ a: 0, b: 0, g: {} }, true],\n                    [{ a: 0, b: 0, c: {}, g: {} }, false, {\n                        message: '\"c.d\" is required',\n                        path: ['c', 'd'],\n                        type: 'any.required',\n                        context: { label: 'c.d', key: 'd' }\n                    }],\n                    [{ a: 0, b: 0, c: { d: 0 }, g: {} }, true],\n                    [{ a: 0, b: 0, c: { d: 0, e: {} }, g: {} }, false, {\n                        message: '\"c.e.f\" is required',\n                        path: ['c', 'e', 'f'],\n                        type: 'any.required',\n                        context: { label: 'c.e.f', key: 'f' }\n                    }],\n                    [{ a: 0, b: 0, c: { d: 0, e: { f: 0 } }, g: {} }, true]\n                ]);\n            });\n\n            it('sets keys as optional', () => {\n\n                const schema = Joi.object({\n                    a: Joi.number().required(),\n                    b: Joi.number().required()\n                }).\n                    fork(['a', 'b'], (x) => x.optional());\n\n                Helper.validate(schema, [\n                    [{}, true],\n                    [{ a: 0 }, true],\n                    [{ a: 0, b: 0 }, true]\n                ]);\n            });\n\n            it('sets keys as forbidden', () => {\n\n                const schema = Joi.object({\n                    a: Joi.number().required(),\n                    b: Joi.number().required()\n                }).\n                    fork(['a', 'b'], (x) => x.forbidden());\n\n                Helper.validate(schema, [\n                    [{}, true],\n                    [{ a: undefined }, true],\n                    [{ a: undefined, b: undefined }, true],\n                    [{ a: 0 }, false, {\n                        message: '\"a\" is not allowed',\n                        path: ['a'],\n                        type: 'any.unknown',\n                        context: { label: 'a', key: 'a', value: 0 }\n                    }],\n                    [{ b: 0 }, false, {\n                        message: '\"b\" is not allowed',\n                        path: ['b'],\n                        type: 'any.unknown',\n                        context: { label: 'b', key: 'b', value: 0 }\n                    }]\n                ]);\n            });\n\n            it('adjusts object assert', () => {\n\n                const before = Joi.object({\n                    a: Joi.object({\n                        b: Joi.number()\n                    }),\n                    b: Joi.object({\n                        d: Joi.number()\n                    })\n                })\n                    .assert('b.d', Joi.valid(1))\n                    .assert('a.b', Joi.valid(Joi.ref('b.d')).id('assert'));\n\n                const after = Joi.object({\n                    a: Joi.object({\n                        b: Joi.number()\n                    }),\n                    b: Joi.object({\n                        d: Joi.number()\n                    })\n                })\n                    .assert('b.d', Joi.valid(1))\n                    .assert('a.b', Joi.valid(Joi.ref('b.d'), 'x').id('assert'));\n\n                expect(before.fork('assert', (schema) => schema.valid('x')).describe()).to.equal(after.describe());\n            });\n\n            it('adjusts object assert (with pattern)', () => {\n\n                const before = Joi.object({\n                    a: Joi.object({\n                        b: Joi.number()\n                    }),\n                    b: Joi.object({\n                        d: Joi.number()\n                    })\n                })\n                    .min(2)\n                    .pattern(/\\d/, Joi.any())\n                    .assert('a.b', Joi.valid(Joi.ref('b.d')).id('assert'));\n\n                const after = Joi.object({\n                    a: Joi.object({\n                        b: Joi.number()\n                    }),\n                    b: Joi.object({\n                        d: Joi.number()\n                    })\n                })\n                    .min(2)\n                    .pattern(/\\d/, Joi.any())\n                    .assert('a.b', Joi.valid(Joi.ref('b.d'), 'x').id('assert'));\n\n                expect(before.fork('assert', (schema) => schema.valid('x')).describe()).to.equal(after.describe());\n            });\n\n            it('adjusts object pattern', () => {\n\n                const before = Joi.object()\n                    .pattern(/.*/, Joi.valid('x').id('pattern'));\n\n                const after = Joi.object()\n                    .pattern(/.*/, Joi.valid('x', 'y').id('pattern'));\n\n                expect(before.fork('pattern', (schema) => schema.valid('y')).describe()).to.equal(after.describe());\n            });\n        });\n    });\n\n    describe('id()', () => {\n\n        it('unsets id', () => {\n\n            const schema = Joi.any().id('x');\n            Helper.equal(schema.id(), Joi.any());\n        });\n\n        it('errors on invalid id', () => {\n\n            expect(() => Joi.any().id('a.b')).to.throw('id cannot contain period character');\n        });\n\n        it('overrides id', () => {\n\n            const schema = Joi.any().id('x');\n            Helper.equal(schema.id('y'), Joi.any().id('y'));\n        });\n    });\n\n    describe('labels()', () => {\n\n        it('extracts nested schema', () => {\n\n            const d = Joi.number();\n            const c = Joi.object({ d });\n            const b = Joi.object({ c });\n            const a = Joi.object({ b });\n\n            expect(a.$_mapLabels('b')).to.equal('b');\n            expect(a.$_mapLabels('b.c.d')).to.equal('b.c.d');\n            expect(a.$_mapLabels(['b', 'c', 'd'])).to.equal('b.c.d');\n        });\n\n        it('extracts nested schema with ids', () => {\n\n            const d = Joi.number().label('D');\n            const c = Joi.object({ d }).label('C');\n            const b = Joi.object({ c }).label('B');\n            const a = Joi.object({ b });\n\n            expect(a.$_mapLabels('b')).to.equal('B');\n            expect(a.$_mapLabels('b.c.d')).to.equal('B.C.D');\n        });\n    });\n\n    describe('schema()', () => {\n\n        it('changes multiple schemas in different sources', () => {\n\n            const custom = Joi.extend({\n                type: 'special',\n                coerce(value, helpers) {\n\n                    const swap = helpers.schema.$_getFlag('swap');\n                    if (swap &&\n                        swap.$_match(value, helpers.state.nest(swap), helpers.prefs)) {\n\n                        return { value: ['swapped'] };\n                    }\n                },\n                terms: {\n                    x: { init: [] }\n                },\n                rules: {\n                    swap: {\n                        method(schema) {\n\n                            return this.$_setFlag('swap', this.$_compile(schema));\n                        }\n                    },\n                    pattern: {\n                        method(schema) {\n\n                            return this.$_addRule({ name: 'pattern', args: { schema: this.$_compile(schema) } });\n                        },\n                        validate() { }\n                    },\n                    term: {\n                        method(schema) {\n\n                            this.$_terms.x.push(schema);\n                            return this;\n                        }\n                    }\n                }\n            });\n\n            const schema = custom.special()\n                .swap(Joi.number())\n                .empty(Joi.object())\n                .pattern(Joi.binary())\n                .term(Joi.number());\n\n            const each = (item) => item.min(10);\n\n            expect(schema.$_modify({ each, ref: false, schema: false })).to.equal(schema);\n\n            const modified = schema.$_modify({ each, ref: false });\n\n            expect(modified.describe()).to.equal({\n                type: 'special',\n                flags: {\n                    empty: {\n                        rules: [{ args: { limit: 10 }, name: 'min' }],\n                        type: 'object'\n                    },\n                    swap: {\n                        rules: [{ args: { limit: 10 }, name: 'min' }],\n                        type: 'number'\n                    }\n                },\n                rules: [{\n                    args: {\n                        schema: {\n                            rules: [{ args: { limit: 10 }, name: 'min' }],\n                            type: 'binary'\n                        }\n                    },\n                    name: 'pattern'\n                }],\n                x: [\n                    {\n                        rules: [{ args: { limit: 10 }, name: 'min' }],\n                        type: 'number'\n                    }\n                ]\n            });\n        });\n    });\n});\n"
  },
  {
    "path": "test/ref.js",
    "content": "'use strict';\n\nconst Code = require('@hapi/code');\nconst Lab = require('@hapi/lab');\nconst Joi = require('..');\n\nconst Helper = require('./helper');\n\n\nconst internals = {};\n\n\nconst { describe, it } = exports.lab = Lab.script();\nconst { expect } = Code;\n\n\ndescribe('ref', () => {\n\n    it('detects references', () => {\n\n        expect(Joi.isRef(Joi.ref('a.b'))).to.be.true();\n    });\n\n    it('throws when reference reaches beyond the schema root', () => {\n\n        const schema = Joi.object({\n            a: Joi.any(),\n            b: Joi.ref('...c')\n        });\n\n        expect(() => schema.validate({ a: 1, b: 2 })).to.throw('Invalid reference exceeds the schema root: ref:...c');\n    });\n\n    it('reaches self', () => {\n\n        const schema = Joi.number().min(10).message('{#label} is {[.]} and that is not good enough');\n        Helper.validate(schema, [[1, false, '\"value\" is 1 and that is not good enough']]);\n    });\n\n    it('reaches own property', () => {\n\n        const schema = Joi.object({\n            x: Joi.alternatives([\n                Joi.number(),\n                Joi.object({\n                    a: Joi.boolean().required()\n                })\n                    .when('.a', {\n                        is: true,\n                        then: {\n                            b: Joi.string().required()\n                        }\n                    })\n            ])\n        });\n\n        Helper.validate(schema, [\n            [{ x: 1 }, true],\n            [{ x: { a: true, b: 'x' } }, true]\n        ]);\n    });\n\n    it('reaches parent', () => {\n\n        const schema = Joi.object({\n            a: Joi.any(),\n            a1: Joi.ref('a'),\n            a2: Joi.ref('..a')\n        });\n\n        Helper.validate(schema, [\n            [\n                {\n                    a: 1,\n                    a1: 1,\n                    a2: 1\n                }, true\n            ]\n        ]);\n    });\n\n    it('reaches grandparent', () => {\n\n        const schema = Joi.object({\n            a: Joi.any(),\n            b: {\n                a1: Joi.ref('...a'),\n                a2: Joi.ref('...a')\n            }\n        });\n\n        Helper.validate(schema, [\n            [\n                {\n                    a: 1,\n                    b: {\n                        a1: 1,\n                        a2: 1\n                    }\n                }, true\n            ]\n        ]);\n\n        expect(schema.describe()).to.equal({\n            type: 'object',\n            keys: {\n                a: {\n                    type: 'any'\n                },\n                b: {\n                    type: 'object',\n                    keys: {\n                        a1: {\n                            flags: {\n                                only: true\n                            },\n                            type: 'any',\n                            allow: [{ override: true }, { ref: { ancestor: 2, path: ['a'] } }]\n                        },\n                        a2: {\n                            flags: {\n                                only: true\n                            },\n                            type: 'any',\n                            allow: [{ override: true }, { ref: { ancestor: 2, path: ['a'] } }]\n                        }\n                    }\n                }\n            }\n        });\n    });\n\n    it('reaches literal', () => {\n\n        const schema = Joi.object({\n            a: Joi.any(),\n            b: {\n                '...a': Joi.any(),\n                c: Joi.ref('...a', { separator: false })\n            }\n        });\n\n        Helper.validate(schema, [\n            [\n                {\n                    a: 1,\n                    b: {\n                        '...a': 2,\n                        c: 2\n                    }\n                }, true\n            ]\n        ]);\n\n        expect(schema.describe()).to.equal({\n            type: 'object',\n            keys: {\n                a: {\n                    type: 'any'\n                },\n                b: {\n                    type: 'object',\n                    keys: {\n                        '...a': {\n                            type: 'any'\n                        },\n                        c: {\n                            flags: {\n                                only: true\n                            },\n                            type: 'any',\n                            allow: [{ override: true }, { ref: { path: ['...a'], separator: false } }]\n                        }\n                    }\n                }\n            }\n        });\n    });\n\n    it('reaches ancestor literal', () => {\n\n        const schema = Joi.object({\n            a: Joi.any(),\n            '...a': Joi.any(),\n            b: {\n                '...a': Joi.any(),\n                c: Joi.ref('...a', { separator: false, ancestor: 2 })\n            }\n        });\n\n        Helper.validate(schema, [\n            [\n                {\n                    a: 1,\n                    '...a': 3,\n                    b: {\n                        '...a': 2,\n                        c: 3\n                    }\n                }, true\n            ]\n        ]);\n    });\n\n    it('reaches any level of the relative value structure', () => {\n\n        const ix = Joi.ref('...i');\n\n        const schema = Joi.object({\n            a: {\n                b: {\n                    c: {\n                        d: Joi.any()\n                    },\n                    e: 2,\n                    dx: Joi.ref('c.d'),\n                    ex: Joi.ref('..e'),\n                    ix,\n                    gx: Joi.ref('....f.g'),\n                    hx: Joi.ref('....h')\n                },\n                i: Joi.any()\n            },\n            f: {\n                g: Joi.any()\n            },\n            h: Joi.any()\n        });\n\n        Helper.validate(schema, [\n            [\n                {\n                    a: {\n                        b: {\n                            c: {\n                                d: 1\n                            },\n                            e: 2,\n                            dx: 1,\n                            ex: 2,\n                            gx: 3,\n                            hx: 4,\n                            ix: 5\n                        },\n                        i: 5\n                    },\n                    f: {\n                        g: 3\n                    },\n                    h: 4\n                }, true\n            ],\n            [\n                {\n                    a: {\n                        b: {\n                            c: {\n                                d: 1\n                            },\n                            e: 2,\n                            dx: 1,\n                            ex: 2,\n                            gx: 3,\n                            hx: 4,\n                            ix: 5\n                        },\n                        i: 10\n                    },\n                    f: {\n                        g: 3\n                    },\n                    h: 4\n                }, false, {\n                    message: '\"a.b.ix\" must be [ref:...i]',\n                    path: ['a', 'b', 'ix'],\n                    type: 'any.only',\n                    context: {\n                        value: 5,\n                        valids: [ix],\n                        key: 'ix',\n                        label: 'a.b.ix'\n                    }\n                }\n            ]\n        ]);\n    });\n\n    it('reaches any level of the relative value structure (ancestor option)', () => {\n\n        const ix = Joi.ref('i', { ancestor: 2 });\n\n        const schema = Joi.object({\n            a: {\n                b: {\n                    c: {\n                        d: Joi.any()\n                    },\n                    e: 2,\n                    dx: Joi.ref('c.d', { ancestor: 1 }),\n                    ex: Joi.ref('e', { ancestor: 1 }),\n                    ix,\n                    gx: Joi.ref('f.g', { ancestor: 3 }),\n                    hx: Joi.ref('h', { ancestor: 3 })\n                },\n                i: Joi.any()\n            },\n            f: {\n                g: Joi.any()\n            },\n            h: Joi.any()\n        });\n\n        Helper.validate(schema, [\n            [\n                {\n                    a: {\n                        b: {\n                            c: {\n                                d: 1\n                            },\n                            e: 2,\n                            dx: 1,\n                            ex: 2,\n                            gx: 3,\n                            hx: 4,\n                            ix: 5\n                        },\n                        i: 5\n                    },\n                    f: {\n                        g: 3\n                    },\n                    h: 4\n                }, true\n            ],\n            [\n                {\n                    a: {\n                        b: {\n                            c: {\n                                d: 1\n                            },\n                            e: 2,\n                            dx: 1,\n                            ex: 2,\n                            gx: 3,\n                            hx: 4,\n                            ix: 5\n                        },\n                        i: 10\n                    },\n                    f: {\n                        g: 3\n                    },\n                    h: 4\n                }, false, {\n                    message: '\"a.b.ix\" must be [ref:...i]',\n                    path: ['a', 'b', 'ix'],\n                    type: 'any.only',\n                    context: {\n                        value: 5,\n                        valids: [ix],\n                        key: 'ix',\n                        label: 'a.b.ix'\n                    }\n                }\n            ]\n        ]);\n    });\n\n    it('reaches own key value', () => {\n\n        const object = Joi.object().schema('object');\n        const schema = Joi.object({\n            key: Joi.object().when('.', {\n                is: Joi.object().schema(),\n                then: object,\n                otherwise: Joi.object().pattern(/.*/, object)\n            })\n                .required()\n        });\n\n        Helper.validate(schema, [\n            [{ key: object }, true],\n            [{ key: 1 }, false, {\n                message: '\"key\" must be of type object',\n                type: 'object.base',\n                path: ['key'],\n                context: {\n                    key: 'key',\n                    label: 'key',\n                    value: 1,\n                    type: 'object'\n                }\n            }],\n            [{ key: Joi.number() }, false, {\n                message: '\"key\" must be a Joi schema of object type',\n                type: 'object.schema',\n                path: ['key'],\n                context: {\n                    key: 'key',\n                    label: 'key',\n                    type: 'object',\n                    value: Joi.number()\n                }\n            }],\n            [{ key: { a: 1 } }, false, {\n                message: '\"key.a\" must be of type object',\n                type: 'object.base',\n                path: ['key', 'a'],\n                context: {\n                    key: 'a',\n                    label: 'key.a',\n                    value: 1,\n                    type: 'object'\n                }\n            }],\n            [{ key: { a: {} } }, false, {\n                message: '\"key.a\" must be a Joi schema of object type',\n                type: 'object.schema',\n                path: ['key', 'a'],\n                context: {\n                    key: 'a',\n                    label: 'key.a',\n                    type: 'object',\n                    value: {}\n                }\n            }]\n        ]);\n    });\n\n    it('reaches into set and map', () => {\n\n        const schema = Joi.object({\n            a: {\n                b: Joi.array()\n                    .items({\n                        x: Joi.number(),\n                        y: Joi.object().cast('map')\n                    })\n                    .cast('set')\n            },\n            d: Joi.ref('a.b.2.y.w', { iterables: true })\n        });\n\n        const value = {\n            a: {\n                b: [\n                    { x: 1 },\n                    { x: 2 },\n                    {\n                        y: {\n                            v: 4,\n                            w: 5\n                        }\n                    }\n                ]\n            },\n            d: 5\n        };\n\n        Helper.validate(schema, [[value, true, {\n            a: {\n                b: new Set([\n                    { x: 1 },\n                    { x: 2 },\n                    {\n                        y: new Map([\n                            ['v', 4],\n                            ['w', 5]\n                        ])\n                    }\n                ])\n            },\n            d: 5\n        }]]);\n\n        value.d = 6;\n        Helper.validate(schema, [[value, false, '\"d\" must be [ref:a.b.2.y.w]']]);\n    });\n\n    it('reaches root', () => {\n\n        const schema = Joi.object({\n            a: Joi.any(),\n            b: {\n                c: Joi.ref('/a'),\n                d: Joi.ref('@a', { prefix: { root: '@' } })\n            }\n        });\n\n        Helper.validate(schema, [\n            [{ a: 1, b: { c: 1, d: 1 } }, true]\n        ]);\n\n        expect(schema.describe()).to.equal({\n            type: 'object',\n            keys: {\n                a: {\n                    type: 'any'\n                },\n                b: {\n                    type: 'object',\n                    keys: {\n                        c: {\n                            flags: {\n                                only: true\n                            },\n                            type: 'any',\n                            allow: [{ override: true }, { ref: { ancestor: 'root', path: ['a'] } }]\n                        },\n                        d: {\n                            flags: {\n                                only: true\n                            },\n                            type: 'any',\n                            allow: [{ override: true }, { ref: { ancestor: 'root', path: ['a'] } }]\n                        }\n                    }\n                }\n            }\n        });\n    });\n\n    it('errors on missing iterables flag when reaching into set and map', () => {\n\n        const schema = Joi.object({\n            a: {\n                b: Joi.array()\n                    .items({\n                        x: Joi.number(),\n                        y: Joi.object().cast('map')\n                    })\n                    .cast('set')\n            },\n            d: Joi.ref('a.b.2.y.w')\n        });\n\n        const value = {\n            a: {\n                b: [\n                    { x: 1 },\n                    { x: 2 },\n                    {\n                        y: {\n                            v: 4,\n                            w: 5\n                        }\n                    }\n                ]\n            },\n            d: 5\n        };\n\n        Helper.validate(schema, [[value, false, '\"d\" must be [ref:a.b.2.y.w]']]);\n    });\n\n    it('errors on invalid separator)', () => {\n\n        expect(() => Joi.ref('x', { separator: 0 })).to.throw('Invalid separator');\n        expect(() => Joi.ref('x', { separator: '' })).to.throw('Invalid separator');\n        expect(() => Joi.ref('x', { separator: '$$' })).to.throw('Invalid separator');\n    });\n\n    it('errors on prefix + ancestor option)', () => {\n\n        expect(() => Joi.ref('..x', { ancestor: 0 })).to.throw('Cannot combine prefix with ancestor option');\n    });\n\n    it('errors on root with ancestor prefix', () => {\n\n        expect(() => Joi.ref('/.x')).to.throw('Cannot specify relative path with root prefix');\n        expect(() => Joi.ref('/.x', { separator: false })).to.not.throw();\n    });\n\n    it('errors on ancestor circular dependency', () => {\n\n        const schema = {\n            a: {\n                x: Joi.any(),\n                b: {\n                    c: {\n                        d: Joi.ref('....b.x')\n                    }\n                }\n            },\n            b: {\n                x: Joi.any(),\n                y: {\n                    z: {\n                        o: Joi.ref('....a.x')\n                    }\n                }\n            }\n        };\n\n        expect(() => Joi.compile(schema)).to.throw('Item cannot come after itself: b (a.b)');\n    });\n\n    it('references array length', () => {\n\n        const ref = Joi.ref('length');\n        const schema = Joi.object({\n            x: Joi.array().items(Joi.number().valid(ref))\n        });\n\n        Helper.validate(schema, [\n            [{ x: [1] }, true],\n            [{ x: [2, 2] }, true],\n            [{ x: [2, 2, 2] }, false, {\n                message: '\"x[0]\" must be [ref:length]',\n                path: ['x', 0],\n                type: 'any.only',\n                context: {\n                    value: 2,\n                    valids: [ref],\n                    key: 0,\n                    label: 'x[0]'\n                }\n            }]\n        ]);\n    });\n\n    it('splits when based on own array length', () => {\n\n        const pair = Joi.array().items(2);\n        const lucky = Joi.array().items(7);\n\n        const schema = Joi.object({\n            x: Joi.array().when('.length', { is: 2, then: pair, otherwise: lucky })\n        });\n\n        Helper.validate(schema, [\n            [{ x: [7] }, true],\n            [{ x: [7, 7, 7] }, true],\n            [{ x: [2, 2] }, true],\n            [{ x: [2, 2, 2] }, false, {\n                message: '\"x[0]\" must be [7]',\n                path: ['x', 0],\n                type: 'any.only',\n                context: { value: 2, valids: [7], key: 0, label: 'x[0]' }\n            }]\n        ]);\n    });\n\n    it('references array item', () => {\n\n        const ref = Joi.ref('0');\n        const schema = Joi.array().ordered(Joi.number(), Joi.number().min(ref));\n\n        Helper.validate(schema, [\n            [[1, 2], true],\n            [[10, 20], true],\n            [[10, 5], false, {\n                message: '\"[1]\" must be greater than or equal to ref:0',\n                path: [1],\n                type: 'number.min',\n                context: { limit: ref, value: 5, key: 1, label: '[1]' }\n            }]\n        ]);\n    });\n\n    it('references object own child', () => {\n\n        const ref = Joi.ref('.length');\n        const schema = Joi.object({\n            length: Joi.number().required()\n        })\n            .length(ref)\n            .unknown();\n\n        expect(schema._refs.refs).to.equal([]);     // Does not register reference to itself\n\n        Helper.validate(schema, [\n            [{ length: 1 }, true],\n            [{ length: 2, x: 3 }, true],\n            [{ length: 2, x: 3, y: 4 }, false, {\n                message: '\"value\" must have ref:.length keys',\n                path: [],\n                type: 'object.length',\n                context: {\n                    value: { length: 2, x: 3, y: 4 },\n                    limit: ref,\n                    label: 'value'\n                }\n            }]\n        ]);\n    });\n\n    it('uses ref as a valid value', () => {\n\n        const ref = Joi.ref('b');\n        const schema = Joi.object({\n            a: ref,\n            b: Joi.any()\n        });\n\n        Helper.validate(schema, [[{ a: 5, b: 6 }, false, {\n            message: '\"a\" must be [ref:b]',\n            path: ['a'],\n            type: 'any.only',\n            context: { value: 5, valids: [ref], label: 'a', key: 'a' }\n        }]]);\n\n        Helper.validate(schema, [\n            [{ a: 5 }, false, {\n                message: '\"a\" must be [ref:b]',\n                path: ['a'],\n                type: 'any.only',\n                context: { value: 5, valids: [ref], label: 'a', key: 'a' }\n            }],\n            [{ b: 5 }, true],\n            [{ a: 5, b: 5 }, true],\n            [{ a: '5', b: '5' }, true]\n        ]);\n    });\n\n    it('sets adjust function', () => {\n\n        const adjust = (v) => 2 * v;\n        const ref = Joi.ref('b', { adjust });\n        const schema = Joi.object({\n            a: ref,\n            b: Joi.number()\n        });\n\n        Helper.validate(schema, [\n            [{ b: 5 }, true],\n            [{ a: 10, b: 5 }, true],\n            [{ a: 10, b: '5' }, true, { a: 10, b: 5 }],\n            [{ a: 5 }, false, {\n                message: '\"a\" must be [ref:b]',\n                path: ['a'],\n                type: 'any.only',\n                context: { value: 5, valids: [ref], label: 'a', key: 'a' }\n            }],\n            [{ a: 5, b: 5 }, false, {\n                message: '\"a\" must be [ref:b]',\n                path: ['a'],\n                type: 'any.only',\n                context: { value: 5, valids: [ref], label: 'a', key: 'a' }\n            }]\n        ]);\n\n        expect(schema.describe()).to.equal({\n            type: 'object',\n            keys: {\n                b: {\n                    type: 'number'\n                },\n                a: {\n                    type: 'any',\n                    flags: { only: true },\n                    allow: [{ override: true }, { ref: { path: ['b'], adjust } }]\n                }\n            }\n        });\n    });\n\n    it('sets map', () => {\n\n        const map = [[5, 10]];\n        const ref = Joi.ref('b', { map });\n        const schema = Joi.object({\n            a: ref,\n            b: Joi.number()\n        });\n\n        Helper.validate(schema, [\n            [{ b: 5 }, true],\n            [{ a: 10, b: 5 }, true],\n            [{ a: 10, b: '5' }, true, { a: 10, b: 5 }],\n            [{ a: 5 }, false, {\n                message: '\"a\" must be [ref:b]',\n                path: ['a'],\n                type: 'any.only',\n                context: { value: 5, valids: [ref], label: 'a', key: 'a' }\n            }],\n            [{ a: 5, b: 5 }, false, {\n                message: '\"a\" must be [ref:b]',\n                path: ['a'],\n                type: 'any.only',\n                context: { value: 5, valids: [ref], label: 'a', key: 'a' }\n            }]\n        ]);\n\n        expect(schema.describe()).to.equal({\n            type: 'object',\n            keys: {\n                b: {\n                    type: 'number'\n                },\n                a: {\n                    type: 'any',\n                    flags: { only: true },\n                    allow: [{ override: true }, { ref: { path: ['b'], map } }]\n                }\n            }\n        });\n    });\n\n    it('uses ref as a valid value (empty key)', () => {\n\n        const ref = Joi.ref('');\n        const schema = Joi.object({\n            a: ref,\n            '': Joi.any()\n        });\n\n        Helper.validate(schema, [\n            [{ a: 5, '': 6 }, false, {\n                message: '\"a\" must be [ref:..]',\n                path: ['a'],\n                type: 'any.only',\n                context: { value: 5, valids: [ref], label: 'a', key: 'a' }\n            }],\n            [{ a: 5 }, false, {\n                message: '\"a\" must be [ref:..]',\n                path: ['a'],\n                type: 'any.only',\n                context: { value: 5, valids: [ref], label: 'a', key: 'a' }\n            }],\n            [{ '': 5 }, true],\n            [{ a: 5, '': 5 }, true],\n            [{ a: '5', '': '5' }, true]\n        ]);\n    });\n\n    it('uses ref with nested keys as a valid value', () => {\n\n        const ref = Joi.ref('b.c');\n        const schema = Joi.object({\n            a: ref,\n            b: {\n                c: Joi.any()\n            }\n        });\n\n        const err = schema.validate({ a: 5, b: { c: 6 } }).error;\n\n        expect(err).to.be.an.error('\"a\" must be [ref:b.c]');\n        expect(err.details).to.equal([{\n            message: '\"a\" must be [ref:b.c]',\n            path: ['a'],\n            type: 'any.only',\n            context: { value: 5, valids: [ref], label: 'a', key: 'a' }\n        }]);\n\n        Helper.validate(schema, [\n            [{ a: 5 }, false, {\n                message: '\"a\" must be [ref:b.c]',\n                path: ['a'],\n                type: 'any.only',\n                context: { value: 5, valids: [ref], label: 'a', key: 'a' }\n            }],\n            [{ b: { c: 5 } }, true],\n            [{ a: 5, b: 5 }, false, {\n                message: '\"b\" must be of type object',\n                path: ['b'],\n                type: 'object.base',\n                context: { label: 'b', key: 'b', value: 5, type: 'object' }\n            }],\n            [{ a: '5', b: { c: '5' } }, true]\n        ]);\n    });\n\n    it('uses ref with combined nested keys in sub child', () => {\n\n        const ref = Joi.ref('b.c');\n        expect(ref.root).to.equal('b');\n\n        const schema = Joi.object({\n            a: ref,\n            b: {\n                c: Joi.any()\n            }\n        });\n\n        const input = { a: 5, b: { c: 5 } };\n        Helper.validate(schema, [[input, true]]);\n\n        const parent = Joi.object({\n            e: schema\n        });\n\n        Helper.validate(parent, [[{ e: input }, true]]);\n    });\n\n    it('uses ref reach options', () => {\n\n        const ref = Joi.ref('b/c', { separator: '/' });\n        expect(ref.root).to.equal('b');\n\n        const schema = Joi.object({\n            a: ref,\n            b: {\n                c: Joi.any()\n            }\n        });\n\n        Helper.validate(schema, [[{ a: 5, b: { c: 5 } }, true]]);\n    });\n\n    it('ignores the order in which keys are defined', () => {\n\n        const ab = Joi.object({\n            a: {\n                c: Joi.number()\n            },\n            b: Joi.ref('a.c')\n        });\n\n        Helper.validate(ab, [[{ a: { c: '5' }, b: 5 }, true, { a: { c: 5 }, b: 5 }]]);\n\n        const ba = Joi.object({\n            b: Joi.ref('a.c'),\n            a: {\n                c: Joi.number()\n            }\n        });\n\n        Helper.validate(ba, [[{ a: { c: '5' }, b: 5 }, true, { a: { c: 5 }, b: 5 }]]);\n    });\n\n    it('uses ref as default value', async () => {\n\n        const schema = Joi.object({\n            a: Joi.any().default(Joi.ref('b')),\n            b: Joi.any()\n        });\n\n        const value = await schema.validateAsync({ b: 6 });\n        expect(value).to.equal({ a: 6, b: 6 });\n    });\n\n    it('uses ref mixed with normal values', async () => {\n\n        const schema = Joi.object({\n            a: Joi.number().valid(1, Joi.ref('b')),\n            b: Joi.any()\n        });\n\n        expect(await schema.validateAsync({ a: 6, b: 6 })).to.equal({ a: 6, b: 6 });\n        expect(await schema.validateAsync({ a: 1, b: 6 })).to.equal({ a: 1, b: 6 });\n        Helper.validate(schema, [[{ a: 6, b: 1 }, false, '\"a\" must be one of [1, ref:b]']]);\n    });\n\n    it('uses ref as default value regardless of order', async () => {\n\n        const ab = Joi.object({\n            a: Joi.any().default(Joi.ref('b')),\n            b: Joi.number()\n        });\n\n        const value = await ab.validateAsync({ b: '6' });\n        expect(value).to.equal({ a: 6, b: 6 });\n\n        const ba = Joi.object({\n            b: Joi.number(),\n            a: Joi.any().default(Joi.ref('b'))\n        });\n\n        const value2 = await ba.validateAsync({ b: '6' });\n        expect(value2).to.equal({ a: 6, b: 6 });\n    });\n\n    it('ignores the order in which keys are defined with alternatives', () => {\n\n        const ref1 = Joi.ref('a.c');\n        const ref2 = Joi.ref('c');\n        const a = Joi.object({ c: Joi.number() });\n        const b = Joi.alternatives([ref1, ref2]);\n        const c = Joi.number();\n\n        for (const schema of [{ a, b, c }, { b, a, c }, { b, c, a }, { a, c, b }, { c, a, b }, { c, b, a }]) {\n            Helper.validate(Joi.object().keys(schema), [\n                [{ a: {} }, true],\n                [{ a: { c: '5' }, b: 5 }, true, { a: { c: 5 }, b: 5 }],\n                [{ a: { c: '5' }, b: 6, c: '6' }, true, { a: { c: 5 }, b: 6, c: 6 }],\n                [{ a: { c: '5' }, b: 7, c: '6' }, false, {\n                    message: '\"b\" must be one of [ref:a.c, ref:c]',\n                    type: 'alternatives.types',\n                    path: ['b'],\n                    context: {\n                        key: 'b',\n                        label: 'b',\n                        value: 7,\n                        types: [ref1, ref2]\n                    }\n                }]\n            ]);\n        }\n    });\n\n    it('uses context as default value', async () => {\n\n        const schema = Joi.object({\n            a: Joi.any().default(Joi.ref('$x')),\n            b: Joi.any()\n        });\n\n        const value = await schema.validateAsync({ b: 6 }, { context: { x: 22 } });\n        expect(value).to.equal({ a: 22, b: 6 });\n    });\n\n    it('uses context as default value with custom prefix', async () => {\n\n        const schema = Joi.object({\n            a: Joi.any().default(Joi.ref('@x', { prefix: { global: '@' } })),\n            b: Joi.any()\n        });\n\n        const value = await schema.validateAsync({ b: 6 }, { context: { x: 22 } });\n        expect(value).to.equal({ a: 22, b: 6 });\n    });\n\n    it('uses context as a valid value', () => {\n\n        const ref = Joi.ref('$x');\n        const schema = Joi.object({\n            a: ref,\n            b: Joi.any()\n        });\n\n        Helper.validate(schema, { context: { x: 22 } }, [\n            [{ a: 5, b: 6 }, false, {\n                message: '\"a\" must be [ref:global:x]',\n                path: ['a'],\n                type: 'any.only',\n                context: { value: 5, valids: [ref], label: 'a', key: 'a' }\n            }],\n            [{ a: 5 }, false, {\n                message: '\"a\" must be [ref:global:x]',\n                path: ['a'],\n                type: 'any.only',\n                context: { value: 5, valids: [ref], label: 'a', key: 'a' }\n            }],\n            [{ a: 22 }, true],\n            [{ b: 5 }, true],\n            [{ a: 22, b: 5 }, true],\n            [{ a: '22', b: '5' }, false, {\n                message: '\"a\" must be [ref:global:x]',\n                path: ['a'],\n                type: 'any.only',\n                context: { value: '22', valids: [ref], label: 'a', key: 'a' }\n            }]\n        ]);\n    });\n\n    it('uses context in when condition', () => {\n\n        const schema = Joi.object({\n            a: Joi.boolean().when('$x', { is: Joi.exist(), otherwise: Joi.forbidden() })\n        });\n\n        Helper.validate(schema, [\n            [{}, true],\n            [{ a: 'x' }, false, {\n                message: '\"a\" is not allowed',\n                path: ['a'],\n                type: 'any.unknown',\n                context: { label: 'a', key: 'a', value: 'x' }\n            }],\n            [{ a: true }, false, {\n                message: '\"a\" is not allowed',\n                path: ['a'],\n                type: 'any.unknown',\n                context: { label: 'a', key: 'a', value: true }\n            }]\n        ]);\n\n        Helper.validate(schema, { context: {} }, [\n            [{}, true],\n            [{ a: 'x' }, false, {\n                message: '\"a\" is not allowed',\n                path: ['a'],\n                type: 'any.unknown',\n                context: { label: 'a', key: 'a', value: 'x' }\n            }],\n            [{ a: true }, false, {\n                message: '\"a\" is not allowed',\n                path: ['a'],\n                type: 'any.unknown',\n                context: { label: 'a', key: 'a', value: true }\n            }]\n        ]);\n\n        Helper.validate(schema, { context: { x: 1 } }, [\n            [{}, true],\n            [{ a: 'x' }, false, {\n                message: '\"a\" must be a boolean',\n                path: ['a'],\n                type: 'boolean.base',\n                context: { label: 'a', key: 'a', value: 'x' }\n            }],\n            [{ a: true }, true]\n        ]);\n    });\n\n    it('uses nested context in when condition', () => {\n\n        const schema = Joi.object({\n            a: Joi.boolean().when('$x.y', { is: Joi.exist(), otherwise: Joi.forbidden() })\n        });\n\n        Helper.validate(schema, [\n            [{}, true],\n            [{ a: 'x' }, false, {\n                message: '\"a\" is not allowed',\n                path: ['a'],\n                type: 'any.unknown',\n                context: { label: 'a', key: 'a', value: 'x' }\n            }],\n            [{ a: true }, false, {\n                message: '\"a\" is not allowed',\n                path: ['a'],\n                type: 'any.unknown',\n                context: { label: 'a', key: 'a', value: true }\n            }]\n        ]);\n\n        Helper.validate(schema, { context: {} }, [\n            [{}, true],\n            [{ a: 'x' }, false, {\n                message: '\"a\" is not allowed',\n                path: ['a'],\n                type: 'any.unknown',\n                context: { label: 'a', key: 'a', value: 'x' }\n            }],\n            [{ a: true }, false, {\n                message: '\"a\" is not allowed',\n                path: ['a'],\n                type: 'any.unknown',\n                context: { label: 'a', key: 'a', value: true }\n            }]\n        ]);\n\n        Helper.validate(schema, { context: { x: 1 } }, [\n            [{}, true],\n            [{ a: 'x' }, false, {\n                message: '\"a\" is not allowed',\n                path: ['a'],\n                type: 'any.unknown',\n                context: { label: 'a', key: 'a', value: 'x' }\n            }],\n            [{ a: true }, false, {\n                message: '\"a\" is not allowed',\n                path: ['a'],\n                type: 'any.unknown',\n                context: { label: 'a', key: 'a', value: true }\n            }]\n        ]);\n\n        Helper.validate(schema, { context: { x: {} } }, [\n            [{}, true],\n            [{ a: 'x' }, false, {\n                message: '\"a\" is not allowed',\n                path: ['a'],\n                type: 'any.unknown',\n                context: { label: 'a', key: 'a', value: 'x' }\n            }],\n            [{ a: true }, false, {\n                message: '\"a\" is not allowed',\n                path: ['a'],\n                type: 'any.unknown',\n                context: { label: 'a', key: 'a', value: true }\n            }]\n        ]);\n\n        Helper.validate(schema, { context: { x: { y: 1 } } }, [\n            [{}, true],\n            [{ a: 'x' }, false, {\n                message: '\"a\" must be a boolean',\n                path: ['a'],\n                type: 'boolean.base',\n                context: { label: 'a', key: 'a', value: 'x' }\n            }],\n            [{ a: true }, true]\n        ]);\n    });\n\n    it('describes schema with ref', () => {\n\n        const schema = Joi\n            .valid(Joi.ref('a.b'))\n            .invalid(Joi.ref('$b.c'))\n            .default(Joi.ref('a.b'))\n            .when('a.b', {\n                is: Joi.date().min(Joi.ref('a.b')).max(Joi.ref('a.b')),\n                then: Joi.number().min(Joi.ref('a.b')).max(Joi.ref('a.b')).greater(Joi.ref('a.b')).less(Joi.ref('a.b')),\n                otherwise: Joi.object({\n                    a: Joi.string().min(Joi.ref('b.c')).max(Joi.ref('b.c')).length(Joi.ref('b.c'))\n                }).with('a', 'b').without('b', 'c').assert('a.b', Joi.ref('a.b'))\n            });\n\n        expect(schema.describe()).to.equal({\n            type: 'any',\n            flags: { only: true, default: { ref: { path: ['a', 'b'] } } },\n            allow: [{ ref: { path: ['a', 'b'] } }],\n            invalid: [{ ref: { type: 'global', path: ['b', 'c'] } }],\n            whens: [{\n                ref: { path: ['a', 'b'] },\n                is: {\n                    type: 'date',\n                    rules: [\n                        { name: 'min', args: { date: { ref: { path: ['a', 'b'] } } } },\n                        { name: 'max', args: { date: { ref: { path: ['a', 'b'] } } } }\n                    ]\n                },\n                then: {\n                    type: 'number',\n                    rules: [\n                        { name: 'min', args: { limit: { ref: { path: ['a', 'b'] } } } },\n                        { name: 'max', args: { limit: { ref: { path: ['a', 'b'] } } } },\n                        { name: 'greater', args: { limit: { ref: { path: ['a', 'b'] } } } },\n                        { name: 'less', args: { limit: { ref: { path: ['a', 'b'] } } } }\n                    ]\n                },\n                otherwise: {\n                    type: 'object',\n                    rules: [{\n                        name: 'assert',\n                        args: {\n                            schema: {\n                                type: 'any',\n                                flags: { only: true },\n                                allow: [{ override: true }, { ref: { path: ['a', 'b'] } }]\n                            },\n                            subject: { ref: { path: ['a', 'b'] } }\n                        }\n                    }],\n                    keys: {\n                        a: {\n                            type: 'string',\n                            rules: [\n                                { name: 'min', args: { limit: { ref: { path: ['b', 'c'] } } } },\n                                { name: 'max', args: { limit: { ref: { path: ['b', 'c'] } } } },\n                                { name: 'length', args: { limit: { ref: { path: ['b', 'c'] } } } }\n                            ]\n                        }\n                    },\n                    dependencies: [{\n                        rel: 'with',\n                        key: 'a',\n                        peers: ['b']\n                    },\n                    {\n                        rel: 'without',\n                        key: 'b',\n                        peers: ['c']\n                    }]\n                }\n            }]\n        });\n    });\n\n    describe('clone()', () => {\n\n        it('clones reference', () => {\n\n            const ref = Joi.ref('a.b.c');\n            const clone = ref.clone();\n            expect(ref).to.equal(clone);\n            expect(ref).to.not.shallow.equal(clone);\n        });\n    });\n\n    describe('context()', () => {\n\n        it('ignores global prefix when conflicts with separator', () => {\n\n            expect(Joi.ref('$a$b', { separator: '$' })).to.equal({\n                adjust: null,\n                in: false,\n                iterables: null,\n                map: null,\n                separator: '$',\n                type: 'value',\n                ancestor: 0,\n                path: ['a', 'b'],\n                depth: 2,\n                key: 'a$b',\n                root: 'a',\n                display: 'ref:$a$b'\n            });\n        });\n\n        it('ignores local prefix when conflicts with separator', () => {\n\n            expect(Joi.ref('#a#b', { separator: '#' })).to.equal({\n                adjust: null,\n                in: false,\n                iterables: null,\n                map: null,\n                separator: '#',\n                type: 'value',\n                ancestor: 0,\n                path: ['a', 'b'],\n                depth: 2,\n                key: 'a#b',\n                root: 'a',\n                display: 'ref:#a#b'\n            });\n        });\n    });\n\n    describe('create()', () => {\n\n        it('throws when key is missing', () => {\n\n            expect(() => Joi.ref(5)).to.throw('Invalid reference key: 5');\n        });\n\n        it('finds root with default separator', () => {\n\n            expect(Joi.ref('a.b.c').root).to.equal('a');\n        });\n\n        it('finds root with default separator and options', () => {\n\n            expect(Joi.ref('a.b.c', {}).root).to.equal('a');\n        });\n\n        it('finds root with custom separator', () => {\n\n            expect(Joi.ref('a+b+c', { separator: '+' }).root).to.equal('a');\n        });\n    });\n});\n"
  },
  {
    "path": "test/template.js",
    "content": "'use strict';\n\nconst Code = require('@hapi/code');\nconst Lab = require('@hapi/lab');\nconst Joi = require('..');\n\nconst Helper = require('./helper');\n\nconst internals = {};\n\n\nconst { describe, it } = exports.lab = Lab.script();\nconst { expect } = Code;\n\n\ndescribe('Template', () => {\n\n    it('skips template without {', () => {\n\n        const source = 'text without variables';\n        const template = Joi.x(source);\n\n        expect(template.source).to.equal(source);\n        expect(template.isDynamic()).to.be.false();\n        expect(template.render()).to.equal(source);\n    });\n\n    it('skips template without variables', () => {\n\n        const source = 'text {{{ without }}} any }} variables';\n        const template = Joi.x(source);\n\n        expect(template.source).to.equal(source);\n        expect(template.isDynamic()).to.be.false();\n        expect(template.render()).to.equal(source);\n    });\n\n    it('skips template without variables (trailing {)', () => {\n\n        const source = 'text {{{ without }}} any }} variables {';\n        const template = Joi.x(source);\n\n        expect(template.source).to.equal(source);\n        expect(template.isDynamic()).to.be.false();\n        expect(template.render()).to.equal(source);\n    });\n\n    it('skips template without variables (trailing {{)', () => {\n\n        const source = 'text {{{ without }}} any }} variables {{';\n        const template = Joi.x(source);\n\n        expect(template.source).to.equal(source);\n        expect(template.isDynamic()).to.be.false();\n        expect(template.render()).to.equal(source);\n    });\n\n    it('skips template without reference variables (trailing {{)', () => {\n\n        const source = 'text {\"x\"} {{{ without }}} any }} variables {{';\n        const template = Joi.x(source);\n\n        expect(template.source).to.equal(source);\n        expect(template.isDynamic()).to.be.false();\n        expect(template.render()).to.equal('text x {{{ without }}} any }} variables {{');\n    });\n\n    it('skips template without variables (escaped)', () => {\n\n        const source = 'text {{{ without }}} any }} \\\\{{escaped}} variables';\n        const template = Joi.x(source);\n\n        expect(template.source).to.equal(source);\n        expect(template.isDynamic()).to.be.false();\n        expect(template.render()).to.equal('text {{{ without }}} any }} {{escaped}} variables');\n    });\n\n    it('parses template (escaped)', () => {\n\n        const source = 'text {{$x}}{{$y}}{{$z}} \\\\{{escaped}} xxx abc {{{ignore}} 123 {{x';\n        const template = Joi.x(source);\n\n        expect(template.source).to.equal(source);\n        expect(template.render({}, {}, { context: { x: 'hello', y: '!' } })).to.equal('text hello&#x21; {{escaped}} xxx abc {{{ignore}} 123 {{x');\n        expect(template.render({}, {}, { context: { x: 'hello', y: '!' } }, {}, { errors: { escapeHtml: false } })).to.equal('text hello! {{escaped}} xxx abc {{{ignore}} 123 {{x');\n    });\n\n    it('parses template with missing elements in binary operation', () => {\n\n        const source = 'text {$x || $y}';\n        const template = Joi.x(source);\n\n        expect(template.source).to.equal(source);\n        expect(template.render({}, {}, { context: { x: 'hello' } })).to.equal('text hello');\n        expect(template.render({}, {}, { context: { y: 'hello' } })).to.equal('text hello');\n        expect(template.render({}, {}, { context: {} })).to.equal('text null');\n    });\n\n    it('parses template with single variable', () => {\n\n        const source = '{$x}';\n        const template = Joi.x(source);\n\n        expect(template.source).to.equal(source);\n        expect(template.render({}, {}, { context: { x: 'hello' } })).to.equal('hello');\n    });\n\n    it('parses template (raw)', () => {\n\n        const source = 'text {$x}{$y}{$z} \\\\{{escaped}} xxx abc {{{ignore}} 123 {{x';\n        const template = Joi.x(source);\n\n        expect(template.source).to.equal(source);\n        expect(template.render({}, {}, { context: { x: 'hello', y: '!' } })).to.equal('text hello! {{escaped}} xxx abc {{{ignore}} 123 {{x');\n    });\n\n    it('parses template with odd {{ variables', () => {\n\n        const source = 'text {{$\\\\{{\\\\}} }} \\\\{{boom}} {{!\\\\}}';\n        const template = Joi.x(source);\n\n        expect(template.source).to.equal(source);\n        expect(template.render({}, {}, { context: { '{{}}': 'and' } })).to.equal('text and {{boom}} {{!}}');\n    });\n\n    it('throws on invalid characters', () => {\n\n        expect(() => Joi.x('test\\u0000')).to.throw('Template source cannot contain reserved control characters');\n        expect(() => Joi.x('test\\u0001')).to.throw('Template source cannot contain reserved control characters');\n    });\n\n    describe('isExpression()', () => {\n\n        it('checks if item is a joi template', () => {\n\n            expect(Joi.isExpression(null)).to.be.false();\n            expect(Joi.isExpression({})).to.be.false();\n            expect(Joi.isExpression('test')).to.be.false();\n            expect(Joi.isExpression(Joi.x('test'))).to.be.true();\n        });\n    });\n\n    describe('_ref()', () => {\n\n        it('errors on template with invalid formula', () => {\n\n            const source = '{x +}';\n            expect(() => Joi.x(source)).to.throw('Invalid template variable \"x +\" fails due to: Formula contains invalid trailing operator');\n        });\n    });\n\n    describe('stringify()', () => {\n\n        it('resolves ref', () => {\n\n            const ref = Joi.ref('a', { render: 'true' });\n            const schema = Joi.object({\n                a: Joi.number(),\n                b: Joi.number().min(ref)\n            });\n\n            expect(schema.validate({ a: 10, b: 5 }).error).to.be.an.error('\"b\" must be greater than or equal to 10');\n        });\n\n        it('resolves ref (in)', () => {\n\n            const ref = Joi.in('a', { render: 'true' });\n            const schema = Joi.object({\n                a: Joi.array().items(Joi.number()),\n                b: Joi.number().valid(ref)\n            });\n\n            expect(schema.validate({ a: [1, 2, 3], b: 5 }).error).to.be.an.error('\"b\" must be [1, 2, 3]');\n        });\n    });\n\n    describe('functions', () => {\n\n        describe('extensions', () => {\n\n            it('allow new functions', () => {\n\n                const schema = Joi.object().rename(/.*/, Joi.x('{ uppercase(#0) }', {\n                    functions: {\n                        uppercase(value) {\n\n                            if (typeof value === 'string') {\n                                return value.toUpperCase();\n                            }\n\n                            return value;\n                        }\n                    }\n                }));\n                Helper.validate(schema, {}, [\n                    [{ a: 1, b: true }, true, { A: 1, B: true }],\n                    [{ a: 1, [Symbol.for('b')]: true }, true, { A: 1, [Symbol.for('b')]: true }]\n                ]);\n            });\n\n            it('overrides built-in functions', () => {\n\n                const schema = Joi.object({\n                    a: Joi.array().length(Joi.x('{length(b)}', {\n                        functions: {\n                            length(value) {\n\n                                return value.length - 1;\n                            }\n                        }\n                    })),\n                    b: Joi.string()\n                });\n\n                Helper.validate(schema, [\n                    [{ a: [1], b: 'xx' }, true],\n                    [{ a: [1], b: 'x' }, false, '\"a\" must contain {length(b)} items']\n                ]);\n            });\n        });\n\n        describe('msg()', () => {\n\n            it('ignores missing options', () => {\n\n                const template = Joi.x('{msg(\"x\")}');\n                expect(template.render({}, {}, {}, {})).to.equal('');\n            });\n\n            it('ignores missing codes', () => {\n\n                const template = Joi.x('{msg(\"x\")}');\n                expect(template.render({}, {}, {}, {}, { messages: [] })).to.equal('');\n            });\n\n            it('uses code in first set', () => {\n\n                const template = Joi.x('{msg(\"x\")}');\n                expect(template.render({}, {}, { errors: {} }, {}, { messages: [{ x: Joi.x('X') }] })).to.equal('X');\n            });\n\n            it('uses code in second set', () => {\n\n                const template = Joi.x('{msg(\"x\")}');\n                expect(template.render({}, {}, { errors: {} }, {}, { messages: [null, { x: Joi.x('X') }] })).to.equal('X');\n            });\n        });\n\n        describe('number()', () => {\n\n            it('casts values to numbers', () => {\n\n                const schema = Joi.valid(Joi.x('{number(1) + number(true) + number(false) + number(\"1\") + number($x)}'));\n                Helper.validate(schema, { context: { x: {} } }, [[3, true]]);\n                Helper.validate(schema, { context: { x: {} } }, [[4, false, '\"value\" must be [{number(1) + number(true) + number(false) + number(\"1\") + number($x)}]']]);\n            });\n        });\n\n        describe('length()', () => {\n\n            it('calculates object size', () => {\n\n                const schema = Joi.object({\n                    a: Joi.array().length(Joi.x('{length(b)}')),\n                    b: Joi.object()\n                });\n\n                Helper.validate(schema, [\n                    [{ a: [1, 2], b: { a: true, b: true } }, true],\n                    [{ a: [1, 2, 3], b: { a: true, b: true } }, false, '\"a\" must contain {length(b)} items']\n                ]);\n            });\n\n            it('calcualtes array size', () => {\n\n                const schema = Joi.object({\n                    a: Joi.array().length(Joi.x('{length(b)}')),\n                    b: Joi.array()\n                });\n\n                Helper.validate(schema, [\n                    [{ a: [1, 2], b: [2, 3] }, true],\n                    [{ a: [1, 2, 3], b: [1] }, false, '\"a\" must contain {length(b)} items']\n                ]);\n            });\n\n            it('handles null', () => {\n\n                const schema = Joi.object({\n                    a: Joi.array().length(Joi.x('{length(b)}')),\n                    b: Joi.array().allow(null)\n                });\n\n                Helper.validate(schema, [\n                    [{ a: [1], b: null }, false, '\"a\" limit references \"{length(b)}\" which must be a positive integer']\n                ]);\n            });\n\n            it('handles strings', () => {\n\n                const schema = Joi.object({\n                    a: Joi.array().length(Joi.x('{length(b)}')),\n                    b: Joi.string()\n                });\n\n                Helper.validate(schema, [\n                    [{ a: [1], b: 'x' }, true],\n                    [{ a: [1], b: 'xx' }, false, '\"a\" must contain {length(b)} items']\n                ]);\n            });\n\n            it('handles items without length', () => {\n\n                const schema = Joi.object({\n                    a: Joi.array().length(Joi.x('{length(b)}')),\n                    b: Joi.number()\n                });\n\n                Helper.validate(schema, [\n                    [{ a: [1], b: 1 }, false, '\"a\" limit references \"{length(b)}\" which must be a positive integer']\n                ]);\n            });\n        });\n    });\n});\n"
  },
  {
    "path": "test/trace.js",
    "content": "'use strict';\n\nconst Code = require('@hapi/code');\nconst Lab = require('@hapi/lab');\nconst Joi = require('..');\nconst Pinpoint = require('@hapi/pinpoint');\n\n\nconst internals = {};\n\n\nconst { describe, it, afterEach } = exports.lab = Lab.script();\nconst { expect } = Code;\n\n\ndescribe('Trace', () => {\n\n    describe('trace()', () => {\n\n        afterEach(() => Joi.untrace());\n\n        it('reuses tracer', () => {\n\n            const tracer = Joi.trace();\n            expect(Joi.trace()).to.shallow.equal(tracer);\n            Joi.untrace();\n\n            expect(Joi.trace()).to.not.shallow.equal(tracer);\n        });\n\n        it('tracks rules', () => {\n\n            const tracer = Joi.trace();\n\n            const schema = Joi.string()\n                .lowercase()\n                .pattern(/\\d/);\n\n            schema.validate('a');\n            schema.validate('4');\n\n            expect(tracer.report()).to.be.null();\n        });\n\n        it('reports coverage', () => {\n\n            const tracer = Joi.trace();\n\n            const schema = Joi.string()\n                .lowercase()\n                .pattern(/\\d/)\n                .max(20)\n                .min(10);\n\n            schema.validate('123456789012345678901');\n\n            expect(tracer.report(__filename)).to.equal([\n                {\n                    filename: __filename,\n                    line: Pinpoint.location().line - 5,\n                    message: 'Schema missing tests for pattern (always pass), max (always error), min (never used)',\n                    missing: [\n                        {\n                            status: 'always pass',\n                            rule: 'pattern'\n                        },\n                        {\n                            status: 'always error',\n                            rule: 'max'\n                        },\n                        {\n                            status: 'never used',\n                            rule: 'min'\n                        }\n                    ],\n                    severity: 'error'\n                }\n            ]);\n\n            expect(tracer.report('missing')).to.be.null();\n        });\n\n        it('reports nested coverage', () => {\n\n            const tracer = Joi.trace();\n\n            const schema = Joi.object({\n                a: {\n                    x: Joi.string()\n                        .lowercase()\n                        .pattern(/\\d/)\n                        .max(20)\n                        .min(10)\n                },\n\n                b: Joi.number()\n                    .min(100)\n            });\n\n            schema.validate({ a: { x: '123456789012345678901' }, b: 11 });\n\n            expect(tracer.report(__filename)).to.equal([\n                {\n                    filename: __filename,\n                    line: Pinpoint.location().line - 5,\n                    severity: 'error',\n                    message: 'Schema missing tests for a.x:pattern (always pass), a.x:max (always error), a.x:min (never used), b (never reached)',\n                    missing: [\n                        {\n                            paths: [['a', 'x']],\n                            status: 'always pass',\n                            rule: 'pattern'\n                        },\n                        {\n                            paths: [['a', 'x']],\n                            status: 'always error',\n                            rule: 'max'\n                        },\n                        {\n                            paths: [['a', 'x']],\n                            status: 'never used',\n                            rule: 'min'\n                        },\n                        {\n                            paths: [['b']],\n                            status: 'never reached'\n                        }\n                    ]\n                }\n            ]);\n        });\n\n        it('uses schema id', () => {\n\n            const tracer = Joi.trace();\n\n            const schema = Joi.object({\n                a: Joi.object({\n                    x: Joi.string()\n                        .lowercase()\n                        .pattern(/\\d/)\n                        .max(20)\n                        .min(10)\n                })\n                    .id('A'),\n\n                b: Joi.number()\n                    .min(100),\n\n                c: Joi.any()\n                    .empty(Joi.number().max(10))\n            });\n\n            schema.validate({ a: { x: '123456789012345678901' }, b: 11, c: 5 });\n\n            expect(tracer.report(__filename)).to.equal([\n                {\n                    filename: __filename,\n                    line: Pinpoint.location().line - 5,\n                    severity: 'error',\n                    message: 'Schema missing tests for A.x:pattern (always pass), A.x:max (always error), A.x:min (never used), b (never reached), c (never reached)',\n                    missing: [\n                        {\n                            paths: [['A', 'x']],\n                            status: 'always pass',\n                            rule: 'pattern'\n                        },\n                        {\n                            paths: [['A', 'x']],\n                            status: 'always error',\n                            rule: 'max'\n                        },\n                        {\n                            paths: [['A', 'x']],\n                            status: 'never used',\n                            rule: 'min'\n                        },\n                        {\n                            paths: [['b']],\n                            status: 'never reached'\n                        },\n                        {\n                            paths: [['c']],\n                            status: 'never reached'\n                        }\n                    ]\n                }\n            ]);\n        });\n\n        it('handles reused schema', () => {\n\n            const tracer = Joi.trace();\n\n            const key = Joi.any().empty(Joi.number().max(10));\n            const schema = Joi.object({\n                a: key,\n                b: key\n            });\n\n            schema.validate({ a: 5, b: 6 });\n\n            expect(tracer.report(__filename)).to.equal([\n                {\n                    filename: __filename,\n                    line: Pinpoint.location().line - 5,\n                    message: 'Schema missing tests for a.@empty:max (always pass)',\n                    severity: 'error',\n                    missing: [\n                        {\n                            paths: [['a', '@empty'], ['b', '@empty']],\n                            rule: 'max',\n                            status: 'always pass'\n                        }\n                    ]\n                }\n            ]);\n        });\n\n        it('reports skipped schema without rule', () => {\n\n            const tracer = Joi.trace();\n\n            const schema = Joi.alternatives([\n                Joi.string(),\n                Joi.number()\n            ]);\n\n            schema.validate('x');\n\n            expect(tracer.report(__filename)).to.equal([\n                {\n                    filename: __filename,\n                    line: Pinpoint.location().line - 5,\n                    message: 'Schema missing tests for @matches[1] (never reached)',\n                    severity: 'error',\n                    missing: [\n                        {\n                            paths: [['@matches', 1]],\n                            status: 'never reached'\n                        }\n                    ]\n                }\n            ]);\n        });\n\n        it('reports array items', () => {\n\n            const tracer = Joi.trace();\n\n            const schema = Joi.array().items(Joi.boolean(), Joi.string());\n\n            schema.validate([true]);\n\n            expect(tracer.report(__filename)).to.equal([\n                {\n                    filename: __filename,\n                    line: Pinpoint.location().line - 5,\n                    severity: 'error',\n                    message: 'Schema missing tests for items (always pass), @items[1] (never reached)',\n                    missing: [\n                        {\n                            rule: 'items',\n                            status: 'always pass'\n                        },\n                        {\n                            paths: [['@items', 1]],\n                            status: 'never reached'\n                        }\n                    ]\n                }\n            ]);\n        });\n\n        it('tracks valid and invalid values', () => {\n\n            const tracer = Joi.trace();\n\n            const schema = Joi.string()\n                .allow('a', 'b', 'c')\n                .deny('x', 'y', 'z');\n\n            schema.validate('a');\n            schema.validate('b');\n            schema.validate('x');\n            schema.validate('y');\n\n            expect(tracer.report(__filename)).to.equal([\n                {\n                    filename: __filename,\n                    line: Pinpoint.location().line - 8,\n                    message: 'Schema missing tests for valids (c), invalids (z)',\n                    severity: 'error',\n                    missing: [\n                        {\n                            rule: 'valids',\n                            status: ['c']\n                        },\n                        {\n                            rule: 'invalids',\n                            status: ['z']\n                        }\n                    ]\n                }\n            ]);\n\n            schema.validate('c');\n            schema.validate('z');\n\n            expect(tracer.report(__filename)).to.be.null();\n        });\n\n        it('tracks valid values (refs)', () => {\n\n            const tracer = Joi.trace();\n\n            const schema = Joi.object({\n                a: Joi.string(),\n                b: Joi.string(),\n                c: Joi.string()\n                    .allow(Joi.ref('a'), Joi.ref('b'))\n            });\n\n            schema.validate({ a: 'a', b: 'b', c: 'a' });\n\n            expect(tracer.report(__filename)).to.equal([\n                {\n                    filename: __filename,\n                    line: Pinpoint.location().line - 5,\n                    message: 'Schema missing tests for valids (ref:b)',\n                    missing: [\n                        {\n                            rule: 'valids',\n                            status: ['ref:b']\n                        }\n                    ],\n                    severity: 'error'\n                }\n            ]);\n\n            schema.validate({ a: 'a', b: 'b', c: 'b' });\n\n            expect(tracer.report(__filename)).to.be.null();\n        });\n\n        it('tracks default and failover', () => {\n\n            const tracer = Joi.trace();\n\n            const schema = Joi.string()\n                .default('x')\n                .failover('y');\n\n            schema.validate('test');\n\n            expect(tracer.report(__filename)).to.equal([\n                {\n                    filename: __filename,\n                    line: Pinpoint.location().line - 5,\n                    message: 'Schema missing tests for default (never used), failover (never used)',\n                    missing: [\n                        {\n                            rule: 'default',\n                            status: 'never used'\n                        },\n                        {\n                            rule: 'failover',\n                            status: 'never used'\n                        }\n                    ],\n                    severity: 'error'\n                }\n            ]);\n\n            schema.validate();\n            schema.validate({});\n\n            expect(tracer.report(__filename)).to.be.null();\n        });\n\n        it('reports coverage with manual location', () => {\n\n            const tracer = Joi.trace();\n\n            const schema = Joi.string().tracer()\n                .lowercase()\n                .pattern(/\\d/)\n                .max(20)\n                .min(10);\n\n            schema.validate('123456789012345678901');\n\n            expect(tracer.report(__filename)).to.equal([\n                {\n                    filename: __filename,\n                    line: Pinpoint.location().line - 11,\n                    message: 'Schema missing tests for pattern (always pass), max (always error), min (never used)',\n                    missing: [\n                        {\n                            status: 'always pass',\n                            rule: 'pattern'\n                        },\n                        {\n                            status: 'always error',\n                            rule: 'max'\n                        },\n                        {\n                            status: 'never used',\n                            rule: 'min'\n                        }\n                    ],\n                    severity: 'error'\n                }\n            ]);\n        });\n\n        it('reports coverage with manual location (concat override)', () => {\n\n            const tracer = Joi.trace();\n\n            const base = Joi.string().lowercase()\n                .pattern(/\\d/)\n                .tracer();\n\n            const schema = Joi.any().concat(base).tracer()\n                .max(20)\n                .min(10);\n\n            schema.validate('123456789012345678901');\n\n            expect(tracer.report(__filename)).to.equal([\n                {\n                    filename: __filename,\n                    line: Pinpoint.location().line - 9,\n                    message: 'Schema missing tests for pattern (always pass), max (always error), min (never used)',\n                    missing: [\n                        {\n                            status: 'always pass',\n                            rule: 'pattern'\n                        },\n                        {\n                            status: 'always error',\n                            rule: 'max'\n                        },\n                        {\n                            status: 'never used',\n                            rule: 'min'\n                        }\n                    ],\n                    severity: 'error'\n                }\n            ]);\n        });\n\n        it('reports coverage with manual location (concat)', () => {\n\n            const tracer = Joi.trace();\n\n            const base = Joi.string().lowercase()\n                .pattern(/\\d/)\n                .tracer();\n\n            const schema = Joi.any().concat(base)\n                .max(20)\n                .min(10);\n\n            schema.validate('123456789012345678901');\n\n            expect(tracer.report(__filename)).to.equal([\n                {\n                    filename: __filename,\n                    line: Pinpoint.location().line - 11,\n                    message: 'Schema missing tests for pattern (always pass), max (always error), min (never used)',\n                    missing: [\n                        {\n                            status: 'always pass',\n                            rule: 'pattern'\n                        },\n                        {\n                            status: 'always error',\n                            rule: 'max'\n                        },\n                        {\n                            status: 'never used',\n                            rule: 'min'\n                        }\n                    ],\n                    severity: 'error'\n                }\n            ]);\n        });\n\n        it('handles when()', () => {\n\n            const tracer = Joi.trace();\n\n            const schema = Joi.object({\n                a: Joi.boolean(),\n                b: Joi.boolean(),\n                c: Joi.string()\n                    .when('a', { is: true, then: Joi.string().min(10).allow('y') })\n                    .when('b', { is: true, then: Joi.string().max(100) })\n            });\n\n            schema.validate({ a: true, b: true, c: 'x' });\n            schema.validate({ a: true, b: true, c: 'y' });\n\n            expect(tracer.report(__filename)).to.equal([\n                {\n                    filename: __filename,\n                    line: Pinpoint.location().line - 6,\n                    message: 'Schema missing tests for c.@whens[0]:min (always error), c.@whens[1]:max (never used)',\n                    severity: 'error',\n                    missing: [\n                        {\n                            paths: [['c', '@whens', 0]],\n                            rule: 'min',\n                            status: 'always error'\n                        },\n                        {\n                            paths: [['c', '@whens', 1]],\n                            rule: 'max',\n                            status: 'never used'\n                        }\n                    ]\n                }\n            ]);\n        });\n    });\n\n    describe('debug', () => {\n\n        it('creates debug log', () => {\n\n            const schema = Joi.object({\n                a: {\n                    x: Joi.string()\n                        .lowercase()\n                        .pattern(/\\d/)\n                        .max(20)\n                        .min(10)\n                },\n\n                b: Joi.number()\n                    .min(100),\n\n                c: Joi.not('y')\n            });\n\n            const debug = schema.validate({ a: { x: '12345678901234567890' }, b: 110, c: 'y' }, { debug: true }).debug;\n            expect(debug).to.equal([\n                { type: 'entry', path: [] },\n                { type: 'entry', path: ['a'] },\n                { type: 'entry', path: ['a', 'x'] },\n                { type: 'rule', name: 'case', result: 'pass', path: ['a', 'x'] },\n                { type: 'rule', name: 'pattern', result: 'pass', path: ['a', 'x'] },\n                { type: 'rule', name: 'max', result: 'pass', path: ['a', 'x'] },\n                { type: 'rule', name: 'min', result: 'pass', path: ['a', 'x'] },\n                { type: 'entry', path: ['b'] },\n                { type: 'rule', name: 'min', result: 'pass', path: ['b'] },\n                { type: 'entry', path: ['c'] },\n                { type: 'invalid', value: 'y', path: ['c'] },\n                { type: 'resolve', ref: 'ref:local:label', to: 'c', path: ['c'] }\n            ]);\n        });\n\n        it('creates debug log (async)', async () => {\n\n            const schema = Joi.object({\n                a: {\n                    x: Joi.string()\n                        .lowercase()\n                        .pattern(/\\d/)\n                        .max(20)\n                        .min(10)\n                },\n\n                b: Joi.number()\n                    .min(100),\n\n                c: Joi.not('y')\n            });\n\n            const err = await expect(schema.validateAsync({ a: { x: '12345678901234567890' }, b: 110, c: 'y' }, { debug: true })).to.reject();\n            expect(err.debug).to.equal([\n                { type: 'entry', path: [] },\n                { type: 'entry', path: ['a'] },\n                { type: 'entry', path: ['a', 'x'] },\n                { type: 'rule', name: 'case', result: 'pass', path: ['a', 'x'] },\n                { type: 'rule', name: 'pattern', result: 'pass', path: ['a', 'x'] },\n                { type: 'rule', name: 'max', result: 'pass', path: ['a', 'x'] },\n                { type: 'rule', name: 'min', result: 'pass', path: ['a', 'x'] },\n                { type: 'entry', path: ['b'] },\n                { type: 'rule', name: 'min', result: 'pass', path: ['b'] },\n                { type: 'entry', path: ['c'] },\n                { type: 'invalid', value: 'y', path: ['c'] },\n                { type: 'resolve', ref: 'ref:local:label', to: 'c', path: ['c'] }\n            ]);\n\n            const { debug } = await schema.validateAsync({ a: { x: '12345678901234567890' }, b: 110, c: 'x' }, { debug: true });\n            expect(debug).to.equal([\n                { type: 'entry', path: [] },\n                { type: 'entry', path: ['a'] },\n                { type: 'entry', path: ['a', 'x'] },\n                { type: 'rule', name: 'case', result: 'pass', path: ['a', 'x'] },\n                { type: 'rule', name: 'pattern', result: 'pass', path: ['a', 'x'] },\n                { type: 'rule', name: 'max', result: 'pass', path: ['a', 'x'] },\n                { type: 'rule', name: 'min', result: 'pass', path: ['a', 'x'] },\n                { type: 'entry', path: ['b'] },\n                { type: 'rule', name: 'min', result: 'pass', path: ['b'] },\n                { type: 'entry', path: ['c'] }\n            ]);\n        });\n\n        it('logs when forks', () => {\n\n            const schema = Joi.object({\n                a: Joi.number(),\n\n                b: Joi.number()\n                    .when('a', { is: 1, then: 2, otherwise: 3 })\n                    .when('a', { is: 4, then: 5, otherwise: 6 }),       // This conflicts with the first when()\n\n                c: Joi.number()\n                    .when('a', [\n                        { is: 1, then: 2 },\n                        { is: 3, then: 4 }\n                    ])\n            });\n\n            const debug = schema.validate({ a: 1, b: 6 }, { debug: true }).debug;\n            expect(debug).to.equal([\n                { type: 'entry', path: [] },\n                { type: 'entry', path: ['a'] },\n                { type: 'resolve', ref: 'ref:a', to: 1, path: ['b'] },\n                { type: 'entry', path: ['b', '0.is'] },\n                { type: 'valid', value: 1, path: ['b', '0.is'] },\n                { type: 'resolve', ref: 'ref:a', to: 1, path: ['b'] },\n                { type: 'entry', path: ['b', '1.is'] },\n                { type: 'rule', name: 'when', result: '0.then, 1.otherwise', path: ['b'] },\n                { type: 'entry', path: ['b'] },\n                { type: 'valid', value: 6, path: ['b'] },\n                { type: 'resolve', ref: 'ref:a', to: 1, path: ['c'] },\n                { type: 'entry', path: ['c', '0.0.is'] },\n                { type: 'valid', value: 1, path: ['c', '0.0.is'] },\n                { type: 'rule', name: 'when', result: '0.0.then', path: ['c'] },\n                { type: 'entry', path: ['c'] }\n            ]);\n        });\n\n        it('logs when on sub key', () => {\n\n            const building = Joi.object({\n                a: Joi.object({\n                    name: Joi.string().cache(),\n                    lucky: Joi.string()\n                        .when('name', { is: 'thirteen', then: Joi.valid('no') })\n                })\n            });\n\n            const structure = {\n                a: { name: 'first' }\n            };\n\n            const debug = building.validate(structure, { debug: true }).debug;\n            expect(debug).to.equal([\n                { type: 'entry', path: [] },\n                { type: 'entry', path: ['a'] },\n                { type: 'validate', name: 'cached', result: false, path: ['a', 'name'] },\n                { type: 'entry', path: ['a', 'name'] },\n                { type: 'resolve', ref: 'ref:name', to: 'first', path: ['a', 'lucky'] },\n                { type: 'entry', path: ['a', 'lucky', '0.is'] },\n                { type: 'rule', name: 'when', result: '', path: ['a', 'lucky'] },\n                { type: 'entry', path: ['a', 'lucky'] }\n            ]);\n        });\n\n        it('logs sub when condition (then)', () => {\n\n            const sub = Joi.when('b', { is: true, then: 2, otherwise: 3 });\n            const schema = Joi.object({\n                a: Joi.boolean().required(),\n                b: Joi.boolean().required(),\n                c: Joi.number()\n                    .when('a', { is: true, then: sub, otherwise: 1 })\n            });\n\n            const debug = schema.validate({ a: true, b: true, c: 2 }, { debug: true }).debug;\n            expect(debug).to.equal([\n                { type: 'entry', path: [] },\n                { type: 'entry', path: ['a'] },\n                { type: 'entry', path: ['b'] },\n                { type: 'resolve', ref: 'ref:a', to: true, path: ['c'] },\n                { type: 'entry', path: ['c', '0.is'] },\n                { type: 'valid', value: true, path: ['c', '0.is'] },\n                { type: 'resolve', ref: 'ref:b', to: true, path: ['c', '0.then'] },\n                { type: 'entry', path: ['c', '0.then', '0.is'] },\n                { type: 'valid', value: true, path: ['c', '0.then', '0.is'] },\n                { type: 'rule', name: 'when', result: '0.then', path: ['c', '0.then'] },\n                { type: 'rule', name: 'when', result: '0.then(0.then)', path: ['c'] },\n                { type: 'entry', path: ['c'] },\n                { type: 'valid', value: 2, path: ['c'] }\n            ]);\n        });\n\n        it('logs sub when condition (otherwise)', () => {\n\n            const sub = Joi.when('b', { is: true, then: 2, otherwise: 3 });\n            const schema = Joi.object({\n                a: Joi.boolean().required(),\n                b: Joi.boolean().required(),\n                c: Joi.number()\n                    .when('a', { is: true, then: 1, otherwise: sub })\n            });\n\n            const debug = schema.validate({ a: false, b: true, c: 2 }, { debug: true }).debug;\n            expect(debug).to.equal([\n                { type: 'entry', path: [] },\n                { type: 'entry', path: ['a'] },\n                { type: 'entry', path: ['b'] },\n                { type: 'resolve', ref: 'ref:a', to: false, path: ['c'] },\n                { type: 'entry', path: ['c', '0.is'] },\n                { type: 'resolve', ref: 'ref:b', to: true, path: ['c', '0.otherwise'] },\n                { type: 'entry', path: ['c', '0.otherwise', '0.is'] },\n                { type: 'valid', value: true, path: ['c', '0.otherwise', '0.is'] },\n                { type: 'rule', name: 'when', result: '0.then', path: ['c', '0.otherwise'] },\n                { type: 'rule', name: 'when', result: '0.otherwise(0.then)', path: ['c'] },\n                { type: 'entry', path: ['c'] },\n                { type: 'valid', value: 2, path: ['c'] }\n            ]);\n        });\n\n        it('logs compounded whens', () => {\n\n            const schema = Joi.object({\n                a: Joi.string(),\n                b: Joi.boolean()\n            })\n                .when('.a', {\n                    is: 'x',\n                    then: Joi.object({\n                        c: Joi.boolean()\n                    })\n                })\n                .when('.b', {\n                    is: true,\n                    then: Joi.object({\n                        c: Joi.forbidden()\n                    })\n                });\n\n            const debug = schema.validate({ a: 'x', b: true }, { debug: true }).debug;\n            expect(debug).to.equal([\n                { type: 'resolve', ref: 'ref:.a', to: 'x', path: [] },\n                { type: 'entry', path: ['0.is'] },\n                { type: 'valid', value: 'x', path: ['0.is'] },\n                { type: 'resolve', ref: 'ref:.b', to: true, path: [] },\n                { type: 'entry', path: ['1.is'] },\n                { type: 'valid', value: true, path: ['1.is'] },\n                { type: 'rule', name: 'when', result: '0.then, 1.then', path: [] },\n                { type: 'entry', path: [] },\n                { type: 'entry', path: ['a'] },\n                { type: 'entry', path: ['b'] },\n                { type: 'entry', path: ['c'] }\n            ]);\n        });\n\n        it('logs changes in value', () => {\n\n            const schema = Joi.number().raw();\n\n            const debug = schema.validate('123', { debug: true }).debug;\n            expect(debug).to.equal([\n                { type: 'entry', path: [] },\n                { type: 'value', by: 'coerced', from: '123', to: 123, path: [] },\n                { type: 'value', by: 'raw', from: 123, to: '123', path: [] }\n            ]);\n        });\n\n        it('logs changes in value (with name)', () => {\n\n            const schema = Joi.number().cast('string');\n\n            const debug = schema.validate('123', { debug: true }).debug;\n            expect(debug).to.equal([\n                { type: 'entry', path: [] },\n                { type: 'value', by: 'coerced', from: '123', to: 123, path: [] },\n                { type: 'value', by: 'cast', name: 'string', from: 123, to: '123', path: [] }\n            ]);\n        });\n\n        it('debugs multiple time same schema (link)', () => {\n\n            const schema = Joi.object({\n                a: Joi.boolean(),\n                b: Joi.link('a')\n                    .when('a', { then: Joi.forbidden() })\n            });\n\n            const debug1 = schema.validate({ a: true, b: true }, { debug: true }).debug;\n            expect(debug1).to.equal([\n                { type: 'entry', path: [] },\n                { type: 'entry', path: ['a'] },\n                { type: 'resolve', ref: 'ref:a', to: true, path: ['b'] },\n                { type: 'entry', path: ['b', '0.is'] },\n                { type: 'rule', name: 'when', result: '0.then', path: ['b'] },\n                { type: 'entry', path: ['b'] },\n                { type: 'resolve', ref: 'ref:local:label', to: 'b', path: ['b'] }\n            ]);\n\n            const debug2 = schema.validate({ a: false, b: false }, { debug: true }).debug;\n            expect(debug2).to.equal([\n                { type: 'entry', path: [] },\n                { type: 'entry', path: ['a'] },\n                { type: 'resolve', ref: 'ref:a', to: false, path: ['b'] },\n                { type: 'entry', path: ['b', '0.is'] },\n                { type: 'invalid', value: false, path: ['b', '0.is'] },\n                { type: 'rule', name: 'when', result: '', path: ['b'] },\n                { type: 'entry', path: ['b'] },\n                { type: 'entry', path: ['b', 'link:ref:a:boolean'] }\n            ]);\n        });\n\n        it('debugs multiple time same schema (whens)', () => {\n\n            const schema = Joi.object({\n                a: Joi.number(),\n                b: Joi.number()\n                    .when('a', { is: 1, then: 2 })\n                    .when('a', { is: 2, then: 3 })\n            });\n\n            const values = [\n                { a: 1, b: 2 },\n                { a: 1, b: 3 },\n                { a: 2, b: 2 },\n                { a: 2, b: 3 }\n            ];\n\n            schema.validate({ a: 1, b: 2 });        // No debug\n\n            for (const value of values) {\n                schema.validate(value, { debug: true });\n            }\n        });\n\n        it('debugs multiple time same schema (whens + partial validate)', () => {\n\n            const x = Joi.object({\n                a: Joi.number(),\n                b: Joi.number()\n                    .when('a', { is: 1, then: 2 })\n                    .when('a', { is: 2, then: 3 })\n            });\n\n            const values = [\n                { x: { a: 1, b: 2 } },\n                { x: { a: 1, b: 3 } },\n                { x: { a: 2, b: 2 } },\n                { x: { a: 2, b: 3 } }\n            ];\n\n            x.validate({ a: 1, b: 2 });         // No debug\n\n            const schema = Joi.object({ x });\n\n            for (const value of values) {\n                schema.validate(value, { debug: true });\n            }\n        });\n\n        it('debugs multiple time same schema (recursive)', () => {\n\n            const schema = Joi.object({\n                a: Joi.number(),\n                b: Joi.link('..')\n            });\n\n            const debug1 = schema.validate({ a: 1, b: { a: 2 } }, { debug: true }).debug;\n            expect(debug1).to.equal([\n                { type: 'entry', path: [] },\n                { type: 'entry', path: ['a'] },\n                { type: 'entry', path: ['b'] },\n                { type: 'entry', path: ['b', 'link:ref:..:object'] },\n                { type: 'entry', path: ['b', 'a'] },\n                { type: 'entry', path: ['b', 'b'] }\n            ]);\n\n            const debug2 = schema.validate({ a: 1, b: { a: 3, b: { a: 4 } } }, { debug: true }).debug;\n            expect(debug2).to.equal([\n                { type: 'entry', path: [] },\n                { type: 'entry', path: ['a'] },\n                { type: 'entry', path: ['b'] },\n                { type: 'entry', path: ['b', 'link:ref:..:object'] },\n                { type: 'entry', path: ['b', 'a'] },\n                { type: 'entry', path: ['b', 'b'] },\n                { type: 'entry', path: ['b', 'b', 'link:ref:..:object'] },\n                { type: 'entry', path: ['b', 'b', 'a'] },\n                { type: 'entry', path: ['b', 'b', 'b'] }\n            ]);\n        });\n\n        it('handles array item exclusions', () => {\n\n            const schema = Joi.array().items(Joi.string().forbidden());\n\n            const debug = schema.validate(['x'], { debug: true }).debug;\n            expect(debug).to.equal([\n                { type: 'entry', path: [] },\n                { type: 'entry', path: [0] },\n                { type: 'rule', name: 'items', result: 'error', path: [] },\n                { type: 'resolve', ref: 'ref:local:label', to: '[0]', path: [0] }\n            ]);\n        });\n\n        it('handles recursive links with when', () => {\n\n            const schema = Joi.object({\n                must: Joi.boolean().required(),\n                child: Joi.link('..')\n                    .when('must', { then: Joi.required() })\n            });\n\n            const debug = schema.validate({ must: true, child: { must: true, child: { must: false } } }, { debug: true }).debug;\n            expect(debug).to.equal([\n                { type: 'entry', path: [] },\n                { type: 'entry', path: ['must'] },\n                { type: 'resolve', ref: 'ref:must', to: true, path: ['child'] },\n                { type: 'entry', path: ['child', '0.is'] },\n                { type: 'rule', name: 'when', result: '0.then', path: ['child'] },\n                { type: 'entry', path: ['child'] },\n                { type: 'entry', path: ['child', 'must'] },\n                { type: 'resolve', ref: 'ref:must', to: true, path: ['child', 'child'] },\n                { type: 'entry', path: ['child', 'child', '0.is'] },\n                { type: 'rule', name: 'when', result: '0.then', path: ['child', 'child'] },\n                { type: 'entry', path: ['child', 'child'] },\n                { type: 'entry', path: ['child', 'child', 'must'] },\n                { type: 'resolve', ref: 'ref:must', to: false, path: ['child', 'child', 'child'] },\n                { type: 'entry', path: ['child', 'child', 'child', '0.is'] },\n                { type: 'invalid', value: false, path: ['child', 'child', 'child', '0.is'] },\n                { type: 'rule', name: 'when', result: '', path: ['child', 'child', 'child'] },\n                { type: 'entry', path: ['child', 'child', 'child'] }\n            ]);\n        });\n\n        it('handles link.concat() after resolved', () => {\n\n            const a = Joi.object({\n                x: Joi.link('..')\n            });\n\n            const b = Joi.object({\n                x: Joi.forbidden()\n            });\n\n            a.validate({ x: {} }, { debug: true });\n\n            const schema = a.concat(b);\n            const debug = schema.validate({ x: {} }, { debug: true }).debug;\n            expect(debug).to.equal([\n                { type: 'entry', path: [] },\n                { type: 'rule', name: 'when', result: '0.concat', path: ['x'] },\n                { type: 'entry', path: ['x'] },\n                { type: 'resolve', ref: 'ref:local:label', to: 'x', path: ['x'] }\n            ]);\n        });\n    });\n});\n"
  },
  {
    "path": "test/types/alternatives.js",
    "content": "'use strict';\n\nconst Code = require('@hapi/code');\nconst Lab = require('@hapi/lab');\nconst Joi = require('../..');\n\nconst Helper = require('../helper');\n\n\nconst internals = {};\n\n\nconst { describe, it } = exports.lab = Lab.script();\nconst { expect } = Code;\n\n\ndescribe('alternatives', () => {\n\n    it('fails when no alternatives are provided', () => {\n\n        Helper.validate(Joi.alternatives(), [\n            ['a', false, {\n                context: {\n                    label: 'value',\n                    value: 'a'\n                },\n                message: '\"value\" does not match any of the allowed types',\n                path: [],\n                type: 'alternatives.any'\n            }]\n        ]);\n    });\n\n    it('allows undefined when no alternatives are provided', () => {\n\n        Helper.validate(Joi.alternatives(), [\n            [undefined, true]\n        ]);\n    });\n\n    it('applies modifiers when higher priority converts', () => {\n\n        const schema = Joi.object({\n            a: [\n                Joi.number(),\n                Joi.string()\n            ]\n        });\n\n        Helper.validate(schema, [\n            [{ a: '5' }, true, { a: 5 }]\n        ]);\n    });\n\n    it('applies modifiers when lower priority valid is a match', () => {\n\n        const schema = Joi.object({\n            a: [\n                Joi.number(),\n                Joi.valid('5')\n            ]\n        });\n\n        Helper.validate(schema, [\n            [{ a: '5' }, true, { a: 5 }]\n        ]);\n    });\n\n    it('does not apply modifier if alternative fails', () => {\n\n        const schema = Joi.object({\n            a: [\n                Joi.object({ c: Joi.any(), d: Joi.number() }).rename('b', 'c'),\n                { b: Joi.any(), d: Joi.string() }\n            ]\n        });\n\n        Helper.validate(schema, [\n            [{ a: { b: 'any', d: 'string' } }, true]\n        ]);\n    });\n\n    it('consolidates types only when all coming from top level base errors', () => {\n\n        const schema = Joi.alternatives(\n            Joi.string(),\n            Joi.array().items(Joi.string())\n        );\n\n        Helper.validate(schema, [\n            [[1], false, '\"[0]\" must be a string']\n        ]);\n    });\n\n    it('consolidates types only when all are base or valids', () => {\n\n        const schema = Joi.alternatives(\n            Joi.string(),\n            Joi.number().min(1)\n        );\n\n        Helper.validate(schema, [\n            [0, false, '\"value\" must be greater than or equal to 1']\n        ]);\n    });\n\n    it('consolidates types with valid values', () => {\n\n        const schema = Joi.alternatives(Joi.boolean(), 'xyz');\n\n        Helper.validate(schema, [\n            ['x', false, '\"value\" must be one of [boolean, xyz]']\n        ]);\n    });\n\n    it('consolidates types', () => {\n\n        const schema = Joi.alternatives(Joi.boolean(), Joi.binary());\n\n        Helper.validate(schema, [\n            [[], false, '\"value\" must be one of [boolean, binary]']\n        ]);\n    });\n\n    it('passes errors through when abortEarly is false', () => {\n\n        const schema = Joi.alternatives(Joi.number().min(1).positive(), Joi.binary());\n\n        Helper.validate(schema, { abortEarly: false }, [\n            [-1, false, '\"value\" does not match any of the allowed types']\n        ]);\n    });\n\n    it('abstracts multiple complex object errors', () => {\n\n        const schema = Joi.alternatives([\n            Joi.object({ a: Joi.string() }),\n            Joi.object({ b: Joi.string() })\n        ]);\n\n        Helper.validate(schema, [\n            [{ c: 1 }, false, '\"value\" does not match any of the allowed types']\n        ]);\n    });\n\n    describe('conditional()', () => {\n\n        it('throws on invalid ref (not string)', () => {\n\n            expect(() => Joi.alternatives().conditional(5, { is: 6, then: Joi.number() })).to.throw('Invalid condition: 5');\n        });\n\n        it('throws on unreachable condition', () => {\n\n            expect(() => {\n\n                Joi.object({\n\n                    a: Joi.alternatives().conditional('b', { is: 6, then: 7, otherwise: 0 }).conditional('b', { is: 6, then: 7 }),\n                    b: Joi.any()\n                });\n            }).to.throw('Unreachable condition');\n        });\n\n        it('tests only otherwise', () => {\n\n            const schema = Joi.object({\n                a: Joi.number().required(),\n                b: Joi.alternatives().conditional('a', { is: 0, otherwise: Joi.valid(1) })\n            });\n\n            Helper.validate(schema, [\n                [{ a: 1, b: 1 }, true],\n                [{ a: 0, b: 2 }, false, {\n                    message: '\"b\" does not match any of the allowed types',\n                    path: ['b'],\n                    type: 'alternatives.any',\n                    context: { value: 2, label: 'b', key: 'b' }\n                }]\n            ]);\n        });\n\n        describe('with ref', () => {\n\n            it('validates conditional alternatives', () => {\n\n                const schema = Joi.object({\n                    a: Joi.alternatives()\n                        .conditional('b', { is: 5, then: 'x', otherwise: 'y' }),\n                    b: Joi.any()\n                });\n\n                Helper.validate(schema, [\n                    [{ a: 'x', b: 5 }, true],\n                    [{ a: 'x', b: 6 }, false, {\n                        message: '\"a\" must be [y]',\n                        path: ['a'],\n                        type: 'any.only',\n                        context: { value: 'x', valids: ['y'], label: 'a', key: 'a' }\n                    }],\n                    [{ a: 'y', b: 5 }, false, {\n                        message: '\"a\" must be [x]',\n                        path: ['a'],\n                        type: 'any.only',\n                        context: { value: 'y', valids: ['x'], label: 'a', key: 'a' }\n                    }],\n                    [{ a: 'y', b: 6 }, true],\n                    [{ a: 'z', b: 5 }, false, {\n                        message: '\"a\" must be [x]',\n                        path: ['a'],\n                        type: 'any.only',\n                        context: { value: 'z', valids: ['x'], label: 'a', key: 'a' }\n                    }],\n                    [{ a: 'z', b: 6 }, false, {\n                        message: '\"a\" must be [y]',\n                        path: ['a'],\n                        type: 'any.only',\n                        context: { value: 'z', valids: ['y'], label: 'a', key: 'a' }\n                    }]\n                ]);\n            });\n\n            it('validates conditional alternatives (self reference, explicit)', () => {\n\n                const schema = Joi.alternatives()\n                    .conditional(Joi.ref('a', { ancestor: 0 }), {\n                        is: true,\n                        then: {\n                            a: Joi.boolean().required(),\n                            b: Joi.string().required()\n                        },\n                        otherwise: {\n                            a: Joi.boolean().required(),\n                            c: Joi.string().required()\n                        }\n                    });\n\n                Helper.validate(schema, [\n                    [{ a: true, b: 'x' }, true],\n                    [{ a: true, b: 5 }, false, {\n                        message: '\"b\" must be a string',\n                        path: ['b'],\n                        type: 'string.base',\n                        context: { value: 5, key: 'b', label: 'b' }\n                    }],\n                    [{ a: true }, false, {\n                        message: '\"b\" is required',\n                        path: ['b'],\n                        type: 'any.required',\n                        context: { key: 'b', label: 'b' }\n                    }],\n                    [{ a: true, c: 5 }, false, {\n                        message: '\"b\" is required',\n                        path: ['b'],\n                        type: 'any.required',\n                        context: { key: 'b', label: 'b' }\n                    }],\n                    [{ a: true, c: 'x' }, false, {\n                        message: '\"b\" is required',\n                        path: ['b'],\n                        type: 'any.required',\n                        context: { key: 'b', label: 'b' }\n                    }],\n\n                    [{ a: false, b: 'x' }, false, {\n                        message: '\"c\" is required',\n                        path: ['c'],\n                        type: 'any.required',\n                        context: { key: 'c', label: 'c' }\n                    }],\n                    [{ a: false, b: 5 }, false, {\n                        message: '\"c\" is required',\n                        path: ['c'],\n                        type: 'any.required',\n                        context: { key: 'c', label: 'c' }\n                    }],\n                    [{ a: false }, false, {\n                        message: '\"c\" is required',\n                        path: ['c'],\n                        type: 'any.required',\n                        context: { key: 'c', label: 'c' }\n                    }],\n                    [{ a: false, c: 5 }, false, {\n                        message: '\"c\" must be a string',\n                        path: ['c'],\n                        type: 'string.base',\n                        context: { value: 5, key: 'c', label: 'c' }\n                    }],\n                    [{ a: false, c: 'x' }, true]\n                ]);\n            });\n\n            it('validates conditional alternatives (self reference, implicit)', () => {\n\n                const schema = Joi.alternatives()\n                    .conditional('.a', {\n                        is: true,\n                        then: {\n                            a: Joi.boolean().required(),\n                            b: Joi.string().required()\n                        },\n                        otherwise: {\n                            a: Joi.boolean().required(),\n                            c: Joi.string().required()\n                        }\n                    });\n\n                Helper.validate(schema, [\n                    [{ a: true, b: 'x' }, true],\n                    [{ a: true, b: 5 }, false, {\n                        message: '\"b\" must be a string',\n                        path: ['b'],\n                        type: 'string.base',\n                        context: { value: 5, key: 'b', label: 'b' }\n                    }],\n                    [{ a: true }, false, {\n                        message: '\"b\" is required',\n                        path: ['b'],\n                        type: 'any.required',\n                        context: { key: 'b', label: 'b' }\n                    }],\n                    [{ a: true, c: 5 }, false, {\n                        message: '\"b\" is required',\n                        path: ['b'],\n                        type: 'any.required',\n                        context: { key: 'b', label: 'b' }\n                    }],\n                    [{ a: true, c: 'x' }, false, {\n                        message: '\"b\" is required',\n                        path: ['b'],\n                        type: 'any.required',\n                        context: { key: 'b', label: 'b' }\n                    }],\n\n                    [{ a: false, b: 'x' }, false, {\n                        message: '\"c\" is required',\n                        path: ['c'],\n                        type: 'any.required',\n                        context: { key: 'c', label: 'c' }\n                    }],\n                    [{ a: false, b: 5 }, false, {\n                        message: '\"c\" is required',\n                        path: ['c'],\n                        type: 'any.required',\n                        context: { key: 'c', label: 'c' }\n                    }],\n                    [{ a: false }, false, {\n                        message: '\"c\" is required',\n                        path: ['c'],\n                        type: 'any.required',\n                        context: { key: 'c', label: 'c' }\n                    }],\n                    [{ a: false, c: 5 }, false, {\n                        message: '\"c\" must be a string',\n                        path: ['c'],\n                        type: 'string.base',\n                        context: { value: 5, key: 'c', label: 'c' }\n                    }],\n                    [{ a: false, c: 'x' }, true]\n                ]);\n            });\n\n            it('validates conditional alternatives (empty key)', () => {\n\n                const schema = Joi.object({\n                    a: Joi.alternatives()\n                        .conditional('', { is: 5, then: 'x', otherwise: 'y' }),\n                    '': Joi.any()\n                });\n\n                Helper.validate(schema, [\n                    [{ a: 'x', '': 5 }, true],\n                    [{ a: 'x', '': 6 }, false, {\n                        message: '\"a\" must be [y]',\n                        path: ['a'],\n                        type: 'any.only',\n                        context: { value: 'x', valids: ['y'], label: 'a', key: 'a' }\n                    }],\n                    [{ a: 'y', '': 5 }, false, {\n                        message: '\"a\" must be [x]',\n                        path: ['a'],\n                        type: 'any.only',\n                        context: { value: 'y', valids: ['x'], label: 'a', key: 'a' }\n                    }],\n                    [{ a: 'y', '': 6 }, true],\n                    [{ a: 'z', '': 5 }, false, {\n                        message: '\"a\" must be [x]',\n                        path: ['a'],\n                        type: 'any.only',\n                        context: { value: 'z', valids: ['x'], label: 'a', key: 'a' }\n                    }],\n                    [{ a: 'z', '': 6 }, false, {\n                        message: '\"a\" must be [y]',\n                        path: ['a'],\n                        type: 'any.only',\n                        context: { value: 'z', valids: ['y'], label: 'a', key: 'a' }\n                    }]\n                ]);\n            });\n\n            it('validates only then', () => {\n\n                const schema = Joi.object({\n                    a: Joi.alternatives()\n                        .conditional(Joi.ref('b'), { is: 5, then: 'x' })\n                        .try('z'),\n                    b: Joi.any()\n                });\n\n                Helper.validate(schema, [\n                    [{ a: 'x', b: 5 }, true],\n                    [{ a: 'x', b: 6 }, false, {\n                        message: '\"a\" must be [z]',\n                        path: ['a'],\n                        type: 'any.only',\n                        context: { value: 'x', valids: ['z'], label: 'a', key: 'a' }\n                    }],\n                    [{ a: 'y', b: 5 }, false, {\n                        message: '\"a\" must be [x]',\n                        path: ['a'],\n                        type: 'any.only',\n                        context: { value: 'y', valids: ['x'], label: 'a', key: 'a' }\n                    }],\n                    [{ a: 'y', b: 6 }, false, {\n                        message: '\"a\" must be [z]',\n                        path: ['a'],\n                        type: 'any.only',\n                        context: { value: 'y', valids: ['z'], label: 'a', key: 'a' }\n                    }],\n                    [{ a: 'z', b: 5 }, false, {\n                        message: '\"a\" must be [x]',\n                        path: ['a'],\n                        type: 'any.only',\n                        context: { value: 'z', valids: ['x'], label: 'a', key: 'a' }\n                    }],\n                    [{ a: 'z', b: 6 }, true]\n                ]);\n            });\n\n            it('validates only otherwise', () => {\n\n                const schema = Joi.object({\n                    a: Joi.alternatives()\n                        .conditional('b', { is: 5, otherwise: 'y' })\n                        .try('z'),\n                    b: Joi.any()\n                });\n\n                Helper.validate(schema, [\n                    [{ a: 'x', b: 5 }, false, {\n                        message: '\"a\" must be [z]',\n                        path: ['a'],\n                        type: 'any.only',\n                        context: { value: 'x', valids: ['z'], label: 'a', key: 'a' }\n                    }],\n                    [{ a: 'x', b: 6 }, false, {\n                        message: '\"a\" must be [y]',\n                        path: ['a'],\n                        type: 'any.only',\n                        context: { value: 'x', valids: ['y'], label: 'a', key: 'a' }\n                    }],\n                    [{ a: 'y', b: 5 }, false, {\n                        message: '\"a\" must be [z]',\n                        path: ['a'],\n                        type: 'any.only',\n                        context: { value: 'y', valids: ['z'], label: 'a', key: 'a' }\n                    }],\n                    [{ a: 'y', b: 6 }, true],\n                    [{ a: 'z', b: 5 }, true],\n                    [{ a: 'z', b: 6 }, false, {\n                        message: '\"a\" must be [y]',\n                        path: ['a'],\n                        type: 'any.only',\n                        context: { value: 'z', valids: ['y'], label: 'a', key: 'a' }\n                    }]\n                ]);\n            });\n\n            it('validates \"then\" when a preceding \"when\" has only \"otherwise\"', () => {\n\n                const schema = Joi.object({\n                    a: Joi.number(),\n                    b: Joi.number(),\n                    c: Joi.alternatives()\n                        .conditional('a', { is: 1, otherwise: Joi.number().min(1) })\n                        .conditional('b', { is: 1, then: Joi.number().min(1), otherwise: Joi.number() })\n                });\n\n                Helper.validate(schema, [\n                    [{ a: 1, b: 1, c: 0 }, false, {\n                        message: '\"c\" must be greater than or equal to 1',\n                        path: ['c'],\n                        type: 'number.min',\n                        context: { limit: 1, value: 0, label: 'c', key: 'c' }\n                    }],\n                    [{ a: 1, b: 1, c: 1 }, true],\n                    [{ a: 0, b: 1, c: 1 }, true],\n                    [{ a: 1, b: 0, c: 0 }, true]\n                ]);\n            });\n\n            it('validates when is is null', () => {\n\n                const schema = Joi.object({\n                    a: Joi.alternatives().conditional('b', { is: null, then: 'x', otherwise: Joi.number() }),\n                    b: Joi.any()\n                });\n\n                Helper.validate(schema, [\n                    [{ a: 1 }, true],\n                    [{ a: 'y' }, false, {\n                        message: '\"a\" must be a number',\n                        path: ['a'],\n                        type: 'number.base',\n                        context: { label: 'a', key: 'a', value: 'y' }\n                    }],\n                    [{ a: 'x', b: null }, true],\n                    [{ a: 'y', b: null }, false, {\n                        message: '\"a\" must be [x]',\n                        path: ['a'],\n                        type: 'any.only',\n                        context: { value: 'y', valids: ['x'], label: 'a', key: 'a' }\n                    }],\n                    [{ a: 1, b: null }, false, {\n                        message: '\"a\" must be [x]',\n                        path: ['a'],\n                        type: 'any.only',\n                        context: { value: 1, valids: ['x'], label: 'a', key: 'a' }\n                    }]\n                ]);\n            });\n\n            it('validates when is has ref', () => {\n\n                const schema = Joi.object({\n                    a: Joi.alternatives().conditional('b', { is: Joi.ref('c'), then: 'x' }),\n                    b: Joi.any(),\n                    c: Joi.number()\n                });\n\n                Helper.validate(schema, [\n                    [{ a: 'x', b: 5, c: '5' }, true, { a: 'x', b: 5, c: 5 }],\n                    [{ a: 'x', b: 5, c: '1' }, false, {\n                        message: '\"a\" does not match any of the allowed types',\n                        path: ['a'],\n                        type: 'alternatives.any',\n                        context: { label: 'a', key: 'a', value: 'x' }\n                    }],\n                    [{ a: 'x', b: '5', c: '5' }, false, {\n                        message: '\"a\" does not match any of the allowed types',\n                        path: ['a'],\n                        type: 'alternatives.any',\n                        context: { label: 'a', key: 'a', value: 'x' }\n                    }],\n                    [{ a: 'y', b: 5, c: 5 }, false, {\n                        message: '\"a\" must be [x]',\n                        path: ['a'],\n                        type: 'any.only',\n                        context: { value: 'y', valids: ['x'], label: 'a', key: 'a' }\n                    }],\n                    [{ a: 'y' }, false, {\n                        message: '\"a\" must be [x]',\n                        path: ['a'],\n                        type: 'any.only',\n                        context: { value: 'y', valids: ['x'], label: 'a', key: 'a' }\n                    }]\n                ]);\n            });\n\n            it('validates when is has ref pointing to a complex type', () => {\n\n                const date = new Date(42);\n\n                const schema = Joi.object({\n                    a: Joi.alternatives().conditional('b', { is: Joi.ref('c'), then: 'x' }),\n                    b: Joi.date(),\n                    c: Joi.date()\n                });\n\n                Helper.validate(schema, [\n                    [{ a: 'x', b: date, c: date }, true],\n                    [{ a: 'x', b: date, c: Date.now() }, false, {\n                        message: '\"a\" does not match any of the allowed types',\n                        path: ['a'],\n                        type: 'alternatives.any',\n                        context: { label: 'a', key: 'a', value: 'x' }\n                    }],\n                    [{ a: 'y', b: date, c: date }, false, {\n                        message: '\"a\" must be [x]',\n                        path: ['a'],\n                        type: 'any.only',\n                        context: { value: 'y', valids: ['x'], label: 'a', key: 'a' }\n                    }],\n                    [{ a: 'y' }, false, {\n                        message: '\"a\" must be [x]',\n                        path: ['a'],\n                        type: 'any.only',\n                        context: { value: 'y', valids: ['x'], label: 'a', key: 'a' }\n                    }]\n                ]);\n            });\n\n\n            it('validates when is has either ref pointing to a complex type or value', () => {\n\n                const date = new Date(42);\n                const now = Date.now();\n\n                const schema = Joi.object({\n                    a: Joi.alternatives().conditional('b', {\n                        is: Joi.valid(\n                            new Date(+date), // Intentional cloning of the date to change the reference\n                            Joi.ref('c')\n                        ),\n                        then: 'x'\n                    }),\n                    b: Joi.date(),\n                    c: Joi.date()\n                });\n\n                Helper.validate(schema, [\n                    [{ a: 'x', b: date, c: date }, true],\n                    [{ a: 'x', b: date, c: now }, true, { a: 'x', b: date, c: new Date(now) }],\n                    [{ a: 'y', b: date, c: date }, false, {\n                        message: '\"a\" must be [x]',\n                        path: ['a'],\n                        type: 'any.only',\n                        context: { value: 'y', valids: ['x'], label: 'a', key: 'a' }\n                    }],\n                    [{ a: 'y' }, false, {\n                        message: '\"a\" must be [x]',\n                        path: ['a'],\n                        type: 'any.only',\n                        context: { value: 'y', valids: ['x'], label: 'a', key: 'a' }\n                    }]\n                ]);\n            });\n\n            it('validates when then has ref', () => {\n\n                const ref = Joi.ref('c');\n                const schema = Joi.object({\n                    a: Joi.alternatives().conditional('b', { is: 5, then: ref }),\n                    b: Joi.any(),\n                    c: Joi.number()\n                });\n\n                Helper.validate(schema, [\n                    [{ a: 'x', b: 5, c: '1' }, false, {\n                        message: '\"a\" must be [ref:c]',\n                        path: ['a'],\n                        type: 'any.only',\n                        context: { value: 'x', valids: [ref], label: 'a', key: 'a' }\n                    }],\n                    [{ a: 1, b: 5, c: '1' }, true, { a: 1, b: 5, c: 1 }],\n                    [{ a: '1', b: 5, c: '1' }, false, {\n                        message: '\"a\" must be [ref:c]',\n                        path: ['a'],\n                        type: 'any.only',\n                        context: { value: '1', valids: [ref], label: 'a', key: 'a' }\n                    }]\n                ]);\n            });\n\n            it('validates when otherwise has ref', () => {\n\n                const ref = Joi.ref('c');\n                const schema = Joi.object({\n                    a: Joi.alternatives().conditional('b', { is: 6, otherwise: ref }),\n                    b: Joi.any(),\n                    c: Joi.number()\n                });\n\n                Helper.validate(schema, [\n                    [{ a: 'x', b: 5, c: '1' }, false, {\n                        message: '\"a\" must be [ref:c]',\n                        path: ['a'],\n                        type: 'any.only',\n                        context: { value: 'x', valids: [ref], label: 'a', key: 'a' }\n                    }],\n                    [{ a: 1, b: 5, c: '1' }, true, { a: 1, b: 5, c: 1 }],\n                    [{ a: '1', b: 5, c: '1' }, false, {\n                        message: '\"a\" must be [ref:c]',\n                        path: ['a'],\n                        type: 'any.only',\n                        context: { value: '1', valids: [ref], label: 'a', key: 'a' }\n                    }]\n                ]);\n            });\n\n            it('validates when empty value', () => {\n\n                const schema = Joi.object({\n                    a: Joi.alternatives().conditional('b', { is: true, then: Joi.required() }),\n                    b: Joi.boolean().default(false)\n                });\n\n                Helper.validate(schema, [\n                    [{ b: false }, true],\n                    [{ b: true }, true]           // true because required() only applies to the one alternative\n                ]);\n            });\n\n            it('validates when missing value', () => {\n\n                const schema = Joi.object({\n                    a: Joi.alternatives().conditional('b', {\n                        is: 5,\n                        then: Joi.optional(),\n                        otherwise: Joi.required()\n                    }).required(),\n                    b: Joi.number()\n                });\n\n                Helper.validate(schema, [\n                    [{ a: 1 }, true],\n                    [{}, false, {\n                        message: '\"a\" is required',\n                        path: ['a'],\n                        type: 'any.required',\n                        context: { label: 'a', key: 'a' }\n                    }],\n                    [{ b: 1 }, false, {\n                        message: '\"a\" is required',\n                        path: ['a'],\n                        type: 'any.required',\n                        context: { label: 'a', key: 'a' }\n                    }],\n                    [{ a: 1, b: 1 }, true],\n                    [{ a: 1, b: 5 }, true],\n                    [{ b: 5 }, false, {\n                        message: '\"a\" is required',\n                        path: ['a'],\n                        type: 'any.required',\n                        context: { label: 'a', key: 'a' }\n                    }]\n                ]);\n            });\n\n            it('validates with nested whens', () => {\n\n                // If ((b === 0 && a === 123) ||\n                //     (b !== 0 && a === anything))\n                // then c === 456\n                // else c === 789\n\n                const schema = Joi.object({\n                    a: Joi.number().required(),\n                    b: Joi.number().required(),\n                    c: Joi.alternatives().conditional('a', {\n                        is: Joi.alternatives().conditional('b', {\n                            is: Joi.valid(0),\n                            then: Joi.valid(123),\n                            otherwise: Joi.any()\n                        }),\n                        then: Joi.valid(456),\n                        otherwise: Joi.valid(789)\n                    })\n                });\n\n                Helper.validate(schema, [\n                    [{ a: 123, b: 0, c: 456 }, true],\n                    [{ a: 0, b: 1, c: 456 }, true],\n                    [{ a: 0, b: 0, c: 789 }, true],\n                    [{ a: 123, b: 456, c: 456 }, true],\n                    [{ a: 0, b: 0, c: 456 }, false, {\n                        message: '\"c\" must be [789]',\n                        path: ['c'],\n                        type: 'any.only',\n                        context: { value: 456, valids: [789], label: 'c', key: 'c' }\n                    }],\n                    [{ a: 123, b: 456, c: 789 }, false, {\n                        message: '\"c\" must be [456]',\n                        path: ['c'],\n                        type: 'any.only',\n                        context: { value: 789, valids: [456], label: 'c', key: 'c' }\n                    }]\n                ]);\n            });\n        });\n\n        describe('with schema', () => {\n\n            it('should peek inside a simple value', () => {\n\n                const schema = Joi.alternatives()\n                    .conditional(Joi.number().min(0), { then: Joi.number().min(10), otherwise: Joi.number() });\n\n                Helper.validate(schema, [\n                    [-1, true, -1],\n                    [1, false, {\n                        message: '\"value\" must be greater than or equal to 10',\n                        path: [],\n                        type: 'number.min',\n                        context: { limit: 10, value: 1, label: 'value' }\n                    }],\n                    [10, true, 10]\n                ]);\n            });\n\n            it('should peek inside an object', () => {\n\n                const schema = Joi.alternatives()\n                    .conditional(Joi.object().keys({ foo: Joi.valid('hasBar').required() }).unknown(), {\n                        then: Joi.object({\n                            foo: Joi.string(),\n                            bar: Joi.number().required()\n                        }),\n                        otherwise: Joi.object({\n                            foo: Joi.string(),\n                            bar: Joi.number()\n                        })\n                    });\n\n                Helper.validate(schema, [\n                    [{ foo: 'whatever' }, true, { foo: 'whatever' }],\n                    [{ foo: 'hasBar' }, false, {\n                        message: '\"bar\" is required',\n                        path: ['bar'],\n                        type: 'any.required',\n                        context: { key: 'bar', label: 'bar' }\n                    }],\n                    [{ foo: 'hasBar', bar: 42 }, true, { foo: 'hasBar', bar: 42 }],\n                    [{}, true, {}]\n                ]);\n            });\n        });\n\n        describe('with switch', () => {\n\n            it('sets value based on multiple conditions', () => {\n\n                const schema = Joi.object({\n                    a: Joi.number().required(),\n                    b: Joi.alternatives()\n                        .conditional('a', [\n                            { is: 0, then: Joi.valid(1) },\n                            { is: 1, then: Joi.valid(2) },\n                            { is: 2, then: Joi.valid(3), otherwise: Joi.valid(4) }\n                        ])\n                });\n\n                Helper.validate(schema, [\n                    [{ a: 0, b: 1 }, true],\n                    [{ a: 0, b: 2 }, false, {\n                        message: '\"b\" must be [1]',\n                        path: ['b'],\n                        type: 'any.only',\n                        context: { value: 2, valids: [1], label: 'b', key: 'b' }\n                    }],\n                    [{ a: 1, b: 2 }, true],\n                    [{ a: 1, b: 3 }, false, {\n                        message: '\"b\" must be [2]',\n                        path: ['b'],\n                        type: 'any.only',\n                        context: { value: 3, valids: [2], label: 'b', key: 'b' }\n                    }],\n                    [{ a: 2, b: 3 }, true],\n                    [{ a: 2, b: 2 }, false, {\n                        message: '\"b\" must be [3]',\n                        path: ['b'],\n                        type: 'any.only',\n                        context: { value: 2, valids: [3], label: 'b', key: 'b' }\n                    }],\n                    [{ a: 42, b: 4 }, true],\n                    [{ a: 42, b: 128 }, false, {\n                        message: '\"b\" must be [4]',\n                        path: ['b'],\n                        type: 'any.only',\n                        context: { value: 128, valids: [4], label: 'b', key: 'b' }\n                    }]\n                ]);\n            });\n        });\n    });\n\n    describe('describe()', () => {\n\n        it('describes when', () => {\n\n            const schema = Joi.object({\n                a: Joi.alternatives()\n                    .conditional('b', { is: 5, then: 'x' })\n                    .conditional('b', { is: 6, otherwise: 'y' })\n                    .try('z'),\n                b: Joi.any()\n            });\n\n            expect(schema.describe()).to.equal({\n                type: 'object',\n                keys: {\n                    b: {\n                        type: 'any'\n                    },\n                    a: {\n                        type: 'alternatives',\n                        matches: [\n                            {\n                                ref: { path: ['b'] },\n                                is: {\n                                    type: 'any',\n                                    flags: {\n                                        only: true,\n                                        presence: 'required'\n                                    },\n                                    allow: [{ override: true }, 5]\n                                },\n                                then: {\n                                    type: 'any',\n                                    flags: {\n                                        only: true\n                                    },\n                                    allow: [{ override: true }, 'x']\n                                }\n                            },\n                            {\n                                ref: { path: ['b'] },\n                                is: {\n                                    type: 'any',\n                                    flags: {\n                                        only: true,\n                                        presence: 'required'\n                                    },\n                                    allow: [{ override: true }, 6]\n                                },\n                                otherwise: {\n                                    type: 'any',\n                                    flags: {\n                                        only: true\n                                    },\n                                    allow: [{ override: true }, 'y']\n                                }\n                            },\n                            {\n                                schema: {\n                                    type: 'any',\n                                    flags: {\n                                        only: true\n                                    },\n                                    allow: [{ override: true }, 'z']\n                                }\n                            }\n                        ]\n                    }\n                }\n            });\n        });\n\n        it('describes when (only then)', () => {\n\n            const schema = Joi.object({\n                a: Joi.alternatives()\n                    .conditional('b', { is: 5, then: 'x' })\n                    .try(Joi.valid('z')),\n                b: Joi.any()\n            });\n\n            expect(schema.describe()).to.equal({\n                type: 'object',\n                keys: {\n                    b: {\n                        type: 'any'\n                    },\n                    a: {\n                        type: 'alternatives',\n                        matches: [\n                            {\n                                ref: { path: ['b'] },\n                                is: {\n                                    type: 'any',\n                                    flags: {\n                                        only: true,\n                                        presence: 'required'\n                                    },\n                                    allow: [{ override: true }, 5]\n                                },\n                                then: {\n                                    type: 'any',\n                                    flags: {\n                                        only: true\n                                    },\n                                    allow: [{ override: true }, 'x']\n                                }\n                            },\n                            {\n                                schema: {\n                                    type: 'any',\n                                    flags: {\n                                        only: true\n                                    },\n                                    allow: ['z']\n                                }\n                            }\n                        ]\n                    }\n                }\n            });\n        });\n\n        it('describes when (only otherwise)', () => {\n\n            const schema = Joi.object({\n                a: Joi.alternatives()\n                    .conditional('b', { is: 5, otherwise: 'y' })\n                    .try('z'),\n                b: Joi.any()\n            });\n\n            expect(schema.describe()).to.equal({\n                type: 'object',\n                keys: {\n                    b: {\n                        type: 'any'\n                    },\n                    a: {\n                        type: 'alternatives',\n                        matches: [\n                            {\n                                ref: { path: ['b'] },\n                                is: {\n                                    type: 'any',\n                                    flags: {\n                                        only: true,\n                                        presence: 'required'\n                                    },\n                                    allow: [{ override: true }, 5]\n                                },\n                                otherwise: {\n                                    type: 'any',\n                                    flags: {\n                                        only: true\n                                    },\n                                    allow: [{ override: true }, 'y']\n                                }\n                            },\n                            {\n                                schema: {\n                                    type: 'any',\n                                    flags: {\n                                        only: true\n                                    },\n                                    allow: [{ override: true }, 'z']\n                                }\n                            }\n                        ]\n                    }\n                }\n            });\n        });\n\n        it('describes when (with schema)', () => {\n\n            const schema = Joi.alternatives()\n                .conditional(Joi.string().label('foo'), {\n                    then: Joi.string().required().min(1),\n                    otherwise: Joi.boolean()\n                });\n\n            const outcome = {\n                type: 'alternatives',\n                matches: [{\n                    is: {\n                        type: 'string',\n                        flags: {\n                            label: 'foo'\n                        }\n                    },\n                    then: {\n                        type: 'string',\n                        flags: { presence: 'required' },\n                        rules: [{ args: { limit: 1 }, name: 'min' }]\n                    },\n                    otherwise: {\n                        type: 'boolean'\n                    }\n                }]\n            };\n\n            expect(schema.describe()).to.equal(outcome);\n        });\n\n        it('describes inherited fields (from any)', () => {\n\n            const schema = Joi.alternatives()\n                .try('a')\n                .description('d')\n                .example('a')\n                .meta('b')\n                .meta('c')\n                .note('f')\n                .tag('g');\n\n            const outcome = {\n                type: 'alternatives',\n                notes: ['f'],\n                tags: ['g'],\n                metas: ['b', 'c'],\n                examples: ['a'],\n                flags: {\n                    description: 'd'\n                },\n                matches: [{\n                    schema: {\n                        type: 'any',\n                        flags: {\n                            only: true\n                        },\n                        allow: [{ override: true }, 'a']\n                    }\n                }]\n            };\n\n            expect(schema.describe()).to.equal(outcome);\n        });\n    });\n\n    describe('error()', () => {\n\n        it('overrides single try error', () => {\n\n            const override = new Error('failed!');\n\n            const schema = Joi.object({\n                x: Joi.alternatives([\n                    Joi.number(),\n                    Joi.string().error(override)\n                ])\n            });\n\n            Helper.validate(schema, [\n                [{ x: [] }, false, {\n                    message: '\"x\" does not match any of the allowed types',\n                    type: 'alternatives.match',\n                    path: ['x'],\n                    context: {\n                        message: '\"x\" must be a number. Error: failed!',\n                        key: 'x',\n                        label: 'x',\n                        value: [],\n                        details: [\n                            {\n                                message: '\"x\" must be a number',\n                                path: ['x'],\n                                type: 'number.base',\n                                context: { key: 'x', label: 'x', value: [] }\n                            },\n                            {\n                                context: { error: override },\n                                message: 'Error: failed!',\n                                type: 'override'\n                            }\n                        ]\n                    }\n                }]\n            ]);\n        });\n\n        it('overrides top level error', () => {\n\n            const schema = Joi.object({\n                x: Joi.alternatives([\n                    Joi.number(),\n                    Joi.string()\n                ])\n                    .error(new Error('failed!'))\n            });\n\n            Helper.validate(schema, [\n                [{ x: [] }, false, 'failed!']\n            ]);\n        });\n    });\n\n    describe('label()', () => {\n\n        it('passes the label to the underlying schema', () => {\n\n            const schema = Joi.object().keys({\n                a: Joi.boolean(),\n\n                b: Joi.alternatives().conditional('a', {\n                    is: true,\n                    then: Joi.string().empty('').allow(null),\n                    otherwise: Joi.any()\n                })\n                    .label('Label b'),\n\n                c: Joi.alternatives().conditional('a', {\n                    is: true,\n                    then: Joi.any(),\n                    otherwise: Joi.string().empty('').allow(null)\n                })\n                    .label('Label c'),\n\n                d: Joi.alt()\n                    .try(Joi.string())\n                    .label('Label d')\n            })\n                .or('b', 'c', 'd');\n\n            Helper.validate(schema, [\n                [{ a: true, b: 1 }, false, {\n                    message: '\"Label b\" must be a string',\n                    path: ['b'],\n                    type: 'string.base',\n                    context: { value: 1, key: 'b', label: 'Label b' }\n                }],\n                [{ a: false, b: 1, d: 1 }, false, {\n                    message: '\"Label d\" must be a string',\n                    path: ['d'],\n                    type: 'string.base',\n                    context: { value: 1, key: 'd', label: 'Label d' }\n                }],\n                [{ a: false, b: 1, c: 1 }, false, {\n                    message: '\"Label c\" must be a string',\n                    path: ['c'],\n                    type: 'string.base',\n                    context: { value: 1, key: 'c', label: 'Label c' }\n                }]\n            ]);\n        });\n\n        it('does not modify the original schema', () => {\n\n            const schema = Joi.alternatives().conditional('a', {\n                is: true,\n                then: Joi.string().empty('').allow(null),\n                otherwise: Joi.any()\n            });\n\n            const labeled = schema.label('Label b');\n\n            expect(schema.describe()).to.equal({\n                type: 'alternatives',\n                matches: [{\n                    is: {\n                        type: 'any',\n                        flags: { only: true, presence: 'required' },\n                        allow: [{ override: true }, true]\n                    },\n                    ref: { path: ['a'] },\n                    then: {\n                        type: 'string',\n                        flags: {\n                            empty: {\n                                flags: { only: true },\n                                type: 'any',\n                                allow: ['']\n                            }\n                        },\n                        allow: [null]\n                    },\n                    otherwise: { type: 'any' }\n                }]\n            });\n\n            expect(labeled.describe()).to.equal({\n                flags: {\n                    label: 'Label b'\n                },\n                type: 'alternatives',\n                matches: [{\n                    is: {\n                        type: 'any',\n                        flags: { only: true, presence: 'required' },\n                        allow: [{ override: true }, true]\n                    },\n                    ref: { path: ['a'] },\n                    then: {\n                        type: 'string',\n                        flags: {\n                            label: 'Label b',\n                            empty: {\n                                flags: { only: true },\n                                type: 'any',\n                                allow: ['']\n                            }\n                        },\n                        allow: [null]\n                    },\n                    otherwise: {\n                        type: 'any',\n                        flags: { label: 'Label b' }\n                    }\n                }]\n            });\n        });\n\n        it('applies label to then', () => {\n\n            const schema = Joi.object({\n                a: Joi.boolean(),\n                b: Joi.alternatives()\n                    .conditional('a', { is: true, then: Joi.string() })\n                    .label('x')\n            });\n\n            expect(schema.describe()).to.equal({\n                type: 'object',\n                keys: {\n                    a: {\n                        type: 'boolean'\n                    },\n                    b: {\n                        type: 'alternatives',\n                        flags: {\n                            label: 'x'\n                        },\n                        matches: [\n                            {\n                                is: {\n                                    type: 'any',\n                                    allow: [{ override: true }, true],\n                                    flags: {\n                                        only: true,\n                                        presence: 'required'\n                                    }\n                                },\n                                ref: {\n                                    path: ['a']\n                                },\n                                then: {\n                                    type: 'string',\n                                    flags: {\n                                        label: 'x'\n                                    }\n                                }\n                            }\n                        ]\n                    }\n                }\n            });\n        });\n\n        it('applies label to otherwise', () => {\n\n            const schema = Joi.object({\n                a: Joi.boolean(),\n                b: Joi.alternatives()\n                    .conditional('a', { is: true, otherwise: Joi.string() })\n                    .label('x')\n            });\n\n            expect(schema.describe()).to.equal({\n                type: 'object',\n                keys: {\n                    a: {\n                        type: 'boolean'\n                    },\n                    b: {\n                        type: 'alternatives',\n                        flags: {\n                            label: 'x'\n                        },\n                        matches: [\n                            {\n                                is: {\n                                    type: 'any',\n                                    allow: [{ override: true }, true],\n                                    flags: {\n                                        only: true,\n                                        presence: 'required'\n                                    }\n                                },\n                                ref: {\n                                    path: ['a']\n                                },\n                                otherwise: {\n                                    type: 'string',\n                                    flags: {\n                                        label: 'x'\n                                    }\n                                }\n                            }\n                        ]\n                    }\n                }\n            });\n        });\n\n        it('does not override existing labels in nested alternatives', () => {\n\n            const schema = Joi.alternatives().try(\n                Joi.boolean().label('boolean'),\n                Joi.alternatives().try(\n                    Joi.string().label('string'),\n                    Joi.number().label('number')\n                ).label('string or number')\n            );\n\n            expect(schema.describe()).to.equal({\n                type: 'alternatives',\n                matches: [\n                    {\n                        schema: {\n                            type: 'boolean',\n                            flags: {\n                                label: 'boolean'\n                            }\n                        }\n                    },\n                    {\n                        schema: {\n                            type: 'alternatives',\n                            flags: {\n                                label: 'string or number'\n                            },\n                            matches: [\n                                {\n                                    schema: {\n                                        type: 'string',\n                                        flags: {\n                                            label: 'string'\n                                        }\n                                    }\n                                },\n                                {\n                                    schema: {\n                                        type: 'number',\n                                        flags: {\n                                            label: 'number'\n                                        }\n                                    }\n                                }\n                            ]\n                        }\n                    }\n                ]\n            });\n        });\n\n        it('does not override existing label of then', () => {\n\n            const schema = Joi.object({\n                a: Joi.boolean(),\n                b: Joi.alternatives()\n                    .conditional('a', { is: true, then: Joi.string().label('not x') })\n                    .label('x')\n            });\n\n            expect(schema.describe()).to.equal({\n                type: 'object',\n                keys: {\n                    a: {\n                        type: 'boolean'\n                    },\n                    b: {\n                        type: 'alternatives',\n                        flags: {\n                            label: 'x'\n                        },\n                        matches: [\n                            {\n                                is: {\n                                    type: 'any',\n                                    allow: [{ override: true }, true],\n                                    flags: {\n                                        only: true,\n                                        presence: 'required'\n                                    }\n                                },\n                                ref: {\n                                    path: ['a']\n                                },\n                                then: {\n                                    type: 'string',\n                                    flags: {\n                                        label: 'not x'\n                                    }\n                                }\n                            }\n                        ]\n                    }\n                }\n            });\n        });\n\n        it('does not override existing label of otherwise', () => {\n\n            const schema = Joi.object({\n                a: Joi.boolean(),\n                b: Joi.alternatives()\n                    .conditional('a', { is: true, otherwise: Joi.string().label('not x') })\n                    .label('x')\n            });\n\n            expect(schema.describe()).to.equal({\n                type: 'object',\n                keys: {\n                    a: {\n                        type: 'boolean'\n                    },\n                    b: {\n                        type: 'alternatives',\n                        flags: {\n                            label: 'x'\n                        },\n                        matches: [\n                            {\n                                is: {\n                                    type: 'any',\n                                    allow: [{ override: true }, true],\n                                    flags: {\n                                        only: true,\n                                        presence: 'required'\n                                    }\n                                },\n                                ref: {\n                                    path: ['a']\n                                },\n                                otherwise: {\n                                    type: 'string',\n                                    flags: {\n                                        label: 'not x'\n                                    }\n                                }\n                            }\n                        ]\n                    }\n                }\n            });\n        });\n    });\n\n    describe('match()', () => {\n\n        it('matches one', () => {\n\n            const schema = Joi.alternatives([\n                Joi.number(),\n                Joi.string()\n            ])\n                .match('one');\n\n            Helper.validate(schema, [\n                [0, true, 0],\n                ['x', true, 'x'],\n                ['2', false, {\n                    message: '\"value\" matches more than one allowed type',\n                    path: [],\n                    type: 'alternatives.one',\n                    context: {\n                        label: 'value',\n                        value: '2'\n                    }\n                }],\n                [true, false, {\n                    message: '\"value\" does not match any of the allowed types',\n                    path: [],\n                    type: 'alternatives.any',\n                    context: {\n                        label: 'value',\n                        value: true,\n                        details: [\n                            {\n                                details: [\n                                    {\n                                        context: {\n                                            label: 'value',\n                                            value: true\n                                        },\n                                        message: '\"value\" must be a number',\n                                        path: [],\n                                        type: 'number.base'\n                                    }\n                                ],\n                                message: '\"value\" must be a number'\n                            },\n                            {\n                                details: [\n                                    {\n                                        context: {\n                                            label: 'value',\n                                            value: true\n                                        },\n                                        message: '\"value\" must be a string',\n                                        path: [],\n                                        type: 'string.base'\n                                    }\n                                ],\n                                message: '\"value\" must be a string'\n                            }\n                        ]\n                    }\n                }]\n            ]);\n        });\n\n        it('matches one (retains coerce)', () => {\n\n            const schema = Joi.alternatives([\n                Joi.number(),\n                Joi.boolean()\n            ])\n                .match('one');\n\n            Helper.validate(schema, [\n                ['2', true, 2]\n            ]);\n        });\n\n        it('matches all', () => {\n\n            const schema = Joi.alternatives([\n                Joi.number(),\n                Joi.string()\n            ])\n                .match('all');\n\n            Helper.validate(schema, [\n                ['2', true, '2'],\n                ['x', false, {\n                    message: '\"value\" does not match all of the required types',\n                    path: [],\n                    type: 'alternatives.all',\n                    context: {\n                        label: 'value',\n                        value: 'x',\n                        details: [\n                            {\n                                details: [\n                                    {\n                                        context: {\n                                            label: 'value',\n                                            value: 'x'\n                                        },\n                                        message: '\"value\" must be a number',\n                                        path: [],\n                                        type: 'number.base'\n                                    }\n                                ],\n                                message: '\"value\" must be a number'\n                            }\n                        ]\n                    }\n                }],\n                [2, false, {\n                    message: '\"value\" does not match all of the required types',\n                    path: [],\n                    type: 'alternatives.all',\n                    context: {\n                        label: 'value',\n                        value: 2,\n                        details: [\n                            {\n                                details: [\n                                    {\n                                        context: {\n                                            label: 'value',\n                                            value: 2\n                                        },\n                                        message: '\"value\" must be a string',\n                                        path: [],\n                                        type: 'string.base'\n                                    }\n                                ],\n                                message: '\"value\" must be a string'\n                            }\n                        ]\n                    }\n                }],\n                [true, false, {\n                    message: '\"value\" does not match any of the allowed types',\n                    path: [],\n                    type: 'alternatives.any',\n                    context: {\n                        label: 'value',\n                        value: true,\n                        details: [\n                            {\n                                details: [\n                                    {\n                                        context: {\n                                            label: 'value',\n                                            value: true\n                                        },\n                                        message: '\"value\" must be a number',\n                                        path: [],\n                                        type: 'number.base'\n                                    }\n                                ],\n                                message: '\"value\" must be a number'\n                            },\n                            {\n                                details: [\n                                    {\n                                        context: {\n                                            label: 'value',\n                                            value: true\n                                        },\n                                        message: '\"value\" must be a string',\n                                        path: [],\n                                        type: 'string.base'\n                                    }\n                                ],\n                                message: '\"value\" must be a string'\n                            }\n                        ]\n                    }\n                }]\n            ]);\n        });\n\n        it('merges return values when matching all objects', () => {\n\n            const schema = Joi.alternatives([\n                Joi.object({ foo: Joi.string().default('bar') }).unknown(),\n                Joi.object({\n                    baz: Joi.string().default('buz'),\n                    lol: Joi.string()\n                })\n            ]).match('all');\n\n            Helper.validate(schema, [\n                [{}, true, { foo: 'bar', baz: 'buz' }],\n                [{ lol: 'rofl' }, true, { foo: 'bar', baz: 'buz', lol: 'rofl' }],\n                [{ lol: 5 }, false, {\n                    message: '\"value\" does not match all of the required types',\n                    path: [],\n                    type: 'alternatives.all',\n                    context: {\n                        label: 'value',\n                        value: { lol: 5 },\n                        details: [\n                            {\n                                details: [\n                                    {\n                                        context: {\n                                            key: 'lol',\n                                            label: 'lol',\n                                            value: 5\n                                        },\n                                        message: '\"lol\" must be a string',\n                                        path: ['lol'],\n                                        type: 'string.base'\n                                    }\n                                ],\n                                message: '\"lol\" must be a string'\n                            }\n                        ]\n                    }\n                }]\n            ]);\n        });\n\n        it('merges defaults when nesting alternatives', () => {\n\n            const schema = Joi.alternatives([\n                Joi.alternatives(\n                    Joi.object({ foo: Joi.string().default('bar') }).unknown(),\n                    Joi.object({ baz: Joi.boolean().default(false) }).unknown()\n                ).\n                    match('all'),\n\n                Joi.object({ lol: Joi.boolean().default(true) }).unknown()\n            ])\n                .match('all');\n\n            Helper.validate(schema, [\n                [{}, true, { foo: 'bar', baz: false, lol: true }],\n                [{ foo: 'rofl' }, true, { foo: 'rofl', baz: false, lol: true }],\n                [{ foo: 9 }, false, '\"value\" does not match all of the required types']\n            ]);\n        });\n\n        it('ignores defaults when nesting does not include objects', () => {\n\n            const schema = Joi.alternatives([\n                Joi.alternatives(\n                    Joi.number(),\n                    Joi.string()\n                ).\n                    match('all'),\n\n                Joi.any()\n            ])\n                .match('all');\n\n            Helper.validate(schema, [\n                ['123', true, '123']\n            ]);\n        });\n\n        it('errors on mix with conditional', () => {\n\n            expect(() => Joi.alternatives().match('all').conditional('$a', { is: true, then: false })).to.throw('Cannot combine match mode all with conditional rule');\n            expect(() => Joi.alternatives().conditional('$a', { is: true, then: false }).match('all')).to.throw('Cannot combine match mode all with conditional rules');\n\n            expect(() => Joi.alternatives().match('one').conditional('$a', { is: true, then: false })).to.throw('Cannot combine match mode one with conditional rule');\n            expect(() => Joi.alternatives().conditional('$a', { is: true, then: false }).match('one')).to.throw('Cannot combine match mode one with conditional rules');\n\n            expect(() => Joi.alternatives().match('any').conditional('$a', { is: true, then: false })).to.not.throw();\n            expect(() => Joi.alternatives().conditional('$a', { is: true, then: false }).match('any')).to.not.throw();\n        });\n    });\n\n    describe('tailor()', () => {\n\n        it('customizes schema', () => {\n\n            const alternatives = {\n                v: (s) => s.min(10)\n            };\n\n            const before = Joi.object({\n                x: Joi.alternatives([\n                    Joi.number().alter(alternatives),\n                    Joi.string().alter(alternatives)\n                ])\n            });\n\n            const bd = before.describe();\n\n            const first = before.tailor('v');\n\n            const after = Joi.object({\n                x: Joi.alternatives([\n                    Joi.number().min(10).alter(alternatives),\n                    Joi.string().min(10).alter(alternatives)\n                ])\n            });\n\n            Helper.equal(first, after);\n            expect(first.describe()).to.equal(after.describe());\n            expect(before.describe()).to.equal(bd);\n        });\n\n        it('customizes schema with outter rule', () => {\n\n            const alt1 = {\n                v: (s) => s.min(10)\n            };\n\n            const alt2 = {\n                v: (s) => s.try(Joi.valid('x'))\n            };\n\n            const before = Joi.object({\n                x: Joi.alternatives([\n                    Joi.number().alter(alt1),\n                    Joi.string().alter(alt1)\n                ])\n                    .alter(alt2)\n            });\n\n            const bd = before.describe();\n\n            const first = before.tailor('v');\n\n            const after = Joi.object({\n                x: Joi.alternatives([\n                    Joi.number().min(10).alter(alt1),\n                    Joi.string().min(10).alter(alt1),\n                    Joi.valid('x')\n                ])\n                    .alter(alt2)\n            });\n\n            Helper.equal(first, after);\n            expect(first.describe()).to.equal(after.describe());\n            expect(before.describe()).to.equal(bd);\n        });\n    });\n\n    describe('try()', () => {\n\n        it('throws when missing alternatives', () => {\n\n            expect(() => Joi.alternatives().try()).to.throw('Missing alternative schemas');\n        });\n\n        it('throws on unreachable condition', () => {\n\n            expect(() => {\n\n                Joi.object({\n\n                    a: Joi.alternatives().conditional('b', { is: 6, then: 7, otherwise: 0 }).try(5),\n                    b: Joi.any()\n                });\n            }).to.throw('Unreachable condition');\n        });\n\n        it('validates deep alternatives', () => {\n\n            const schema = Joi.alternatives().try(\n                Joi.boolean(),\n                Joi.object({\n                    p: Joi.alternatives().try(\n                        Joi.boolean(),\n                        Joi.string().valid('foo', 'bar')\n                    )\n                })\n            );\n\n            Helper.validate(schema, [\n                [{ p: 1 }, false, {\n                    message: '\"p\" must be one of [boolean, foo, bar]',\n                    path: ['p'],\n                    type: 'alternatives.types',\n                    context: {\n                        key: 'p',\n                        label: 'p',\n                        types: ['boolean', 'foo', 'bar'],\n                        value: 1\n                    }\n                }],\n                [{ p: '...' }, false, {\n                    message: '\"p\" must be one of [boolean, foo, bar]',\n                    path: ['p'],\n                    type: 'alternatives.types',\n                    context: {\n                        key: 'p',\n                        label: 'p',\n                        types: ['boolean', 'foo', 'bar'],\n                        value: '...'\n                    }\n                }],\n                [1, false, {\n                    message: '\"value\" must be one of [boolean, object]',\n                    path: [],\n                    type: 'alternatives.types',\n                    context: { types: ['boolean', 'object'], label: 'value', value: 1 }\n                }]\n            ]);\n        });\n\n        it('validates deep alternatives (with wrap.array false)', () => {\n\n            const schema = Joi.alternatives().try(\n                Joi.boolean(),\n                Joi.object({\n                    p: Joi.alternatives().try(\n                        Joi.boolean(),\n                        Joi.string().valid('foo', 'bar')\n                    )\n                })\n            ).prefs({ errors: { wrap: { array: false } } });\n\n            Helper.validate(schema, [\n                [{ p: 1 }, false, {\n                    message: '\"p\" must be one of boolean, foo, bar',\n                    path: ['p'],\n                    type: 'alternatives.types',\n                    context: {\n                        key: 'p',\n                        label: 'p',\n                        types: ['boolean', 'foo', 'bar'],\n                        value: 1\n                    }\n                }],\n                [{ p: '...' }, false, {\n                    message: '\"p\" must be one of boolean, foo, bar',\n                    path: ['p'],\n                    type: 'alternatives.types',\n                    context: {\n                        key: 'p',\n                        label: 'p',\n                        types: ['boolean', 'foo', 'bar'],\n                        value: '...'\n                    }\n                }],\n                [1, false, {\n                    message: '\"value\" must be one of boolean, object',\n                    path: [],\n                    type: 'alternatives.types',\n                    context: { types: ['boolean', 'object'], label: 'value', value: 1 }\n                }]\n            ]);\n        });\n\n        it('validates deep alternatives (with custom error)', () => {\n\n            const schema = Joi.alternatives([\n                Joi.boolean(),\n                Joi.object({\n                    p: Joi.number()\n                })\n            ])\n                .error(new Error('oops'));\n\n            Helper.validate(schema, [\n                [{ p: 'a' }, false, 'oops']\n            ]);\n        });\n\n        it('validates alternatives with the correct type', () => {\n\n            const schema = Joi.alternatives([\n                Joi.boolean(),\n                Joi.function()\n            ]);\n\n            Helper.validate(schema, [\n                ['wrong', false, '\"value\" must be one of [boolean, function]']\n            ]);\n        });\n    });\n\n    describe('when()', () => {\n\n        it('combines when() with tries', () => {\n\n            const schema = Joi.object({\n                a: Joi.boolean(),\n                b: Joi.alternatives([\n                    Joi.string(),\n                    Joi.number()\n                ])\n                    .when('a', { is: true, then: Joi.required() })\n            });\n\n            Helper.validate(schema, [\n                [{ a: false }, true],\n                [{ a: true }, false, '\"b\" is required'],\n                [{ a: true, b: true }, false, '\"b\" must be one of [string, number]']\n            ]);\n        });\n    });\n});\n"
  },
  {
    "path": "test/types/any.js",
    "content": "'use strict';\n\nconst Code = require('@hapi/code');\nconst Joi = require('../..');\nconst Lab = require('@hapi/lab');\n\nconst Helper = require('../helper');\n\n\nconst internals = {};\n\n\nconst { describe, it } = exports.lab = Lab.script();\nconst { expect } = Code;\n\n\ndescribe('any', () => {\n\n    describe('custom()', () => {\n\n        it('uses custom validation', () => {\n\n            const error = new Error('nope');\n            const method = (value, helpers) => {\n\n                if (value === '1') {\n                    throw error;\n                }\n\n                if (value === '2') {\n                    return '3';\n                }\n\n                if (value === '4') {\n                    return helpers.error('any.invalid');\n                }\n\n                if (value === '5') {\n                    return undefined;\n                }\n\n                return value;\n            };\n\n            const schema = Joi.string().custom(method, 'custom validation');\n            Helper.validate(schema, [\n                ['x', true, 'x'],\n                ['2', true, '3'],\n                ['5', true, undefined],\n                ['1', false, {\n                    message: '\"value\" failed custom validation because nope',\n                    path: [],\n                    type: 'any.custom',\n                    context: { label: 'value', value: '1', error }\n                }],\n                ['4', false, {\n                    message: '\"value\" contains an invalid value',\n                    path: [],\n                    type: 'any.invalid',\n                    context: { label: 'value', value: '4' }\n                }]\n            ]);\n        });\n\n        it('errors on invalid arguments', () => {\n\n            const method = () => null;\n            expect(() => Joi.any().custom({})).to.throw('Method must be a function');\n            expect(() => Joi.any().custom(method)).to.not.throw();\n            expect(() => Joi.any().custom(method, '')).to.throw('Description must be a non-empty string');\n            expect(() => Joi.any().custom(method, 0)).to.throw('Description must be a non-empty string');\n            expect(() => Joi.any().custom(method, [])).to.throw('Description must be a non-empty string');\n        });\n    });\n\n    describe('messages()', () => {\n\n        it('aliases preferences', () => {\n\n            const messages = {\n                english: {\n                    value: 'it'\n                }\n            };\n\n            Helper.equal(Joi.string().valid('x').messages(messages), Joi.string().valid('x').prefs({ messages }));\n        });\n    });\n\n    describe('shared()', () => {\n\n        it('errors on missing id', () => {\n\n            expect(() => Joi.any().shared(Joi.number())).to.throw('Schema must be a schema with an id');\n            expect(() => Joi.any().shared(1)).to.throw('Schema must be a schema with an id');\n        });\n    });\n\n    describe('warning()', () => {\n\n        it('errors on invalid code', () => {\n\n            expect(() => Joi.any().warning()).to.throw('Invalid warning code');\n            expect(() => Joi.any().warning(123)).to.throw('Invalid warning code');\n        });\n    });\n});\n"
  },
  {
    "path": "test/types/array.js",
    "content": "'use strict';\n\nconst Code = require('@hapi/code');\nconst Lab = require('@hapi/lab');\nconst Joi = require('../..');\n\nconst Helper = require('../helper');\n\n\nconst internals = {};\n\n\nconst { describe, it } = exports.lab = Lab.script();\nconst { expect } = Code;\n\n\ndescribe('array', () => {\n\n    it('should throw an exception if arguments were passed.', () => {\n\n        expect(() => Joi.array('invalid argument.')).to.throw('The array type does not allow arguments');\n    });\n\n    it('errors on invalid type', () => {\n\n        Helper.validate(Joi.array(), [\n            [3, false, {\n                message: '\"value\" must be an array',\n                path: [],\n                type: 'array.base',\n                context: { label: 'value', value: 3 }\n            }],\n            ['3', false, {\n                message: '\"value\" must be an array',\n                path: [],\n                type: 'array.base',\n                context: { label: 'value', value: '3' }\n            }]\n        ]);\n    });\n\n    describe('cast()', () => {\n\n        it('casts value to set', () => {\n\n            const schema = Joi.array().items(Joi.number()).cast('set');\n            Helper.validate(schema, [\n                [['1', '2', '3', '4', '5'], true, new Set([1, 2, 3, 4, 5])]\n            ]);\n        });\n\n        it('does not leak casts to any', () => {\n\n            expect(() => Joi.any().cast('set')).to.throw('Type any does not support casting to set');\n        });\n    });\n\n    describe('describe()', () => {\n\n        it('returns an empty description when no rules are applied', () => {\n\n            const schema = Joi.array();\n            const desc = schema.describe();\n            expect(desc).to.equal({\n                type: 'array'\n            });\n        });\n\n        it('returns an updated description when sparse rule is applied', () => {\n\n            const schema = Joi.array().sparse();\n            const desc = schema.describe();\n            expect(desc).to.equal({\n                type: 'array',\n                flags: { sparse: true }\n            });\n        });\n\n        it('returns an items array only if items are specified', () => {\n\n            const schema = Joi.array().items().max(5);\n            const desc = schema.describe();\n            expect(desc.items).to.not.exist();\n        });\n\n        it('returns a recursively defined array of items when specified', () => {\n\n            const schema = Joi.array()\n                .items(Joi.number(), Joi.string())\n                .items(Joi.boolean().forbidden())\n                .ordered(Joi.number(), Joi.string())\n                .ordered(Joi.string().required());\n            const desc = schema.describe();\n            expect(desc.items).to.have.length(3);\n            expect(desc).to.equal({\n                type: 'array',\n                ordered: [\n                    { type: 'number' },\n                    { type: 'string' },\n                    { type: 'string', flags: { presence: 'required' } }\n                ],\n                items: [\n                    { type: 'number' },\n                    { type: 'string' },\n                    { type: 'boolean', flags: { presence: 'forbidden' } }\n                ]\n            });\n        });\n\n        it('describes an array with array items', () => {\n\n            const schema = Joi.array().items(Joi.array());\n            const desc = schema.describe();\n            expect(desc).to.equal({\n                type: 'array',\n                items: [\n                    {\n                        type: 'array'\n                    }\n                ]\n            });\n        });\n    });\n\n    describe('has()', () => {\n\n        it('shows path to errors in schema', () => {\n\n            expect(() => {\n\n                Joi.array().has({\n                    a: {\n                        b: {\n                            c: {\n                                d: undefined\n                            }\n                        }\n                    }\n                });\n            }).to.throw(Error, 'Invalid undefined schema (a.b.c.d)');\n        });\n\n        it('shows errors in schema', () => {\n\n            expect(() => Joi.array().has(undefined)).to.throw('Invalid undefined schema');\n        });\n\n        it('works with object.assert', () => {\n\n            const schema = Joi.array().items(\n                Joi.object().keys({\n                    a: {\n                        b: Joi.string(),\n                        c: Joi.number()\n                    },\n                    d: {\n                        e: Joi.any()\n                    }\n                })\n            ).has(Joi.object().assert('d.e', Joi.ref('a.c'), 'equal to a.c'));\n\n            Helper.validate(schema, [\n                [[{ a: { b: 'x', c: 5 }, d: { e: 5 } }], true]\n            ]);\n        });\n\n        it('does not throw if assertion passes', () => {\n\n            const schema = Joi.array().has(Joi.string());\n            Helper.validate(schema, [\n                [['foo'], true]\n            ]);\n        });\n\n        it('throws with proper message if assertion fails on unknown schema', () => {\n\n            const schema = Joi.array().has(Joi.string());\n            Helper.validate(schema, [\n                [[0], false, {\n                    message: '\"value\" does not contain at least one required match',\n                    path: [],\n                    type: 'array.hasUnknown',\n                    context: { label: 'value', value: [0] }\n                }]\n            ]);\n        });\n\n        it('throws with proper message if assertion fails on known schema', () => {\n\n            const schema = Joi.array().has(Joi.string().label('foo'));\n            Helper.validate(schema, [\n                [[0], false, {\n                    message: '\"value\" does not contain at least one required match for type \"foo\"',\n                    path: [],\n                    type: 'array.hasKnown',\n                    context: { label: 'value', patternLabel: 'foo', value: [0] }\n                }]\n            ]);\n        });\n\n        it('shows correct path for error', () => {\n\n            const schema = Joi.object({\n                arr: Joi.array().has(Joi.string())\n            });\n            Helper.validate(schema, [\n                [{ arr: [0] }, false, {\n                    message: '\"arr\" does not contain at least one required match',\n                    path: ['arr'],\n                    type: 'array.hasUnknown',\n                    context: { label: 'arr', key: 'arr', value: [0] }\n                }]\n            ]);\n        });\n\n        it('supports nested arrays', () => {\n\n            const schema = Joi.object({\n                arr: Joi.array().items(\n                    Joi.object({ foo: Joi.array().has(Joi.string()) })\n                )\n            });\n            Helper.validate(schema, [\n                [{ arr: [{ foo: ['bar'] }] }, true]\n            ]);\n        });\n\n        it('supports peer item references', () => {\n\n            const schema = Joi.object({\n                array: Joi.array()\n                    .items(Joi.number())\n                    .has(Joi.number().greater(Joi.ref('..0')))\n            });\n\n            Helper.validate(schema, [\n                [{ array: [10, 1, 11, 5] }, true],\n                [{ array: [10, 1, 2, 5, 12] }, true],\n                [{ array: [10, 1, 2, 5, 1] }, false, {\n                    message: '\"array\" does not contain at least one required match',\n                    path: ['array'],\n                    type: 'array.hasUnknown',\n                    context: { label: 'array', key: 'array', value: [10, 1, 2, 5, 1] }\n                }]\n            ]);\n        });\n\n        it('provides accurate error message for nested arrays', () => {\n\n            const schema = Joi.object({\n                arr: Joi.array().items(\n                    Joi.object({ foo: Joi.array().has(Joi.string()) })\n                )\n            });\n            Helper.validate(schema, [\n                [{ arr: [{ foo: [0] }] }, false, {\n                    message: '\"arr[0].foo\" does not contain at least one required match',\n                    path: ['arr', 0, 'foo'],\n                    type: 'array.hasUnknown',\n                    context: { label: 'arr[0].foo', key: 'foo', value: [0] }\n                }]\n            ]);\n        });\n\n        it('handles multiple assertions', () => {\n\n            const schema = Joi.array().has(Joi.string()).has(Joi.number());\n            Helper.validate(schema, [\n                [['foo', 0], true]\n            ]);\n\n            Helper.validate(schema, [\n                [['foo'], false, {\n                    message: '\"value\" does not contain at least one required match',\n                    path: [],\n                    type: 'array.hasUnknown',\n                    context: { label: 'value', value: ['foo'] }\n                }]\n            ]);\n        });\n\n        it('describes the pattern schema', () => {\n\n            const schema = Joi.array().has(Joi.string()).has(Joi.number());\n            expect(schema.describe()).to.equal({\n                type: 'array',\n                rules: [\n                    { name: 'has', args: { schema: { type: 'string' } } },\n                    { name: 'has', args: { schema: { type: 'number' } } }\n                ]\n            });\n        });\n    });\n\n    describe('items()', () => {\n\n        it('converts members', () => {\n\n            const schema = Joi.array().items(Joi.number());\n            Helper.validate(schema, [\n                [['1', '2', '3'], true, [1, 2, 3]]\n            ]);\n        });\n\n        it('shows path to errors in array items', () => {\n\n            expect(() => {\n\n                Joi.array().items({\n                    a: {\n                        b: {\n                            c: {\n                                d: undefined\n                            }\n                        }\n                    }\n                });\n            }).to.throw(Error, 'Invalid undefined schema (0.a.b.c.d)');\n\n            expect(() => Joi.array().items({ foo: 'bar' }, undefined)).to.throw('Invalid undefined schema (1)');\n        });\n\n        it('allows zero size', () => {\n\n            const schema = Joi.object({\n                test: Joi.array().items(Joi.object({\n                    foo: Joi.string().required()\n                }))\n            });\n\n            Helper.validate(schema, [\n                [{ test: [] }, true, { test: [] }]\n            ]);\n        });\n\n        it('allows Error items', () => {\n\n            const schema = Joi.array().items(Joi.object());\n            const error = new Error();\n            Helper.validate(schema, [\n                [[error], true, [error]]\n            ]);\n        });\n\n        it('returns the first error when only one inclusion', () => {\n\n            const schema = Joi.object({\n                test: Joi.array().items(Joi.object({\n                    foo: Joi.string().required()\n                }))\n            });\n\n            Helper.validate(schema, [\n                [{ test: [{ foo: 'a' }, { bar: 2 }] }, false, {\n                    message: '\"test[1].foo\" is required',\n                    path: ['test', 1, 'foo'],\n                    type: 'any.required',\n                    context: { label: 'test[1].foo', key: 'foo' }\n                }]\n            ]);\n        });\n\n        it('validates multiple types added in two calls', () => {\n\n            const schema = Joi.array()\n                .items(Joi.number())\n                .items(Joi.string());\n\n            Helper.validate(schema, [\n                [[1, 2, 3], true],\n                [[50, 100, 1000], true],\n                [[1, 'a', 5, 10], true],\n                [['joi', 'everydaylowprices', 5000], true]\n            ]);\n        });\n\n        it('validates multiple types with stripUnknown', () => {\n\n            const schema = Joi.array().items(Joi.number(), Joi.string()).prefs({ stripUnknown: true });\n\n            Helper.validate(schema, [\n                [[1, 2, 'a'], true, [1, 2, 'a']],\n                [[1, { foo: 'bar' }, 'a', 2], false, {\n                    context: {\n                        key: 1,\n                        label: '[1]',\n                        pos: 1,\n                        value: { foo: 'bar' }\n                    },\n                    message: '\"[1]\" does not match any of the allowed types',\n                    path: [1],\n                    type: 'array.includes'\n                }]\n            ]);\n        });\n\n        it('validates multiple types with stripUnknown (as an object)', () => {\n\n            const schema = Joi.array().items(Joi.number(), Joi.string()).prefs({ stripUnknown: { arrays: true, objects: false } });\n\n            Helper.validate(schema, [\n                [[1, 2, 'a'], true, [1, 2, 'a']],\n                [[1, { foo: 'bar' }, 'a', 2], true, [1, 'a', 2]]\n            ]);\n        });\n\n        it('allows forbidden to restrict values', () => {\n\n            const schema = Joi.array().items(Joi.string().valid('four').forbidden(), Joi.string());\n            Helper.validate(schema, [\n                [['one', 'two', 'three', 'four'], false, {\n                    message: '\"[3]\" contains an excluded value',\n                    path: [3],\n                    type: 'array.excludes',\n                    context: { pos: 3, value: 'four', label: '[3]', key: 3 }\n                }]\n            ]);\n        });\n\n        it('allows forbidden to restrict values (ref)', () => {\n\n            const schema = Joi.object({\n                array: Joi.array().items(Joi.valid(Joi.ref('...value')).forbidden(), Joi.string()),\n                value: Joi.string().required()\n            });\n            Helper.validate(schema, [\n                [{\n                    array: ['one', 'two', 'three', 'four'],\n                    value: 'four'\n                }, false, {\n                    message: '\"array[3]\" contains an excluded value',\n                    path: ['array', 3],\n                    type: 'array.excludes',\n                    context: { pos: 3, value: 'four', label: 'array[3]', key: 3 }\n                }]\n            ]);\n        });\n\n        it('process referenced property first when referenced by an item rule', () => {\n\n            const schema = Joi.object({\n                array: Joi.array().items(Joi.valid(Joi.ref('...value')).forbidden(), Joi.number()),\n                value: Joi.number().required()\n            });\n\n            Helper.validate(schema, [\n                [{\n                    array: [1, 2, 3, 4],\n                    value: '4'\n                }, false, {\n                    message: '\"array[3]\" contains an excluded value',\n                    path: ['array', 3],\n                    type: 'array.excludes',\n                    context: { pos: 3, value: 4, label: 'array[3]', key: 3 }\n                }]\n            ]);\n        });\n\n        it('validates that a required value exists', () => {\n\n            const schema = Joi.array().items(Joi.string().valid('four').required(), Joi.string());\n            Helper.validate(schema, [\n                [['one', 'two', 'three'], false, {\n                    message: '\"value\" does not contain 1 required value(s)',\n                    path: [],\n                    type: 'array.includesRequiredUnknowns',\n                    context: { unknownMisses: 1, label: 'value', value: ['one', 'two', 'three'] }\n                }]\n            ]);\n        });\n\n        it('validates that values are validated when all items are required', () => {\n\n            const schema = Joi.array().items(Joi.string().valid('one').required(), Joi.string().valid('two').required());\n            Helper.validate(schema, [\n                [['one', 'one', 'two', 'two', 'three'], false, {\n                    message: '\"[4]\" does not match any of the allowed types',\n                    path: [4],\n                    type: 'array.includes',\n                    context: { pos: 4, value: 'three', label: '[4]', key: 4 }\n                }]\n            ]);\n        });\n\n        it('validates that a required value exists with abortEarly = false', () => {\n\n            const schema = Joi.array()\n                .items(Joi.string().valid('four').required(), Joi.string())\n                .prefs({ abortEarly: false });\n\n            const input = ['one', 'two', 'three'];\n\n            Helper.validate(schema, [\n                [input, false, {\n                    message: '\"value\" does not contain 1 required value(s)',\n                    details: [{\n                        message: '\"value\" does not contain 1 required value(s)',\n                        path: [],\n                        type: 'array.includesRequiredUnknowns',\n                        context: { unknownMisses: 1, label: 'value', value: input }\n                    }]\n                }]\n            ]);\n        });\n\n        it('does not re-run required tests that have already been matched', () => {\n\n            const schema = Joi.array().items(Joi.string().valid('four').required(), Joi.string());\n            Helper.validate(schema, [\n                [['one', 'two', 'three', 'four', 'four', 'four'], true]\n            ]);\n        });\n\n        it('does not re-run required tests that have already failed', () => {\n\n            const schema = Joi.array().items(Joi.string().valid('four').required(), Joi.boolean().required(), Joi.number());\n            Helper.validate(schema, [\n                [['one', 'two', 'three', 'four', 'four', 'four'], false, {\n                    message: '\"[0]\" does not match any of the allowed types',\n                    path: [0],\n                    type: 'array.includes',\n                    context: { pos: 0, value: 'one', label: '[0]', key: 0 }\n                }]\n            ]);\n        });\n\n        it('can require duplicates of the same schema and fail', () => {\n\n            const schema = Joi.array().items(Joi.string().valid('four').required(), Joi.string().valid('four').required(), Joi.string());\n\n            Helper.validate(schema, [\n                [['one', 'two', 'three', 'four'], false, {\n                    message: '\"value\" does not contain 1 required value(s)',\n                    path: [],\n                    type: 'array.includesRequiredUnknowns',\n                    context: { unknownMisses: 1, label: 'value', value: ['one', 'two', 'three', 'four'] }\n                }]\n            ]);\n        });\n\n        it('can require duplicates of the same schema and pass', () => {\n\n            const schema = Joi.array().items(Joi.string().valid('four').required(), Joi.string().valid('four').required(), Joi.string());\n\n            Helper.validate(schema, [\n                [['one', 'two', 'three', 'four', 'four'], true]\n            ]);\n        });\n\n        it('continues to validate after a required match', () => {\n\n            const schema = Joi.array().items(Joi.string().required(), Joi.boolean());\n\n            Helper.validate(schema, [\n                [[true, 'one', false, 'two'], true]\n            ]);\n        });\n\n        it('can use a label on a required parameter', () => {\n\n            const schema = Joi.array().items(Joi.string().required().label('required string'), Joi.boolean());\n\n            Helper.validate(schema, [\n                [[true, false], false, {\n                    message: '\"value\" does not contain [required string]',\n                    path: [],\n                    type: 'array.includesRequiredKnowns',\n                    context: { knownMisses: ['required string'], label: 'value', value: [true, false] }\n                }]\n            ]);\n        });\n\n        it('can use a label on one required parameter, and no label on another', () => {\n\n            const schema = Joi.array().items(Joi.string().required().label('required string'), Joi.string().required(), Joi.boolean());\n\n            Helper.validate(schema, [\n                [[true, false], false, {\n                    message: '\"value\" does not contain [required string] and 1 other required value(s)',\n                    path: [],\n                    type: 'array.includesRequiredBoth',\n                    context: {\n                        knownMisses: ['required string'],\n                        unknownMisses: 1,\n                        label: 'value',\n                        value: [true, false]\n                    }\n                }]\n            ]);\n        });\n\n        it('can fill the default values into the value array', () => {\n\n            const schema = Joi.array().ordered(Joi.string().required(), Joi.number().default(0), Joi.number().default(6)).required();\n\n            Helper.validate(schema, [\n                [['a'], true, ['a', 0, 6]]\n            ]);\n        });\n\n        it('ignore trailing undefined', () => {\n\n            const schema = Joi.array().ordered(Joi.string().required(), Joi.string(), Joi.number().default(5), Joi.string(), Joi.string());\n\n            Helper.validate(schema, [\n                [['a'], true, ['a', undefined, 5]]\n            ]);\n        });\n\n        it('generate sparse array', () => {\n\n            const schema = Joi.array().ordered(Joi.string(), Joi.number().default(5), Joi.string(), Joi.string().default('f'));\n\n            Helper.validate(schema, [\n                [[], true, [undefined, 5, undefined, 'f']]\n            ]);\n        });\n\n        it('fills defaults correctly when nested items contain references', () => {\n\n            const schema = Joi.object({\n                info: Joi.object(),\n                values: Joi.array().ordered(\n                    Joi.string().required(),\n                    Joi.string().default(Joi.ref('...info.firstname')),\n                    Joi.string(),\n                    Joi.string().default(Joi.ref('...info.lastname')),\n                    Joi.string()\n                ).required()\n            });\n\n            Helper.validate(schema, [\n                [{ info: { firstname: 'f', lastname: 'e' }, values: ['h'] }, true, { info: { firstname: 'f', lastname: 'e' }, values: ['h', 'f', undefined, 'e'] }],\n                [{ info: { firstname: 'f', lastname: 'e' }, values: ['h', 'test'] }, true, { info: { firstname: 'f', lastname: 'e' }, values: ['h', 'test', undefined, 'e'] }]\n            ]);\n        });\n\n        it('fills defaults correctly when nested items contain references', () => {\n\n            const schema = Joi.object({\n                info: Joi.object().required(),\n                values: Joi.array().ordered(\n                    Joi.string().required(),\n                    Joi.array().ordered(\n                        Joi.string(),\n                        Joi.string().default('normal value'),\n                        Joi.string(),\n                        Joi.number().default(Joi.x('{number(\"202099\")}'))\n                    ),\n                    Joi.string(),\n                    Joi.string().default(Joi.ref('...info.firstname')),\n                    Joi.string()\n                ).required()\n            });\n\n            Helper.validate(schema, [\n                [{ info: { firstname: 'f' }, values: ['h', []] }, true, { info: { firstname: 'f' }, values: ['h', [undefined, 'normal value', undefined, 202099], undefined, 'f'] }]\n            ]);\n        });\n\n        it('can strip matching items', () => {\n\n            const schema = Joi.array().items(Joi.string(), Joi.any().strip());\n\n            Helper.validate(schema, [\n                [['one', 'two', 3, 4], true, ['one', 'two']]\n            ]);\n        });\n\n        it('returns custom item error', () => {\n\n            const schema = Joi.array().items(\n                Joi.object({\n                    a: Joi.number().error(new Error('custom'))\n                })\n            );\n\n            Helper.validate(schema, [\n                [[{ a: 'x' }], false, 'custom'],\n                [[{ a: 'x' }, { a: 'y' }], false, 'custom']\n            ]);\n        });\n\n        it('returns custom item error (required)', () => {\n\n            const schema = Joi.array().items(\n                Joi.object({\n                    a: Joi.number().error(new Error('custom'))\n                })\n                    .required()\n            );\n\n            Helper.validate(schema, [\n                [[{ a: 'x' }], false, 'custom'],\n                [[{ a: 'x' }, { a: 'y' }], false, 'custom']\n            ]);\n        });\n    });\n\n    describe('length()', () => {\n\n        it('validates array size', () => {\n\n            const schema = Joi.array().length(2);\n            Helper.validate(schema, [\n                [[1, 2], true],\n                [[1], false, {\n                    message: '\"value\" must contain 2 items',\n                    path: [],\n                    type: 'array.length',\n                    context: { limit: 2, value: [1], label: 'value' }\n                }]\n            ]);\n        });\n\n        it('overrides rule when called multiple times', () => {\n\n            const schema = Joi.array().length(2).length(1);\n            Helper.validate(schema, [\n                [[1], true],\n                [[1, 2], false, {\n                    message: '\"value\" must contain 1 items',\n                    path: [],\n                    type: 'array.length',\n                    context: { limit: 1, value: [1, 2], label: 'value' }\n                }]\n            ]);\n        });\n\n        it('throws when limit is not a number', () => {\n\n            expect(() => Joi.array().length('a')).to.throw('limit must be a positive integer or reference');\n        });\n\n        it('throws when limit is not an integer', () => {\n\n            expect(() => Joi.array().length(1.2)).to.throw('limit must be a positive integer or reference');\n        });\n\n        it('throws when limit is negative', () => {\n\n            expect(() => Joi.array().length(-1)).to.throw('limit must be a positive integer or reference');\n        });\n\n        it('validates array size when a reference', () => {\n\n            const ref = Joi.ref('limit');\n            const schema = Joi.object().keys({\n                limit: Joi.any(),\n                arr: Joi.array().length(ref)\n            });\n\n            Helper.validate(schema, [\n                [{ limit: 2, arr: [1, 2] }, true],\n                [{ limit: 2, arr: [1] }, false, {\n                    message: '\"arr\" must contain ref:limit items',\n                    path: ['arr'],\n                    type: 'array.length',\n                    context: { limit: ref, value: [1], label: 'arr', key: 'arr' }\n                }]\n            ]);\n        });\n\n        it('handles references within a when', () => {\n\n            const schema = Joi.object({\n                limit: Joi.any(),\n                arr: Joi.array(),\n                arr2: Joi.when('arr', {\n                    is: Joi.array().length(Joi.ref('limit')),\n                    then: Joi.array()\n                })\n            });\n\n            Helper.validate(schema, [\n                [{ limit: 2, arr: [1, 2], arr2: [1, 2] }, true]\n            ]);\n        });\n\n        it('validates reference is a safe integer', () => {\n\n            const ref = Joi.ref('limit');\n            const schema = Joi.object().keys({\n                limit: Joi.any(),\n                arr: Joi.array().length(ref)\n            });\n\n            Helper.validate(schema, [\n                [{ limit: Math.pow(2, 53), arr: [1, 2] }, false, {\n                    message: '\"arr\" limit references \"ref:limit\" which must be a positive integer',\n                    path: ['arr'],\n                    type: 'any.ref',\n                    context: { ref, label: 'arr', key: 'arr', value: Math.pow(2, 53), arg: 'limit', reason: 'must be a positive integer' }\n                }],\n                [{ limit: 'I like turtles', arr: [1] }, false, {\n                    message: '\"arr\" limit references \"ref:limit\" which must be a positive integer',\n                    path: ['arr'],\n                    type: 'any.ref',\n                    context: { ref, label: 'arr', key: 'arr', value: 'I like turtles', arg: 'limit', reason: 'must be a positive integer' }\n                }]\n            ]);\n        });\n    });\n\n    describe('max()', () => {\n\n        it('validates array size', () => {\n\n            const schema = Joi.array().max(1);\n            Helper.validate(schema, [\n                [[1, 2], false, {\n                    message: '\"value\" must contain less than or equal to 1 items',\n                    path: [],\n                    type: 'array.max',\n                    context: { limit: 1, value: [1, 2], label: 'value' }\n                }],\n                [[1], true]\n            ]);\n        });\n\n        it('overrides rule when called multiple times', () => {\n\n            const schema = Joi.array().max(1).max(2);\n            Helper.validate(schema, [\n                [[1, 2], true],\n                [[1], true]\n            ]);\n        });\n\n        it('throws when limit is not a number', () => {\n\n            expect(() => Joi.array().max('a')).to.throw('limit must be a positive integer or reference');\n        });\n\n        it('throws when limit is not an integer', () => {\n\n            expect(() => Joi.array().max(1.2)).to.throw('limit must be a positive integer or reference');\n        });\n\n        it('throws when limit is negative', () => {\n\n            expect(() => Joi.array().max(-1)).to.throw('limit must be a positive integer or reference');\n        });\n\n        it('validates array size when a reference', () => {\n\n            const ref = Joi.ref('limit');\n            const schema = Joi.object().keys({\n                limit: Joi.any(),\n                arr: Joi.array().max(ref)\n            });\n\n            Helper.validate(schema, [\n                [{ limit: 2, arr: [1, 2] }, true],\n                [{ limit: 2, arr: [1, 2, 3] }, false, {\n                    message: '\"arr\" must contain less than or equal to ref:limit items',\n                    path: ['arr'],\n                    type: 'array.max',\n                    context: { limit: ref, value: [1, 2, 3], label: 'arr', key: 'arr' }\n                }]\n            ]);\n        });\n\n        it('handles references within a when', () => {\n\n            const schema = Joi.object({\n                limit: Joi.any(),\n                arr: Joi.array(),\n                arr2: Joi.when('arr', {\n                    is: Joi.array().max(Joi.ref('limit')),\n                    then: Joi.array()\n                })\n            });\n\n            Helper.validate(schema, [\n                [{ limit: 2, arr: [1, 2], arr2: [1, 2] }, true]\n            ]);\n        });\n\n        it('validates reference is a safe integer', () => {\n\n            const ref = Joi.ref('limit');\n            const schema = Joi.object().keys({\n                limit: Joi.any(),\n                arr: Joi.array().max(ref)\n            });\n\n            Helper.validate(schema, [\n                [{ limit: Math.pow(2, 53), arr: [1, 2] }, false, {\n                    message: '\"arr\" limit references \"ref:limit\" which must be a positive integer',\n                    path: ['arr'],\n                    type: 'any.ref',\n                    context: { ref, label: 'arr', key: 'arr', value: Math.pow(2, 53), arg: 'limit', reason: 'must be a positive integer' }\n                }],\n                [{ limit: 'I like turtles', arr: [1] }, false, {\n                    message: '\"arr\" limit references \"ref:limit\" which must be a positive integer',\n                    path: ['arr'],\n                    type: 'any.ref',\n                    context: { ref, label: 'arr', key: 'arr', value: 'I like turtles', arg: 'limit', reason: 'must be a positive integer' }\n                }]\n            ]);\n        });\n    });\n\n    describe('min()', () => {\n\n        it('validates array size', () => {\n\n            const schema = Joi.array().min(2);\n            Helper.validate(schema, [\n                [[1, 2], true],\n                [[1], false, {\n                    message: '\"value\" must contain at least 2 items',\n                    path: [],\n                    type: 'array.min',\n                    context: { limit: 2, value: [1], label: 'value' }\n                }]\n            ]);\n        });\n\n        it('overrides rule when called multiple times', () => {\n\n            const schema = Joi.array().min(2).min(1);\n            Helper.validate(schema, [\n                [[1, 2], true],\n                [[1], true]\n            ]);\n        });\n\n        it('throws when limit is not a number', () => {\n\n            expect(() => Joi.array().min('a')).to.throw('limit must be a positive integer or reference');\n        });\n\n        it('throws when limit is not an integer', () => {\n\n            expect(() => Joi.array().min(1.2)).to.throw('limit must be a positive integer or reference');\n        });\n\n        it('throws when limit is negative', () => {\n\n            expect(() => Joi.array().min(-1)).to.throw('limit must be a positive integer or reference');\n        });\n\n        it('validates array size when a reference', () => {\n\n            const ref = Joi.ref('limit');\n            const schema = Joi.object().keys({\n                limit: Joi.any(),\n                arr: Joi.array().min(ref)\n            });\n\n            Helper.validate(schema, [\n                [{ limit: 2, arr: [1, 2] }, true],\n                [{ limit: 2, arr: [1] }, false, {\n                    message: '\"arr\" must contain at least ref:limit items',\n                    path: ['arr'],\n                    type: 'array.min',\n                    context: { limit: ref, value: [1], label: 'arr', key: 'arr' }\n                }]\n            ]);\n        });\n\n        it('handles references within a when', () => {\n\n            const schema = Joi.object({\n                limit: Joi.any(),\n                arr: Joi.array(),\n                arr2: Joi.when('arr', {\n                    is: Joi.array().min(Joi.ref('limit')),\n                    then: Joi.array()\n                })\n            });\n\n            Helper.validate(schema, [\n                [{ limit: 2, arr: [1, 2], arr2: [1, 2] }, true]\n            ]);\n        });\n\n        it('validates reference is a safe integer', () => {\n\n            const ref = Joi.ref('limit');\n            const schema = Joi.object().keys({\n                limit: Joi.any(),\n                arr: Joi.array().min(ref)\n            });\n\n            Helper.validate(schema, [\n                [{ limit: Math.pow(2, 53), arr: [1, 2] }, false, {\n                    message: '\"arr\" limit references \"ref:limit\" which must be a positive integer',\n                    path: ['arr'],\n                    type: 'any.ref',\n                    context: { ref, label: 'arr', key: 'arr', value: Math.pow(2, 53), arg: 'limit', reason: 'must be a positive integer' }\n                }],\n                [{ limit: 'I like turtles', arr: [1] }, false, {\n                    message: '\"arr\" limit references \"ref:limit\" which must be a positive integer',\n                    path: ['arr'],\n                    type: 'any.ref',\n                    context: { ref, label: 'arr', key: 'arr', value: 'I like turtles', arg: 'limit', reason: 'must be a positive integer' }\n                }]\n            ]);\n        });\n    });\n\n    describe('options()', () => {\n\n        it('ignores stripUnknown when true', () => {\n\n            const schema = Joi.array().items(Joi.string()).prefs({ stripUnknown: true });\n\n            Helper.validate(schema, [\n                [['one', 'two', 3, 4, true, false], false, '\"[2]\" must be a string']\n            ]);\n        });\n\n        it('respects stripUnknown (as an object)', () => {\n\n            const schema = Joi.array().items(Joi.string()).prefs({ stripUnknown: { arrays: true, objects: false } });\n\n            Helper.validate(schema, [\n                [['one', 'two', 3, 4, true, false], true, ['one', 'two']]\n            ]);\n        });\n    });\n\n    describe('ordered()', () => {\n\n        it('shows path to errors in array ordered items', () => {\n\n            expect(() => {\n\n                Joi.array().ordered({\n                    a: {\n                        b: {\n                            c: {\n                                d: undefined\n                            }\n                        }\n                    }\n                });\n            }).to.throw('Invalid undefined schema (0.a.b.c.d)');\n\n            expect(() => Joi.array().ordered({ foo: 'bar' }, undefined)).to.throw('Invalid undefined schema (1)');\n        });\n\n        it('validates input against items in order', () => {\n\n            const schema = Joi.array().ordered(Joi.string().required(), Joi.number().required());\n\n            Helper.validate(schema, [\n                [['s1', 2], true]\n            ]);\n        });\n\n        it('validates input with optional item', () => {\n\n            const schema = Joi.array().ordered(Joi.string().required(), Joi.number().required(), Joi.number());\n\n            Helper.validate(schema, [\n                [['s1', 2, 3], true]\n            ]);\n        });\n\n        it('validates input without optional item', () => {\n\n            const schema = Joi.array().ordered(Joi.string().required(), Joi.number().required(), Joi.number());\n\n            Helper.validate(schema, [\n                [['s1', 2], true]\n            ]);\n        });\n\n        it('validates input without optional item', () => {\n\n            const schema = Joi.array().ordered(Joi.string().required(), Joi.number().required(), Joi.number()).sparse(true);\n\n            Helper.validate(schema, [\n                [['s1', 2, undefined], true]\n            ]);\n        });\n\n        it('validates input without optional item in a sparse array', () => {\n\n            const schema = Joi.array().ordered(Joi.string().required(), Joi.number(), Joi.number().required()).sparse(true);\n\n            Helper.validate(schema, [\n                [['s1', undefined, 3], true]\n            ]);\n        });\n\n        it('validates when input matches ordered items and matches regular items', () => {\n\n            const schema = Joi.array().ordered(Joi.string().required(), Joi.number().required()).items(Joi.number());\n\n            Helper.validate(schema, [\n                [['s1', 2, 3, 4, 5], true]\n            ]);\n        });\n\n        it('errors when input does not match ordered items', () => {\n\n            const schema = Joi.array().ordered(Joi.number().required(), Joi.string().required());\n\n            Helper.validate(schema, [\n                [['s1', 2], false, {\n                    message: '\"[0]\" must be a number',\n                    path: [0],\n                    type: 'number.base',\n                    context: { label: '[0]', key: 0, value: 's1' }\n                }]\n            ]);\n        });\n\n        it('errors when input has more items than ordered items', () => {\n\n            const schema = Joi.array().ordered(Joi.number().required(), Joi.string().required());\n\n            Helper.validate(schema, [\n                [[1, 's2', 3], false, {\n                    message: '\"value\" must contain at most 2 items',\n                    path: [],\n                    type: 'array.orderedLength',\n                    context: { pos: 2, limit: 2, label: 'value', value: [1, 's2', 3] }\n                }]\n            ]);\n        });\n\n        it('errors when input has more items than ordered items with abortEarly = false', () => {\n\n            const schema = Joi.array().ordered(Joi.string(), Joi.number()).prefs({ abortEarly: false });\n\n            Helper.validate(schema, [\n                [[1, 2, 3, 4, 5], false, {\n                    message: '\"[0]\" must be a string. \"value\" must contain at most 2 items',\n                    details: [\n                        {\n                            message: '\"[0]\" must be a string',\n                            path: [0],\n                            type: 'string.base',\n                            context: { value: 1, label: '[0]', key: 0 }\n                        },\n                        {\n                            message: '\"value\" must contain at most 2 items',\n                            path: [],\n                            type: 'array.orderedLength',\n                            context: { pos: 2, limit: 2, label: 'value', value: [1, 2, 3, 4, 5] }\n                        }\n                    ]\n                }]\n            ]);\n        });\n\n        it('errors when input has less items than ordered items', () => {\n\n            const schema = Joi.array().ordered(Joi.number().required(), Joi.string().required());\n\n            Helper.validate(schema, [\n                [[1], false, {\n                    message: '\"value\" does not contain 1 required value(s)',\n                    path: [],\n                    type: 'array.includesRequiredUnknowns',\n                    context: { unknownMisses: 1, label: 'value', value: [1] }\n                }]\n            ]);\n        });\n\n        it('errors when input matches ordered items but not matches regular items', () => {\n\n            const schema = Joi.array()\n                .ordered(Joi.string().required(), Joi.number().required())\n                .items(Joi.number())\n                .prefs({ abortEarly: false });\n\n            Helper.validate(schema, [\n                [['s1', 2, 3, 4, 's5'], false, {\n                    message: '\"[4]\" must be a number',\n                    details: [{\n                        message: '\"[4]\" must be a number',\n                        path: [4],\n                        type: 'number.base',\n                        context: { label: '[4]', key: 4, value: 's5' }\n                    }]\n                }]\n            ]);\n        });\n\n        it('errors when input does not match ordered items but matches regular items', () => {\n\n            const schema = Joi.array()\n                .ordered(Joi.string(), Joi.number())\n                .items(Joi.number())\n                .prefs({ abortEarly: false });\n\n            Helper.validate(schema, [\n                [[1, 2, 3, 4, 5], false, {\n                    message: '\"[0]\" must be a string',\n                    details: [{\n                        message: '\"[0]\" must be a string',\n                        path: [0],\n                        type: 'string.base',\n                        context: { value: 1, label: '[0]', key: 0 }\n                    }]\n                }]\n            ]);\n        });\n\n        it('errors when input does not match ordered items not matches regular items', () => {\n\n            const schema = Joi.array().ordered(Joi.string(), Joi.number()).items(Joi.string()).prefs({ abortEarly: false });\n\n            Helper.validate(schema, [\n                [[1, 2, 3, 4, 5], false, {\n                    message: '\"[0]\" must be a string. \"[2]\" must be a string. \"[3]\" must be a string. \"[4]\" must be a string',\n                    details: [\n                        {\n                            message: '\"[0]\" must be a string',\n                            path: [0],\n                            type: 'string.base',\n                            context: { value: 1, label: '[0]', key: 0 }\n                        },\n                        {\n                            message: '\"[2]\" must be a string',\n                            path: [2],\n                            type: 'string.base',\n                            context: { value: 3, label: '[2]', key: 2 }\n                        },\n                        {\n                            message: '\"[3]\" must be a string',\n                            path: [3],\n                            type: 'string.base',\n                            context: { value: 4, label: '[3]', key: 3 }\n                        },\n                        {\n                            message: '\"[4]\" must be a string',\n                            path: [4],\n                            type: 'string.base',\n                            context: { value: 5, label: '[4]', key: 4 }\n                        }\n                    ]\n                }]\n            ]);\n        });\n\n        it('errors but continues when abortEarly is set to false', () => {\n\n            const schema = Joi.array().ordered(Joi.number().required(), Joi.string().required()).prefs({ abortEarly: false });\n\n            Helper.validate(schema, [\n                [['s1', 2], false, {\n                    message: '\"[0]\" must be a number. \"[1]\" must be a string',\n                    details: [\n                        {\n                            message: '\"[0]\" must be a number',\n                            path: [0],\n                            type: 'number.base',\n                            context: { label: '[0]', key: 0, value: 's1' }\n                        },\n                        {\n                            message: '\"[1]\" must be a string',\n                            path: [1],\n                            type: 'string.base',\n                            context: { value: 2, label: '[1]', key: 1 }\n                        }\n                    ]\n                }]\n            ]);\n        });\n\n        it('errors on sparse arrays and continues when abortEarly is set to false', () => {\n\n            const schema = Joi.array().ordered(\n                Joi.number().min(0),\n                Joi.string().min(2),\n                Joi.number().max(0),\n                Joi.string().max(3)\n            )\n                .prefs({ abortEarly: false });\n\n            Helper.validate(schema, [\n                [[0, 'ab', 0, 'ab'], true],\n                [[undefined, 'foo', 2, 'bar'], false, {\n                    message: '\"[0]\" must not be a sparse array item. \"[2]\" must be less than or equal to 0',\n                    details: [{\n                        message: '\"[0]\" must not be a sparse array item',\n                        path: [0],\n                        type: 'array.sparse',\n                        context: { key: 0, label: '[0]', path: [0], pos: 0, value: undefined }\n                    },\n                    {\n                        message: '\"[2]\" must be less than or equal to 0',\n                        path: [2],\n                        type: 'number.max',\n                        context: { key: 2, label: '[2]', limit: 0, value: 2 }\n                    }]\n                }],\n                [[undefined, 'foo', 2, undefined], false, {\n                    message: '\"[0]\" must not be a sparse array item. \"[2]\" must be less than or equal to 0. \"[3]\" must not be a sparse array item',\n                    details: [{\n                        message: '\"[0]\" must not be a sparse array item',\n                        path: [0],\n                        type: 'array.sparse',\n                        context: { key: 0, label: '[0]', path: [0], pos: 0, value: undefined }\n                    },\n                    {\n                        message: '\"[2]\" must be less than or equal to 0',\n                        path: [2],\n                        type: 'number.max',\n                        context: { key: 2, label: '[2]', limit: 0, value: 2 }\n                    },\n                    {\n                        message: '\"[3]\" must not be a sparse array item',\n                        path: [3],\n                        type: 'array.sparse',\n                        context: { key: 3, label: '[3]', path: [3], pos: 3, value: undefined }\n                    }]\n                }]\n            ]);\n        });\n\n        it('errors on forbidden items and continues when abortEarly is set to false', () => {\n\n            const schema = Joi.array().items(Joi.bool().forbidden()).ordered(\n                Joi.number().min(0),\n                Joi.string().min(2),\n                Joi.number().max(0),\n                Joi.string().max(3)\n            )\n                .prefs({ abortEarly: false });\n\n            Helper.validate(schema, [\n                [[0, 'ab', 0, 'ab'], true],\n                [[undefined, 'foo', 2, 'bar'], false, {\n                    message: '\"[0]\" must not be a sparse array item. \"[2]\" must be less than or equal to 0',\n                    details: [{\n                        message: '\"[0]\" must not be a sparse array item',\n                        path: [0],\n                        type: 'array.sparse',\n                        context: { key: 0, label: '[0]', path: [0], pos: 0, value: undefined }\n                    }, {\n                        message: '\"[2]\" must be less than or equal to 0',\n                        path: [2],\n                        type: 'number.max',\n                        context: { key: 2, label: '[2]', limit: 0, value: 2 }\n                    }]\n                }],\n                [[undefined, 'foo', 2, undefined], false, {\n                    message: '\"[0]\" must not be a sparse array item. \"[2]\" must be less than or equal to 0. \"[3]\" must not be a sparse array item',\n                    details: [{\n                        message: '\"[0]\" must not be a sparse array item',\n                        path: [0],\n                        type: 'array.sparse',\n                        context: { key: 0, label: '[0]', path: [0], pos: 0, value: undefined }\n                    },\n                    {\n                        message: '\"[2]\" must be less than or equal to 0',\n                        path: [2],\n                        type: 'number.max',\n                        context: { key: 2, label: '[2]', limit: 0, value: 2 }\n                    },\n                    {\n                        message: '\"[3]\" must not be a sparse array item',\n                        path: [3],\n                        type: 'array.sparse',\n                        context: { key: 3, label: '[3]', path: [3], pos: 3, value: undefined }\n                    }]\n                }],\n                [[undefined, false, 2, undefined], false, {\n                    message: '\"[0]\" must not be a sparse array item. \"[1]\" contains an excluded value. \"[2]\" must be less than or equal to 0. \"[3]\" must not be a sparse array item',\n                    details: [{\n                        message: '\"[0]\" must not be a sparse array item',\n                        path: [0],\n                        type: 'array.sparse',\n                        context: { key: 0, label: '[0]', path: [0], pos: 0, value: undefined }\n                    },\n                    {\n                        message: '\"[1]\" contains an excluded value',\n                        path: [1],\n                        type: 'array.excludes',\n                        context: { key: 1, label: '[1]', pos: 1, value: false }\n                    },\n                    {\n                        message: '\"[2]\" must be less than or equal to 0',\n                        path: [2],\n                        type: 'number.max',\n                        context: { key: 2, label: '[2]', limit: 0, value: 2 }\n                    },\n                    {\n                        message: '\"[3]\" must not be a sparse array item',\n                        path: [3],\n                        type: 'array.sparse',\n                        context: { key: 3, label: '[3]', path: [3], pos: 3, value: undefined }\n                    }]\n                }]\n            ]);\n        });\n\n        it('strips item', () => {\n\n            const schema = Joi.array().ordered(Joi.string().required(), Joi.number().strip(), Joi.number().required());\n\n            Helper.validate(schema, [\n                [['s1', 2, 3], true, ['s1', 3]]\n            ]);\n        });\n\n        it('strips multiple items', () => {\n\n            const schema = Joi.array().ordered(Joi.string().strip(), Joi.number(), Joi.number().strip());\n\n            Helper.validate(schema, [\n                [['s1', 2, 3], true, [2]]\n            ]);\n        });\n\n        it('references array members', () => {\n\n            const schema = Joi.array().ordered(Joi.number(), Joi.number().greater(Joi.ref('..0')));\n\n            Helper.validate(schema, [\n                [[1, 2], true],\n                [[1, 0], false, '\"[1]\" must be greater than ref:0']\n            ]);\n        });\n    });\n\n    describe('single()', () => {\n\n        it('allows a single element', () => {\n\n            const schema = Joi.array().items(Joi.number()).items(Joi.boolean().forbidden()).single();\n\n            Helper.validate(schema, [\n                [[1, 2, 3], true],\n                [1, true, [1]],\n                [['a'], false, {\n                    message: '\"[0]\" must be a number',\n                    path: [0],\n                    type: 'number.base',\n                    context: { label: '[0]', key: 0, value: 'a' }\n                }],\n                ['a', false, {\n                    message: '\"value\" must be a number',\n                    path: [],\n                    type: 'number.base',\n                    context: { label: 'value', value: 'a' }\n                }],\n                [true, false, {\n                    message: '\"value\" contains an excluded value',\n                    path: [],\n                    type: 'array.excludes',\n                    context: { pos: 0, value: true, label: 'value' }\n                }]\n            ]);\n        });\n\n        it('allows a single element with multiple types', () => {\n\n            const schema = Joi.array().items(Joi.number(), Joi.string()).single();\n\n            Helper.validate(schema, [\n                [[1, 2, 3], true],\n                [1, true, [1]],\n                [[1, 'a'], true],\n                ['a', true, ['a']],\n                [true, false, {\n                    message: '\"value\" does not match any of the allowed types',\n                    path: [],\n                    type: 'array.includes',\n                    context: { pos: 0, value: true, label: 'value' }\n                }]\n            ]);\n        });\n\n        it('errors on single with array items', () => {\n\n            expect(() => Joi.array().items(Joi.array()).single()).to.throw('Cannot specify single rule when array has array items');\n            expect(() => Joi.array().items(Joi.alternatives([Joi.array()])).single()).to.throw('Cannot specify single rule when array has array items');\n            expect(() => Joi.array().items(Joi.alternatives([Joi.number(), Joi.string()])).single()).to.not.throw();\n\n            expect(() => Joi.array().single().items(Joi.array())).to.throw('Cannot specify array item with single rule enabled');\n            expect(() => Joi.array().single().items(Joi.alternatives([Joi.array()]))).to.throw('Cannot specify array item with single rule enabled');\n\n            expect(() => Joi.array().ordered(Joi.array()).single()).to.throw('Cannot specify single rule when array has array items');\n            expect(() => Joi.array().ordered(Joi.alternatives([Joi.array()])).single()).to.throw('Cannot specify single rule when array has array items');\n\n            expect(() => Joi.array().single().ordered(Joi.array())).to.throw('Cannot specify array item with single rule enabled');\n            expect(() => Joi.array().single().ordered(Joi.alternatives([Joi.array()]))).to.throw('Cannot specify array item with single rule enabled');\n        });\n\n        it('switches the single flag with explicit value', () => {\n\n            const schema = Joi.array().single(true);\n            const desc = schema.describe();\n            expect(desc).to.equal({\n                type: 'array',\n                flags: { single: true }\n            });\n        });\n\n        it('switches the single flag back', () => {\n\n            const schema = Joi.array().single().single(false);\n            const desc = schema.describe();\n            expect(desc).to.equal({ type: 'array' });\n        });\n\n        it('avoids unnecessary cloning when called twice', () => {\n\n            const schema = Joi.array().single();\n            expect(schema.single()).to.shallow.equal(schema);\n        });\n    });\n\n    describe('sort()', () => {\n\n        it('validates array sorts order', () => {\n\n            const schema = Joi.array().sort().prefs({ convert: false });\n\n            Helper.validate(schema, [\n                [[1, 2], true],\n                [['a', 'b'], true],\n                [['a', 'b', null], true],\n                [['a', 'b', null, null], true],\n                [['a', 'b', undefined], true],\n                [['a', 'b', undefined, undefined], true],\n                [[1, 0], false, {\n                    message: '\"value\" must be sorted in ascending order by value',\n                    path: [],\n                    type: 'array.sort',\n                    context: {\n                        order: 'ascending',\n                        by: 'value',\n                        label: 'value',\n                        value: [1, 0]\n                    }\n                }],\n                [['1', '0'], false, {\n                    message: '\"value\" must be sorted in ascending order by value',\n                    path: [],\n                    type: 'array.sort',\n                    context: {\n                        order: 'ascending',\n                        by: 'value',\n                        label: 'value',\n                        value: ['1', '0']\n                    }\n                }],\n                [[null, 1, 2], false, {\n                    message: '\"value\" must be sorted in ascending order by value',\n                    path: [],\n                    type: 'array.sort',\n                    context: {\n                        order: 'ascending',\n                        by: 'value',\n                        label: 'value',\n                        value: [null, 1, 2]\n                    }\n                }],\n                [{}, false, '\"value\" must be an array']\n            ]);\n        });\n\n        it('validates array sorts order (ascending)', () => {\n\n            const schema = Joi.array().sort({ order: 'ascending' }).prefs({ convert: false });\n\n            Helper.validate(schema, [\n                [[1, 2], true],\n                [['a', 'b'], true],\n                [['a', 'b', null], true],\n                [['a', 'b', null, null], true],\n                [['a', 'b', undefined], true],\n                [['a', 'b', undefined, undefined], true],\n                [['a', 'b', null, undefined], true],\n                [[1, 0], false, {\n                    message: '\"value\" must be sorted in ascending order by value',\n                    path: [],\n                    type: 'array.sort',\n                    context: {\n                        order: 'ascending',\n                        by: 'value',\n                        label: 'value',\n                        value: [1, 0]\n                    }\n                }],\n                [['1', '0'], false, {\n                    message: '\"value\" must be sorted in ascending order by value',\n                    path: [],\n                    type: 'array.sort',\n                    context: {\n                        order: 'ascending',\n                        by: 'value',\n                        label: 'value',\n                        value: ['1', '0']\n                    }\n                }],\n                [[null, 1, 2], false, {\n                    message: '\"value\" must be sorted in ascending order by value',\n                    path: [],\n                    type: 'array.sort',\n                    context: {\n                        order: 'ascending',\n                        by: 'value',\n                        label: 'value',\n                        value: [null, 1, 2]\n                    }\n                }]\n            ]);\n        });\n\n        it('validates array sorts order (descending)', () => {\n\n            const schema = Joi.array().sort({ order: 'descending' }).prefs({ convert: false });\n\n            Helper.validate(schema, [\n                [[2, 1], true],\n                [['b', 'a'], true],\n                [[null, 'b', 'a'], true],\n                [[null, null, 'b', 'a'], true],\n                [['b', 'a', undefined], true],\n                [['b', 'a', undefined, undefined], true],\n                [[null, 'b', 'a', undefined], true],\n                [[0, 1], false, {\n                    message: '\"value\" must be sorted in descending order by value',\n                    path: [],\n                    type: 'array.sort',\n                    context: {\n                        order: 'descending',\n                        by: 'value',\n                        label: 'value',\n                        value: [0, 1]\n                    }\n                }],\n                [['0', '1'], false, {\n                    message: '\"value\" must be sorted in descending order by value',\n                    path: [],\n                    type: 'array.sort',\n                    context: {\n                        order: 'descending',\n                        by: 'value',\n                        label: 'value',\n                        value: ['0', '1']\n                    }\n                }],\n                [[2, 1, null], false, {\n                    message: '\"value\" must be sorted in descending order by value',\n                    path: [],\n                    type: 'array.sort',\n                    context: {\n                        order: 'descending',\n                        by: 'value',\n                        label: 'value',\n                        value: [2, 1, null]\n                    }\n                }]\n            ]);\n        });\n\n        it('validates array sorts order (object key)', () => {\n\n            const schema = Joi.array().sort({ by: 'x' }).prefs({ convert: false });\n\n            Helper.validate(schema, [\n                [[{ x: 1 }, { x: 2 }], true],\n                [[{ x: 'a' }, { x: 'b' }], true],\n                [[{ x: 'a' }, { x: 'b' }, { x: null }], true],\n                [[{ x: 'a' }, { x: 'b' }, { x: null }, { x: null }], true],\n                [[{ x: 'a' }, { x: 'b' }, undefined], true],\n                [[{ x: 'a' }, { x: 'b' }, undefined, undefined], true],\n                [[{ x: 'a' }, { x: 'b' }, { x: null }, {}, null, undefined, undefined], true],\n                [[{ x: 1 }, { x: 0 }], false, {\n                    message: '\"value\" must be sorted in ascending order by x',\n                    path: [],\n                    type: 'array.sort',\n                    context: {\n                        order: 'ascending',\n                        by: 'x',\n                        label: 'value',\n                        value: [{ x: 1 }, { x: 0 }]\n                    }\n                }],\n                [[{ x: '1' }, { x: '0' }], false, {\n                    message: '\"value\" must be sorted in ascending order by x',\n                    path: [],\n                    type: 'array.sort',\n                    context: {\n                        order: 'ascending',\n                        by: 'x',\n                        label: 'value',\n                        value: [{ x: '1' }, { x: '0' }]\n                    }\n                }],\n                [[{ x: null }, { x: 1 }, { x: 2 }], false, {\n                    message: '\"value\" must be sorted in ascending order by x',\n                    path: [],\n                    type: 'array.sort',\n                    context: {\n                        order: 'ascending',\n                        by: 'x',\n                        label: 'value',\n                        value: [{ x: null }, { x: 1 }, { x: 2 }]\n                    }\n                }]\n            ]);\n        });\n\n        it('sorts array (ascending)', () => {\n\n            const schema = Joi.array().sort({ order: 'ascending' });\n\n            Helper.validate(schema, [\n                [[2, 1], true, [1, 2]],\n                [['b', 'a'], true, ['a', 'b']],\n                [[null, 'a', 'b'], true, ['a', 'b', null]],\n                [[null, 'b', 'a'], true, ['a', 'b', null]],\n                [['a', null, 'b'], true, ['a', 'b', null]],\n                [[null, 'a', null, 'b'], true, ['a', 'b', null, null]],\n                [['b', 'a', undefined], true, ['a', 'b', undefined]],\n                [['b', undefined, 'a'], true, ['a', 'b', undefined]],\n                [[0, '1'], false, {\n                    message: '\"value\" cannot be sorted due to mismatching types',\n                    path: [],\n                    type: 'array.sort.mismatching',\n                    context: {\n                        label: 'value',\n                        value: [0, '1']\n                    }\n                }],\n                [{}, false, '\"value\" must be an array']\n            ]);\n        });\n\n        it('sorts array (object key)', () => {\n\n            const schema = Joi.array().sort({ by: 'x' });\n\n            Helper.validate(schema, [\n                [[{ x: 1 }, { x: 2 }], true, [{ x: 1 }, { x: 2 }]],\n                [[{ x: 'b' }, { x: 'a' }], true, [{ x: 'a' }, { x: 'b' }]],\n                [[{ x: 'b' }, { x: null }, { x: 'a' }], true, [{ x: 'a' }, { x: 'b' }, { x: null }]],\n                [[{}, { x: 'b' }, undefined, null, { x: null }, { x: 'a' }, undefined], true, [{ x: 'a' }, { x: 'b' }, { x: null }, {}, null, undefined, undefined]],\n                [[{ x: 0 }, { x: '1' }], false, {\n                    message: '\"value\" cannot be sorted due to mismatching types',\n                    path: [],\n                    type: 'array.sort.mismatching',\n                    context: {\n                        label: 'value',\n                        value: [{ x: 0 }, { x: '1' }]\n                    }\n                }]\n            ]);\n        });\n\n        it('errors on unsupported type', () => {\n\n            const schema = Joi.array().sort().prefs({ convert: false });\n\n            Helper.validate(schema, [\n                [[{}, {}], false, '\"value\" cannot be sorted due to unsupported type object']\n            ]);\n        });\n\n        it('errors on mismatching types', () => {\n\n            const schema = Joi.array().sort().prefs({ convert: false });\n\n            Helper.validate(schema, [\n                [[1, 'x'], false, '\"value\" cannot be sorted due to mismatching types']\n            ]);\n        });\n    });\n\n    describe('sparse()', () => {\n\n        it('errors on undefined value', () => {\n\n            const schema = Joi.array().items(Joi.number());\n\n            Helper.validate(schema, [\n                [[undefined], false, {\n                    message: '\"[0]\" must not be a sparse array item',\n                    path: [0],\n                    type: 'array.sparse',\n                    context: { label: '[0]', key: 0, path: [0], pos: 0, value: undefined }\n                }],\n                [[2, undefined], false, {\n                    message: '\"[1]\" must not be a sparse array item',\n                    path: [1],\n                    type: 'array.sparse',\n                    context: { label: '[1]', key: 1, path: [1], pos: 1, value: undefined }\n                }]\n            ]);\n        });\n\n        it('errors on undefined value after validation', () => {\n\n            const schema = Joi.array().items(Joi.object().empty({}));\n\n            Helper.validate(schema, [\n                [[{ a: 1 }, {}, { c: 3 }], false, {\n                    message: '\"[1]\" must not be a sparse array item',\n                    path: [1],\n                    type: 'array.sparse',\n                    context: { label: '[1]', key: 1, path: [1], pos: 1, value: undefined }\n                }]\n            ]);\n        });\n\n        it('errors on undefined value after validation with abortEarly false', () => {\n\n            const schema = Joi.array().items(Joi.object().empty({})).prefs({ abortEarly: false });\n\n            Helper.validate(schema, [\n                [[{ a: 1 }, {}, 3], false, {\n                    message: '\"[1]\" must not be a sparse array item. \"[2]\" must be of type object',\n                    details: [\n                        {\n                            message: '\"[1]\" must not be a sparse array item',\n                            path: [1],\n                            type: 'array.sparse',\n                            context: { label: '[1]', key: 1, path: [1], pos: 1, value: undefined }\n                        },\n                        {\n                            message: '\"[2]\" must be of type object',\n                            path: [2],\n                            type: 'object.base',\n                            context: { label: '[2]', key: 2, value: 3, type: 'object' }\n                        }\n                    ]\n                }]\n            ]);\n        });\n\n        it('errors on undefined value after validation with required', () => {\n\n            const schema = Joi.array().items(Joi.object().empty({}).required());\n\n            Helper.validate(schema, [\n                [[{}, { c: 3 }], false, {\n                    message: '\"[0]\" is required',\n                    path: [0],\n                    type: 'any.required',\n                    context: { label: '[0]', key: 0 }\n                }]\n            ]);\n        });\n\n        it('errors on undefined value after custom validation with required', () => {\n\n            const custom = Joi.extend({\n                type: 'extended',\n                rules: {\n                    foo: {\n                        method() {\n\n                            return this.$_addRule('foo');\n                        },\n                        validate: () => undefined\n                    }\n                }\n            });\n\n            const schema = custom.array().items(custom.extended().foo().required());\n\n            Helper.validate(schema, [\n                [[{}, { c: 3 }], false, {\n                    message: '\"[0]\" must not be a sparse array item',\n                    path: [0],\n                    type: 'array.sparse',\n                    context: { label: '[0]', key: 0, path: [0], pos: 0, value: undefined }\n                }]\n            ]);\n        });\n\n        it('errors on undefined value after custom validation with required and abortEarly false', () => {\n\n            const custom = Joi.extend({\n                type: 'extended',\n                rules: {\n                    foo: {\n                        method() {\n\n                            return this.$_addRule('foo');\n                        },\n                        validate: () => undefined\n                    }\n                }\n            });\n\n            const schema = custom.array().items(custom.extended().foo().required()).prefs({ abortEarly: false });\n\n            Helper.validate(schema, [\n                [[{}, { c: 3 }], false, {\n                    message: '\"[0]\" must not be a sparse array item. \"[1]\" must not be a sparse array item',\n                    details: [\n                        {\n                            message: '\"[0]\" must not be a sparse array item',\n                            path: [0],\n                            type: 'array.sparse',\n                            context: { label: '[0]', key: 0, path: [0], pos: 0, value: undefined }\n                        },\n                        {\n                            message: '\"[1]\" must not be a sparse array item',\n                            path: [1],\n                            type: 'array.sparse',\n                            context: { label: '[1]', key: 1, path: [1], pos: 1, value: undefined }\n                        }\n                    ]\n                }]\n            ]);\n        });\n\n        it('errors on undefined value after validation with required and abortEarly false', () => {\n\n            const schema = Joi.array().items(Joi.object().empty({}).required()).prefs({ abortEarly: false });\n\n            Helper.validate(schema, [\n                [[{}, 3], false, {\n                    message: '\"[0]\" is required. \"[1]\" must be of type object. \"value\" does not contain 1 required value(s)',\n                    details: [\n                        {\n                            message: '\"[0]\" is required',\n                            path: [0],\n                            type: 'any.required',\n                            context: { label: '[0]', key: 0 }\n                        },\n                        {\n                            message: '\"[1]\" must be of type object',\n                            path: [1],\n                            type: 'object.base',\n                            context: { label: '[1]', key: 1, value: 3, type: 'object' }\n                        },\n                        {\n                            message: '\"value\" does not contain 1 required value(s)',\n                            path: [],\n                            type: 'array.includesRequiredUnknowns',\n                            context: { unknownMisses: 1, label: 'value', value: [{}, 3] }\n                        }\n                    ]\n                }]\n            ]);\n        });\n\n        it('errors on undefined value after validation with ordered', () => {\n\n            const schema = Joi.array().ordered(Joi.object().empty({}));\n\n            Helper.validate(schema, [\n                [[{}], false, {\n                    message: '\"[0]\" must not be a sparse array item',\n                    path: [0],\n                    type: 'array.sparse',\n                    context: { label: '[0]', key: 0, path: [0], pos: 0, value: undefined }\n                }]\n            ]);\n        });\n\n        it('errors on undefined value after validation with ordered and abortEarly false', () => {\n\n            const schema = Joi.array().ordered(Joi.object().empty({})).prefs({ abortEarly: false });\n\n            Helper.validate(schema, [\n                [[{}, 3], false, {\n                    message: '\"[0]\" must not be a sparse array item. \"value\" must contain at most 1 items',\n                    details: [\n                        {\n                            message: '\"[0]\" must not be a sparse array item',\n                            path: [0],\n                            type: 'array.sparse',\n                            context: { label: '[0]', key: 0, path: [0], pos: 0, value: undefined }\n                        },\n                        {\n                            message: '\"value\" must contain at most 1 items',\n                            path: [],\n                            type: 'array.orderedLength',\n                            context: { pos: 1, limit: 1, label: 'value', value: [{}, 3] }\n                        }\n                    ]\n                }]\n            ]);\n        });\n\n        it('validates on undefined value with sparse', () => {\n\n            const schema = Joi.array().items(Joi.number()).sparse();\n\n            Helper.validate(schema, [\n                [[undefined], true],\n                [[2, undefined], true]\n            ]);\n        });\n\n        it('validates on undefined value after validation', () => {\n\n            const schema = Joi.array().items(Joi.object().empty({})).sparse();\n\n            Helper.validate(schema, [\n                [[{ a: 1 }, {}, { c: 3 }], true, [{ a: 1 }, undefined, { c: 3 }]]\n            ]);\n        });\n\n        it('validates on undefined value after validation with required', () => {\n\n            const schema = Joi.array().items(Joi.object().empty({}).required()).sparse();\n\n            Helper.validate(schema, [\n                [[{ a: 1 }, {}, { c: 3 }], false, {\n                    message: '\"[1]\" is required',\n                    path: [1],\n                    type: 'any.required',\n                    context: { label: '[1]', key: 1 }\n                }]\n            ]);\n        });\n\n        it('validates on undefined value after validation with ordered', () => {\n\n            const schema = Joi.array().ordered(Joi.object().empty({})).sparse();\n\n            Helper.validate(schema, [\n                [[{}], true, [undefined]]\n            ]);\n        });\n\n        it('switches the sparse flag', () => {\n\n            const schema = Joi.array().sparse();\n            const desc = schema.describe();\n            expect(desc).to.equal({\n                type: 'array',\n                flags: { sparse: true }\n            });\n        });\n\n        it('switches the sparse flag with explicit value', () => {\n\n            const schema = Joi.array().sparse(true);\n            const desc = schema.describe();\n            expect(desc).to.equal({\n                type: 'array',\n                flags: { sparse: true }\n            });\n        });\n\n        it('switches the sparse flag back', () => {\n\n            const schema = Joi.array().sparse().sparse(false);\n            const desc = schema.describe();\n            expect(desc).to.equal({\n                type: 'array'\n            });\n        });\n\n        it('avoids unnecessary cloning when called twice', () => {\n\n            const schema = Joi.array().sparse();\n            expect(schema.sparse()).to.shallow.equal(schema);\n        });\n    });\n\n    describe('unique()', () => {\n\n        it('errors if duplicate numbers, strings, objects, binaries, functions, dates, booleans and bigints', () => {\n\n            const buffer = Buffer.from('hello world');\n            const func = function () { };\n            const now = new Date();\n            const schema = Joi.array().sparse().unique();\n\n            Helper.validate(schema, [\n                [[2, 2], false, {\n                    message: '\"[1]\" contains a duplicate value',\n                    path: [1],\n                    type: 'array.unique',\n                    context: {\n                        pos: 1,\n                        value: 2,\n                        dupePos: 0,\n                        dupeValue: 2,\n                        label: '[1]',\n                        key: 1\n                    }\n                }],\n                [[0x2, 2], false, {\n                    message: '\"[1]\" contains a duplicate value',\n                    path: [1],\n                    type: 'array.unique',\n                    context: {\n                        pos: 1,\n                        value: 2,\n                        dupePos: 0,\n                        dupeValue: 0x2,\n                        label: '[1]',\n                        key: 1\n                    }\n                }],\n                [['duplicate', 'duplicate'], false, {\n                    message: '\"[1]\" contains a duplicate value',\n                    path: [1],\n                    type: 'array.unique',\n                    context: {\n                        pos: 1,\n                        value: 'duplicate',\n                        dupePos: 0,\n                        dupeValue: 'duplicate',\n                        label: '[1]',\n                        key: 1\n                    }\n                }],\n                [[{ a: 'b' }, { a: 'b' }], false, {\n                    message: '\"[1]\" contains a duplicate value',\n                    path: [1],\n                    type: 'array.unique',\n                    context: {\n                        pos: 1,\n                        value: { a: 'b' },\n                        dupePos: 0,\n                        dupeValue: { a: 'b' },\n                        label: '[1]',\n                        key: 1\n                    }\n                }],\n                [[buffer, buffer], false, {\n                    message: '\"[1]\" contains a duplicate value',\n                    path: [1],\n                    type: 'array.unique',\n                    context: {\n                        pos: 1,\n                        value: buffer,\n                        dupePos: 0,\n                        dupeValue: buffer,\n                        label: '[1]',\n                        key: 1\n                    }\n                }],\n                [[func, func], false, {\n                    message: '\"[1]\" contains a duplicate value',\n                    path: [1],\n                    type: 'array.unique',\n                    context: {\n                        pos: 1,\n                        value: func,\n                        dupePos: 0,\n                        dupeValue: func,\n                        label: '[1]',\n                        key: 1\n                    }\n                }],\n                [[now, now], false, {\n                    message: '\"[1]\" contains a duplicate value',\n                    path: [1],\n                    type: 'array.unique',\n                    context: {\n                        pos: 1,\n                        value: now,\n                        dupePos: 0,\n                        dupeValue: now,\n                        label: '[1]',\n                        key: 1\n                    }\n                }],\n                [[true, true], false, {\n                    message: '\"[1]\" contains a duplicate value',\n                    path: [1],\n                    type: 'array.unique',\n                    context: {\n                        pos: 1,\n                        value: true,\n                        dupePos: 0,\n                        dupeValue: true,\n                        label: '[1]',\n                        key: 1\n                    }\n                }],\n                [[undefined, undefined], false, {\n                    message: '\"[1]\" contains a duplicate value',\n                    path: [1],\n                    type: 'array.unique',\n                    context: {\n                        pos: 1,\n                        dupePos: 0,\n                        dupeValue: undefined,\n                        label: '[1]',\n                        key: 1,\n                        value: undefined\n                    }\n                }],\n                [[1n, 1n], false, {\n                    message: '\"[1]\" contains a duplicate value',\n                    path: [1],\n                    type: 'array.unique',\n                    context: {\n                        pos: 1,\n                        dupePos: 0,\n                        dupeValue: 1n,\n                        label: '[1]',\n                        key: 1,\n                        value: 1n\n                    }\n                }]\n\n            ]);\n        });\n\n        it('errors with the correct details', () => {\n\n            Helper.validate(Joi.array().items(Joi.number()).unique(), [\n                [[1, 2, 3, 1, 4], false, {\n                    context: {\n                        key: 3,\n                        label: '[3]',\n                        pos: 3,\n                        value: 1,\n                        dupePos: 0,\n                        dupeValue: 1\n                    },\n                    message: '\"[3]\" contains a duplicate value',\n                    path: [3],\n                    type: 'array.unique'\n                }]\n            ]);\n\n            Helper.validate(Joi.array().items(Joi.number()).unique((a, b) => a === b), [\n                [[1, 2, 3, 1, 4], false, {\n                    context: {\n                        key: 3,\n                        label: '[3]',\n                        pos: 3,\n                        value: 1,\n                        dupePos: 0,\n                        dupeValue: 1\n                    },\n                    message: '\"[3]\" contains a duplicate value',\n                    path: [3],\n                    type: 'array.unique'\n                }]\n            ]);\n\n            Helper.validate(Joi.object({ a: Joi.array().items(Joi.number()).unique() }), [\n                [{ a: [1, 2, 3, 1, 4] }, false, {\n                    context: {\n                        key: 3,\n                        label: 'a[3]',\n                        pos: 3,\n                        value: 1,\n                        dupePos: 0,\n                        dupeValue: 1\n                    },\n                    message: '\"a[3]\" contains a duplicate value',\n                    path: ['a', 3],\n                    type: 'array.unique'\n                }]\n            ]);\n\n            Helper.validate(Joi.object({ a: Joi.array().items(Joi.number()).unique((a, b) => a === b) }), [\n                [{ a: [1, 2, 3, 1, 4] }, false, {\n                    context: {\n                        key: 3,\n                        label: 'a[3]',\n                        pos: 3,\n                        value: 1,\n                        dupePos: 0,\n                        dupeValue: 1\n                    },\n                    message: '\"a[3]\" contains a duplicate value',\n                    path: ['a', 3],\n                    type: 'array.unique'\n                }]\n            ]);\n        });\n\n        it('ignores duplicates if they are of different types', () => {\n\n            const schema = Joi.array().unique();\n\n            Helper.validate(schema, [\n                [[2, '2'], true]\n            ]);\n        });\n\n        it('validates without duplicates', () => {\n\n            const buffer = Buffer.from('hello world');\n            const buffer2 = Buffer.from('Hello world');\n            const func = function () { };\n            const func2 = function () { };\n            const now = new Date();\n            const now2 = new Date(+now + 100);\n            const schema = Joi.array().unique();\n\n            Helper.validate(schema, [\n                [[1, 2], true],\n                [['s1', 's2'], true],\n                [[{ a: 'b' }, { a: 'c' }], true],\n                [[buffer, buffer2], true],\n                [[func, func2], true],\n                [[now, now2], true],\n                [[true, false], true]\n            ]);\n        });\n\n        it('validates using a comparator', () => {\n\n            const schema = Joi.array().unique((left, right) => left.a === right.a);\n\n            Helper.validate(schema, [\n                [[{ a: 'b' }, { a: 'c' }], true],\n                [[{ a: 'b', c: 'd' }, { a: 'c', c: 'd' }], true],\n                [[{ a: 'b', c: 'd' }, { a: 'b', c: 'd' }], false, {\n                    message: '\"[1]\" contains a duplicate value',\n                    path: [1],\n                    type: 'array.unique',\n                    context: {\n                        pos: 1,\n                        value: { a: 'b', c: 'd' },\n                        dupePos: 0,\n                        dupeValue: { a: 'b', c: 'd' },\n                        label: '[1]',\n                        key: 1\n                    }\n                }],\n                [[{ a: 'b', c: 'c' }, { a: 'b', c: 'd' }], false, {\n                    message: '\"[1]\" contains a duplicate value',\n                    path: [1],\n                    type: 'array.unique',\n                    context: {\n                        pos: 1,\n                        value: { a: 'b', c: 'd' },\n                        dupePos: 0,\n                        dupeValue: { a: 'b', c: 'c' },\n                        label: '[1]',\n                        key: 1\n                    }\n                }]\n            ]);\n        });\n\n        it('validates using a comparator with different types', () => {\n\n            const schema = Joi.array().items(Joi.string(), Joi.object({ a: Joi.string() })).unique((left, right) => {\n\n                if (typeof left === 'object') {\n                    if (typeof right === 'object') {\n                        return left.a === right.a;\n                    }\n\n                    return left.a === right;\n                }\n\n                if (typeof right === 'object') {\n                    return left === right.a;\n                }\n\n                return left === right;\n            });\n\n            Helper.validate(schema, [\n                [[{ a: 'b' }, { a: 'c' }], true],\n                [[{ a: 'b' }, 'c'], true],\n                [[{ a: 'b' }, 'c', { a: 'd' }, 'e'], true],\n                [[{ a: 'b' }, { a: 'b' }], false, {\n                    message: '\"[1]\" contains a duplicate value',\n                    path: [1],\n                    type: 'array.unique',\n                    context: {\n                        pos: 1,\n                        value: { a: 'b' },\n                        dupePos: 0,\n                        dupeValue: { a: 'b' },\n                        label: '[1]',\n                        key: 1\n                    }\n                }],\n                [[{ a: 'b' }, 'b'], false, {\n                    message: '\"[1]\" contains a duplicate value',\n                    path: [1],\n                    type: 'array.unique',\n                    context: {\n                        pos: 1,\n                        value: 'b',\n                        dupePos: 0,\n                        dupeValue: { a: 'b' },\n                        label: '[1]',\n                        key: 1\n                    }\n                }]\n            ]);\n        });\n\n        it('validates using a path comparator', () => {\n\n            let schema = Joi.array().items(Joi.object({ id: Joi.number() })).unique('id');\n\n            Helper.validate(schema, [\n                [[{ id: 1 }, { id: 2 }, { id: 3 }], true],\n                [[{ id: 1 }, { id: 2 }, {}], true],\n                [[{ id: 1 }, { id: 2 }, { id: 1 }], false, {\n                    context: {\n                        dupePos: 0,\n                        dupeValue: { id: 1 },\n                        key: 2,\n                        label: '[2]',\n                        path: 'id',\n                        pos: 2,\n                        value: { id: 1 }\n                    },\n                    message: '\"[2]\" contains a duplicate value',\n                    path: [2],\n                    type: 'array.unique'\n                }],\n                [[{ id: 1 }, { id: 2 }, {}, { id: 3 }, {}], false, {\n                    context: {\n                        dupePos: 2,\n                        dupeValue: {},\n                        key: 4,\n                        label: '[4]',\n                        path: 'id',\n                        pos: 4,\n                        value: {}\n                    },\n                    message: '\"[4]\" contains a duplicate value',\n                    path: [4],\n                    type: 'array.unique'\n                }]\n            ]);\n\n            schema = Joi.array().items(Joi.object({ nested: { id: Joi.number() } })).unique('nested.id');\n\n            Helper.validate(schema, [\n                [[{ nested: { id: 1 } }, { nested: { id: 2 } }, { nested: { id: 3 } }], true],\n                [[{ nested: { id: 1 } }, { nested: { id: 2 } }, {}], true],\n                [[{ nested: { id: 1 } }, { nested: { id: 2 } }, { nested: { id: 1 } }], false, {\n                    context: {\n                        dupePos: 0,\n                        dupeValue: { nested: { id: 1 } },\n                        key: 2,\n                        label: '[2]',\n                        path: 'nested.id',\n                        pos: 2,\n                        value: { nested: { id: 1 } }\n                    },\n                    message: '\"[2]\" contains a duplicate value',\n                    path: [2],\n                    type: 'array.unique'\n                }],\n                [[{ nested: { id: 1 } }, { nested: { id: 2 } }, {}, { nested: { id: 3 } }, {}], false, {\n                    context: {\n                        dupePos: 2,\n                        dupeValue: {},\n                        key: 4,\n                        label: '[4]',\n                        path: 'nested.id',\n                        pos: 4,\n                        value: {}\n                    },\n                    message: '\"[4]\" contains a duplicate value',\n                    path: [4],\n                    type: 'array.unique'\n                }]\n            ]);\n\n            schema = Joi.array().items(Joi.object({ nested: { id: Joi.number() } })).unique('nested');\n\n            Helper.validate(schema, [\n                [[{ nested: { id: 1 } }, { nested: { id: 2 } }, { nested: { id: 3 } }], true],\n                [[{ nested: { id: 1 } }, { nested: { id: 2 } }, {}], true],\n                [[{ nested: { id: 1 } }, { nested: { id: 2 } }, { nested: { id: 1 } }], false, {\n                    context: {\n                        dupePos: 0,\n                        dupeValue: { nested: { id: 1 } },\n                        key: 2,\n                        label: '[2]',\n                        path: 'nested',\n                        pos: 2,\n                        value: { nested: { id: 1 } }\n                    },\n                    message: '\"[2]\" contains a duplicate value',\n                    path: [2],\n                    type: 'array.unique'\n                }],\n                [[{ nested: { id: 1 } }, { nested: { id: 2 } }, {}, { nested: { id: 3 } }, {}], false, {\n                    context: {\n                        dupePos: 2,\n                        dupeValue: {},\n                        key: 4,\n                        label: '[4]',\n                        path: 'nested',\n                        pos: 4,\n                        value: {}\n                    },\n                    message: '\"[4]\" contains a duplicate value',\n                    path: [4],\n                    type: 'array.unique'\n                }]\n            ]);\n        });\n\n        it('ignores undefined value when ignoreUndefined is true', () => {\n\n            const schema = Joi.array().unique('a', { ignoreUndefined: true });\n\n            Helper.validate(schema, [\n                [[{ a: 'b' }, { a: 'c' }], true],\n                [[{ c: 'd' }, { c: 'd' }], true],\n                [[{ a: 'b', c: 'd' }, { a: 'b', c: 'd' }], false, {\n                    message: '\"[1]\" contains a duplicate value',\n                    path: [1],\n                    type: 'array.unique',\n                    context: {\n                        pos: 1,\n                        value: { a: 'b', c: 'd' },\n                        dupePos: 0,\n                        dupeValue: { a: 'b', c: 'd' },\n                        label: '[1]',\n                        key: 1,\n                        path: 'a'\n                    }\n                }],\n                [[{ a: 'b', c: 'c' }, { a: 'b', c: 'd' }], false, {\n                    message: '\"[1]\" contains a duplicate value',\n                    path: [1],\n                    type: 'array.unique',\n                    context: {\n                        pos: 1,\n                        value: { a: 'b', c: 'd' },\n                        dupePos: 0,\n                        dupeValue: { a: 'b', c: 'c' },\n                        label: '[1]',\n                        key: 1,\n                        path: 'a'\n                    }\n                }]\n            ]);\n        });\n\n        it('fails with invalid configs', () => {\n\n            expect(() => Joi.array().unique('id', 'invalid configs')).to.throw('Options must be of type object');\n            expect(() => Joi.array().unique('id', {})).to.not.throw();\n        });\n\n        it('fails with invalid comparator', () => {\n\n            expect(() => Joi.array().unique({})).to.throw(Error, 'comparator must be a function or a string');\n        });\n\n        it('handles period in key names', () => {\n\n            const schema = Joi.array().unique('a.b', { separator: false });\n\n            Helper.validate(schema, [\n                [[{ 'a.b': 1 }, { 'a.b': 2 }], true]\n            ]);\n        });\n    });\n\n    describe('validate()', () => {\n\n        it('should, by default, allow undefined, allow empty array', () => {\n\n            Helper.validate(Joi.array(), [\n                [undefined, true],\n                [[], true]\n            ]);\n        });\n\n        it('should, when .required(), deny undefined', () => {\n\n            Helper.validate(Joi.array().required(), [\n                [undefined, false, {\n                    message: '\"value\" is required',\n                    path: [],\n                    type: 'any.required',\n                    context: { label: 'value' }\n                }]\n            ]);\n        });\n\n        it('allows empty arrays', () => {\n\n            Helper.validate(Joi.array(), [\n                [undefined, true],\n                [[], true]\n            ]);\n        });\n\n        it('excludes values when items are forbidden', () => {\n\n            Helper.validate(Joi.array().items(Joi.string().forbidden()), [\n                [['2', '1'], false, {\n                    message: '\"[0]\" contains an excluded value',\n                    path: [0],\n                    type: 'array.excludes',\n                    context: { pos: 0, value: '2', label: '[0]', key: 0 }\n                }],\n                [['1'], false, {\n                    message: '\"[0]\" contains an excluded value',\n                    path: [0],\n                    type: 'array.excludes',\n                    context: { pos: 0, value: '1', label: '[0]', key: 0 }\n                }],\n                [[2], true]\n            ]);\n        });\n\n        it('allows types to be forbidden', () => {\n\n            const schema = Joi.array().items(Joi.number().forbidden());\n\n            Helper.validate(schema, [\n                [['x', 'y', 'z'], true],\n                [[1, 2, 'hippo'], false, {\n                    message: '\"[0]\" contains an excluded value',\n                    path: [0],\n                    type: 'array.excludes',\n                    context: { pos: 0, value: 1, label: '[0]', key: 0 }\n                }]\n            ]);\n        });\n\n        it('validates array of Numbers', () => {\n\n            Helper.validate(Joi.array().items(Joi.number()), [\n                [[1, 2, 3], true],\n                [[50, 100, 1000], true],\n                [['a', 1, 2], false, {\n                    message: '\"[0]\" must be a number',\n                    path: [0],\n                    type: 'number.base',\n                    context: { label: '[0]', key: 0, value: 'a' }\n                }],\n                [['1', '2', 4], true, [1, 2, 4]]\n            ]);\n        });\n\n        it('validates array of mixed Numbers & Strings', () => {\n\n            Helper.validate(Joi.array().items(Joi.number(), Joi.string()), [\n                [[1, 2, 3], true],\n                [[50, 100, 1000], true],\n                [[1, 'a', 5, 10], true],\n                [['joi', 'everydaylowprices', 5000], true]\n            ]);\n        });\n\n        it('validates array of objects with schema', () => {\n\n            Helper.validate(Joi.array().items(Joi.object({ h1: Joi.number().required() })), [\n                [[{ h1: 1 }, { h1: 2 }, { h1: 3 }], true],\n                [[{ h2: 1, h3: 'somestring' }, { h1: 2 }, { h1: 3 }], false, {\n                    message: '\"[0].h1\" is required',\n                    path: [0, 'h1'],\n                    type: 'any.required',\n                    context: { label: '[0].h1', key: 'h1' }\n                }],\n                [[1, 2, [1]], false, {\n                    message: '\"[0]\" must be of type object',\n                    path: [0],\n                    type: 'object.base',\n                    context: { label: '[0]', key: 0, value: 1, type: 'object' }\n                }]\n            ]);\n        });\n\n        it('errors on array of unallowed mixed types (Array)', () => {\n\n            Helper.validate(Joi.array().items(Joi.number()), [\n                [[1, 2, 3], true],\n                [[1, 2, [1]], false, {\n                    message: '\"[2]\" must be a number',\n                    path: [2],\n                    type: 'number.base',\n                    context: { label: '[2]', key: 2, value: [1] }\n                }]\n            ]);\n        });\n\n        it('errors on invalid number rule using includes', () => {\n\n            const schema = Joi.object({\n                arr: Joi.array().items(Joi.number().integer())\n            });\n\n            Helper.validate(schema, [\n                [{ arr: [1, 2, 2.1] }, false, {\n                    message: '\"arr[2]\" must be an integer',\n                    path: ['arr', 2],\n                    type: 'number.integer',\n                    context: { value: 2.1, label: 'arr[2]', key: 2 }\n                }]\n            ]);\n        });\n\n        it('validates an array within an object', () => {\n\n            const schema = Joi.object({\n                array: Joi.array().items(Joi.string().min(5), Joi.number().min(3))\n            }).prefs({ convert: false });\n\n            Helper.validate(schema, [\n                [{ array: ['12345'] }, true],\n                [{ array: ['1'] }, false, {\n                    message: '\"array[0]\" does not match any of the allowed types',\n                    path: ['array', 0],\n                    type: 'array.includes',\n                    context: { pos: 0, value: '1', label: 'array[0]', key: 0 }\n                }],\n                [{ array: [3] }, true],\n                [{ array: ['12345', 3] }, true]\n            ]);\n        });\n\n        it('should not change original value', () => {\n\n            const schema = Joi.array().items(Joi.number()).unique();\n            const input = ['1', '2'];\n\n            Helper.validate(schema, [\n                [input, true, [1, 2]]\n            ]);\n\n            expect(input).to.equal(['1', '2']);\n        });\n\n        it('returns multiple errors if abort early is false', () => {\n\n            const schema = Joi.array().items(Joi.number(), Joi.object()).items(Joi.boolean().forbidden());\n\n            Helper.validate(schema, { abortEarly: false }, [\n                [[1, undefined, true, 'a'], false, {\n                    message: '\"[1]\" must not be a sparse array item. \"[2]\" contains an excluded value. \"[3]\" does not match any of the allowed types',\n                    details: [{\n                        message: '\"[1]\" must not be a sparse array item',\n                        path: [1],\n                        type: 'array.sparse',\n                        context: {\n                            key: 1,\n                            label: '[1]',\n                            path: [1],\n                            pos: 1,\n                            value: undefined\n                        }\n                    },\n                    {\n                        message: '\"[2]\" contains an excluded value',\n                        path: [2],\n                        type: 'array.excludes',\n                        context: {\n                            pos: 2,\n                            key: 2,\n                            label: '[2]',\n                            value: true\n                        }\n                    },\n                    {\n                        message: '\"[3]\" does not match any of the allowed types',\n                        path: [3],\n                        type: 'array.includes',\n                        context: {\n                            pos: 3,\n                            key: 3,\n                            label: '[3]',\n                            value: 'a'\n                        }\n                    }]\n                }]\n            ]);\n        });\n\n        it('returns multiple errors if abort early is false across items() and unique()', () => {\n\n            const item = Joi.object({\n                test: Joi.string(),\n                hello: Joi.string().required()\n            });\n\n            const schema = Joi.array().items(item).unique('test');\n\n            Helper.validate(schema, { abortEarly: false }, [\n                [[{ test: 'test', hello: 'world' }, { test: 'test' }], false, {\n                    message: '\"[1].hello\" is required. \"[1]\" contains a duplicate value',\n                    details: [\n                        {\n                            context: {\n                                key: 'hello',\n                                label: '[1].hello'\n                            },\n                            message: '\"[1].hello\" is required',\n                            path: [1, 'hello'],\n                            type: 'any.required'\n                        },\n                        {\n                            context: {\n                                dupePos: 0,\n                                dupeValue: {\n                                    hello: 'world',\n                                    test: 'test'\n                                },\n                                key: 1,\n                                label: '[1]',\n                                path: 'test',\n                                pos: 1,\n                                value: {\n                                    test: 'test'\n                                }\n                            },\n                            message: '\"[1]\" contains a duplicate value',\n                            path: [1],\n                            type: 'array.unique'\n                        }\n                    ]\n                }]\n            ]);\n        });\n    });\n});\n"
  },
  {
    "path": "test/types/binary.js",
    "content": "'use strict';\n\nconst Code = require('@hapi/code');\nconst Lab = require('@hapi/lab');\nconst Joi = require('../..');\n\nconst Helper = require('../helper');\n\n\nconst internals = {};\n\n\nconst { describe, it } = exports.lab = Lab.script();\nconst { expect } = Code;\n\n\ndescribe('binary', () => {\n\n    it('should throw an exception if arguments were passed.', () => {\n\n        expect(() => Joi.binary('invalid argument.')).to.throw('The binary type does not allow arguments');\n    });\n\n    it('converts a string to a buffer', () => {\n\n        const value = Joi.binary().validate('test').value;\n        expect(value instanceof Buffer).to.equal(true);\n        expect(value.length).to.equal(4);\n        expect(value.toString('utf8')).to.equal('test');\n    });\n\n    it('converts a JSON encoded and decoded buffer to a buffer', () => {\n\n        const testPngMagicNumber = Buffer.from('89504E470D0A', 'hex');\n        const jsonEncodedBuffer = JSON.stringify(testPngMagicNumber);\n        const jsonDecodedBuffer = JSON.parse(jsonEncodedBuffer);\n\n        const value = Joi.binary().validate(jsonDecodedBuffer).value;\n        expect(value instanceof Buffer).to.equal(true);\n        expect(value.length).to.equal(testPngMagicNumber.length);\n        expect(value).to.equal(testPngMagicNumber);\n    });\n\n    it('validates allowed buffer content', () => {\n\n        const hello = Buffer.from('hello');\n        const schema = Joi.binary().valid(hello);\n\n        Helper.validate(schema, [\n            ['hello', true, Buffer.from('hello')],\n            [hello, true],\n            [Buffer.from('hello'), true],\n            ['goodbye', false, {\n                message: '\"value\" must be [hello]',\n                path: [],\n                type: 'any.only',\n                context: { value: Buffer.from('goodbye'), valids: [hello], label: 'value' }\n            }],\n            [Buffer.from('goodbye'), false, {\n                message: '\"value\" must be [hello]',\n                path: [],\n                type: 'any.only',\n                context: { value: Buffer.from('goodbye'), valids: [hello], label: 'value' }\n            }],\n            [Buffer.from('HELLO'), false, {\n                message: '\"value\" must be [hello]',\n                path: [],\n                type: 'any.only',\n                context: { value: Buffer.from('HELLO'), valids: [hello], label: 'value' }\n            }]\n        ]);\n    });\n\n    describe('cast()', () => {\n\n        it('casts value to string', () => {\n\n            const schema = Joi.binary().cast('string');\n\n            Helper.validate(schema, [\n                [Buffer.from('test'), true, 'test']\n            ]);\n        });\n\n        it('casts value to string (in object)', () => {\n\n            const schema = Joi.object({\n                a: Joi.binary().cast('string')\n            });\n\n            Helper.validate(schema, [\n                [{ a: Buffer.from('test') }, true, { a: 'test' }],\n                [{}, true]\n            ]);\n        });\n\n        it('ignores null', () => {\n\n            const schema = Joi.binary().allow(null).cast('string');\n\n            Helper.validate(schema, [\n                [null, true]\n            ]);\n        });\n\n        it('ignores string', () => {\n\n            const schema = Joi.binary().allow('x').cast('string');\n\n            Helper.validate(schema, [\n                ['x', true]\n            ]);\n        });\n\n        it('does not leak casts to any', () => {\n\n            expect(() => Joi.any().cast('string')).to.throw('Type any does not support casting to string');\n        });\n    });\n\n    describe('validate()', () => {\n\n        it('returns an error when a non-buffer or non-string is used', () => {\n\n            Helper.validate(Joi.binary(), [\n                [5, false, {\n                    message: '\"value\" must be a buffer or a string',\n                    path: [],\n                    type: 'binary.base',\n                    context: { label: 'value', value: 5 }\n                }]\n            ]);\n        });\n\n        it('returns an error when malformed JSON object is used', () => {\n\n            Helper.validate(Joi.binary(), [\n                [{ foo: 'bar' }, false, {\n                    message: '\"value\" must be a buffer or a string',\n                    path: [],\n                    type: 'binary.base',\n                    context: { label: 'value', value: { foo: 'bar' } }\n                }],\n                [null, false, {\n                    message: '\"value\" must be a buffer or a string',\n                    path: [],\n                    type: 'binary.base',\n                    context: { label: 'value', value: null }\n                }],\n                [{ type: 'Buffer' }, false, {\n                    message: '\"value\" must be a buffer or a string',\n                    path: [],\n                    type: 'binary.base',\n                    context: { label: 'value', value: { type: 'Buffer' } }\n                }]\n            ]);\n        });\n\n        it('returns an error when a JSON encoded & decoded buffer object is used in strict mode', () => {\n\n            // Generate Buffer and stringify it as JSON.\n            const testPngMagicNumber = Buffer.from('89504E470D0A', 'hex');\n            const jsonEncodedBuffer = JSON.stringify(testPngMagicNumber);\n            const jsonDecodedBuffer = JSON.parse(jsonEncodedBuffer);\n\n            Helper.validate(Joi.binary().strict(), [\n                [jsonDecodedBuffer, false, {\n                    message: '\"value\" must be a buffer or a string',\n                    path: [],\n                    type: 'binary.base',\n                    context: { label: 'value', value: jsonDecodedBuffer }\n                }]\n            ]);\n        });\n\n        it('accepts a buffer object', () => {\n\n            Helper.validate(Joi.binary(), [\n                [Buffer.from('hello world'), true]\n            ]);\n        });\n\n        it('accepts a buffer object in strict mode', () => {\n\n            Helper.validate(Joi.binary().strict(), [\n                [Buffer.from('hello world'), true],\n                ['hello world', false, '\"value\" must be a buffer or a string']\n            ]);\n        });\n    });\n\n    describe('encoding()', () => {\n\n        it('applies encoding', () => {\n\n            const schema = Joi.binary().encoding('base64');\n\n            Helper.validate(schema, [\n                [Buffer.from('abcdef'), true]\n            ]);\n        });\n\n        it('throws when encoding is invalid', () => {\n\n            expect(() => Joi.binary().encoding('base6')).to.throw('Invalid encoding: base6');\n        });\n\n        it('avoids unnecessary cloning when called twice', () => {\n\n            const schema = Joi.binary().encoding('base64');\n            expect(schema.encoding('base64')).to.shallow.equal(schema);\n        });\n    });\n\n    describe('min()', () => {\n\n        it('validates buffer size', () => {\n\n            const schema = Joi.binary().min(5);\n\n            Helper.validate(schema, [\n                [Buffer.from('testing'), true],\n                [Buffer.from('test'), false, {\n                    message: '\"value\" must be at least 5 bytes',\n                    path: [],\n                    type: 'binary.min',\n                    context: { limit: 5, value: Buffer.from('test'), label: 'value' }\n                }]\n            ]);\n        });\n\n        it('throws when min is not a number', () => {\n\n            expect(() => Joi.binary().min('a')).to.throw('limit must be a positive integer or reference');\n        });\n\n        it('throws when min is not an integer', () => {\n\n            expect(() => Joi.binary().min(1.2)).to.throw('limit must be a positive integer or reference');\n        });\n    });\n\n    describe('max()', () => {\n\n        it('validates buffer size', () => {\n\n            const schema = Joi.binary().max(5);\n\n            Helper.validate(schema, [\n                [Buffer.from('testing'), false, {\n                    message: '\"value\" must be less than or equal to 5 bytes',\n                    path: [],\n                    type: 'binary.max',\n                    context: {\n                        limit: 5,\n                        value: Buffer.from('testing'),\n                        label: 'value'\n                    }\n                }],\n                [Buffer.from('test'), true]\n            ]);\n        });\n\n        it('throws when max is not a number', () => {\n\n            expect(() => Joi.binary().max('a')).to.throw('limit must be a positive integer or reference');\n        });\n\n        it('throws when max is not an integer', () => {\n\n            expect(() => Joi.binary().max(1.2)).to.throw('limit must be a positive integer or reference');\n        });\n    });\n\n    describe('length()', () => {\n\n        it('validates buffer size', () => {\n\n            const schema = Joi.binary().length(4);\n\n            Helper.validate(schema, [\n                [Buffer.from('test'), true],\n                [Buffer.from('testing'), false, {\n                    message: '\"value\" must be 4 bytes',\n                    path: [],\n                    type: 'binary.length',\n                    context: {\n                        limit: 4,\n                        value: Buffer.from('testing'),\n                        label: 'value'\n                    }\n                }]\n            ]);\n        });\n\n        it('throws when length is not a number', () => {\n\n            expect(() => Joi.binary().length('a')).to.throw('limit must be a positive integer or reference');\n        });\n\n        it('throws when length is not an integer', () => {\n\n            expect(() => Joi.binary().length(1.2)).to.throw('limit must be a positive integer or reference');\n        });\n    });\n});\n"
  },
  {
    "path": "test/types/boolean.js",
    "content": "'use strict';\n\nconst Code = require('@hapi/code');\nconst Lab = require('@hapi/lab');\nconst Joi = require('../..');\n\nconst Helper = require('../helper');\n\n\nconst internals = {};\n\n\nconst { describe, it } = exports.lab = Lab.script();\nconst { expect } = Code;\n\n\ndescribe('boolean', () => {\n\n    it('should throw an exception if arguments were passed.', () => {\n\n        expect(() => Joi.boolean('invalid argument.')).to.throw('The boolean type does not allow arguments');\n    });\n\n    it('converts boolean string to a boolean', () => {\n\n        Helper.validate(Joi.boolean(), [\n            ['true', true, true],\n            ['false', true, false],\n            ['TrUe', true, true],\n            ['FalSe', true, false]\n        ]);\n    });\n\n    it('converts boolean string with trailing spaces to a boolean', () => {\n\n        Helper.validate(Joi.boolean(), [\n            ['true ', true, true],\n            ['false ', true, false]\n        ]);\n    });\n\n    it('does not convert boolean string to a boolean in strict mode', () => {\n\n        Helper.validate(Joi.boolean().strict(), [\n            ['true', false, {\n                message: '\"value\" must be a boolean',\n                path: [],\n                type: 'boolean.base',\n                context: { label: 'value', value: 'true' }\n            }],\n            ['false', false, {\n                message: '\"value\" must be a boolean',\n                path: [],\n                type: 'boolean.base',\n                context: { label: 'value', value: 'false' }\n            }],\n            ['TrUe', false, {\n                message: '\"value\" must be a boolean',\n                path: [],\n                type: 'boolean.base',\n                context: { label: 'value', value: 'TrUe' }\n            }],\n            ['FalSe', false, {\n                message: '\"value\" must be a boolean',\n                path: [],\n                type: 'boolean.base',\n                context: { label: 'value', value: 'FalSe' }\n            }]\n        ]);\n    });\n\n    it('errors on a number', () => {\n\n        Helper.validate(Joi.boolean(), [\n            [1, false, {\n                message: '\"value\" must be a boolean',\n                path: [],\n                type: 'boolean.base',\n                context: { label: 'value', value: 1 }\n            }],\n            [0, false, {\n                message: '\"value\" must be a boolean',\n                path: [],\n                type: 'boolean.base',\n                context: { label: 'value', value: 0 }\n            }],\n            [2, false, {\n                message: '\"value\" must be a boolean',\n                path: [],\n                type: 'boolean.base',\n                context: { label: 'value', value: 2 }\n            }]\n        ]);\n    });\n\n    it('respects case for allow', () => {\n\n        const schema = Joi.boolean().allow('X');\n        Helper.validate(schema, [\n            ['X', true],\n            ['x', false, '\"value\" must be a boolean']\n        ]);\n    });\n\n    describe('cast()', () => {\n\n        it('casts value to number', () => {\n\n            const schema = Joi.boolean().cast('number');\n            Helper.validate(schema, [\n                [true, true, 1],\n                [false, true, 0]\n            ]);\n        });\n\n        it('casts value to string', () => {\n\n            const schema = Joi.boolean().cast('string');\n            Helper.validate(schema, [\n                [true, true, 'true'],\n                [false, true, 'false']\n            ]);\n        });\n\n        it('ignores null', () => {\n\n            const schema = Joi.boolean().allow(null).cast('string');\n            Helper.validate(schema, [[null, true, null]]);\n        });\n\n        it('ignores string', () => {\n\n            const schema = Joi.boolean().allow('x').cast('string');\n            Helper.validate(schema, [['x', true, 'x']]);\n        });\n    });\n\n    describe('falsy()', () => {\n\n        it('works with additional falsy value', () => {\n\n            const rule = Joi.boolean().falsy('N');\n            Helper.validate(rule, [\n                ['N', true, false],\n                ['Y', false, {\n                    message: '\"value\" must be a boolean',\n                    path: [],\n                    type: 'boolean.base',\n                    context: { label: 'value', value: 'Y' }\n                }],\n                [true, true],\n                [false, true],\n                [null, false, {\n                    message: '\"value\" must be a boolean',\n                    path: [],\n                    type: 'boolean.base',\n                    context: { label: 'value', value: null }\n                }]\n            ]);\n        });\n\n        it('works with additional falsy arguments', () => {\n\n            const rule = Joi.boolean().falsy('N', 'Never');\n            Helper.validate(rule, [\n                ['N', true, false],\n                ['Never', true, false],\n                ['Y', false, {\n                    message: '\"value\" must be a boolean',\n                    path: [],\n                    type: 'boolean.base',\n                    context: { label: 'value', value: 'Y' }\n                }],\n                [null, false, {\n                    message: '\"value\" must be a boolean',\n                    path: [],\n                    type: 'boolean.base',\n                    context: { label: 'value', value: null }\n                }],\n                [true, true],\n                [false, true]\n            ]);\n        });\n\n        it('works with additional falsy statements', () => {\n\n            const rule = Joi.boolean().falsy('N').falsy('Never');\n            Helper.validate(rule, [\n                ['N', true, false],\n                ['Never', true, false],\n                ['Y', false, {\n                    message: '\"value\" must be a boolean',\n                    path: [],\n                    type: 'boolean.base',\n                    context: { label: 'value', value: 'Y' }\n                }],\n                [null, false, {\n                    message: '\"value\" must be a boolean',\n                    path: [],\n                    type: 'boolean.base',\n                    context: { label: 'value', value: null }\n                }],\n                [true, true],\n                [false, true]\n            ]);\n        });\n\n        it('errors on falsy without convert', () => {\n\n            const schema = Joi.boolean().falsy('n');\n            expect(schema.validate('n', { convert: false }).error).be.an.error('\"value\" must be a boolean');\n        });\n    });\n\n    describe('sensitive()', () => {\n\n        it('should default to case insensitive', () => {\n\n            const schema = Joi.boolean().truthy('Y');\n            Helper.validate(schema, [['y', true, true]]);\n        });\n\n        it('should stick to case insensitive if called', () => {\n\n            const schema = Joi.boolean().truthy('Y').sensitive(false);\n            expect(schema.validate('y').error).not.to.exist();\n        });\n\n        it('should be able to do strict comparison', () => {\n\n            const schema = Joi.boolean().truthy('Y').sensitive();\n            Helper.validate(schema, [['y', false, {\n                message: '\"value\" must be a boolean',\n                path: [],\n                type: 'boolean.base',\n                context: { label: 'value', value: 'y' }\n            }]]);\n        });\n\n        it('should return the same instance if nothing changed', () => {\n\n            const insensitiveSchema = Joi.boolean();\n            expect(insensitiveSchema.sensitive(false)).to.shallow.equal(insensitiveSchema);\n            expect(insensitiveSchema.sensitive()).to.not.shallow.equal(insensitiveSchema);\n\n            const sensitiveSchema = Joi.boolean().sensitive();\n            expect(sensitiveSchema.sensitive()).to.shallow.equal(sensitiveSchema);\n            expect(sensitiveSchema.sensitive(false)).to.not.shallow.equal(sensitiveSchema);\n        });\n\n        it('converts boolean string to a boolean with a sensitive case', () => {\n\n            Helper.validate(Joi.boolean().sensitive(), [\n                ['true', true, true],\n                ['false', true, false],\n                ['TrUe', false, {\n                    message: '\"value\" must be a boolean',\n                    path: [],\n                    type: 'boolean.base',\n                    context: { label: 'value', value: 'TrUe' }\n                }],\n                ['FalSe', false, {\n                    message: '\"value\" must be a boolean',\n                    path: [],\n                    type: 'boolean.base',\n                    context: { label: 'value', value: 'FalSe' }\n                }]\n            ]);\n        });\n\n    });\n\n    describe('validate()', () => {\n\n        it('does not convert string values and validates', () => {\n\n            const rule = Joi.boolean();\n            Helper.validate(rule, [\n                ['1234', false, {\n                    message: '\"value\" must be a boolean',\n                    path: [],\n                    type: 'boolean.base',\n                    context: { label: 'value', value: '1234' }\n                }],\n                [false, true],\n                [true, true],\n                [null, false, {\n                    message: '\"value\" must be a boolean',\n                    path: [],\n                    type: 'boolean.base',\n                    context: { label: 'value', value: null }\n                }],\n                ['on', false, {\n                    message: '\"value\" must be a boolean',\n                    path: [],\n                    type: 'boolean.base',\n                    context: { label: 'value', value: 'on' }\n                }],\n                ['off', false, {\n                    message: '\"value\" must be a boolean',\n                    path: [],\n                    type: 'boolean.base',\n                    context: { label: 'value', value: 'off' }\n                }],\n                ['yes', false, {\n                    message: '\"value\" must be a boolean',\n                    path: [],\n                    type: 'boolean.base',\n                    context: { label: 'value', value: 'yes' }\n                }],\n                ['no', false, {\n                    message: '\"value\" must be a boolean',\n                    path: [],\n                    type: 'boolean.base',\n                    context: { label: 'value', value: 'no' }\n                }],\n                ['1', false, {\n                    message: '\"value\" must be a boolean',\n                    path: [],\n                    type: 'boolean.base',\n                    context: { label: 'value', value: '1' }\n                }],\n                ['0', false, {\n                    message: '\"value\" must be a boolean',\n                    path: [],\n                    type: 'boolean.base',\n                    context: { label: 'value', value: '0' }\n                }]\n            ]);\n        });\n\n        it('works with required', () => {\n\n            const rule = Joi.boolean().required();\n            Helper.validate(rule, [\n                ['1234', false, {\n                    message: '\"value\" must be a boolean',\n                    path: [],\n                    type: 'boolean.base',\n                    context: { label: 'value', value: '1234' }\n                }],\n                [false, true],\n                [true, true],\n                [null, false, {\n                    message: '\"value\" must be a boolean',\n                    path: [],\n                    type: 'boolean.base',\n                    context: { label: 'value', value: null }\n                }]\n            ]);\n        });\n\n        it('works with allow', () => {\n\n            const rule = Joi.boolean().allow(false);\n            Helper.validate(rule, [\n                ['1234', false, {\n                    message: '\"value\" must be a boolean',\n                    path: [],\n                    type: 'boolean.base',\n                    context: { label: 'value', value: '1234' }\n                }],\n                [false, true],\n                [null, false, {\n                    message: '\"value\" must be a boolean',\n                    path: [],\n                    type: 'boolean.base',\n                    context: { label: 'value', value: null }\n                }]\n            ]);\n        });\n\n        it('works with invalid', () => {\n\n            const rule = Joi.boolean().invalid(false);\n            Helper.validate(rule, [\n                ['1234', false, {\n                    message: '\"value\" must be a boolean',\n                    path: [],\n                    type: 'boolean.base',\n                    context: { label: 'value', value: '1234' }\n                }],\n                [false, false, {\n                    message: '\"value\" contains an invalid value',\n                    path: [],\n                    type: 'any.invalid',\n                    context: { value: false, invalids: [false], label: 'value' }\n                }],\n                [true, true],\n                [null, false, {\n                    message: '\"value\" must be a boolean',\n                    path: [],\n                    type: 'boolean.base',\n                    context: { label: 'value', value: null }\n                }]\n            ]);\n        });\n\n        it('works with invalid and null allowed', () => {\n\n            const rule = Joi.boolean().invalid(false).allow(null);\n            Helper.validate(rule, [\n                ['1234', false, {\n                    message: '\"value\" must be a boolean',\n                    path: [],\n                    type: 'boolean.base',\n                    context: { label: 'value', value: '1234' }\n                }],\n                [false, false, {\n                    message: '\"value\" contains an invalid value',\n                    path: [],\n                    type: 'any.invalid',\n                    context: { value: false, invalids: [false], label: 'value' }\n                }],\n                [true, true],\n                [null, true]\n            ]);\n        });\n\n        it('works with allow and invalid', () => {\n\n            const rule = Joi.boolean().invalid(true).allow(false);\n            Helper.validate(rule, [\n                ['1234', false, {\n                    message: '\"value\" must be a boolean',\n                    path: [],\n                    type: 'boolean.base',\n                    context: { label: 'value', value: '1234' }\n                }],\n                [false, true],\n                [true, false, {\n                    message: '\"value\" contains an invalid value',\n                    path: [],\n                    type: 'any.invalid',\n                    context: { value: true, invalids: [true], label: 'value' }\n                }],\n                [null, false, {\n                    message: '\"value\" must be a boolean',\n                    path: [],\n                    type: 'boolean.base',\n                    context: { label: 'value', value: null }\n                }]\n            ]);\n        });\n\n        it('works with allow, invalid, and null allowed', () => {\n\n            const rule = Joi.boolean().invalid(true).allow(false).allow(null);\n            Helper.validate(rule, [\n                ['1234', false, {\n                    message: '\"value\" must be a boolean',\n                    path: [],\n                    type: 'boolean.base',\n                    context: { label: 'value', value: '1234' }\n                }],\n                [false, true],\n                [true, false, {\n                    message: '\"value\" contains an invalid value',\n                    path: [],\n                    type: 'any.invalid',\n                    context: { value: true, invalids: [true], label: 'value' }\n                }],\n                [null, true]\n            ]);\n        });\n\n        it('works with required, null allowed, and both additional truthy and falsy values', () => {\n\n            const rule = Joi.boolean().truthy('Y', 'Si', 1).falsy('N', 'Never', 0).allow(null).required();\n            Helper.validate(rule, [\n                ['N', true, false],\n                ['Never', true, false],\n                ['Y', true, true],\n                ['y', true, true],\n                ['Si', true, true],\n                [true, true],\n                [false, true],\n                [1, true, true],\n                [0, true, false],\n                [null, true],\n                ['M', false, {\n                    message: '\"value\" must be a boolean',\n                    path: [],\n                    type: 'boolean.base',\n                    context: { label: 'value', value: 'M' }\n                }],\n                ['Yes', false, {\n                    message: '\"value\" must be a boolean',\n                    path: [],\n                    type: 'boolean.base',\n                    context: { label: 'value', value: 'Yes' }\n                }]\n            ]);\n        });\n\n        it('should handle concatenated schema', () => {\n\n            const a = Joi.boolean().truthy('yes');\n            const b = Joi.boolean().falsy('no');\n\n            Helper.validate(a, [\n                ['yes', true, true],\n                ['no', false, {\n                    message: '\"value\" must be a boolean',\n                    path: [],\n                    type: 'boolean.base',\n                    context: { label: 'value', value: 'no' }\n                }]\n            ]);\n\n            Helper.validate(b, [\n                ['no', true, false],\n                ['yes', false, {\n                    message: '\"value\" must be a boolean',\n                    path: [],\n                    type: 'boolean.base',\n                    context: { label: 'value', value: 'yes' }\n                }]\n            ]);\n\n            Helper.validate(a.concat(b), [\n                ['yes', true, true],\n                ['no', true, false]\n            ]);\n        });\n\n        it('should describe truthy and falsy values', () => {\n\n            const schema = Joi.boolean().truthy('yes').falsy('no').required().describe();\n            expect(schema).to.equal({\n                type: 'boolean',\n                flags: {\n                    presence: 'required'\n                },\n                truthy: ['yes'],\n                falsy: ['no']\n            });\n        });\n    });\n\n    describe('truthy()', () => {\n\n        it('works with additional truthy value', () => {\n\n            const rule = Joi.boolean().truthy('Y');\n            Helper.validate(rule, [\n                ['Y', true, true],\n                [true, true],\n                [false, true],\n                ['N', false, {\n                    message: '\"value\" must be a boolean',\n                    path: [],\n                    type: 'boolean.base',\n                    context: { label: 'value', value: 'N' }\n                }],\n                [null, false, {\n                    message: '\"value\" must be a boolean',\n                    path: [],\n                    type: 'boolean.base',\n                    context: { label: 'value', value: null }\n                }]\n            ]);\n        });\n\n        it('works with additional truthy arguments', () => {\n\n            const rule = Joi.boolean().truthy('Y', 'Si');\n            Helper.validate(rule, [\n                ['Si', true, true],\n                ['Y', true, true],\n                [true, true],\n                [false, true],\n                ['N', false, {\n                    message: '\"value\" must be a boolean',\n                    path: [],\n                    type: 'boolean.base',\n                    context: { label: 'value', value: 'N' }\n                }],\n                [null, false, {\n                    message: '\"value\" must be a boolean',\n                    path: [],\n                    type: 'boolean.base',\n                    context: { label: 'value', value: null }\n                }]\n            ]);\n        });\n\n        it('works with multiple truthy arguments', () => {\n\n            const rule = Joi.boolean().truthy('Y').truthy('Si');\n            Helper.validate(rule, [\n                ['Si', true, true],\n                ['Y', true, true],\n                [true, true],\n                [false, true],\n                ['N', false, {\n                    message: '\"value\" must be a boolean',\n                    path: [],\n                    type: 'boolean.base',\n                    context: { label: 'value', value: 'N' }\n                }],\n                [null, false, {\n                    message: '\"value\" must be a boolean',\n                    path: [],\n                    type: 'boolean.base',\n                    context: { label: 'value', value: null }\n                }]\n            ]);\n        });\n\n        it('errors on truthy without convert', () => {\n\n            const schema = Joi.boolean().truthy('y');\n            Helper.validate(schema, { convert: false }, [['y', false, '\"value\" must be a boolean']]);\n        });\n    });\n});\n"
  },
  {
    "path": "test/types/date.js",
    "content": "'use strict';\n\nconst Code = require('@hapi/code');\nconst Lab = require('@hapi/lab');\nconst Joi = require('../..');\n\nconst Helper = require('../helper');\n\n\nconst internals = {};\n\n\nconst { describe, it, before, after } = exports.lab = Lab.script();\nconst { expect } = Code;\n\n\ndescribe('date', () => {\n\n    before(() => {\n\n        // Mock Date.now so we don't have to deal with sub-second differences in the tests\n\n        const original = Date.now;\n\n        Date.now = function () {\n\n            return 1485907200000;   // Random date\n        };\n\n        Date.now.restore = function () {\n\n            Date.now = original;\n        };\n    });\n\n    after(() => {\n\n        Date.now.restore();\n    });\n\n    it('should throw an exception if arguments were passed.', () => {\n\n        expect(() => Joi.date('invalid argument.')).to.throw('The date type does not allow arguments');\n    });\n\n    it('fails on boolean', () => {\n\n        const schema = Joi.date();\n        Helper.validate(schema, [\n            [true, false, {\n                message: '\"value\" must be a valid date',\n                path: [],\n                type: 'date.base',\n                context: { label: 'value', value: true }\n            }],\n            [false, false, {\n                message: '\"value\" must be a valid date',\n                path: [],\n                type: 'date.base',\n                context: { label: 'value', value: false }\n            }]\n        ]);\n    });\n\n    it('fails on non-finite numbers', () => {\n\n        const schema = Joi.date();\n        Helper.validate(schema, [\n            [Infinity, false, {\n                message: '\"value\" must be a valid date',\n                path: [],\n                type: 'date.base',\n                context: { label: 'value', value: Infinity }\n            }],\n            [-Infinity, false, {\n                message: '\"value\" must be a valid date',\n                path: [],\n                type: 'date.base',\n                context: { label: 'value', value: -Infinity }\n            }],\n            [NaN, false, {\n                message: '\"value\" must be a valid date',\n                path: [],\n                type: 'date.base',\n                context: { label: 'value', value: NaN }\n            }]\n        ]);\n    });\n\n    it('matches specific date', () => {\n\n        const now = Date.now();\n        Helper.validate(Joi.date().valid(new Date(now)), [\n            [new Date(now), true],\n            [new Date(now).toISOString(), true, new Date(now)]\n        ]);\n    });\n\n    it('errors on invalid input and convert disabled', () => {\n\n        Helper.validate(Joi.date().prefs({ convert: false }), [['1-1-2013 UTC', false, {\n            message: '\"value\" must be a valid date',\n            path: [],\n            type: 'date.base',\n            context: { label: 'value', value: '1-1-2013 UTC' }\n        }]]);\n    });\n\n    it('validates date', () => {\n\n        Helper.validate(Joi.date(), [[new Date(), true]]);\n    });\n\n    it('validates millisecond date as a string', () => {\n\n        const now = new Date();\n        const mili = now.getTime();\n\n        Helper.validate(Joi.date(), [[mili.toString(), true, now]]);\n    });\n\n    it('validates only valid dates', () => {\n\n        const now = new Date();\n        const invalidDate = new Date('not a valid date');\n\n        Helper.validate(Joi.date(), [\n            ['1-1-2013 UTC', true, new Date('1-1-2013 UTC')],\n            [now.getTime(), true, now],\n            [now.getTime().toFixed(4), true, now],\n            ['not a valid date', false, {\n                message: '\"value\" must be a valid date',\n                path: [],\n                type: 'date.base',\n                context: { label: 'value', value: 'not a valid date' }\n            }],\n            [invalidDate, false, {\n                message: '\"value\" must be a valid date',\n                path: [],\n                type: 'date.base',\n                context: { label: 'value', value: invalidDate }\n            }]\n        ]);\n    });\n\n    describe('cast()', () => {\n\n        it('casts value to number', () => {\n\n            const schema = Joi.date().cast('number');\n            Helper.validate(schema, [[new Date('1974-05-07'), true, 137116800000]]);\n        });\n\n        it('casts value to string', () => {\n\n            const schema = Joi.date().cast('string');\n            Helper.validate(schema, [[new Date('1974-05-07'), true, '1974-05-07T00:00:00.000Z']]);\n        });\n\n        it('casts value to string (custom format)', () => {\n\n            const schema = Joi.date().prefs({ dateFormat: 'date' }).cast('string');\n            Helper.validate(schema, [[new Date('1974-05-07'), true, 'Tue May 07 1974']]);\n        });\n\n        it('ignores null', () => {\n\n            const schema = Joi.date().allow(null).cast('string');\n            Helper.validate(schema, [[null, true, null]]);\n        });\n\n        it('ignores string', () => {\n\n            const schema = Joi.date().allow('x').cast('string');\n            Helper.validate(schema, [['x', true, 'x']]);\n        });\n    });\n\n    describe('format()', () => {\n\n        it('ignores unknown formats', () => {\n\n            const custom = Joi.extend({\n                type: 'date',\n                base: Joi.date(),\n                overrides: {\n                    format(format) {\n\n                        if (['iso', 'javascript', 'unix'].includes(format)) {\n                            return this.$_parent('format', format);\n                        }\n\n                        return this.$_setFlag('format', format);\n                    }\n                }\n            });\n\n            const now = Date.now();\n            Helper.validate(custom.date().format('unknown'), [\n                ['x', false, '\"value\" must be in unknown format'],\n                [now, true, new Date(now)]\n            ]);\n\n            Helper.validate(custom.date().format(['unknown']), [\n                ['x', false, '\"value\" must be in [unknown] format']\n            ]);\n        });\n\n        it('enforces format when value is a string', () => {\n\n            const schema = Joi.date().$_setFlag('format', 'MM-DD-YY');\n\n            // Cannot use Helper since format is set to unknown value\n\n            expect(schema.validate(new Date()).error).to.not.exist();\n            expect(schema.validate(Date.now()).error).to.not.exist();\n            expect(schema.validate('1').error).to.be.an.error('\"value\" must be in MM-DD-YY format');\n        });\n    });\n\n    describe('greater()', () => {\n\n        it('validates greater', () => {\n\n            const d = new Date('1-1-2000 UTC');\n            const message = `\"value\" must be greater than \"${d.toISOString()}\"`;\n            Helper.validate(Joi.date().greater('1-1-2000 UTC'), [\n                ['1-1-2001 UTC', true, new Date('1-1-2001 UTC')],\n                ['1-1-2000 UTC', false, {\n                    message,\n                    path: [],\n                    type: 'date.greater',\n                    context: { limit: d, label: 'value', value: new Date('1-1-2000 UTC') }\n                }],\n                [0, false, {\n                    message,\n                    path: [],\n                    type: 'date.greater',\n                    context: { limit: d, label: 'value', value: new Date(0) }\n                }],\n                ['0', false, {\n                    message,\n                    path: [],\n                    type: 'date.greater',\n                    context: { limit: d, label: 'value', value: new Date(0) }\n                }],\n                ['-1', false, {\n                    message,\n                    path: [],\n                    type: 'date.greater',\n                    context: { limit: d, label: 'value', value: new Date(-1) }\n                }],\n                ['1-1-1999 UTC', false, {\n                    message,\n                    path: [],\n                    type: 'date.greater',\n                    context: { limit: d, label: 'value', value: new Date('1-1-1999 UTC') }\n                }]\n            ]);\n        });\n\n        it('accepts \"now\" as the greater date', () => {\n\n            const future = new Date(Date.now() + 1000000);\n            expect(Joi.date().greater('now').validate(future)).to.equal({ value: future });\n        });\n\n        it('errors if .greater(\"now\") is used with a past date', () => {\n\n            const now = Date.now();\n            const past = new Date(now - 1000000);\n\n            Helper.validate(Joi.date().greater('now'), [\n                [past, false, {\n                    message: '\"value\" must be greater than \"now\"',\n                    path: [],\n                    type: 'date.greater',\n                    context: { limit: 'now', label: 'value', value: past }\n                }]\n            ]);\n        });\n\n        it('accepts references as greater date', () => {\n\n            const ref = Joi.ref('a');\n            const schema = Joi.object({ a: Joi.date(), b: Joi.date().greater(ref) });\n            const now = Date.now();\n\n            Helper.validate(schema, [\n                [{ a: now, b: now }, false, {\n                    message: '\"b\" must be greater than \"ref:a\"',\n                    path: ['b'],\n                    type: 'date.greater',\n                    context: { limit: ref, label: 'b', key: 'b', value: new Date(now) }\n                }],\n                [{ a: now, b: now + 1e3 }, true, { a: new Date(now), b: new Date(now + 1e3) }],\n                [{ a: now, b: now - 1e3 }, false, {\n                    message: '\"b\" must be greater than \"ref:a\"',\n                    path: ['b'],\n                    type: 'date.greater',\n                    context: { limit: ref, label: 'b', key: 'b', value: new Date(now - 1e3) }\n                }]\n            ]);\n        });\n\n        it('accepts references as greater date within a when', () => {\n\n            const ref = Joi.ref('b');\n            const schema = Joi.object({\n                a: Joi.date().required(),\n                b: Joi.date().required(),\n                c: Joi.number().required().when('a', {\n                    is: Joi.date().greater(ref), // a > b\n                    then: Joi.number().valid(0)\n                })\n            });\n\n            Helper.validate(schema, [\n                [{ a: 123, b: 123, c: 0 }, true, { a: new Date(123), b: new Date(123), c: 0 }],\n                [{ a: 123, b: 456, c: 42 }, true, { a: new Date(123), b: new Date(456), c: 42 }],\n                [{ a: 456, b: 123, c: 0 }, true, { a: new Date(456), b: new Date(123), c: 0 }],\n                [{ a: 123, b: 123, c: 42 }, true, { a: new Date(123), b: new Date(123), c: 42 }],\n                [{ a: 456, b: 123, c: 42 }, false, {\n                    message: '\"c\" must be [0]',\n                    path: ['c'],\n                    type: 'any.only',\n                    context: { value: 42, valids: [0], label: 'c', key: 'c' }\n                }]\n            ]);\n        });\n\n        it('accepts context references as greater date', () => {\n\n            const ref = Joi.ref('$a');\n            const schema = Joi.object({ b: Joi.date().greater(ref) });\n            const now = Date.now();\n\n            Helper.validate(schema, { context: { a: now } }, [\n                [{ b: now }, false, {\n                    message: '\"b\" must be greater than \"ref:global:a\"',\n                    path: ['b'],\n                    type: 'date.greater',\n                    context: { limit: ref, label: 'b', key: 'b', value: new Date(now) }\n                }],\n                [{ b: now + 1e3 }, true, { b: new Date(now + 1e3) }],\n                [{ b: now - 1e3 }, false, {\n                    message: '\"b\" must be greater than \"ref:global:a\"',\n                    path: ['b'],\n                    type: 'date.greater',\n                    context: { limit: ref, label: 'b', key: 'b', value: new Date(now - 1e3) }\n                }]\n            ]);\n        });\n\n        it('errors if reference is not a date', () => {\n\n            const ref = Joi.ref('a');\n            const schema = Joi.object({ a: Joi.string(), b: Joi.date().greater(ref) });\n            const now = Date.now();\n\n            Helper.validate(schema, [\n                [{ a: 'abc', b: now }, false, {\n                    message: '\"b\" date references \"ref:a\" which must have a valid date format',\n                    path: ['b'],\n                    type: 'any.ref',\n                    context: { ref, label: 'b', key: 'b', value: 'abc', arg: 'date', reason: 'must have a valid date format' }\n                }],\n                [{ a: '123', b: now }, true, { a: '123', b: new Date(now) }],\n                [{ a: (now + 1e3).toString(), b: now }, false, {\n                    message: '\"b\" must be greater than \"ref:a\"',\n                    path: ['b'],\n                    type: 'date.greater',\n                    context: { limit: ref, label: 'b', key: 'b', value: new Date(now) }\n                }]\n            ]);\n        });\n\n        it('errors if context reference is not a date', () => {\n\n            const ref = Joi.ref('$a');\n            const schema = Joi.object({ b: Joi.date().greater(ref) });\n            const now = Date.now();\n\n            Helper.validate(schema, { context: { a: 'abc' } }, [\n                [{ b: now }, false, {\n                    message: '\"b\" date references \"ref:global:a\" which must have a valid date format',\n                    path: ['b'],\n                    type: 'any.ref',\n                    context: { ref, label: 'b', key: 'b', value: 'abc', arg: 'date', reason: 'must have a valid date format' }\n                }]\n            ]);\n\n            Helper.validate(schema, { context: { a: (now + 1e3).toString() } }, [\n                [{ b: now }, false, {\n                    message: '\"b\" must be greater than \"ref:global:a\"',\n                    path: ['b'],\n                    type: 'date.greater',\n                    context: { limit: ref, label: 'b', key: 'b', value: new Date(now) }\n                }]\n            ]);\n        });\n    });\n\n    describe('iso()', () => {\n\n        it('avoids unnecessary cloning when called twice', () => {\n\n            const schema = Joi.date().iso();\n            expect(schema.iso()).to.shallow.equal(schema);\n        });\n\n        it('validates isoDate', () => {\n\n            Helper.validate(Joi.date().iso(), [\n                ['+002013-06-07T14:21:46.295Z', true, new Date('+002013-06-07T14:21:46.295Z')],\n                ['-002013-06-07T14:21:46.295Z', true, new Date('-002013-06-07T14:21:46.295Z')],\n                ['002013-06-07T14:21:46.295Z', false, {\n                    message: '\"value\" must be in ISO 8601 date format',\n                    path: [],\n                    type: 'date.format',\n                    context: { label: 'value', value: '002013-06-07T14:21:46.295Z', format: 'iso' }\n                }],\n                ['+2013-06-07T14:21:46.295Z', false, {\n                    message: '\"value\" must be in ISO 8601 date format',\n                    path: [],\n                    type: 'date.format',\n                    context: { label: 'value', value: '+2013-06-07T14:21:46.295Z', format: 'iso' }\n                }],\n                ['-2013-06-07T14:21:46.295Z', false, {\n                    message: '\"value\" must be in ISO 8601 date format',\n                    path: [],\n                    type: 'date.format',\n                    context: { label: 'value', value: '-2013-06-07T14:21:46.295Z', format: 'iso' }\n                }],\n                ['2013-06-07T14:21:46.295Z', true, new Date('2013-06-07T14:21:46.295Z')],\n                ['2013-06-07T14:21:46.295Z0', false, {\n                    message: '\"value\" must be in ISO 8601 date format',\n                    path: [],\n                    type: 'date.format',\n                    context: { label: 'value', value: '2013-06-07T14:21:46.295Z0', format: 'iso' }\n                }],\n                ['2013-06-07T14:21:46.295+07:00', true, new Date('2013-06-07T14:21:46.295+07:00')],\n                ['2013-06-07T14:21:46.295+07:000', false, {\n                    message: '\"value\" must be in ISO 8601 date format',\n                    path: [],\n                    type: 'date.format',\n                    context: { label: 'value', value: '2013-06-07T14:21:46.295+07:000', format: 'iso' }\n                }],\n                ['2013-06-07T14:21:46.295-07:00', true, new Date('2013-06-07T14:21:46.295-07:00')],\n                ['2013-06-07T14:21:46Z', true, new Date('2013-06-07T14:21:46Z')],\n                ['2013-06-07T14:21:46Z0', false, {\n                    message: '\"value\" must be in ISO 8601 date format',\n                    path: [],\n                    type: 'date.format',\n                    context: { label: 'value', value: '2013-06-07T14:21:46Z0', format: 'iso' }\n                }],\n                ['2013-06-07T14:21:46+07:00', true, new Date('2013-06-07T14:21:46+07:00')],\n                ['2013-06-07T14:21:46-07:00', true, new Date('2013-06-07T14:21:46-07:00')],\n                ['2013-06-07T14:21Z', true, new Date('2013-06-07T14:21Z')],\n                ['2013-06-07T14:21+07:00', true, new Date('2013-06-07T14:21+07:00')],\n                ['2013-06-07T14:21+07:000', false, {\n                    message: '\"value\" must be in ISO 8601 date format',\n                    path: [],\n                    type: 'date.format',\n                    context: { label: 'value', value: '2013-06-07T14:21+07:000', format: 'iso' }\n                }],\n                ['2013-06-07T14:21-07:00', true, new Date('2013-06-07T14:21-07:00')],\n                ['2013-06-07T14:21Z+7:00', false, {\n                    message: '\"value\" must be in ISO 8601 date format',\n                    path: [],\n                    type: 'date.format',\n                    context: { label: 'value', value: '2013-06-07T14:21Z+7:00', format: 'iso' }\n                }],\n                ['2013-06-07', true, new Date('2013-06-07')],\n                ['2013-06-07T', false, {\n                    message: '\"value\" must be in ISO 8601 date format',\n                    path: [],\n                    type: 'date.format',\n                    context: { label: 'value', value: '2013-06-07T', format: 'iso' }\n                }],\n                ['2013-06-07T14:21', true, new Date('2013-06-07T14:21')],\n                ['1-1-2013', false, {\n                    message: '\"value\" must be in ISO 8601 date format',\n                    path: [],\n                    type: 'date.format',\n                    context: { label: 'value', value: '1-1-2013', format: 'iso' }\n                }],\n                ['2013', true, new Date('2013')]\n            ]);\n        });\n\n        it('converts expanded isoDates', () => {\n\n            const schema = { item: Joi.date().iso() };\n            expect(Joi.compile(schema).validate({ item: '-002013-06-07T14:21:46.295Z' })).to.equal({ value: { item: new Date('-002013-06-07T14:21:46.295Z') } });\n        });\n\n        it('validates isoDate with a friendly error message', () => {\n\n            const schema = { item: Joi.date().iso() };\n            Helper.validate(Joi.compile(schema), [\n                [{ item: 'something' }, false, {\n                    message: '\"item\" must be in ISO 8601 date format',\n                    path: ['item'],\n                    type: 'date.format',\n                    context: { label: 'item', key: 'item', value: 'something', format: 'iso' }\n                }]\n            ]);\n        });\n\n        it('validates isoDate after clone', () => {\n\n            const schema = { item: Joi.date().iso().clone() };\n            Helper.validate(Joi.compile(schema), [[{ item: '2013-06-07T14:21:46.295Z' }, true, { item: new Date('2013-06-07T14:21:46.295Z') }]]);\n        });\n    });\n\n    describe('less()', () => {\n\n        it('validates less', () => {\n\n            const d = new Date('1-1-1970 UTC');\n            const message = `\"value\" must be less than \"${d}\"`;\n            Helper.validate(Joi.date().less('1-1-1970 UTC').prefs({ dateFormat: 'string' }), [\n                ['1-1-1971 UTC', false, {\n                    message,\n                    path: [],\n                    type: 'date.less',\n                    context: { limit: d, label: 'value', value: new Date('1-1-1971 UTC') }\n                }],\n                ['1-1-1970 UTC', false, {\n                    message,\n                    path: [],\n                    type: 'date.less',\n                    context: { limit: d, label: 'value', value: new Date('1-1-1970 UTC') }\n                }],\n                [0, false, {\n                    message,\n                    path: [],\n                    type: 'date.less',\n                    context: { limit: d, label: 'value', value: new Date(0) }\n                }],\n                [1, false, {\n                    message,\n                    path: [],\n                    type: 'date.less',\n                    context: { limit: d, label: 'value', value: new Date(1) }\n                }],\n                ['0', false, {\n                    message,\n                    path: [],\n                    type: 'date.less',\n                    context: { limit: d, label: 'value', value: new Date(0) }\n                }],\n                ['-1', true, new Date(-1)],\n                ['1-1-2014 UTC', false, {\n                    message,\n                    path: [],\n                    type: 'date.less',\n                    context: { limit: d, label: 'value', value: new Date('1-1-2014 UTC') }\n                }]\n            ]);\n        });\n\n        it('accepts \"now\" as the less date', () => {\n\n            const past = new Date(Date.now() - 1000000);\n            Helper.validate(Joi.date().less('now'), [[past, true, past]]);\n        });\n\n        it('errors if .less(\"now\") is used with a future date', () => {\n\n            const now = Date.now();\n            const future = new Date(now + 1000000);\n\n            Helper.validate(Joi.date().less('now'), [[future, false, {\n                message: '\"value\" must be less than \"now\"',\n                path: [],\n                type: 'date.less',\n                context: { limit: 'now', label: 'value', value: future }\n            }]]);\n        });\n\n        it('accepts references as less date', () => {\n\n            const ref = Joi.ref('a');\n            const schema = Joi.object({ a: Joi.date(), b: Joi.date().less(ref) });\n            const now = Date.now();\n\n            Helper.validate(schema, [\n                [{ a: now, b: now }, false, {\n                    message: '\"b\" must be less than \"ref:a\"',\n                    path: ['b'],\n                    type: 'date.less',\n                    context: { limit: ref, label: 'b', key: 'b', value: new Date(now) }\n                }],\n                [{ a: now, b: now + 1e3 }, false, {\n                    message: '\"b\" must be less than \"ref:a\"',\n                    path: ['b'],\n                    type: 'date.less',\n                    context: { limit: ref, label: 'b', key: 'b', value: new Date(now + 1e3) }\n                }],\n                [{ a: now, b: now - 1e3 }, true, { a: new Date(now), b: new Date(now - 1e3) }]\n            ]);\n        });\n\n        it('accepts references as less date', () => {\n\n            const ref = Joi.ref('$a');\n            const schema = Joi.object({ b: Joi.date().less(ref) });\n            const now = Date.now();\n\n            Helper.validate(schema, { context: { a: now } }, [\n                [{ b: now }, false, {\n                    message: '\"b\" must be less than \"ref:global:a\"',\n                    path: ['b'],\n                    type: 'date.less',\n                    context: { limit: ref, label: 'b', key: 'b', value: new Date(now) }\n                }],\n                [{ b: now + 1e3 }, false, {\n                    message: '\"b\" must be less than \"ref:global:a\"',\n                    path: ['b'],\n                    type: 'date.less',\n                    context: { limit: ref, label: 'b', key: 'b', value: new Date(now + 1e3) }\n                }],\n                [{ b: now - 1e3 }, true, { b: new Date(now - 1e3) }]\n            ]);\n        });\n\n        it('errors if reference is not a date', () => {\n\n            const ref = Joi.ref('a');\n            const schema = Joi.object({ a: Joi.string(), b: Joi.date().less(ref) });\n            const now = Date.now();\n\n            Helper.validate(schema, [\n                [{ a: 'abc', b: new Date() }, false, {\n                    message: '\"b\" date references \"ref:a\" which must have a valid date format',\n                    path: ['b'],\n                    type: 'any.ref',\n                    context: { ref, label: 'b', key: 'b', value: 'abc', arg: 'date', reason: 'must have a valid date format' }\n                }],\n                [{ a: '100000000000000', b: now }, true, { a: '100000000000000', b: new Date(now) }],\n                [{ a: (now - 1e3).toString(), b: now }, false, {\n                    message: '\"b\" must be less than \"ref:a\"',\n                    path: ['b'],\n                    type: 'date.less',\n                    context: { limit: ref, label: 'b', key: 'b', value: new Date(now) }\n                }]\n            ]);\n        });\n\n        it('errors if context reference is not a date', () => {\n\n            const ref = Joi.ref('$a');\n            const schema = Joi.object({ b: Joi.date().less(ref) });\n            const now = Date.now();\n\n            Helper.validate(schema, { context: { a: 'abc' } }, [\n                [{ b: now }, false, {\n                    message: '\"b\" date references \"ref:global:a\" which must have a valid date format',\n                    path: ['b'],\n                    type: 'any.ref',\n                    context: { ref, label: 'b', key: 'b', value: 'abc', arg: 'date', reason: 'must have a valid date format' }\n                }]\n            ]);\n\n            Helper.validate(schema, { context: { a: '100000000000000' } }, [\n                [{ b: now }, true, { b: new Date(now) }]\n            ]);\n\n            Helper.validate(schema, { context: { a: (now - 1e3).toString() } }, [\n                [{ b: now }, false, {\n                    message: '\"b\" must be less than \"ref:global:a\"',\n                    path: ['b'],\n                    type: 'date.less',\n                    context: { limit: ref, label: 'b', key: 'b', value: new Date(now) }\n                }]\n            ]);\n        });\n    });\n\n    describe('max()', () => {\n\n        it('validates max', () => {\n\n            const d = new Date('1-1-1970 UTC');\n            const message = `\"value\" must be less than or equal to \"${d.toISOString()}\"`;\n            Helper.validate(Joi.date().max('1-1-1970 UTC'), [\n                ['1-1-1971 UTC', false, {\n                    message,\n                    path: [],\n                    type: 'date.max',\n                    context: { limit: d, label: 'value', value: new Date('1-1-1971 UTC') }\n                }],\n                ['1-1-1970 UTC', true, new Date('1-1-1970 UTC')],\n                [0, true, new Date(0)],\n                [1, false, {\n                    message,\n                    path: [],\n                    type: 'date.max',\n                    context: { limit: d, label: 'value', value: new Date(1) }\n                }],\n                ['0', true, new Date(0)],\n                ['-1', true, new Date(-1)],\n                ['1-1-2014 UTC', false, {\n                    message,\n                    path: [],\n                    type: 'date.max',\n                    context: { limit: d, label: 'value', value: new Date('1-1-2014 UTC') }\n                }]\n            ]);\n        });\n\n        it('accepts \"now\" as the max date', () => {\n\n            const past = new Date(Date.now() - 1000000);\n            Helper.validate(Joi.date().max('now'), [[past, true, past]]);\n        });\n\n        it('errors if .max(\"now\") is used with a future date', () => {\n\n            const now = Date.now();\n            const future = new Date(now + 1000000);\n\n            Helper.validate(Joi.date().max('now'), [[future, false, {\n                message: '\"value\" must be less than or equal to \"now\"',\n                path: [],\n                type: 'date.max',\n                context: { limit: 'now', label: 'value', value: future }\n            }]]);\n        });\n\n        it('accepts references as max date', () => {\n\n            const ref = Joi.ref('a');\n            const schema = Joi.object({ a: Joi.date(), b: Joi.date().max(ref) });\n            const now = Date.now();\n\n            Helper.validate(schema, [\n                [{ a: now, b: now }, true, { a: new Date(now), b: new Date(now) }],\n                [{ a: now, b: now + 1e3 }, false, {\n                    message: '\"b\" must be less than or equal to \"ref:a\"',\n                    path: ['b'],\n                    type: 'date.max',\n                    context: { limit: ref, label: 'b', key: 'b', value: new Date(now + 1e3) }\n                }],\n                [{ a: now, b: now - 1e3 }, true, { a: new Date(now), b: new Date(now - 1e3) }]\n            ]);\n        });\n\n        it('accepts context references as max date', () => {\n\n            const ref = Joi.ref('$a');\n            const schema = Joi.object({ b: Joi.date().max(ref) });\n            const now = Date.now();\n\n            Helper.validate(schema, { context: { a: now } }, [\n                [{ b: now }, true, { b: new Date(now) }],\n                [{ b: now + 1e3 }, false, {\n                    message: '\"b\" must be less than or equal to \"ref:global:a\"',\n                    path: ['b'],\n                    type: 'date.max',\n                    context: { limit: ref, label: 'b', key: 'b', value: new Date(now + 1e3) }\n                }],\n                [{ b: now - 1e3 }, true, { b: new Date(now - 1000) }]\n            ]);\n        });\n\n        it('supports template operations', () => {\n\n            const ref = Joi.x('{number(from) + 364 * day}');\n            const schema = Joi.object({\n                annual: Joi.boolean().required(),\n                from: Joi.date().required(),\n                to: Joi.date().required()\n                    .when('annual', { is: true, then: Joi.date().max(ref) })\n            });\n\n            Helper.validate(schema, [\n                [{ annual: false, from: '2000-01-01', to: '2010-01-01' }, true, { annual: false, from: new Date('2000-01-01'), to: new Date('2010-01-01') }],\n                [{ annual: true, from: '2000-01-01', to: '2000-12-30' }, true, { annual: true, from: new Date('2000-01-01'), to: new Date('2000-12-30') }],\n                [{ annual: true, from: '2000-01-01', to: '2010-01-01' }, false, {\n                    message: '\"to\" must be less than or equal to \"{number(from) + 364 * day}\"',\n                    path: ['to'],\n                    type: 'date.max',\n                    context: { limit: ref, label: 'to', key: 'to', value: new Date('2010-01-01') }\n                }]\n            ]);\n        });\n\n        it('errors if reference is not a date', () => {\n\n            const ref = Joi.ref('a');\n            const schema = Joi.object({ a: Joi.string(), b: Joi.date().max(ref) });\n            const now = Date.now();\n\n            Helper.validate(schema, [\n                [{ a: 'abc', b: new Date() }, false, {\n                    message: '\"b\" date references \"ref:a\" which must have a valid date format',\n                    path: ['b'],\n                    type: 'any.ref',\n                    context: { ref, label: 'b', key: 'b', value: 'abc', arg: 'date', reason: 'must have a valid date format' }\n                }],\n                [{ a: '100000000000000', b: now }, true, { a: '100000000000000', b: new Date(now) }],\n                [{ a: (now - 1e3).toString(), b: now }, false, {\n                    message: '\"b\" must be less than or equal to \"ref:a\"',\n                    path: ['b'],\n                    type: 'date.max',\n                    context: { limit: ref, label: 'b', key: 'b', value: new Date(now) }\n                }]\n            ]);\n        });\n\n        it('errors if context reference is not a date', () => {\n\n            const ref = Joi.ref('$a');\n            const schema = Joi.object({ b: Joi.date().max(ref) });\n            const now = Date.now();\n\n            Helper.validate(schema, { context: { a: 'abc' } }, [\n                [{ b: now }, false, {\n                    message: '\"b\" date references \"ref:global:a\" which must have a valid date format',\n                    path: ['b'],\n                    type: 'any.ref',\n                    context: { ref, label: 'b', key: 'b', value: 'abc', arg: 'date', reason: 'must have a valid date format' }\n                }]\n            ]);\n\n            Helper.validate(schema, { context: { a: '100000000000000' } }, [\n                [{ b: now }, true, { b: new Date(now) }]\n            ]);\n\n            Helper.validate(schema, { context: { a: (now - 1e3).toString() } }, [\n                [{ b: now }, false, {\n                    message: '\"b\" must be less than or equal to \"ref:global:a\"',\n                    path: ['b'],\n                    type: 'date.max',\n                    context: { limit: ref, label: 'b', key: 'b', value: new Date(now) }\n                }]\n            ]);\n        });\n    });\n\n    describe('min()', () => {\n\n        it('validates min', () => {\n\n            const d = new Date('1-1-2000 UTC');\n            const message = `\"value\" must be greater than or equal to \"${d.toISOString()}\"`;\n            Helper.validate(Joi.date().min('1-1-2000 UTC'), [\n                ['1-1-2001 UTC', true, new Date('1-1-2001 UTC')],\n                ['1-1-2000 UTC', true, d],\n                [0, false, {\n                    message,\n                    path: [],\n                    type: 'date.min',\n                    context: { limit: d, label: 'value', value: new Date(0) }\n                }],\n                ['0', false, {\n                    message,\n                    path: [],\n                    type: 'date.min',\n                    context: { limit: d, label: 'value', value: new Date(0) }\n                }],\n                ['-1', false, {\n                    message,\n                    path: [],\n                    type: 'date.min',\n                    context: { limit: d, label: 'value', value: new Date(-1) }\n                }],\n                ['1-1-1999 UTC', false, {\n                    message,\n                    path: [],\n                    type: 'date.min',\n                    context: { limit: d, label: 'value', value: new Date('1-1-1999 UTC') }\n                }]\n            ]);\n        });\n\n        it('accepts \"now\" as the min date', () => {\n\n            const future = new Date(Date.now() + 1000000);\n            expect(Joi.date().min('now').validate(future)).to.equal({ value: future });\n        });\n\n        it('errors if .min(\"now\") is used with a past date', () => {\n\n            const now = Date.now();\n            const past = new Date(now - 1000000);\n\n            Helper.validate(Joi.date().min('now'), [[past, false, {\n                message: '\"value\" must be greater than or equal to \"now\"',\n                path: [],\n                type: 'date.min',\n                context: { limit: 'now', label: 'value', value: past }\n            }]]);\n        });\n\n        it('accepts references as min date', () => {\n\n            const ref = Joi.ref('a');\n            const schema = Joi.object({ a: Joi.date(), b: Joi.date().min(ref) });\n            const now = Date.now();\n\n            Helper.validate(schema, [\n                [{ a: now, b: now }, true, { a: new Date(now), b: new Date(now) }],\n                [{ a: now, b: now + 1e3 }, true, { a: new Date(now), b: new Date(now + 1000) }],\n                [{ a: now, b: now - 1e3 }, false, {\n                    message: '\"b\" must be greater than or equal to \"ref:a\"',\n                    path: ['b'],\n                    type: 'date.min',\n                    context: { limit: ref, label: 'b', key: 'b', value: new Date(now - 1e3) }\n                }]\n            ]);\n        });\n\n        it('accepts references as min date within a when', () => {\n\n            const ref = Joi.ref('b');\n            const schema = Joi.object({\n                a: Joi.date().required(),\n                b: Joi.date().required(),\n                c: Joi.number().required().when('a', {\n                    is: Joi.date().min(ref), // a >= b\n                    then: Joi.number().valid(0)\n                })\n            });\n\n            Helper.validate(schema, [\n                [{ a: 123, b: 123, c: 0 }, true, { a: new Date(123), b: new Date(123), c: 0 }],\n                [{ a: 123, b: 456, c: 42 }, true, { a: new Date(123), b: new Date(456), c: 42 }],\n                [{ a: 456, b: 123, c: 0 }, true, { a: new Date(456), b: new Date(123), c: 0 }],\n                [{ a: 123, b: 123, c: 42 }, false, {\n                    message: '\"c\" must be [0]',\n                    path: ['c'],\n                    type: 'any.only',\n                    context: { value: 42, valids: [0], label: 'c', key: 'c' }\n                }],\n                [{ a: 456, b: 123, c: 42 }, false, {\n                    message: '\"c\" must be [0]',\n                    path: ['c'],\n                    type: 'any.only',\n                    context: { value: 42, valids: [0], label: 'c', key: 'c' }\n                }]\n            ]);\n        });\n\n        it('accepts context references as min date', () => {\n\n            const ref = Joi.ref('$a');\n            const schema = Joi.object({ b: Joi.date().min(ref) });\n            const now = Date.now();\n\n            Helper.validate(schema, { context: { a: now } }, [\n                [{ b: now }, true, { b: new Date(now) }],\n                [{ b: now + 1e3 }, true, { b: new Date(now + 1000) }],\n                [{ b: now - 1e3 }, false, {\n                    message: '\"b\" must be greater than or equal to \"ref:global:a\"',\n                    path: ['b'],\n                    type: 'date.min',\n                    context: { limit: ref, label: 'b', key: 'b', value: new Date(now - 1e3) }\n                }]\n            ]);\n        });\n\n        it('errors if reference is not a date', () => {\n\n            const ref = Joi.ref('a');\n            const schema = Joi.object({ a: Joi.string(), b: Joi.date().min(ref) });\n            const now = Date.now();\n\n            Helper.validate(schema, [\n                [{ a: 'abc', b: now }, false, {\n                    message: '\"b\" date references \"ref:a\" which must have a valid date format',\n                    path: ['b'],\n                    type: 'any.ref',\n                    context: { ref, label: 'b', key: 'b', value: 'abc', arg: 'date', reason: 'must have a valid date format' }\n                }],\n                [{ a: '123', b: now }, true, { a: '123', b: new Date(now) }],\n                [{ a: (now + 1e3).toString(), b: now }, false, {\n                    message: '\"b\" must be greater than or equal to \"ref:a\"',\n                    path: ['b'],\n                    type: 'date.min',\n                    context: { limit: ref, label: 'b', key: 'b', value: new Date(now) }\n                }]\n            ]);\n        });\n\n        it('errors if context reference is not a date', () => {\n\n            const ref = Joi.ref('$a');\n            const schema = Joi.object({ b: Joi.date().min(ref) });\n            const now = Date.now();\n\n            Helper.validate(schema, { context: { a: 'abc' } }, [\n                [{ b: now }, false, {\n                    message: '\"b\" date references \"ref:global:a\" which must have a valid date format',\n                    path: ['b'],\n                    type: 'any.ref',\n                    context: { ref, label: 'b', key: 'b', value: 'abc', arg: 'date', reason: 'must have a valid date format' }\n                }]\n            ]);\n\n            Helper.validate(schema, { context: { a: (now + 1e3).toString() } }, [\n                [{ b: now }, false, {\n                    message: '\"b\" must be greater than or equal to \"ref:global:a\"',\n                    path: ['b'],\n                    type: 'date.min',\n                    context: { limit: ref, label: 'b', key: 'b', value: new Date(now) }\n                }]\n            ]);\n        });\n    });\n\n    describe('timestamp()', () => {\n\n        it('avoids unnecessary cloning when called twice', () => {\n\n            const schema = Joi.date().timestamp('unix');\n            expect(schema.timestamp('unix')).to.shallow.equal(schema);\n        });\n\n        it('fails on empty strings', () => {\n\n            const schema = Joi.date().timestamp();\n            Helper.validate(schema, [\n                ['', false, {\n                    message: '\"value\" must be in timestamp or number of milliseconds format',\n                    path: [],\n                    type: 'date.format',\n                    context: { label: 'value', value: '', format: 'javascript' }\n                }],\n                [' \\t ', false, {\n                    message: '\"value\" must be in timestamp or number of milliseconds format',\n                    path: [],\n                    type: 'date.format',\n                    context: { label: 'value', value: ' \\t ', format: 'javascript' }\n                }]\n            ]);\n        });\n\n        it('validates javascript timestamp', () => {\n\n            const now = new Date();\n            const milliseconds = now.getTime();\n\n            Helper.validate(Joi.date().timestamp(), [[milliseconds, true, now]]);\n            Helper.validate(Joi.date().timestamp('javascript'), [[milliseconds, true, now]]);\n            Helper.validate(Joi.date().timestamp('unix').timestamp('javascript'), [[milliseconds, true, now]]);\n        });\n\n        it('validates unix timestamp', () => {\n\n            const now = new Date();\n            const seconds = now.getTime() / 1000;\n\n            Helper.validate(Joi.date().timestamp('unix'), [[seconds, true, now]]);\n            Helper.validate(Joi.date().timestamp().timestamp('unix'), [[seconds, true, now]]);\n            Helper.validate(Joi.date().timestamp('javascript').timestamp('unix'), [[seconds, true, now]]);\n        });\n\n        it('validates timestamps with decimals', () => {\n\n            const now = new Date();\n\n            Helper.validate(Joi.date().timestamp(), [\n                [now.getTime().toFixed(4), true, now]\n            ]);\n\n            Helper.validate(Joi.date().timestamp('javascript'), [\n                [now.getTime().toFixed(4), true, now]\n            ]);\n\n            Helper.validate(Joi.date().timestamp('unix'), [\n                [(now.getTime() / 1000).toFixed(4), true, now]\n            ]);\n        });\n\n        it('validates only valid timestamps and returns a friendly error message', () => {\n\n            const invalidDate = new Date('not a valid date');\n            const now = new Date();\n\n            Helper.validate(Joi.date().timestamp(), [\n                [now.getTime(), true, now],\n                [now.getTime().toFixed(4), true, now],\n                ['1.452126061677e+12', true, new Date(1.452126061677e+12)],\n                [1.452126061677e+12, true, new Date(1.452126061677e+12)],\n                [1E3, true, new Date(1000)],\n                ['1E3', true, new Date(1000)],\n                [',', false, {\n                    message: '\"value\" must be in timestamp or number of milliseconds format',\n                    path: [],\n                    type: 'date.format',\n                    context: { label: 'value', value: ',', format: 'javascript' }\n                }],\n                ['123A,0xA', false, {\n                    message: '\"value\" must be in timestamp or number of milliseconds format',\n                    path: [],\n                    type: 'date.format',\n                    context: { label: 'value', value: '123A,0xA', format: 'javascript' }\n                }],\n                ['1-1-2013 UTC', false, {\n                    message: '\"value\" must be in timestamp or number of milliseconds format',\n                    path: [],\n                    type: 'date.format',\n                    context: { label: 'value', value: '1-1-2013 UTC', format: 'javascript' }\n                }],\n                ['not a valid timestamp', false, {\n                    message: '\"value\" must be in timestamp or number of milliseconds format',\n                    path: [],\n                    type: 'date.format',\n                    context: { label: 'value', value: 'not a valid timestamp', format: 'javascript' }\n                }],\n                [invalidDate, false, {\n                    message: '\"value\" must be a valid date',\n                    path: [],\n                    type: 'date.base',\n                    context: { label: 'value', value: invalidDate }\n                }]\n            ]);\n        });\n\n        it('fails with not allowed type', () => {\n\n            expect(() => {\n\n                Joi.date().timestamp('not allowed');\n            }).to.throw(Error, /\"type\" must be one of/);\n        });\n    });\n});\n"
  },
  {
    "path": "test/types/function.js",
    "content": "'use strict';\n\nconst Code = require('@hapi/code');\nconst Lab = require('@hapi/lab');\nconst Joi = require('../..');\n\nconst Helper = require('../helper');\n\n\nconst internals = {};\n\n\nconst { describe, it } = exports.lab = Lab.script();\nconst { expect } = Code;\n\n\ndescribe('function', () => {\n\n    it('throws an exception if arguments were passed.', () => {\n\n        expect(() => Joi.function('invalid argument.')).to.throw('The function type does not allow arguments');\n    });\n\n    it('validates a function', () => {\n\n        Helper.validate(Joi.function().required(), [\n            [function () { }, true],\n            ['', false, {\n                message: '\"value\" must be of type function',\n                path: [],\n                type: 'object.base',\n                context: { label: 'value', value: '', type: 'function' }\n            }]\n        ]);\n    });\n\n    it('supports func() alias', () => {\n\n        Helper.validate(Joi.func().required(), [\n            [function () { }, true],\n            ['', false, {\n                message: '\"value\" must be of type function',\n                path: [],\n                type: 'object.base',\n                context: { label: 'value', value: '', type: 'function' }\n            }]\n        ]);\n    });\n\n    it('validates a function arity', () => {\n\n        const schema = Joi.function().arity(2).required();\n        Helper.validate(schema, [\n            [function (a, b) { }, true],\n            [(a, b) => { }, true],\n            ['', false, {\n                message: '\"value\" must be of type function',\n                path: [],\n                type: 'object.base',\n                context: { label: 'value', value: '', type: 'function' }\n            }]\n        ]);\n\n        const f1 = function (a, b, c) {};\n        const f2 = function (a) { };\n        const f3 = (a, b, c) => { };\n        const f4 = (a) => { };\n\n        Helper.validate(schema, [\n            [f1, false, {\n                message: '\"value\" must have an arity of 2',\n                path: [],\n                type: 'function.arity',\n                context: { n: 2, label: 'value', value: f1 }\n            }],\n            [f2, false, {\n                message: '\"value\" must have an arity of 2',\n                path: [],\n                type: 'function.arity',\n                context: { n: 2, label: 'value', value: f2 }\n            }],\n            [f3, false, {\n                message: '\"value\" must have an arity of 2',\n                path: [],\n                type: 'function.arity',\n                context: { n: 2, label: 'value', value: f3 }\n            }], [f4, false, {\n                message: '\"value\" must have an arity of 2',\n                path: [],\n                type: 'function.arity',\n                context: { n: 2, label: 'value', value: f4 }\n            }]\n        ]);\n    });\n\n    it('validates a function arity unless values are illegal', () => {\n\n        const schemaWithStringArity = function () {\n\n            return Joi.function().arity('deux');\n        };\n\n        const schemaWithNegativeArity = function () {\n\n            return Joi.function().arity(-2);\n        };\n\n        expect(schemaWithStringArity).to.throw(Error, 'n must be a positive integer');\n        expect(schemaWithNegativeArity).to.throw(Error, 'n must be a positive integer');\n    });\n\n    it('validates a function min arity', () => {\n\n        const schema = Joi.function().minArity(2).required();\n        const f1 = function (a) { };\n        const f2 = (a) => { };\n\n        Helper.validate(schema, [\n            [function (a, b) { }, true],\n            [function (a, b, c) { }, true],\n            [(a, b) => { }, true],\n            [(a, b, c) => { }, true],\n            ['', false, {\n                message: '\"value\" must be of type function',\n                path: [],\n                type: 'object.base',\n                context: { label: 'value', value: '', type: 'function' }\n            }],\n            [f1, false, {\n                message: '\"value\" must have an arity greater or equal to 2',\n                path: [],\n                type: 'function.minArity',\n                context: { n: 2, label: 'value', value: f1 }\n            }],\n            [f2, false, {\n                message: '\"value\" must have an arity greater or equal to 2',\n                path: [],\n                type: 'function.minArity',\n                context: { n: 2, label: 'value', value: f2 }\n            }]\n        ]);\n    });\n\n    it('validates a function arity unless values are illegal', () => {\n\n        const schemaWithStringMinArity = function () {\n\n            return Joi.function().minArity('deux');\n        };\n\n        const schemaWithNegativeMinArity = function () {\n\n            return Joi.function().minArity(-2);\n        };\n\n        const schemaWithZeroArity = function () {\n\n            return Joi.function().minArity(0);\n        };\n\n        expect(schemaWithStringMinArity).to.throw(Error, 'n must be a strict positive integer');\n        expect(schemaWithNegativeMinArity).to.throw(Error, 'n must be a strict positive integer');\n        expect(schemaWithZeroArity).to.throw(Error, 'n must be a strict positive integer');\n    });\n\n    it('validates a function max arity', () => {\n\n        const schema = Joi.function().maxArity(2).required();\n        const f1 = function (a, b, c) { };\n        const f2 = (a, b, c) => { };\n\n        Helper.validate(schema, [\n            [function (a, b) { }, true],\n            [function (a) { }, true],\n            [(a, b) => { }, true],\n            [(a) => { }, true],\n            [f1, false, {\n                message: '\"value\" must have an arity lesser or equal to 2',\n                path: [],\n                type: 'function.maxArity',\n                context: { n: 2, label: 'value', value: f1 }\n            }],\n            [f2, false, {\n                message: '\"value\" must have an arity lesser or equal to 2',\n                path: [],\n                type: 'function.maxArity',\n                context: { n: 2, label: 'value', value: f2 }\n            }],\n            ['', false, {\n                message: '\"value\" must be of type function',\n                path: [],\n                type: 'object.base',\n                context: { label: 'value', value: '', type: 'function' }\n            }]\n        ]);\n    });\n\n    it('validates a function arity unless values are illegal', () => {\n\n        const schemaWithStringMaxArity = function () {\n\n            return Joi.function().maxArity('deux');\n        };\n\n        const schemaWithNegativeMaxArity = function () {\n\n            return Joi.function().maxArity(-2);\n        };\n\n        expect(schemaWithStringMaxArity).to.throw('n must be a positive integer');\n        expect(schemaWithNegativeMaxArity).to.throw('n must be a positive integer');\n    });\n\n    it('validates a function with keys', () => {\n\n        const a = function () { };\n        a.a = 'abc';\n\n        const b = function () { };\n        b.a = 123;\n\n        Helper.validate(Joi.function().keys({ a: Joi.string().required() }).required(), [\n            [function () { }, false, {\n                message: '\"a\" is required',\n                path: ['a'],\n                type: 'any.required',\n                context: { label: 'a', key: 'a' }\n            }],\n            [a, true, Helper.skip],\n            [b, false, {\n                message: '\"a\" must be a string',\n                path: ['a'],\n                type: 'string.base',\n                context: { value: 123, label: 'a', key: 'a' }\n            }],\n            ['', false, {\n                message: '\"value\" must be of type function',\n                path: [],\n                type: 'object.base',\n                context: { label: 'value', value: '', type: 'function' }\n            }]\n        ]);\n    });\n\n    it('validates a function with keys and function rules', () => {\n\n        const schema = Joi.function()\n            .keys({ a: Joi.string().required() })\n            .minArity(1)\n            .required();\n\n        const a = function (x) { };\n        a.a = 'abc';\n\n        const b = function (x) { };\n        b.a = 123;\n\n        const c = function () { };\n        c.a = 'abc';\n\n        Helper.validate(schema, [\n            [a, true, Helper.skip],\n            [function (x) { }, false, {\n                message: '\"a\" is required',\n                path: ['a'],\n                type: 'any.required',\n                context: { label: 'a', key: 'a' }\n            }],\n            [b, false, {\n                message: '\"a\" must be a string',\n                path: ['a'],\n                type: 'string.base',\n                context: { value: 123, label: 'a', key: 'a' }\n            }]\n        ]);\n\n        const err = schema.validate(c).error;\n        expect(err).to.be.an.error('\"value\" must have an arity greater or equal to 1');\n        expect(err.details).to.equal([{\n            message: '\"value\" must have an arity greater or equal to 1',\n            path: [],\n            type: 'function.minArity',\n            context: { value: err.details[0].context.value, label: 'value', n: 1 }\n        }]);\n    });\n\n    it('validates a function with object rules and function rules', () => {\n\n        const schema = Joi.function()\n            .min(1)\n            .minArity(1)\n            .required();\n\n        const a = function (x) { };\n        a.a = 'abc';\n\n        const b = function () { };\n        b.a = 'abc';\n\n        const c = function (x) { };\n\n        Helper.validate(schema, [\n            [a, true],\n            [b, false, {\n                message: '\"value\" must have an arity greater or equal to 1',\n                path: [],\n                type: 'function.minArity',\n                context: { value: b, label: 'value', n: 1 }\n            }],\n            [c, false, {\n                message: '\"value\" must have at least 1 key',\n                path: [],\n                type: 'object.min',\n                context: { value: c, label: 'value', limit: 1 }\n            }]\n        ]);\n    });\n\n    it('keeps validated value as a function', () => {\n\n        const schema = Joi.function().keys({ a: Joi.number() });\n\n        const b = 'abc';\n        const value = function () {\n\n            return b;\n        };\n\n        value.a = '123';\n\n        const validated = schema.validate(value).value;\n        expect(validated).to.be.a.function();\n        expect(validated()).to.equal('abc');\n        expect(validated).to.not.equal(value);\n    });\n\n    it('retains validated value prototype', () => {\n\n        const schema = Joi.function().keys({ a: Joi.number() });\n\n        const value = function () {\n\n            this.x = 'o';\n        };\n\n        value.prototype.get = function () {\n\n            return this.x;\n        };\n\n        const validated = schema.validate(value).value;\n        expect(validated).to.be.a.function();\n        const p = new validated();\n        expect(p.get()).to.equal('o');\n        expect(validated).to.not.equal(value);\n    });\n\n    it('keeps validated value as a function (no clone)', () => {\n\n        const schema = Joi.function();\n\n        const b = 'abc';\n        const value = function () {\n\n            return b;\n        };\n\n        value.a = '123';\n\n        const validated = schema.validate(value).value;\n        expect(validated).to.be.a.function();\n        expect(validated()).to.equal('abc');\n        expect(validated).to.equal(value);\n    });\n});\n\ndescribe('function().class()', () => {\n\n    it('should differentiate between classes and functions', () => {\n\n        const classSchema = Joi.object({\n            _class: Joi.function().class()\n        });\n\n        const testFunc = function () { };\n        const testClass = class MyClass { };\n\n        Helper.validate(classSchema, [\n            [{ _class: testClass }, true],\n            [{ _class: testFunc }, false, {\n                message: '\"_class\" must be a class',\n                path: ['_class'],\n                type: 'function.class',\n                context: { key: '_class', label: '_class', value: testFunc }\n            }]\n        ]);\n    });\n\n    it('refuses class look-alikes and bad values', () => {\n\n        const classSchema = Joi.object({\n            _class: Joi.function().class()\n        });\n\n        Helper.validate(classSchema, [\n            [{ _class: ['class '] }, false, {\n                message: '\"_class\" must be of type function',\n                path: ['_class'],\n                type: 'object.base',\n                context: { key: '_class', label: '_class', value: ['class '], type: 'function' }\n            }],\n            [{ _class: null }, false, {\n                message: '\"_class\" must be of type function',\n                path: ['_class'],\n                type: 'object.base',\n                context: { key: '_class', label: '_class', value: null, type: 'function' }\n            }]\n        ]);\n    });\n});\n"
  },
  {
    "path": "test/types/link.js",
    "content": "'use strict';\n\nconst Code = require('@hapi/code');\nconst Lab = require('@hapi/lab');\nconst Joi = require('../..');\n\nconst Helper = require('../helper');\n\n\nconst internals = {};\n\n\nconst { describe, it } = exports.lab = Lab.script();\nconst { expect } = Code;\n\n\ndescribe('link', () => {\n\n    it('errors on uninitialized link', () => {\n\n        expect(() => Joi.link().validate(1)).to.throw('Uninitialized link schema');\n    });\n\n    it('links named schema (explicit)', () => {\n\n        const schema = Joi.object({\n            a: [Joi.string(), Joi.number()],\n            b: Joi.link('#type.a')\n        })\n            .id('type');\n\n        Helper.validate(schema, [\n            [{ a: 1, b: 2 }, true],\n            [{ a: '1', b: '2' }, true],\n            [{ a: [1], b: '2' }, false, '\"a\" must be one of [string, number]']\n        ]);\n    });\n\n    it('links named schema (by ref)', () => {\n\n        const schema = Joi.object({\n            a: [Joi.string(), Joi.number()],\n            b: Joi.link().ref('#type.a')\n        })\n            .id('type');\n\n        Helper.validate(schema, [\n            [{ a: 1, b: 2 }, true],\n            [{ a: '1', b: '2' }, true],\n            [{ a: [1], b: '2' }, false, '\"a\" must be one of [string, number]']\n        ]);\n    });\n\n    it('links named schema (implicit)', () => {\n\n        const schema = Joi.object({\n            a: Joi.object({\n                b: Joi.object({\n                    c: Joi.object({\n                        d: Joi.link('#a.e.f')\n                    })\n                }),\n                e: Joi.object({\n                    f: [Joi.string(), Joi.number()]\n                })\n            })\n        });\n\n        Helper.validate(schema, [\n            [{ a: { b: { c: { d: '1' } }, e: { f: '2' } } }, true],\n            [{ a: { b: { c: { d: 1 } }, e: { f: 2 } } }, true],\n            [{ a: { b: { c: { d: {} } }, e: { f: 2 } } }, false, '\"a.b.c.d\" must be one of [string, number]']\n        ]);\n    });\n\n    it('links shared schema', () => {\n\n        const shared = Joi.number().id('shared');\n\n        const schema = Joi.object({\n            a: [Joi.string(), Joi.link('#shared')],\n            b: Joi.link('#type.a')\n        })\n            .shared(Joi.any().id('ignore'))\n            .shared(shared)\n            .id('type');\n\n        Helper.validate(schema, [\n            [{ a: 1, b: 2 }, true],\n            [{ a: '1', b: '2' }, true],\n            [{ a: [1], b: '2' }, false, '\"a\" must be one of [string, number]']\n        ]);\n    });\n\n    it('links schema nodes', () => {\n\n        const schema = Joi.object({\n            a: [Joi.string(), Joi.number()],\n            b: Joi.link('a')\n        });\n\n        Helper.validate(schema, [\n            [{ a: 1, b: 2 }, true],\n            [{ a: '1', b: '2' }, true],\n            [{ a: [1], b: '2' }, false, '\"a\" must be one of [string, number]']\n        ]);\n    });\n\n    it('links schema cousin nodes', () => {\n\n        const schema = Joi.object({\n            a: [Joi.string(), Joi.number()],\n            b: {\n                c: Joi.link('...a')\n            }\n        });\n\n        Helper.validate(schema, [\n            [{ a: 1, b: { c: 2 } }, true],\n            [{ a: '1', b: { c: '2' } }, true],\n            [{ a: [1], b: { c: '2' } }, false, '\"a\" must be one of [string, number]']\n        ]);\n    });\n\n    it('links schema cousin nodes (root)', () => {\n\n        const schema = Joi.object({\n            a: [Joi.string(), Joi.number()],\n            b: {\n                c: Joi.link('/a')\n            }\n        });\n\n        Helper.validate(schema, [\n            [{ a: 1, b: { c: 2 } }, true],\n            [{ a: '1', b: { c: '2' } }, true],\n            [{ a: [1], b: { c: '2' } }, false, '\"a\" must be one of [string, number]']\n        ]);\n    });\n\n    it('validates a recursive schema', () => {\n\n        const schema = Joi.object({\n            name: Joi.string().required(),\n            keys: Joi.array()\n                .items(Joi.link('...'))         // .item .array .schema\n        });\n\n        expect(schema.validate({ name: 'foo', keys: [{ name: 'bar' }] }).error).to.not.exist();\n\n        Helper.validate(schema, [\n            [{ name: 'foo' }, true],\n            [{ name: 'foo', keys: [] }, true],\n            [{ name: 'foo', keys: [{ name: 'bar' }] }, true],\n            [{ name: 'foo', keys: [{ name: 'bar', keys: [{ name: 'baz' }] }] }, true],\n            [{ name: 'foo', keys: [{ name: 'bar', keys: [{ name: 'baz', keys: [{ name: 'qux' }] }] }] }, true],\n            [{ name: 'foo', keys: [{ name: 'bar', keys: [{ name: 'baz', keys: [{ name: 42 }] }] }] }, false, {\n                message: '\"keys[0].keys[0].keys[0].name\" must be a string',\n                path: ['keys', 0, 'keys', 0, 'keys', 0, 'name'],\n                type: 'string.base',\n                context: { value: 42, label: 'keys[0].keys[0].keys[0].name', key: 'name' }\n            }]\n        ]);\n    });\n\n    it('validates a recursive schema (root)', () => {\n\n        const schema = Joi.object({\n            name: Joi.string().required(),\n            keys: Joi.array()\n                .items(Joi.link('/'))\n        });\n\n        expect(schema.validate({ name: 'foo', keys: [{ name: 'bar' }] }).error).to.not.exist();\n\n        Helper.validate(schema, [\n            [{ name: 'foo' }, true],\n            [{ name: 'foo', keys: [] }, true],\n            [{ name: 'foo', keys: [{ name: 'bar' }] }, true],\n            [{ name: 'foo', keys: [{ name: 'bar', keys: [{ name: 'baz' }] }] }, true],\n            [{ name: 'foo', keys: [{ name: 'bar', keys: [{ name: 'baz', keys: [{ name: 'qux' }] }] }] }, true],\n            [{ name: 'foo', keys: [{ name: 'bar', keys: [{ name: 'baz', keys: [{ name: 42 }] }] }] }, false, {\n                message: '\"keys[0].keys[0].keys[0].name\" must be a string',\n                path: ['keys', 0, 'keys', 0, 'keys', 0, 'name'],\n                type: 'string.base',\n                context: { value: 42, label: 'keys[0].keys[0].keys[0].name', key: 'name' }\n            }]\n        ]);\n    });\n\n    it('caches resolved schema', () => {\n\n        const link = Joi.link('x');\n        const schema = Joi.object({\n            a: {\n                y: link,\n                x: 1\n            },\n            b: {\n                y: link,\n                x: 2\n            }\n        });\n\n        Helper.validate(schema, [\n            [{ a: { y: 1 }, b: { y: 1 } }, true],\n            [{ a: { y: 1 }, b: { y: 2 } }, false, '\"b.y\" must be [1]']\n        ]);\n    });\n\n    it('re-resolves schema', () => {\n\n        const link = Joi.link('x').relative();\n        const schema = Joi.object({\n            a: {\n                y: link,\n                x: 1\n            },\n            b: {\n                y: link,\n                x: 2\n            }\n        });\n\n        Helper.validate(schema, [\n            [{ a: { y: 1 }, b: { y: 2 } }, true],\n            [{ a: { y: 1 }, b: { y: 1 } }, false, '\"b.y\" must be [2]']\n        ]);\n    });\n\n    it('validates a recursive schema (in alternatives)', () => {\n\n        const schema = Joi.object({\n            happy: Joi.boolean().required(),\n            children: Joi.object()\n                .pattern(/.*/, [\n                    'none',\n                    Joi.link('....')        // .alternatives .child .children .schema\n                ])\n        });\n\n        Helper.validate(schema, [\n            [{ happy: true }, true],\n            [{ happy: true, children: { a: 'none' } }, true],\n            [{ happy: true, children: { a: { happy: false } } }, true],\n            [{ happy: true, children: { a: { happy: false }, b: { happy: true }, c: 'none' } }, true],\n            [{ happy: true, children: { a: { happy: false }, b: { happy: true }, c: {} } }, false, {\n                context: { key: 'happy', label: 'children.c.happy' },\n                message: '\"children.c.happy\" is required',\n                path: ['children', 'c', 'happy'],\n                type: 'any.required'\n            }]\n        ]);\n    });\n\n    it('concats schemas with links', () => {\n\n        const a = Joi.object({\n            a: [Joi.string(), Joi.number()],\n            b: Joi.link('a')\n        });\n\n        const b = Joi.object({ c: Joi.number() });\n\n        const schema = b.concat(a);\n\n        Helper.validate(schema, [\n            [{ a: 1, b: 2, c: 3 }, true],\n            [{ a: '1', b: '2', c: 3 }, true],\n            [{ a: [1], b: '2' }, false, '\"a\" must be one of [string, number]']\n        ]);\n    });\n\n    it('errors on invalid reference', () => {\n\n        expect(() => Joi.link('.')).to.throw('Link cannot reference itself');\n    });\n\n    it('errors on invalid reference type', () => {\n\n        expect(() => Joi.link('$x')).to.throw('Invalid reference type: global');\n    });\n\n    it('errors on out of boundaries reference', () => {\n\n        const schema = Joi.object({\n            x: Joi.link('...')\n        });\n\n        expect(() => schema.validate({ x: 123 })).to.throw('\"x\" contains link reference \"ref:...\" which is outside of schema boundaries');\n    });\n\n    it('errors on missing reference (relative)', () => {\n\n        const schema = Joi.object({\n            x: Joi.link('y')\n        });\n\n        expect(() => schema.validate({ x: 123 })).to.throw('\"x\" contains link reference \"ref:y\" to non-existing schema');\n    });\n\n    it('errors on missing reference (named)', () => {\n\n        const schema = Joi.object({\n            x: Joi.link('#y')\n        });\n\n        expect(() => schema.validate({ x: 123 })).to.throw('\"x\" contains link reference \"ref:local:y\" which is outside of schema boundaries');\n    });\n\n    it('errors on referenced link', () => {\n\n        const schema = Joi.object({\n            x: Joi.link('y'),\n            y: Joi.link('z'),\n            z: Joi.any()\n        });\n\n        expect(() => schema.validate({ x: 123 })).to.throw('\"x\" contains link reference \"ref:y\" which is another link');\n    });\n\n    describe('when()', () => {\n\n        it('validates a schema with when()', () => {\n\n            const schema = Joi.object({\n                must: Joi.boolean().required(),\n                child: Joi.link('..')\n                    .when('must', { then: Joi.required() })\n            });\n\n            Helper.validate(schema, [\n                [{ must: false }, true],\n                [{ must: false, child: { must: false } }, true],\n                [{ must: true, child: { must: false } }, true],\n                [{ must: true, child: { must: true, child: { must: false } } }, true]\n            ]);\n        });\n\n        it('changes the resolved schema', () => {\n\n            const schema = Joi.object({\n                category: Joi.valid('x', 'y').required(),\n                subs: Joi.array()\n                    .items(Joi.link('#unit').when('...category', { is: 'y', then: Joi.object({ subs: Joi.forbidden() }) }))\n                    .min(1)\n            })\n                .id('unit');\n\n            Helper.validate(schema, [\n                [{ category: 'x', subs: [{ category: 'x' }] }, true],\n                [{ category: 'y', subs: [{ category: 'x' }] }, true],\n                [{ category: 'y', subs: [{ category: 'x', subs: [{ category: 'x' }] }] }, false, {\n                    message: '\"subs[0].subs\" is not allowed',\n                    path: ['subs', 0, 'subs'],\n                    type: 'any.unknown',\n                    context: { label: 'subs[0].subs', value: [{ category: 'x' }], key: 'subs' }\n                }]\n            ]);\n        });\n    });\n\n    describe('concat()', () => {\n\n        it('errors on concat of link to link', () => {\n\n            expect(() => Joi.link('..').concat(Joi.link('..'))).to.throw('Cannot merge type link with another link');\n        });\n\n        it('combines link with any', () => {\n\n            const a = Joi.object({\n                x: Joi.link('..')\n            });\n\n            const b = Joi.object({\n                x: Joi.forbidden()\n            });\n\n            Helper.validate(a, [\n                [{ x: {} }, true]\n            ]);\n\n            Helper.validate(a.concat(b), [\n                [{ x: {} }, false, '\"x\" is not allowed']\n            ]);\n        });\n\n        it('applies concat after ref resolved', () => {\n\n            const shared = Joi.object({\n                a: Joi.number()\n            })\n                .id('shared');\n\n            const schema = Joi.object({\n                x: Joi.link('#shared')\n                    .concat(Joi.object({ a: 3 }))\n            })\n                .shared(shared);\n\n            Helper.validate(schema, [\n                [{ x: { a: 3 } }, true],\n                [{ x: { a: 2 } }, false, {\n                    message: '\"x.a\" must be [3]',\n                    path: ['x', 'a'],\n                    type: 'any.only',\n                    context: { label: 'x.a', value: 2, key: 'a', valids: [3] }\n                }]\n            ]);\n        });\n\n        it('applies concat after ref resolved (with when)', () => {\n\n            const shared = Joi.object({\n                a: Joi.number()\n            })\n                .id('shared');\n\n            const schema = Joi.object({\n                w: Joi.boolean(),\n                x: Joi.link('#shared')\n                    .when('w', { then: Joi.object({ a: 4 }) })\n                    .concat(Joi.object({ a: Joi.valid(3) }))\n            })\n                .shared(shared);\n\n            Helper.validate(schema, [\n                [{ x: { a: 3 } }, true],\n                [{ w: true, x: { a: 3 } }, true],\n                [{ w: true, x: { a: 4 } }, true],\n                [{ x: { a: 2 } }, false, {\n                    message: '\"x.a\" must be [3]',\n                    path: ['x', 'a'],\n                    type: 'any.only',\n                    context: { label: 'x.a', value: 2, key: 'a', valids: [3] }\n                }]\n            ]);\n        });\n\n        it('combines link and linked whens', () => {\n\n            const schema = Joi.object({\n                type: Joi.valid('a', 'b').required()\n            })\n                .when('.type', [\n                    {\n                        is: 'a',\n                        then: Joi.object({\n                            x: Joi.boolean()\n                        })\n                    },\n                    {\n                        is: 'b',\n                        then: Joi.object({\n                            y: Joi.link('#root').concat(Joi.object({ type: 'a' }))\n                        })\n                    }\n                ])\n                .id('root');\n\n            Helper.validate(schema, [\n                [{ type: 'b', y: { type: 'a' } }, true],\n                [{ type: 'b', y: { type: 'a', x: true } }, true]\n            ]);\n        });\n    });\n\n    describe('describe()', () => {\n\n        it('describes link', () => {\n\n            const schema = Joi.object({\n                a: Joi.string(),\n                b: Joi.link('a')\n            });\n\n            expect(schema.describe()).to.equal({\n                type: 'object',\n                keys: {\n                    a: {\n                        type: 'string'\n                    },\n                    b: {\n                        type: 'link',\n                        link: {\n                            ref: { path: ['a'] }\n                        }\n                    }\n                }\n            });\n        });\n    });\n});\n"
  },
  {
    "path": "test/types/number.js",
    "content": "'use strict';\n\nconst Code = require('@hapi/code');\nconst Lab = require('@hapi/lab');\nconst Joi = require('../..');\n\nconst Helper = require('../helper');\n\n\nconst internals = {};\n\n\nconst { describe, it } = exports.lab = Lab.script();\nconst { expect } = Code;\n\n\ndescribe('number', () => {\n\n    it('throws an exception if arguments were passed.', () => {\n\n        expect(() => Joi.number('invalid argument.')).to.throw('The number type does not allow arguments');\n    });\n\n    it('validates 0', () => {\n\n        Helper.validate(Joi.number(), [\n            [0, true, 0],\n            [-0, true, 0],\n            [parseFloat('-0'), true, 0]\n        ]);\n    });\n\n    it('fails on boolean', () => {\n\n        const schema = Joi.number();\n        Helper.validate(schema, [\n            [true, false, {\n                message: '\"value\" must be a number',\n                path: [],\n                type: 'number.base',\n                context: { label: 'value', value: true }\n            }],\n            [false, false, {\n                message: '\"value\" must be a number',\n                path: [],\n                type: 'number.base',\n                context: { label: 'value', value: false }\n            }]\n        ]);\n    });\n\n    it('instantiates separate copies on invocation', () => {\n\n        const result1 = Joi.number().min(5);\n        const result2 = Joi.number().max(5);\n\n        expect(Object.keys(result1)).to.not.shallow.equal(Object.keys(result2));\n    });\n\n    it('shows resulting object with #valueOf', () => {\n\n        const result = Joi.number().min(5);\n        expect(result.valueOf()).to.exist();\n    });\n\n    describe('error message', () => {\n\n        it('displays correctly for int type', () => {\n\n            const t = Joi.number().integer();\n            Helper.validate(Joi.compile(t), [\n                ['1.1', false, {\n                    message: '\"value\" must be an integer',\n                    path: [],\n                    type: 'number.integer',\n                    context: { label: 'value', value: 1.1 }\n                }]\n            ]);\n        });\n    });\n\n    describe('cast()', () => {\n\n        it('casts value to string', () => {\n\n            const schema = Joi.number().cast('string');\n            Helper.validate(schema, [\n                [0, true, '0'],\n                [0.01, true, '0.01'],\n                [-12, true, '-12']\n            ]);\n        });\n\n        it('ignores null', () => {\n\n            const schema = Joi.number().allow(null).cast('string');\n            Helper.validate(schema, [[null, true, null]]);\n        });\n\n        it('ignores string', () => {\n\n            const schema = Joi.number().allow('x').cast('string');\n            Helper.validate(schema, [['x', true, 'x']]);\n        });\n    });\n\n    describe('describe()', () => {\n\n        it('describes a minimum of 0', () => {\n\n            const schema = Joi.number().min(0);\n            expect(schema.describe()).to.equal({\n                type: 'number',\n                rules: [\n                    {\n                        name: 'min',\n                        args: { limit: 0 }\n                    }\n                ]\n            });\n        });\n    });\n\n    describe('greater()', () => {\n\n        it('throws when limit is undefined', () => {\n\n            expect(() => {\n\n                Joi.number().greater();\n            }).to.throw('limit must be a number or reference');\n        });\n\n        it('throws when limit is not a number', () => {\n\n            expect(() => {\n\n                Joi.number().greater('a');\n            }).to.throw('limit must be a number or reference');\n        });\n\n        it('accepts references as greater value', () => {\n\n            const ref = Joi.ref('a');\n            const schema = Joi.object({ a: Joi.number(), b: Joi.number().greater(ref) });\n\n            Helper.validate(schema, [\n                [{ a: 42, b: 1337 }, true],\n                [{ a: 1337, b: 42 }, false, {\n                    message: '\"b\" must be greater than ref:a',\n                    path: ['b'],\n                    type: 'number.greater',\n                    context: { limit: ref, value: 42, label: 'b', key: 'b' }\n                }],\n                [{ a: '1337', b: 42 }, false, {\n                    message: '\"b\" must be greater than ref:a',\n                    path: ['b'],\n                    type: 'number.greater',\n                    context: { limit: ref, value: 42, label: 'b', key: 'b' }\n                }],\n                [{ a: 2.4, b: 4.2 }, true],\n                [{ a: 4.2, b: 4.20000001 }, true],\n                [{ a: 4.20000001, b: 4.2 }, false, {\n                    message: '\"b\" must be greater than ref:a',\n                    path: ['b'],\n                    type: 'number.greater',\n                    context: { limit: ref, value: 4.2, label: 'b', key: 'b' }\n                }],\n                [{ a: 4.2, b: 2.4 }, false, {\n                    message: '\"b\" must be greater than ref:a',\n                    path: ['b'],\n                    type: 'number.greater',\n                    context: { limit: ref, value: 2.4, label: 'b', key: 'b' }\n                }]\n            ]);\n        });\n\n        it('accepts context references as greater value', () => {\n\n            const ref = Joi.ref('$a');\n            const schema = Joi.object({ b: Joi.number().greater(ref) });\n\n            Helper.validate(schema, { context: { a: 42 } }, [\n                [{ b: 1337 }, true]\n            ]);\n\n            Helper.validate(schema, { context: { a: 1337 } }, [\n                [{ b: 42 }, false, {\n                    message: '\"b\" must be greater than ref:global:a',\n                    path: ['b'],\n                    type: 'number.greater',\n                    context: { limit: ref, value: 42, label: 'b', key: 'b' }\n                }]\n            ]);\n\n            Helper.validate(schema, { context: { a: 2.4 } }, [\n                [{ b: 4.2 }, true]\n            ]);\n\n            Helper.validate(schema, { context: { a: 4.2 } }, [\n                [{ b: 4.20000001 }, true]\n            ]);\n\n            Helper.validate(schema, { context: { a: 4.20000001 } }, [\n                [{ b: 4.2 }, false, {\n                    message: '\"b\" must be greater than ref:global:a',\n                    path: ['b'],\n                    type: 'number.greater',\n                    context: { limit: ref, value: 4.2, label: 'b', key: 'b' }\n                }]\n            ]);\n\n            Helper.validate(schema, { context: { a: 4.2 } }, [\n                [{ b: 2.4 }, false, {\n                    message: '\"b\" must be greater than ref:global:a',\n                    path: ['b'],\n                    type: 'number.greater',\n                    context: { limit: ref, value: 2.4, label: 'b', key: 'b' }\n                }]\n            ]);\n        });\n\n        it('errors if reference is not a number', () => {\n\n            const ref = Joi.ref('a');\n            const schema = Joi.object({ a: Joi.string(), b: Joi.number().greater(ref) });\n\n            Helper.validate(schema, [\n                [{ a: 'abc', b: 42 }, false, {\n                    message: '\"b\" limit references \"ref:a\" which must be a number',\n                    path: ['b'],\n                    type: 'any.ref',\n                    context: { ref, label: 'b', key: 'b', value: 'abc', arg: 'limit', reason: 'must be a number' }\n                }]\n            ]);\n        });\n\n        it('errors if context reference is not a number', () => {\n\n            const ref = Joi.ref('$a');\n            const schema = Joi.object({ b: Joi.number().greater(ref) });\n\n            Helper.validate(schema, { context: { a: 'abc' } }, [\n                [{ b: 42 }, false, {\n                    message: '\"b\" limit references \"ref:global:a\" which must be a number',\n                    path: ['b'],\n                    type: 'any.ref',\n                    context: { ref, label: 'b', key: 'b', value: 'abc', arg: 'limit', reason: 'must be a number' }\n                }]\n            ]);\n        });\n    });\n\n    describe('less()', () => {\n\n        it('throws when limit is undefined', () => {\n\n            expect(() => {\n\n                Joi.number().less();\n            }).to.throw('limit must be a number or reference');\n        });\n\n        it('throws when limit is not a number', () => {\n\n            expect(() => {\n\n                Joi.number().less('a');\n            }).to.throw('limit must be a number or reference');\n        });\n\n        it('accepts references as less value', () => {\n\n            const ref = Joi.ref('a');\n            const schema = Joi.object({ a: Joi.number(), b: Joi.number().less(ref) });\n\n            Helper.validate(schema, [\n                [{ a: 1337, b: 42 }, true],\n                [{ a: 42, b: 1337 }, false, {\n                    message: '\"b\" must be less than ref:a',\n                    path: ['b'],\n                    type: 'number.less',\n                    context: { limit: ref, value: 1337, label: 'b', key: 'b' }\n                }],\n                [{ a: '42', b: 1337 }, false, {\n                    message: '\"b\" must be less than ref:a',\n                    path: ['b'],\n                    type: 'number.less',\n                    context: { limit: ref, value: 1337, label: 'b', key: 'b' }\n                }],\n                [{ a: 4.2, b: 2.4 }, true],\n                [{ a: 4.2, b: 4.20000001 }, false, {\n                    message: '\"b\" must be less than ref:a',\n                    path: ['b'],\n                    type: 'number.less',\n                    context: { limit: ref, value: 4.20000001, label: 'b', key: 'b' }\n                }],\n                [{ a: 4.20000001, b: 4.2 }, true],\n                [{ a: 2.4, b: 4.2 }, false, {\n                    message: '\"b\" must be less than ref:a',\n                    path: ['b'],\n                    type: 'number.less',\n                    context: { limit: ref, value: 4.2, label: 'b', key: 'b' }\n                }]\n            ]);\n        });\n\n        it('accepts context references as less value', () => {\n\n            const ref = Joi.ref('$a');\n            const schema = Joi.object({ b: Joi.number().less(ref) });\n\n            Helper.validate(schema, { context: { a: 1337 } }, [\n                [{ b: 42 }, true]\n            ]);\n\n            Helper.validate(schema, { context: { a: 42 } }, [\n                [{ b: 1337 }, false, {\n                    message: '\"b\" must be less than ref:global:a',\n                    path: ['b'],\n                    type: 'number.less',\n                    context: { limit: ref, value: 1337, label: 'b', key: 'b' }\n                }]\n            ]);\n\n            Helper.validate(schema, { context: { a: 4.2 } }, [\n                [{ b: 2.4 }, true],\n                [{ b: 4.20000001 }, false, {\n                    message: '\"b\" must be less than ref:global:a',\n                    path: ['b'],\n                    type: 'number.less',\n                    context: { limit: ref, value: 4.20000001, label: 'b', key: 'b' }\n                }]\n            ]);\n\n            Helper.validate(schema, { context: { a: 4.20000001 } }, [\n                [{ b: 4.2 }, true]\n            ]);\n\n            Helper.validate(schema, { context: { a: 2.4 } }, [\n                [{ b: 4.2 }, false, {\n                    message: '\"b\" must be less than ref:global:a',\n                    path: ['b'],\n                    type: 'number.less',\n                    context: { limit: ref, value: 4.2, label: 'b', key: 'b' }\n                }]\n            ]);\n        });\n\n        it('errors if reference is not a number', () => {\n\n            const ref = Joi.ref('a');\n            const schema = Joi.object({ a: Joi.string(), b: Joi.number().less(ref) });\n\n            Helper.validate(schema, [\n                [{ a: 'abc', b: 42 }, false, {\n                    message: '\"b\" limit references \"ref:a\" which must be a number',\n                    path: ['b'],\n                    type: 'any.ref',\n                    context: { ref, label: 'b', key: 'b', value: 'abc', arg: 'limit', reason: 'must be a number' }\n                }]\n            ]);\n        });\n\n        it('errors if reference is null', () => {\n\n            const ref = Joi.ref('a');\n            const schema = Joi.object({ a: Joi.any(), b: Joi.number().less(ref) });\n\n            Helper.validate(schema, [\n                [{ a: null, b: 42 }, false, {\n                    message: '\"b\" limit references \"ref:a\" which must be a number',\n                    path: ['b'],\n                    type: 'any.ref',\n                    context: { ref, label: 'b', key: 'b', value: null, arg: 'limit', reason: 'must be a number' }\n                }]\n            ]);\n        });\n\n        it('errors if context reference is not a number', () => {\n\n            const ref = Joi.ref('$a');\n            const schema = Joi.object({ a: Joi.string(), b: Joi.number().less(ref) });\n\n            Helper.validate(schema, { context: { a: 'abc' } }, [\n                [{ b: 42 }, false, {\n                    message: '\"b\" limit references \"ref:global:a\" which must be a number',\n                    path: ['b'],\n                    type: 'any.ref',\n                    context: { ref, label: 'b', key: 'b', value: 'abc', arg: 'limit', reason: 'must be a number' }\n                }]\n            ]);\n        });\n    });\n\n    describe('max()', () => {\n\n        it('throws when limit is undefined', () => {\n\n            expect(() => {\n\n                Joi.number().max();\n            }).to.throw('limit must be a number or reference');\n        });\n\n        it('throws when limit is not a number', () => {\n\n            expect(() => {\n\n                Joi.number().max('a');\n            }).to.throw('limit must be a number or reference');\n        });\n\n        it('accepts references as max value', () => {\n\n            const ref = Joi.ref('a');\n            const schema = Joi.object({ a: Joi.number(), b: Joi.number().max(ref) });\n\n            Helper.validate(schema, [\n                [{ a: 1337, b: 42 }, true],\n                [{ a: 42, b: 1337 }, false, {\n                    message: '\"b\" must be less than or equal to ref:a',\n                    path: ['b'],\n                    type: 'number.max',\n                    context: { limit: ref, value: 1337, label: 'b', key: 'b' }\n                }],\n                [{ a: '42', b: 1337 }, false, {\n                    message: '\"b\" must be less than or equal to ref:a',\n                    path: ['b'],\n                    type: 'number.max',\n                    context: { limit: ref, value: 1337, label: 'b', key: 'b' }\n                }],\n                [{ a: 4.2, b: 2.4 }, true],\n                [{ a: 4.2, b: 4.20000001 }, false, {\n                    message: '\"b\" must be less than or equal to ref:a',\n                    path: ['b'],\n                    type: 'number.max',\n                    context: { limit: ref, value: 4.20000001, label: 'b', key: 'b' }\n                }],\n                [{ a: 4.20000001, b: 4.2 }, true],\n                [{ a: 2.4, b: 4.2 }, false, {\n                    message: '\"b\" must be less than or equal to ref:a',\n                    path: ['b'],\n                    type: 'number.max',\n                    context: { limit: ref, value: 4.2, label: 'b', key: 'b' }\n                }]\n            ]);\n        });\n\n        it('accepts context references as max value', () => {\n\n            const ref = Joi.ref('$a');\n            const schema = Joi.object({ b: Joi.number().max(ref) });\n\n            Helper.validate(schema, { context: { a: 1337 } }, [\n                [{ b: 42 }, true]\n            ]);\n\n            Helper.validate(schema, { context: { a: 42 } }, [\n                [{ b: 1337 }, false, {\n                    message: '\"b\" must be less than or equal to ref:global:a',\n                    path: ['b'],\n                    type: 'number.max',\n                    context: { limit: ref, value: 1337, label: 'b', key: 'b' }\n                }]\n            ]);\n\n            Helper.validate(schema, { context: { a: 4.2 } }, [\n                [{ b: 2.4 }, true],\n                [{ b: 4.20000001 }, false, {\n                    message: '\"b\" must be less than or equal to ref:global:a',\n                    path: ['b'],\n                    type: 'number.max',\n                    context: { limit: ref, value: 4.20000001, label: 'b', key: 'b' }\n                }]\n            ]);\n\n            Helper.validate(schema, { context: { a: 4.20000001 } }, [\n                [{ b: 4.2 }, true]\n            ]);\n\n            Helper.validate(schema, { context: { a: 2.4 } }, [\n                [{ b: 4.2 }, false, {\n                    message: '\"b\" must be less than or equal to ref:global:a',\n                    path: ['b'],\n                    type: 'number.max',\n                    context: { limit: ref, value: 4.2, label: 'b', key: 'b' }\n                }]\n            ]);\n        });\n\n        it('errors if reference is not a number', () => {\n\n            const ref = Joi.ref('a');\n            const schema = Joi.object({ a: Joi.string(), b: Joi.number().max(ref) });\n\n            Helper.validate(schema, [\n                [{ a: 'abc', b: 42 }, false, {\n                    message: '\"b\" limit references \"ref:a\" which must be a number',\n                    path: ['b'],\n                    type: 'any.ref',\n                    context: { ref, label: 'b', key: 'b', value: 'abc', arg: 'limit', reason: 'must be a number' }\n                }]\n            ]);\n        });\n\n        it('errors if context reference is not a number', () => {\n\n            const ref = Joi.ref('$a');\n            const schema = Joi.object({ b: Joi.number().max(ref) });\n\n            Helper.validate(schema, { context: { a: 'abc' } }, [\n                [{ b: 42 }, false, {\n                    message: '\"b\" limit references \"ref:global:a\" which must be a number',\n                    path: ['b'],\n                    type: 'any.ref',\n                    context: { ref, label: 'b', key: 'b', value: 'abc', arg: 'limit', reason: 'must be a number' }\n                }]\n            ]);\n        });\n    });\n\n    describe('min()', () => {\n\n        it('throws when limit is undefined', () => {\n\n            expect(() => Joi.number().min()).to.throw('limit must be a number or reference');\n        });\n\n        it('throws when limit is not a number', () => {\n\n            expect(() => Joi.number().min('a')).to.throw('limit must be a number or reference');\n        });\n\n        it('throws when limit is null', () => {\n\n            expect(() => Joi.number().min(null)).to.throw('limit must be a number or reference');\n        });\n\n        it('supports 64bit numbers', () => {\n\n            const schema = Joi.number().min(1394035612500);\n            const input = 1394035612552;\n\n            Helper.validate(schema, [[input, true, input]]);\n        });\n\n        it('accepts references as min value', () => {\n\n            const ref = Joi.ref('a');\n            const schema = Joi.object({ a: Joi.number(), b: Joi.number().min(ref) });\n\n            Helper.validate(schema, [\n                [{ a: 42, b: 1337 }, true],\n                [{ a: 1337, b: 42 }, false, {\n                    message: '\"b\" must be greater than or equal to ref:a',\n                    path: ['b'],\n                    type: 'number.min',\n                    context: { limit: ref, value: 42, label: 'b', key: 'b' }\n                }],\n                [{ a: '1337', b: 42 }, false, {\n                    message: '\"b\" must be greater than or equal to ref:a',\n                    path: ['b'],\n                    type: 'number.min',\n                    context: { limit: ref, value: 42, label: 'b', key: 'b' }\n                }],\n                [{ a: 2.4, b: 4.2 }, true],\n                [{ a: 4.2, b: 4.20000001 }, true],\n                [{ a: 4.20000001, b: 4.2 }, false, {\n                    message: '\"b\" must be greater than or equal to ref:a',\n                    path: ['b'],\n                    type: 'number.min',\n                    context: { limit: ref, value: 4.2, label: 'b', key: 'b' }\n                }],\n                [{ a: 4.2, b: 2.4 }, false, {\n                    message: '\"b\" must be greater than or equal to ref:a',\n                    path: ['b'],\n                    type: 'number.min',\n                    context: { limit: ref, value: 2.4, label: 'b', key: 'b' }\n                }]\n            ]);\n        });\n\n        it('accepts references as min value within a when', () => {\n\n            const schema = Joi.object({\n                a: Joi.number().required(),\n                b: Joi.number().required(),\n                c: Joi.number().required().when('a', {\n                    is: Joi.number().min(Joi.ref('b')), // a >= b\n                    then: Joi.number().valid(0)\n                })\n            });\n\n            Helper.validate(schema, [\n                [{ a: 0, b: 1, c: 42 }, true],\n                [{ a: 1, b: 1, c: 0 }, true],\n                [{ a: 2, b: 1, c: 0 }, true],\n                [{ a: 1, b: 1, c: 42 }, false, {\n                    message: '\"c\" must be [0]',\n                    path: ['c'],\n                    type: 'any.only',\n                    context: { value: 42, valids: [0], label: 'c', key: 'c' }\n                }],\n                [{ a: 2, b: 1, c: 42 }, false, {\n                    message: '\"c\" must be [0]',\n                    path: ['c'],\n                    type: 'any.only',\n                    context: { value: 42, valids: [0], label: 'c', key: 'c' }\n                }]\n            ]);\n        });\n\n        it('accepts context references as min value', () => {\n\n            const ref = Joi.ref('$a');\n            const schema = Joi.object({ b: Joi.number().min(ref) });\n\n            Helper.validate(schema, { context: { a: 42 } }, [\n                [{ b: 1337 }, true]\n            ]);\n\n            Helper.validate(schema, { context: { a: 1337 } }, [\n                [{ b: 42 }, false, {\n                    message: '\"b\" must be greater than or equal to ref:global:a',\n                    path: ['b'],\n                    type: 'number.min',\n                    context: { limit: ref, value: 42, label: 'b', key: 'b' }\n                }]\n            ]);\n\n            Helper.validate(schema, { context: { a: 2.4 } }, [\n                [{ b: 4.2 }, true]\n            ]);\n\n            Helper.validate(schema, { context: { a: 4.2 } }, [\n                [{ b: 4.20000001 }, true]\n            ]);\n\n            Helper.validate(schema, { context: { a: 4.20000001 } }, [\n                [{ b: 4.2 }, false, {\n                    message: '\"b\" must be greater than or equal to ref:global:a',\n                    path: ['b'],\n                    type: 'number.min',\n                    context: { limit: ref, value: 4.2, label: 'b', key: 'b' }\n                }]\n            ]);\n\n            Helper.validate(schema, { context: { a: 4.2 } }, [\n                [{ b: 2.4 }, false, {\n                    message: '\"b\" must be greater than or equal to ref:global:a',\n                    path: ['b'],\n                    type: 'number.min',\n                    context: { limit: ref, value: 2.4, label: 'b', key: 'b' }\n                }]\n            ]);\n        });\n\n        it('errors if reference is not a number', () => {\n\n            const ref = Joi.ref('a');\n            const schema = Joi.object({ a: Joi.string(), b: Joi.number().min(ref) });\n\n            Helper.validate(schema, [\n                [{ a: 'abc', b: 42 }, false, {\n                    message: '\"b\" limit references \"ref:a\" which must be a number',\n                    path: ['b'],\n                    type: 'any.ref',\n                    context: { ref, label: 'b', key: 'b', value: 'abc', arg: 'limit', reason: 'must be a number' }\n                }]\n            ]);\n        });\n\n        it('errors if context reference is not a number', () => {\n\n            const ref = Joi.ref('$a');\n            const schema = Joi.object({ b: Joi.number().min(ref) });\n\n            Helper.validate(schema, { context: { a: 'abc' } }, [\n                [{ b: 42 }, false, {\n                    message: '\"b\" limit references \"ref:global:a\" which must be a number',\n                    path: ['b'],\n                    type: 'any.ref',\n                    context: { ref, label: 'b', key: 'b', value: 'abc', arg: 'limit', reason: 'must be a number' }\n                }]\n            ]);\n        });\n    });\n\n    describe('multiple()', () => {\n\n        it('throws when base is undefined', () => {\n\n            expect(() => {\n\n                Joi.number().multiple();\n            }).to.throw('base must be a positive number or reference');\n        });\n\n        it('throws when multiple is not a number', () => {\n\n            expect(() => {\n\n                Joi.number().multiple('a');\n            }).to.throw('base must be a positive number or reference');\n        });\n\n        it('throws when multiple is 0', () => {\n\n            expect(() => {\n\n                Joi.number().multiple(0);\n            }).to.throw('base must be a positive number or reference');\n        });\n\n        it('handles integer multiples correctly', () => {\n\n            const rule = Joi.number().multiple(3);\n            Helper.validate(rule, [\n                [0, true], // 0 is a multiple of every integer\n                [3, true],\n                [4, false, {\n                    message: '\"value\" must be a multiple of 3',\n                    path: [],\n                    type: 'number.multiple',\n                    context: { multiple: 3, value: 4, label: 'value' }\n                }],\n                [9, true],\n                ['a', false, {\n                    message: '\"value\" must be a number',\n                    path: [],\n                    type: 'number.base',\n                    context: { label: 'value', value: 'a' }\n                }],\n                [9.1, false, {\n                    message: '\"value\" must be a multiple of 3',\n                    path: [],\n                    type: 'number.multiple',\n                    context: { multiple: 3, value: 9.1, label: 'value' }\n                }],\n                [8.9, false, {\n                    message: '\"value\" must be a multiple of 3',\n                    path: [],\n                    type: 'number.multiple',\n                    context: { multiple: 3, value: 8.9, label: 'value' }\n                }]\n            ]);\n        });\n\n        it('handles precision errors correctly', () => {\n\n            const cases = [\n                [3600000, [\n                    [14400000, true]\n                ]],\n                [0.01, [\n                    [2.03, true],\n                    [2.029999999999, false, {\n                        message: '\"value\" must be a multiple of 0.01',\n                        path: [],\n                        type: 'number.multiple',\n                        context: { multiple: 0.01, value: 2.029999999999, label: 'value' }\n                    }],\n                    [2.030000000001, false, {\n                        message: '\"value\" must be a multiple of 0.01',\n                        path: [],\n                        type: 'number.multiple',\n                        context: { multiple: 0.01, value: 2.030000000001, label: 'value' }\n                    }],\n                    [0.03, true]\n                ]],\n                [0.0000000001, [\n                    [0.2, true]\n                ]],\n                [0.000000101, [\n                    [0.101, true],\n                    [0.10101, false, {\n                        message: '\"value\" must be a multiple of 1.01e-7',\n                        path: [],\n                        type: 'number.multiple',\n                        context: { multiple: 0.000000101, value: 0.10101, label: 'value' }\n                    }]\n                ]]\n            ];\n\n            for (const [multiple, tests] of cases) {\n                const schema = Joi.number().multiple(multiple);\n                Helper.validate(schema, tests);\n            }\n        });\n\n        it('handles floats multiples correctly', () => {\n\n            const schema = Joi.number().multiple(3.5);\n            Helper.validate(schema, [\n                [0, true],  // 0 is a multiple of every number\n                [3.5, true],\n                [3.6, false, {\n                    message: '\"value\" must be a multiple of 3.5',\n                    path: [],\n                    type: 'number.multiple',\n                    context: { multiple: 3.5, value: 3.6, label: 'value' }\n                }],\n                [10.5, true],\n                ['a', false, {\n                    message: '\"value\" must be a number',\n                    path: [],\n                    type: 'number.base',\n                    context: { label: 'value', value: 'a' }\n                }],\n                [10.501, false, {\n                    message: '\"value\" must be a multiple of 3.5',\n                    path: [],\n                    type: 'number.multiple',\n                    context: { multiple: 3.5, value: 10.501, label: 'value' }\n                }],\n                [10.499, false, {\n                    message: '\"value\" must be a multiple of 3.5',\n                    path: [],\n                    type: 'number.multiple',\n                    context: { multiple: 3.5, value: 10.499, label: 'value' }\n                }]\n            ]);\n        });\n\n        it('handles 0.1 multiples', () => {\n\n            const schema = Joi.number().multiple(0.1);\n            Helper.validate(schema, [\n                [0, true],  // 0 is a multiple of every number\n                [3.5, true],\n                [100.1, true],\n                [3.61, false, '\"value\" must be a multiple of 0.1']\n            ]);\n        });\n\n        it('handles references correctly', () => {\n\n            const ref = Joi.ref('a');\n            const schema = Joi.object({ a: Joi.number(), b: Joi.number().multiple(ref) });\n            Helper.validate(schema, [\n                [{ a: 2, b: 32 }, true],\n                [{ a: 43, b: 0 }, true],\n                [{ a: 4, b: 25 }, false, {\n                    message: '\"b\" must be a multiple of ref:a',\n                    path: ['b'],\n                    type: 'number.multiple',\n                    context: { multiple: ref, value: 25, label: 'b', key: 'b' }\n                }],\n                [{ a: 0, b: 0 }, false, {\n                    message: '\"b\" base references \"ref:a\" which must be a positive number',\n                    path: ['b'],\n                    type: 'any.ref',\n                    context: { ref, key: 'b', label: 'b', value: 0, arg: 'base', reason: 'must be a positive number' }\n                }]\n            ]);\n        });\n\n        it('handles references correctly within a when', () => {\n\n            const schema = Joi.object({\n                a: Joi.number().required(),\n                b: Joi.number().required(),\n                c: Joi.number().required().when('a', {\n                    is: Joi.number().multiple(Joi.ref('b')), // a % b === 0\n                    then: Joi.number().valid(0)\n                })\n            });\n\n            Helper.validate(schema, [\n                [{ a: 2, b: 3, c: 42 }, true],\n                [{ a: 2, b: 4, c: 42 }, true],\n                [{ a: 4, b: 2, c: 0 }, true],\n                [{ a: 4, b: 2, c: 42 }, false, {\n                    message: '\"c\" must be [0]',\n                    path: ['c'],\n                    type: 'any.only',\n                    context: { value: 42, valids: [0], label: 'c', key: 'c' }\n                }]\n            ]);\n        });\n\n        it('handles non-number references correctly', () => {\n\n            const ref = Joi.ref('a');\n            const schema = Joi.object({ a: Joi.string(), b: Joi.number().multiple(ref) });\n            Helper.validate(schema, [\n                [{ a: 'test', b: 32 }, false, {\n                    message: '\"b\" base references \"ref:a\" which must be a positive number',\n                    path: ['b'],\n                    type: 'any.ref',\n                    context: { ref, label: 'b', key: 'b', value: 'test', arg: 'base', reason: 'must be a positive number' }\n                }],\n                [{ a: 'test', b: 0 }, false, {\n                    message: '\"b\" base references \"ref:a\" which must be a positive number',\n                    path: ['b'],\n                    type: 'any.ref',\n                    context: { ref, label: 'b', key: 'b', value: 'test', arg: 'base', reason: 'must be a positive number' }\n                }],\n                [{ a: 'test', b: NaN }, false, {\n                    message: '\"b\" must be a number',\n                    path: ['b'],\n                    type: 'number.base',\n                    context: { label: 'b', key: 'b', value: NaN }\n                }]\n            ]);\n        });\n\n        it('handles context references correctly', () => {\n\n            const ref = Joi.ref('$a');\n            const schema = Joi.object({ b: Joi.number().multiple(ref) });\n\n            Helper.validate(schema, { context: { a: 2 } }, [\n                [{ b: 32 }, true]\n            ]);\n\n            Helper.validate(schema, { context: { a: 43 } }, [\n                [{ b: 0 }, true]\n            ]);\n\n            Helper.validate(schema, { context: { a: 4 } }, [\n                [{ b: 25 }, false, {\n                    message: '\"b\" must be a multiple of ref:global:a',\n                    path: ['b'],\n                    type: 'number.multiple',\n                    context: { multiple: ref, value: 25, label: 'b', key: 'b' }\n                }]\n            ]);\n\n            Helper.validate(schema, { context: { a: 0 } }, [\n                [{ b: 31 }, false, {\n                    message: '\"b\" base references \"ref:global:a\" which must be a positive number',\n                    path: ['b'],\n                    type: 'any.ref',\n                    context: { ref, key: 'b', label: 'b', value: 0, arg: 'base', reason: 'must be a positive number' }\n                }],\n                [{ b: 0 }, false, {\n                    message: '\"b\" base references \"ref:global:a\" which must be a positive number',\n                    path: ['b'],\n                    type: 'any.ref',\n                    context: { ref, key: 'b', label: 'b', value: 0, arg: 'base', reason: 'must be a positive number' }\n                }]\n            ]);\n\n            Helper.validate(schema, { context: { a: 'test' } }, [\n                [{ b: 32 }, false, {\n                    message: '\"b\" base references \"ref:global:a\" which must be a positive number',\n                    path: ['b'],\n                    type: 'any.ref',\n                    context: { ref, label: 'b', key: 'b', value: 'test', arg: 'base', reason: 'must be a positive number' }\n                }],\n                [{ b: 0 }, false, {\n                    message: '\"b\" base references \"ref:global:a\" which must be a positive number',\n                    path: ['b'],\n                    type: 'any.ref',\n                    context: { ref, label: 'b', key: 'b', value: 'test', arg: 'base', reason: 'must be a positive number' }\n                }]\n            ]);\n\n            Helper.validate(schema, { context: { a: NaN } }, [\n                [{ b: 0 }, false, {\n                    message: '\"b\" base references \"ref:global:a\" which must be a positive number',\n                    path: ['b'],\n                    type: 'any.ref',\n                    context: { ref, label: 'b', key: 'b', value: NaN, arg: 'base', reason: 'must be a positive number' }\n                }]\n            ]);\n        });\n    });\n\n    describe('port()', () => {\n\n        it('validates correctly', () => {\n\n            const schema = Joi.object({ port: Joi.number().port() });\n\n            Helper.validate(schema, [\n                [{ port: 1337 }, true],\n                [{ port: -1 }, false, {\n                    message: '\"port\" must be a valid port',\n                    path: ['port'],\n                    type: 'number.port',\n                    context: { value: -1, label: 'port', key: 'port' }\n                }],\n                [{ port: 65536 }, false, {\n                    message: '\"port\" must be a valid port',\n                    path: ['port'],\n                    type: 'number.port',\n                    context: { value: 65536, label: 'port', key: 'port' }\n                }],\n                [{ port: 8.88 }, false, {\n                    message: '\"port\" must be a valid port',\n                    path: ['port'],\n                    type: 'number.port',\n                    context: { value: 8.88, label: 'port', key: 'port' }\n                }]\n            ]);\n        });\n    });\n\n    describe('precision()', () => {\n\n        it('converts numbers', () => {\n\n            const schema = Joi.number().precision(4);\n            Helper.validate(schema, [\n                [1.5, true, 1.5],\n                [0.12345, true, 0.1235],\n                [123456, true, 123456],\n                [123456.123456, true, 123456.1235],\n                ['123456.123456', true, 123456.1235],\n                ['abc', false, {\n                    message: '\"value\" must be a number',\n                    path: [],\n                    type: 'number.base',\n                    context: { label: 'value', value: 'abc' }\n                }],\n                [NaN, false, {\n                    message: '\"value\" must be a number',\n                    path: [],\n                    type: 'number.base',\n                    context: { label: 'value', value: NaN }\n                }]\n            ]);\n        });\n\n        it('validates with min()', () => {\n\n            const schema = Joi.number()\n                .min(0)\n                .precision(2);\n\n            Helper.validate(schema, [\n                [-0.1, false, '\"value\" must be greater than or equal to 0'],\n                [-0.01, false, '\"value\" must be greater than or equal to 0'],\n                [-0.001, true, 0],\n                [-0.123, false, '\"value\" must be greater than or equal to 0'],\n                [-0.0456, false, '\"value\" must be greater than or equal to 0']\n            ]);\n        });\n    });\n\n    describe('safe', () => {\n\n        it('accepts safe numbers', () => {\n\n            const t = Joi.number();\n            Helper.validate(t, [\n                [Number.MAX_SAFE_INTEGER, true, Number.MAX_SAFE_INTEGER],\n                [Number.MIN_SAFE_INTEGER, true, Number.MIN_SAFE_INTEGER]\n            ]);\n        });\n    });\n\n    describe('validate()', () => {\n\n        it('allows undefined', () => {\n\n            Helper.validate(Joi.number(), [\n                [undefined, true]\n            ]);\n        });\n\n        it('denies undefined when .required()', () => {\n\n            Helper.validate(Joi.number().required(), [\n                [undefined, false, {\n                    message: '\"value\" is required',\n                    path: [],\n                    type: 'any.required',\n                    context: { label: 'value' }\n                }]\n            ]);\n        });\n\n        it('compares valid matching post-coerce value', () => {\n\n            const schema = Joi.number().valid(1, 2, 3);\n            Helper.validate(schema, [['1', true, 1]]);\n        });\n\n        it('ignores invalid matching of pre-coerce value', () => {\n\n            const schema = Joi.number().invalid('1');\n            Helper.validate(schema, [['1', true, 1]]);\n        });\n\n        it('returns false for denied value', () => {\n\n            const text = Joi.number().invalid(50);\n            Helper.validate(text, [\n                [50, false, {\n                    message: '\"value\" contains an invalid value',\n                    path: [],\n                    type: 'any.invalid',\n                    context: { value: 50, invalids: [50], label: 'value' }\n                }]\n            ]);\n        });\n\n        it('validates integer', () => {\n\n            const t = Joi.number().integer();\n            Helper.validate(t, [\n                [100, true],\n                [0, true],\n                ['+42', true, 42],\n                [null, false, {\n                    message: '\"value\" must be a number',\n                    path: [],\n                    type: 'number.base',\n                    context: { label: 'value', value: null }\n                }],\n                [1.02, false, {\n                    message: '\"value\" must be an integer',\n                    path: [],\n                    type: 'number.integer',\n                    context: { value: 1.02, label: 'value' }\n                }],\n                [0.01, false, {\n                    message: '\"value\" must be an integer',\n                    path: [],\n                    type: 'number.integer',\n                    context: { value: 0.01, label: 'value' }\n                }]\n            ]);\n        });\n\n        it('returns false for Infinity', () => {\n\n            const t = Joi.number();\n            Helper.validate(t, [\n                [Infinity, false, {\n                    message: '\"value\" cannot be infinity',\n                    path: [],\n                    type: 'number.infinity',\n                    context: { value: Infinity, label: 'value' }\n                }],\n                [-Infinity, false, {\n                    message: '\"value\" cannot be infinity',\n                    path: [],\n                    type: 'number.infinity',\n                    context: { value: -Infinity, label: 'value' }\n                }]\n            ]);\n        });\n\n        it('returns true for allowed Infinity', () => {\n\n            const t = Joi.number().allow(Infinity, -Infinity);\n            Helper.validate(t, [\n                [Infinity, true],\n                [-Infinity, true]\n            ]);\n        });\n\n        it('returns true for decimal numbers', () => {\n\n            const t = Joi.number();\n            Helper.validate(t, [\n                [0.00000001, true, 0.00000001]\n            ]);\n        });\n\n        it('can accept string numbers', () => {\n\n            const t = Joi.number();\n            Helper.validate(t, [\n                ['1', true, 1],\n                ['100', true, 100],\n                ['+100', true, 100],\n                ['+00100', true, 100],\n                ['1e3', true, 1000],\n                ['1E3', true, 1000],\n                ['1e003', true, 1000],\n                ['1e-003', true, 0.001],\n                ['-1e+3', true, -1000],\n                ['+1e-3', true, 0.001],\n                ['1.0000', true, 1],\n                ['1.10000', true, 1.1],\n                ['000000', true, 0],\n                ['2.', true, 2],\n                ['1.1e4', true, 11000],\n                ['1.100e4', true, 11000],\n                ['100e3', true, 100000],\n                ['-00100e3', true, -100000],\n                ['-00100e-003', true, -0.1],\n                ['-001231.0133210e003', true, -1231013.321],\n                ['+001231.0133210e003', true, 1231013.321],\n                ['1.9642346977926364E-5', true, 0.000019642346977926364],\n                ['9.4e-1', true, 0.94],\n                ['0.00000095', true, 0.00000095],\n                ['.5', true, 0.5],\n                ['1 some text', false, {\n                    message: '\"value\" must be a number',\n                    path: [],\n                    type: 'number.base',\n                    context: { label: 'value', value: '1 some text' }\n                }],\n                ['\\t\\r', false, {\n                    message: '\"value\" must be a number',\n                    path: [],\n                    type: 'number.base',\n                    context: { label: 'value', value: '\\t\\r' }\n                }],\n                [' ', false, {\n                    message: '\"value\" must be a number',\n                    path: [],\n                    type: 'number.base',\n                    context: { label: 'value', value: ' ' }\n                }],\n                [' 2', true, 2],\n                ['\\t\\r43', true, 43],\n                ['43 ', true, 43],\n                ['', false, {\n                    message: '\"value\" must be a number',\n                    path: [],\n                    type: 'number.base',\n                    context: { label: 'value', value: '' }\n                }]\n            ]);\n        });\n\n        it('required validates correctly', () => {\n\n            const t = Joi.number().required();\n            Helper.validate(t, [\n                [NaN, false, {\n                    message: '\"value\" must be a number',\n                    path: [],\n                    type: 'number.base',\n                    context: { label: 'value', value: NaN }\n                }],\n                ['100', true, 100]\n            ]);\n        });\n\n        it('converts an object string to a number', () => {\n\n            const config = { a: Joi.number() };\n            const obj = { a: '123' };\n            Helper.validate(Joi.compile(config), [\n                [obj, true, { a: 123 }]\n            ]);\n        });\n\n        it('converts a string to a number', () => {\n\n            Helper.validate(Joi.number(), [['1', true, 1]]);\n        });\n\n        it('errors on null', () => {\n\n            Helper.validate(Joi.number(), [\n                [null, false, {\n                    message: '\"value\" must be a number',\n                    path: [],\n                    type: 'number.base',\n                    context: { label: 'value', value: null }\n                }]\n            ]);\n        });\n\n        it('handles combination of min and max', () => {\n\n            const rule = Joi.number().min(8).max(10);\n            Helper.validate(rule, [\n                [1, false, {\n                    message: '\"value\" must be greater than or equal to 8',\n                    path: [],\n                    type: 'number.min',\n                    context: { limit: 8, value: 1, label: 'value' }\n                }],\n                [11, false, {\n                    message: '\"value\" must be less than or equal to 10',\n                    path: [],\n                    type: 'number.max',\n                    context: { limit: 10, value: 11, label: 'value' }\n                }],\n                [8, true],\n                [9, true],\n                [null, false, {\n                    message: '\"value\" must be a number',\n                    path: [],\n                    type: 'number.base',\n                    context: { label: 'value', value: null }\n                }]\n            ]);\n        });\n\n        it('handles combination of min, max, and null allowed', () => {\n\n            const rule = Joi.number().min(8).max(10).allow(null);\n            Helper.validate(rule, [\n                [1, false, {\n                    message: '\"value\" must be greater than or equal to 8',\n                    path: [],\n                    type: 'number.min',\n                    context: { limit: 8, value: 1, label: 'value' }\n                }],\n                [11, false, {\n                    message: '\"value\" must be less than or equal to 10',\n                    path: [],\n                    type: 'number.max',\n                    context: { limit: 10, value: 11, label: 'value' }\n                }],\n                [8, true],\n                [9, true],\n                [null, true]\n            ]);\n        });\n\n        it('handles combination of min and positive', () => {\n\n            const rule = Joi.number().min(-3).positive();\n            Helper.validate(rule, [\n                [1, true],\n                [-2, false, {\n                    message: '\"value\" must be a positive number',\n                    path: [],\n                    type: 'number.positive',\n                    context: { value: -2, label: 'value' }\n                }],\n                [8, true],\n                [null, false, {\n                    message: '\"value\" must be a number',\n                    path: [],\n                    type: 'number.base',\n                    context: { label: 'value', value: null }\n                }]\n            ]);\n        });\n\n        it('handles combination of max and positive', () => {\n\n            const rule = Joi.number().max(5).positive();\n            Helper.validate(rule, [\n                [4, true],\n                [-2, false, {\n                    message: '\"value\" must be a positive number',\n                    path: [],\n                    type: 'number.positive',\n                    context: { value: -2, label: 'value' }\n                }],\n                [8, false, {\n                    message: '\"value\" must be less than or equal to 5',\n                    path: [],\n                    type: 'number.max',\n                    context: { limit: 5, value: 8, label: 'value' }\n                }],\n                [null, false, {\n                    message: '\"value\" must be a number',\n                    path: [],\n                    type: 'number.base',\n                    context: { label: 'value', value: null }\n                }]\n            ]);\n        });\n\n        it('handles combination of min and negative', () => {\n\n            const rule = Joi.number().min(-3).negative();\n            Helper.validate(rule, [\n                [4, false, {\n                    message: '\"value\" must be a negative number',\n                    path: [],\n                    type: 'number.negative',\n                    context: { value: 4, label: 'value' }\n                }],\n                [-2, true],\n                [-4, false, {\n                    message: '\"value\" must be greater than or equal to -3',\n                    path: [],\n                    type: 'number.min',\n                    context: { limit: -3, value: -4, label: 'value' }\n                }],\n                [null, false, {\n                    message: '\"value\" must be a number',\n                    path: [],\n                    type: 'number.base',\n                    context: { label: 'value', value: null }\n                }]\n            ]);\n        });\n\n        it('handles combination of negative and allow', () => {\n\n            const rule = Joi.number().negative().allow(1);\n            Helper.validate(rule, [\n                [1, true],\n                [-10, true],\n                [8, false, {\n                    message: '\"value\" must be a negative number',\n                    path: [],\n                    type: 'number.negative',\n                    context: { value: 8, label: 'value' }\n                }],\n                [0, false, {\n                    message: '\"value\" must be a negative number',\n                    path: [],\n                    type: 'number.negative',\n                    context: { value: 0, label: 'value' }\n                }],\n                [null, false, {\n                    message: '\"value\" must be a number',\n                    path: [],\n                    type: 'number.base',\n                    context: { label: 'value', value: null }\n                }]\n            ]);\n        });\n\n        it('handles combination of positive and allow', () => {\n\n            const rule = Joi.number().positive().allow(-1);\n            Helper.validate(rule, [\n                [1, true],\n                [-1, true],\n                [8, true],\n                [-10, false, {\n                    message: '\"value\" must be a positive number',\n                    path: [],\n                    type: 'number.positive',\n                    context: { value: -10, label: 'value' }\n                }],\n                [null, false, {\n                    message: '\"value\" must be a number',\n                    path: [],\n                    type: 'number.base',\n                    context: { label: 'value', value: null }\n                }]\n            ]);\n        });\n\n        it('handles combination of positive, allow, and null allowed', () => {\n\n            const rule = Joi.number().positive().allow(-1).allow(null);\n            Helper.validate(rule, [\n                [1, true],\n                [-1, true],\n                [8, true],\n                [-10, false, {\n                    message: '\"value\" must be a positive number',\n                    path: [],\n                    type: 'number.positive',\n                    context: { value: -10, label: 'value' }\n                }],\n                [null, true]\n            ]);\n        });\n\n        it('handles combination of negative, allow, and null allowed', () => {\n\n            const rule = Joi.number().negative().allow(1).allow(null);\n            Helper.validate(rule, [\n                [1, true],\n                [-10, true],\n                [8, false, {\n                    message: '\"value\" must be a negative number',\n                    path: [],\n                    type: 'number.negative',\n                    context: { value: 8, label: 'value' }\n                }],\n                [0, false, {\n                    message: '\"value\" must be a negative number',\n                    path: [],\n                    type: 'number.negative',\n                    context: { value: 0, label: 'value' }\n                }],\n                [null, true]\n            ]);\n        });\n\n        it('handles combination of positive, allow, null allowed, and invalid', () => {\n\n            const rule = Joi.number().positive().allow(-1).allow(null).invalid(1);\n            Helper.validate(rule, [\n                [1, false, {\n                    message: '\"value\" contains an invalid value',\n                    path: [],\n                    type: 'any.invalid',\n                    context: { value: 1, invalids: [1], label: 'value' }\n                }],\n                [-1, true],\n                [8, true],\n                [-10, false, {\n                    message: '\"value\" must be a positive number',\n                    path: [],\n                    type: 'number.positive',\n                    context: { value: -10, label: 'value' }\n                }],\n                [null, true]\n            ]);\n        });\n\n        it('handles combination of negative, allow, null allowed, and invalid', () => {\n\n            const rule = Joi.number().negative().allow(1).allow(null).invalid(-5);\n            Helper.validate(rule, [\n                [1, true],\n                [-10, true],\n                [-5, false, {\n                    message: '\"value\" contains an invalid value',\n                    path: [],\n                    type: 'any.invalid',\n                    context: { value: -5, invalids: [-5], label: 'value' }\n                }],\n                [8, false, {\n                    message: '\"value\" must be a negative number',\n                    path: [],\n                    type: 'number.negative',\n                    context: { value: 8, label: 'value' }\n                }],\n                [0, false, {\n                    message: '\"value\" must be a negative number',\n                    path: [],\n                    type: 'number.negative',\n                    context: { value: 0, label: 'value' }\n                }],\n                [null, true]\n            ]);\n        });\n\n        it('handles combination of min, max, and allow', () => {\n\n            const rule = Joi.number().min(8).max(10).allow(1);\n            Helper.validate(rule, [\n                [1, true],\n                [11, false, {\n                    message: '\"value\" must be less than or equal to 10',\n                    path: [],\n                    type: 'number.max',\n                    context: { limit: 10, value: 11, label: 'value' }\n                }],\n                [8, true],\n                [9, true],\n                [null, false, {\n                    message: '\"value\" must be a number',\n                    path: [],\n                    type: 'number.base',\n                    context: { label: 'value', value: null }\n                }]\n            ]);\n        });\n\n        it('handles combination of min, max, allow, and null allowed', () => {\n\n            const rule = Joi.number().min(8).max(10).allow(1).allow(null);\n            Helper.validate(rule, [\n                [1, true],\n                [11, false, {\n                    message: '\"value\" must be less than or equal to 10',\n                    path: [],\n                    type: 'number.max',\n                    context: { limit: 10, value: 11, label: 'value' }\n                }],\n                [8, true],\n                [9, true],\n                [null, true]\n            ]);\n        });\n\n        it('handles combination of min, max, allow, and invalid', () => {\n\n            const rule = Joi.number().min(8).max(10).allow(1).invalid(9);\n            Helper.validate(rule, [\n                [1, true],\n                [11, false, {\n                    message: '\"value\" must be less than or equal to 10',\n                    path: [],\n                    type: 'number.max',\n                    context: { limit: 10, value: 11, label: 'value' }\n                }],\n                [8, true],\n                [9, false, {\n                    message: '\"value\" contains an invalid value',\n                    path: [],\n                    type: 'any.invalid',\n                    context: { value: 9, invalids: [9], label: 'value' }\n                }],\n                [null, false, {\n                    message: '\"value\" must be a number',\n                    path: [],\n                    type: 'number.base',\n                    context: { label: 'value', value: null }\n                }]\n            ]);\n        });\n\n        it('handles combination of min, max, allow, invalid, and null allowed', () => {\n\n            const rule = Joi.number().min(8).max(10).allow(1).invalid(9).allow(null);\n            Helper.validate(rule, [\n                [1, true],\n                [11, false, {\n                    message: '\"value\" must be less than or equal to 10',\n                    path: [],\n                    type: 'number.max',\n                    context: { limit: 10, value: 11, label: 'value' }\n                }],\n                [8, true],\n                [9, false, {\n                    message: '\"value\" contains an invalid value',\n                    path: [],\n                    type: 'any.invalid',\n                    context: { value: 9, invalids: [9], label: 'value' }\n                }],\n                [null, true]\n            ]);\n        });\n\n        it('handles combination of min, max, and integer', () => {\n\n            const rule = Joi.number().min(8).max(10).integer();\n            Helper.validate(rule, [\n                [1, false, {\n                    message: '\"value\" must be greater than or equal to 8',\n                    path: [],\n                    type: 'number.min',\n                    context: { limit: 8, value: 1, label: 'value' }\n                }],\n                [11, false, {\n                    message: '\"value\" must be less than or equal to 10',\n                    path: [],\n                    type: 'number.max',\n                    context: { limit: 10, value: 11, label: 'value' }\n                }],\n                [8, true],\n                [9, true],\n                [9.1, false, {\n                    message: '\"value\" must be an integer',\n                    path: [],\n                    type: 'number.integer',\n                    context: { value: 9.1, label: 'value' }\n                }],\n                [null, false, {\n                    message: '\"value\" must be a number',\n                    path: [],\n                    type: 'number.base',\n                    context: { label: 'value', value: null }\n                }]\n            ]);\n        });\n\n        it('handles combination of min, max, integer, and allow', () => {\n\n            const rule = Joi.number().min(8).max(10).integer().allow(9.1);\n            Helper.validate(rule, [\n                [1, false, {\n                    message: '\"value\" must be greater than or equal to 8',\n                    path: [],\n                    type: 'number.min',\n                    context: { limit: 8, value: 1, label: 'value' }\n                }],\n                [11, false, {\n                    message: '\"value\" must be less than or equal to 10',\n                    path: [],\n                    type: 'number.max',\n                    context: { limit: 10, value: 11, label: 'value' }\n                }],\n                [8, true],\n                [9, true],\n                [9.1, true],\n                [9.2, false, {\n                    message: '\"value\" must be an integer',\n                    path: [],\n                    type: 'number.integer',\n                    context: { value: 9.2, label: 'value' }\n                }],\n                [null, false, {\n                    message: '\"value\" must be a number',\n                    path: [],\n                    type: 'number.base',\n                    context: { label: 'value', value: null }\n                }]\n            ]);\n        });\n\n        it('handles combination of min, max, integer, allow, and invalid', () => {\n\n            const rule = Joi.number().min(8).max(10).integer().allow(9.1).invalid(8);\n            Helper.validate(rule, [\n                [1, false, {\n                    message: '\"value\" must be greater than or equal to 8',\n                    path: [],\n                    type: 'number.min',\n                    context: { limit: 8, value: 1, label: 'value' }\n                }],\n                [11, false, {\n                    message: '\"value\" must be less than or equal to 10',\n                    path: [],\n                    type: 'number.max',\n                    context: { limit: 10, value: 11, label: 'value' }\n                }],\n                [8, false, {\n                    message: '\"value\" contains an invalid value',\n                    path: [],\n                    type: 'any.invalid',\n                    context: { value: 8, invalids: [8], label: 'value' }\n                }],\n                [9, true],\n                [9.1, true],\n                [9.2, false, {\n                    message: '\"value\" must be an integer',\n                    path: [],\n                    type: 'number.integer',\n                    context: { value: 9.2, label: 'value' }\n                }],\n                [null, false, {\n                    message: '\"value\" must be a number',\n                    path: [],\n                    type: 'number.base',\n                    context: { label: 'value', value: null }\n                }]\n            ]);\n        });\n\n        it('handles combination of min, max, integer, allow, invalid, and null allowed', () => {\n\n            const rule = Joi.number().min(8).max(10).integer().allow(9.1).invalid(8).allow(null);\n            Helper.validate(rule, [\n                [1, false, {\n                    message: '\"value\" must be greater than or equal to 8',\n                    path: [],\n                    type: 'number.min',\n                    context: { limit: 8, value: 1, label: 'value' }\n                }],\n                [11, false, {\n                    message: '\"value\" must be less than or equal to 10',\n                    path: [],\n                    type: 'number.max',\n                    context: { limit: 10, value: 11, label: 'value' }\n                }],\n                [8, false, {\n                    message: '\"value\" contains an invalid value',\n                    path: [],\n                    type: 'any.invalid',\n                    context: { value: 8, invalids: [8], label: 'value' }\n                }],\n                [9, true],\n                [9.1, true],\n                [9.2, false, {\n                    message: '\"value\" must be an integer',\n                    path: [],\n                    type: 'number.integer',\n                    context: { value: 9.2, label: 'value' }\n                }],\n                [null, true]\n            ]);\n        });\n\n        it('handles limiting the number of decimal places', () => {\n\n            const rule = Joi.number().precision(1).prefs({ convert: false });\n            Helper.validate(rule, [\n                [1, true],\n                [9.1, true],\n                [9.21, false, {\n                    message: '\"value\" must have no more than 1 decimal places',\n                    path: [],\n                    type: 'number.precision',\n                    context: { limit: 1, value: 9.21, label: 'value' }\n                }],\n                [9.9999, false, {\n                    message: '\"value\" must have no more than 1 decimal places',\n                    path: [],\n                    type: 'number.precision',\n                    context: { limit: 1, value: 9.9999, label: 'value' }\n                }],\n                [9.9e-99, false, {\n                    message: '\"value\" must have no more than 1 decimal places',\n                    path: [],\n                    type: 'number.precision',\n                    context: { limit: 1, value: 9.9e-99, label: 'value' }\n                }],\n                [9.9e3, true],\n                [null, false, {\n                    message: '\"value\" must be a number',\n                    path: [],\n                    type: 'number.base',\n                    context: { label: 'value', value: null }\n                }]\n            ]);\n        });\n\n        it('handles combination of min, max, integer, allow, invalid, null allowed and precision', () => {\n\n            const rule = Joi.number().min(8).max(10).integer().allow(9.1).invalid(8).allow(null).precision(1).prefs({ convert: false });\n            Helper.validate(rule, [\n                [1, false, {\n                    message: '\"value\" must be greater than or equal to 8',\n                    path: [],\n                    type: 'number.min',\n                    context: { limit: 8, value: 1, label: 'value' }\n                }],\n                [11, false, {\n                    message: '\"value\" must be less than or equal to 10',\n                    path: [],\n                    type: 'number.max',\n                    context: { limit: 10, value: 11, label: 'value' }\n                }],\n                [8, false, {\n                    message: '\"value\" contains an invalid value',\n                    path: [],\n                    type: 'any.invalid',\n                    context: { value: 8, invalids: [8], label: 'value' }\n                }],\n                [9, true],\n                [9.1, true],\n                [9.11, false, {\n                    message: '\"value\" must be an integer',\n                    path: [],\n                    type: 'number.integer',\n                    context: { value: 9.11, label: 'value' }\n                }],\n                [9.2, false, {\n                    message: '\"value\" must be an integer',\n                    path: [],\n                    type: 'number.integer',\n                    context: { value: 9.2, label: 'value' }\n                }],\n                [9.22, false, {\n                    message: '\"value\" must be an integer',\n                    path: [],\n                    type: 'number.integer',\n                    context: { value: 9.22, label: 'value' }\n                }],\n                [null, true]\n            ]);\n        });\n\n        it('handles combination of greater and less', () => {\n\n            const rule = Joi.number().greater(5).less(10);\n            Helper.validate(rule, [\n                [0, false, {\n                    message: '\"value\" must be greater than 5',\n                    path: [],\n                    type: 'number.greater',\n                    context: { limit: 5, value: 0, label: 'value' }\n                }],\n                [11, false, {\n                    message: '\"value\" must be less than 10',\n                    path: [],\n                    type: 'number.less',\n                    context: { limit: 10, value: 11, label: 'value' }\n                }],\n                [5, false, {\n                    message: '\"value\" must be greater than 5',\n                    path: [],\n                    type: 'number.greater',\n                    context: { limit: 5, value: 5, label: 'value' }\n                }],\n                [10, false, {\n                    message: '\"value\" must be less than 10',\n                    path: [],\n                    type: 'number.less',\n                    context: { limit: 10, value: 10, label: 'value' }\n                }],\n                [8, true],\n                [5.01, true],\n                [9.99, true],\n                [null, false, {\n                    message: '\"value\" must be a number',\n                    path: [],\n                    type: 'number.base',\n                    context: { label: 'value', value: null }\n                }]\n            ]);\n        });\n\n        it('handles combination of greater, less, and integer', () => {\n\n            const rule = Joi.number().integer().greater(5).less(10);\n            Helper.validate(rule, [\n                [0, false, {\n                    message: '\"value\" must be greater than 5',\n                    path: [],\n                    type: 'number.greater',\n                    context: { limit: 5, value: 0, label: 'value' }\n                }],\n                [11, false, {\n                    message: '\"value\" must be less than 10',\n                    path: [],\n                    type: 'number.less',\n                    context: { limit: 10, value: 11, label: 'value' }\n                }],\n                [5, false, {\n                    message: '\"value\" must be greater than 5',\n                    path: [],\n                    type: 'number.greater',\n                    context: { limit: 5, value: 5, label: 'value' }\n                }],\n                [10, false, {\n                    message: '\"value\" must be less than 10',\n                    path: [],\n                    type: 'number.less',\n                    context: { limit: 10, value: 10, label: 'value' }\n                }],\n                [6, true],\n                [9, true],\n                [5.01, false, {\n                    message: '\"value\" must be an integer',\n                    path: [],\n                    type: 'number.integer',\n                    context: { value: 5.01, label: 'value' }\n                }],\n                [9.99, false, {\n                    message: '\"value\" must be an integer',\n                    path: [],\n                    type: 'number.integer',\n                    context: { value: 9.99, label: 'value' }\n                }]\n            ]);\n        });\n\n        it('handles combination of greater, less, and null allowed', () => {\n\n            const rule = Joi.number().greater(5).less(10).allow(null);\n            Helper.validate(rule, [\n                [0, false, {\n                    message: '\"value\" must be greater than 5',\n                    path: [],\n                    type: 'number.greater',\n                    context: { limit: 5, value: 0, label: 'value' }\n                }],\n                [11, false, {\n                    message: '\"value\" must be less than 10',\n                    path: [],\n                    type: 'number.less',\n                    context: { limit: 10, value: 11, label: 'value' }\n                }],\n                [5, false, {\n                    message: '\"value\" must be greater than 5',\n                    path: [],\n                    type: 'number.greater',\n                    context: { limit: 5, value: 5, label: 'value' }\n                }],\n                [10, false, {\n                    message: '\"value\" must be less than 10',\n                    path: [],\n                    type: 'number.less',\n                    context: { limit: 10, value: 10, label: 'value' }\n                }],\n                [8, true],\n                [5.01, true],\n                [9.99, true],\n                [null, true]\n            ]);\n        });\n\n        it('handles combination of greater, less, invalid, and allow', () => {\n\n            const rule = Joi.number().greater(5).less(10).invalid(6).allow(-3);\n            Helper.validate(rule, [\n                [0, false, {\n                    message: '\"value\" must be greater than 5',\n                    path: [],\n                    type: 'number.greater',\n                    context: { limit: 5, value: 0, label: 'value' }\n                }],\n                [11, false, {\n                    message: '\"value\" must be less than 10',\n                    path: [],\n                    type: 'number.less',\n                    context: { limit: 10, value: 11, label: 'value' }\n                }],\n                [5, false, {\n                    message: '\"value\" must be greater than 5',\n                    path: [],\n                    type: 'number.greater',\n                    context: { limit: 5, value: 5, label: 'value' }\n                }],\n                [10, false, {\n                    message: '\"value\" must be less than 10',\n                    path: [],\n                    type: 'number.less',\n                    context: { limit: 10, value: 10, label: 'value' }\n                }],\n                [6, false, {\n                    message: '\"value\" contains an invalid value',\n                    path: [],\n                    type: 'any.invalid',\n                    context: { value: 6, invalids: [6], label: 'value' }\n                }],\n                [8, true],\n                [5.01, true],\n                [9.99, true],\n                [-3, true],\n                [null, false, {\n                    message: '\"value\" must be a number',\n                    path: [],\n                    type: 'number.base',\n                    context: { label: 'value', value: null }\n                }]\n            ]);\n        });\n    });\n\n    describe('unsafe', () => {\n\n        it('returns the same instance if nothing changed', () => {\n\n            const schema = Joi.number();\n            expect(schema.unsafe(false)).to.shallow.equal(schema);\n            expect(schema.unsafe()).to.not.shallow.equal(schema);\n            expect(schema.unsafe(true)).to.not.shallow.equal(schema);\n        });\n\n        it('checks unsafe numbers', () => {\n\n            const t = Joi.number();\n            Helper.validate(t, [\n                ['-0', true, 0],\n                ['9007199254740981.1', false, {\n                    message: '\"value\" must be a safe number',\n                    path: [],\n                    type: 'number.unsafe',\n                    context: { value: '9007199254740981.1', label: 'value' }\n                }],\n                ['90071992547409811e-1', false, {\n                    message: '\"value\" must be a safe number',\n                    path: [],\n                    type: 'number.unsafe',\n                    context: { value: '90071992547409811e-1', label: 'value' }\n                }],\n                ['9007199254740992', false, {\n                    message: '\"value\" must be a safe number',\n                    path: [],\n                    type: 'number.unsafe',\n                    context: { value: 9007199254740992, label: 'value' }\n                }],\n                ['-9007199254740992', false, {\n                    message: '\"value\" must be a safe number',\n                    path: [],\n                    type: 'number.unsafe',\n                    context: { value: -9007199254740992, label: 'value' }\n                }],\n                ['90.071992549e+15', false, {\n                    message: '\"value\" must be a safe number',\n                    path: [],\n                    type: 'number.unsafe',\n                    context: { value: 90071992549000000, label: 'value' }\n                }],\n                ['1.9642346977926366E-5', false, {\n                    message: '\"value\" must be a safe number',\n                    path: [],\n                    type: 'number.unsafe',\n                    context: { value: '1.9642346977926366E-5', label: 'value' }\n                }],\n                [9007199254740992, false, {\n                    message: '\"value\" must be a safe number',\n                    path: [],\n                    type: 'number.unsafe',\n                    context: { value: 9007199254740992, label: 'value' }\n                }],\n                [-9007199254740992, false, {\n                    message: '\"value\" must be a safe number',\n                    path: [],\n                    type: 'number.unsafe',\n                    context: { value: -9007199254740992, label: 'value' }\n                }]\n            ]);\n        });\n\n        it('accepts unsafe numbers with a loss of precision when disabled', () => {\n\n            const t = Joi.number().unsafe();\n            Helper.validate(t, [\n                ['9007199254740981.1', true, 9007199254740981],\n                ['9007199254740992', true, 9007199254740992],\n                ['-9007199254740992', true, -9007199254740992],\n                ['90.071992549e+15', true, 90071992549000000],\n                [9007199254740992, true, 9007199254740992],\n                [-9007199254740992, true, -9007199254740992]\n            ]);\n        });\n    });\n});\n"
  },
  {
    "path": "test/types/object.js",
    "content": "'use strict';\n\nconst Code = require('@hapi/code');\nconst Lab = require('@hapi/lab');\nconst Joi = require('../..');\n\nconst Helper = require('../helper');\n\n\nconst internals = {};\n\n\nconst { describe, it } = exports.lab = Lab.script();\nconst { expect } = Code;\n\n\ndescribe('object', () => {\n\n    it('validates an object', () => {\n\n        const schema = Joi.object().required();\n        Helper.validate(schema, [\n            [{}, true],\n            [{ hi: true }, true],\n            ['', false, {\n                message: '\"value\" must be of type object',\n                path: [],\n                type: 'object.base',\n                context: { label: 'value', value: '', type: 'object' }\n            }]\n        ]);\n    });\n\n    it('validates basic object', () => {\n\n        const schema = Joi.object({\n            a: Joi.number().min(0).max(3),\n            b: Joi.string().valid('a', 'b', 'c'),\n            c: Joi.string().email().optional()\n        }).without('a', 'none');\n\n        expect(Joi.isSchema(schema)).to.be.true();\n        expect(Joi.isSchema({})).to.be.false();\n\n        const obj = {\n            a: 1,\n            b: 'a',\n            c: 'joe@example.com'\n        };\n\n        Helper.validate(schema, [[obj, true]]);\n    });\n\n    it('returns object reference when no rules specified', () => {\n\n        const schema = Joi.object({\n            a: Joi.object()\n        });\n\n        const item = { x: 5 };\n        Helper.validate(schema, [[{ a: item }, true, { a: item }]]);\n    });\n\n    it('retains ignored values', () => {\n\n        const schema = Joi.object();\n        Helper.validate(schema, [[{ a: 5 }, true, { a: 5 }]]);\n    });\n\n    it('retains skipped values', () => {\n\n        const schema = Joi.object({ b: 5 }).unknown(true);\n        Helper.validate(schema, [[{ b: 5, a: 5 }, true, { a: 5, b: 5 }]]);\n    });\n\n    it('retains symbols', () => {\n\n        const schema = Joi.object({ a: Joi.number() });\n\n        const symbol = Symbol();\n        Helper.validate(schema, [[{ [symbol]: 5, a: 5 }, true, { [symbol]: 5, a: 5 }]]);\n    });\n\n    it('retains non-enumerable', () => {\n\n        const schema = Joi.object({ a: Joi.number() });\n\n        const obj = { a: 100 };\n        Object.defineProperty(obj, 'test', { value: 42, enumerable: false });\n        expect(obj.test).to.equal(42);\n        Helper.validate(schema, { nonEnumerables: true }, [[obj, true, { a: 100 }]]);\n    });\n\n    it('retains prototype', () => {\n\n        const schema = Joi.object({ a: Joi.number() });\n\n        const Test = class {\n            constructor() {\n\n                this.a = 5;\n            }\n        };\n\n        expect(schema.validate(new Test()).value).to.be.instanceof(Test);\n    });\n\n    it('allows any key when schema is undefined', () => {\n\n        Helper.validate(Joi.object(), [[{ a: 4 }, true]]);\n        Helper.validate(Joi.object(undefined), [[{ a: 4 }, true]]);\n    });\n\n    it('allows any key when schema is null', () => {\n\n        Helper.validate(Joi.object(null), [[{ a: 4 }, true]]);\n    });\n\n    it('throws on invalid object schema', () => {\n\n        expect(() => {\n\n            Joi.object(4);\n        }).to.throw('Object schema must be a valid object');\n    });\n\n    it('throws on joi object schema', () => {\n\n        expect(() => {\n\n            Joi.object(Joi.object());\n        }).to.throw('Object schema cannot be a joi schema');\n    });\n\n    it('skips conversion when value is undefined', () => {\n\n        Helper.validate(Joi.object({ a: Joi.object() }), [[undefined, true, undefined]]);\n    });\n\n    it('errors on array', () => {\n\n        Helper.validate(Joi.object(), [[[1, 2, 3], false, {\n            message: '\"value\" must be of type object',\n            path: [],\n            type: 'object.base',\n            context: { label: 'value', value: [1, 2, 3], type: 'object' }\n        }]]);\n    });\n\n    it('should prevent extra keys from existing by default', () => {\n\n        const schema = Joi.object({ item: Joi.string().required() }).required();\n        Helper.validate(schema, [\n            [{ item: 'something' }, true],\n            [{ item: 'something', item2: 'something else' }, false, {\n                message: '\"item2\" is not allowed',\n                path: ['item2'],\n                type: 'object.unknown',\n                context: { child: 'item2', label: 'item2', key: 'item2', value: 'something else' }\n            }],\n            ['', false, {\n                message: '\"value\" must be of type object',\n                path: [],\n                type: 'object.base',\n                context: { label: 'value', value: '', type: 'object' }\n            }]\n        ]);\n    });\n\n    it('validates count when min is set', () => {\n\n        const schema = Joi.object().min(3);\n        Helper.validate(schema, [\n            [{ item: 'something' }, false, {\n                message: '\"value\" must have at least 3 keys',\n                path: [],\n                type: 'object.min',\n                context: { limit: 3, label: 'value', value: { item: 'something' } }\n            }],\n            [{ item: 'something', item2: 'something else' }, false, {\n                message: '\"value\" must have at least 3 keys',\n                path: [],\n                type: 'object.min',\n                context: {\n                    limit: 3,\n                    label: 'value',\n                    value: { item: 'something', item2: 'something else' }\n                }\n            }],\n            [{ item: 'something', item2: 'something else', item3: 'something something else' }, true],\n            ['', false, {\n                message: '\"value\" must be of type object',\n                path: [],\n                type: 'object.base',\n                context: { label: 'value', value: '', type: 'object' }\n            }]\n        ]);\n    });\n\n    it('validates count when max is set', () => {\n\n        const schema = Joi.object().max(2);\n        Helper.validate(schema, [\n            [{ item: 'something' }, true],\n            [{ item: 'something', item2: 'something else' }, true],\n            [{ item: 'something', item2: 'something else', item3: 'something something else' }, false, {\n                message: '\"value\" must have less than or equal to 2 keys',\n                path: [],\n                type: 'object.max',\n                context: {\n                    limit: 2,\n                    label: 'value',\n                    value: { item: 'something', item2: 'something else', item3: 'something something else' }\n                }\n            }],\n            ['', false, {\n                message: '\"value\" must be of type object',\n                path: [],\n                type: 'object.base',\n                context: { label: 'value', value: '', type: 'object' }\n            }]\n        ]);\n    });\n\n    it('validates count when min and max is set', () => {\n\n        const schema = Joi.object().max(3).min(2);\n        Helper.validate(schema, [\n            [{ item: 'something' }, false, {\n                message: '\"value\" must have at least 2 keys',\n                path: [],\n                type: 'object.min',\n                context: { limit: 2, label: 'value', value: { item: 'something' } }\n            }],\n            [{ item: 'something', item2: 'something else' }, true],\n            [{ item: 'something', item2: 'something else', item3: 'something something else' }, true],\n            [{ item: 'something', item2: 'something else', item3: 'something something else', item4: 'item4' }, false, {\n                message: '\"value\" must have less than or equal to 3 keys',\n                path: [],\n                type: 'object.max',\n                context: {\n                    limit: 3,\n                    label: 'value',\n                    value: {\n                        item: 'something',\n                        item2: 'something else',\n                        item3: 'something something else',\n                        item4: 'item4'\n                    }\n                }\n            }],\n            ['', false, {\n                message: '\"value\" must be of type object',\n                path: [],\n                type: 'object.base',\n                context: { label: 'value', value: '', type: 'object' }\n            }]\n        ]);\n    });\n\n    it('validates count when length is set', () => {\n\n        const schema = Joi.object().length(2);\n        Helper.validate(schema, [\n            [{ item: 'something' }, false, {\n                message: '\"value\" must have 2 keys',\n                path: [],\n                type: 'object.length',\n                context: { limit: 2, label: 'value', value: { item: 'something' } }\n            }],\n            [{ item: 'something', item2: 'something else' }, true],\n            [{ item: 'something', item2: 'something else', item3: 'something something else' }, false, {\n                message: '\"value\" must have 2 keys',\n                path: [],\n                type: 'object.length',\n                context: {\n                    limit: 2,\n                    label: 'value',\n                    value: { item: 'something', item2: 'something else', item3: 'something something else' }\n                }\n            }],\n            ['', false, {\n                message: '\"value\" must be of type object',\n                path: [],\n                type: 'object.base',\n                context: { label: 'value', value: '', type: 'object' }\n            }]\n        ]);\n    });\n\n    it('validates constructor when type is set', () => {\n\n        const schema = Joi.object().instance(RegExp);\n        const d = new Date();\n        Helper.validate(schema, [\n            [{ item: 'something' }, false, {\n                message: '\"value\" must be an instance of \"RegExp\"',\n                path: [],\n                type: 'object.instance',\n                context: { type: 'RegExp', label: 'value', value: { item: 'something' } }\n            }],\n            ['', false, {\n                message: '\"value\" must be of type object',\n                path: [],\n                type: 'object.base',\n                context: { label: 'value', value: '', type: 'object' }\n            }],\n            [d, false, {\n                message: '\"value\" must be an instance of \"RegExp\"',\n                path: [],\n                type: 'object.instance',\n                context: { type: 'RegExp', label: 'value', value: d }\n            }],\n            [/abcd/, true],\n            [new RegExp(), true]\n        ]);\n    });\n\n    it('should strip unknown values when stripUnknown === true', () => {\n\n        const schema = Joi.object({\n            itemName: Joi.string().required(),\n            nestedItem: Joi.object({\n                nestedItemName: Joi.string().required()\n            }).required()\n        }).required();\n        const prefs = { abortEarly: false, stripUnknown: true };\n\n        // test for valid objects\n        Helper.validate(schema, prefs, [\n            [\n                { itemName: 'John', toStrip: true, nestedItem: { nestedItemName: 'Doe' } },\n                true,\n                { itemName: 'John', nestedItem: { nestedItemName: 'Doe' } }\n            ],\n            [\n                { itemName: 'John', nestedItem: { nestedItemName: 'Doe', toStrip: true } },\n                true,\n                { itemName: 'John', nestedItem: { nestedItemName: 'Doe' } }\n            ]\n        ]);\n\n        // test for invalid object, value to strip in root object\n        let { value } = schema.validate(\n            { itemName: 1, toStrip: true, nestedItem: { nestedItemName: 'Doe' } },\n            prefs\n        );\n        expect(value).to.equal({ itemName: 1, nestedItem: { nestedItemName: 'Doe' } });\n\n        // test for invalid object, value to strip in nested object\n        value = schema.validate(\n            { itemName: 'John', nestedItem: { nestedItemName: 1, toStripNested: true } },\n            prefs\n        ).value;\n        expect(value).to.equal({ itemName: 'John', nestedItem: { nestedItemName: 1 } });\n    });\n\n    it('traverses an object and validate all properties in the top level', () => {\n\n        const schema = Joi.object({\n            num: Joi.number()\n        });\n\n        Helper.validate(schema, [\n            [{ num: 1 }, true],\n            [{ num: [1, 2, 3] }, false, {\n                message: '\"num\" must be a number',\n                path: ['num'],\n                type: 'number.base',\n                context: { label: 'num', key: 'num', value: [1, 2, 3] }\n            }]\n        ]);\n    });\n\n    it('traverses an object and child objects and validate all properties', () => {\n\n        const schema = Joi.object({\n            num: Joi.number(),\n            obj: Joi.object({\n                item: Joi.string()\n            })\n        });\n\n        Helper.validate(schema, [\n            [{ num: 1 }, true],\n            [{ num: [1, 2, 3] }, false, {\n                message: '\"num\" must be a number',\n                path: ['num'],\n                type: 'number.base',\n                context: { label: 'num', key: 'num', value: [1, 2, 3] }\n            }],\n            [{ num: 1, obj: { item: 'something' } }, true],\n            [{ num: 1, obj: { item: 123 } }, false, {\n                message: '\"obj.item\" must be a string',\n                path: ['obj', 'item'],\n                type: 'string.base',\n                context: { value: 123, label: 'obj.item', key: 'item' }\n            }]\n        ]);\n    });\n\n    it('traverses an object several levels', () => {\n\n        const schema = Joi.object({\n            obj: Joi.object({\n                obj: Joi.object({\n                    obj: Joi.object({\n                        item: Joi.boolean()\n                    })\n                })\n            })\n        });\n\n        Helper.validate(schema, [\n            [{ num: 1 }, false, {\n                message: '\"num\" is not allowed',\n                path: ['num'],\n                type: 'object.unknown',\n                context: { child: 'num', label: 'num', key: 'num', value: 1 }\n            }],\n            [{ obj: {} }, true],\n            [{ obj: { obj: {} } }, true],\n            [{ obj: { obj: { obj: {} } } }, true],\n            [{ obj: { obj: { obj: { item: true } } } }, true],\n            [{ obj: { obj: { obj: { item: 10 } } } }, false, {\n                message: '\"obj.obj.obj.item\" must be a boolean',\n                path: ['obj', 'obj', 'obj', 'item'],\n                type: 'boolean.base',\n                context: { label: 'obj.obj.obj.item', key: 'item', value: 10 }\n            }]\n        ]);\n    });\n\n    it('traverses an object several levels with required levels', () => {\n\n        const schema = Joi.object({\n            obj: Joi.object({\n                obj: Joi.object({\n                    obj: Joi.object({\n                        item: Joi.boolean()\n                    })\n                }).required()\n            })\n        });\n\n        Helper.validate(schema, [\n            [null, false, {\n                message: '\"value\" must be of type object',\n                path: [],\n                type: 'object.base',\n                context: { label: 'value', value: null, type: 'object' }\n            }],\n            [undefined, true],\n            [{}, true],\n            [{ obj: {} }, false, {\n                message: '\"obj.obj\" is required',\n                path: ['obj', 'obj'],\n                type: 'any.required',\n                context: { label: 'obj.obj', key: 'obj' }\n            }],\n            [{ obj: { obj: {} } }, true],\n            [{ obj: { obj: { obj: {} } } }, true],\n            [{ obj: { obj: { obj: { item: true } } } }, true],\n            [{ obj: { obj: { obj: { item: 10 } } } }, false, {\n                message: '\"obj.obj.obj.item\" must be a boolean',\n                path: ['obj', 'obj', 'obj', 'item'],\n                type: 'boolean.base',\n                context: { label: 'obj.obj.obj.item', key: 'item', value: 10 }\n            }]\n        ]);\n    });\n\n    it('traverses an object several levels with required levels (without Joi.obj())', () => {\n\n        const schema = Joi.object({\n            obj: {\n                obj: {\n                    obj: {\n                        item: Joi.boolean().required()\n                    }\n                }\n            }\n        });\n\n        Helper.validate(schema, [\n            [null, false, {\n                message: '\"value\" must be of type object',\n                path: [],\n                type: 'object.base',\n                context: { label: 'value', value: null, type: 'object' }\n            }],\n            [undefined, true],\n            [{}, true],\n            [{ obj: {} }, true],\n            [{ obj: { obj: {} } }, true],\n            [{ obj: { obj: { obj: {} } } }, false, {\n                message: '\"obj.obj.obj.item\" is required',\n                path: ['obj', 'obj', 'obj', 'item'],\n                type: 'any.required',\n                context: { label: 'obj.obj.obj.item', key: 'item' }\n            }],\n            [{ obj: { obj: { obj: { item: true } } } }, true],\n            [{ obj: { obj: { obj: { item: 10 } } } }, false, {\n                message: '\"obj.obj.obj.item\" must be a boolean',\n                path: ['obj', 'obj', 'obj', 'item'],\n                type: 'boolean.base',\n                context: { label: 'obj.obj.obj.item', key: 'item', value: 10 }\n            }]\n        ]);\n    });\n\n    it('errors on unknown keys when functions allows', () => {\n\n        const schema = Joi.object({ a: Joi.number() }).prefs({ skipFunctions: true });\n        const obj = { a: 5, b: 'value' };\n        Helper.validate(schema, [[obj, false, {\n            message: '\"b\" is not allowed',\n            path: ['b'],\n            type: 'object.unknown',\n            context: { child: 'b', label: 'b', key: 'b', value: 'value' }\n        }]]);\n    });\n\n    it('validates both valid() and with()', () => {\n\n        const schema = Joi.object({\n            first: Joi.valid('value'),\n            second: Joi.any()\n        }).with('first', 'second');\n\n        Helper.validate(schema, [\n            [{ first: 'value' }, false, {\n                message: '\"first\" missing required peer \"second\"',\n                path: [],\n                type: 'object.with',\n                context: {\n                    main: 'first',\n                    mainWithLabel: 'first',\n                    peer: 'second',\n                    peerWithLabel: 'second',\n                    label: 'value',\n                    value: { first: 'value' }\n                }\n            }]\n        ]);\n    });\n\n    it('validates referenced arrays in valid()', () => {\n\n        const ref = Joi.in('$x');\n        const schema = Joi.object({\n            foo: Joi.valid(ref)\n        });\n\n        Helper.validate(schema, { context: { x: 'bar' } }, [\n            [{ foo: 'bar' }, true]\n        ]);\n\n        Helper.validate(schema, { context: { x: ['baz', 'bar'] } }, [\n            [{ foo: 'bar' }, true]\n        ]);\n\n        Helper.validate(schema, { context: { x: 'baz' } }, [\n            [{ foo: 'bar' }, false, {\n                message: '\"foo\" must be [ref:global:x]',\n                path: ['foo'],\n                type: 'any.only',\n                context: { value: 'bar', valids: [ref], label: 'foo', key: 'foo' }\n            }]\n        ]);\n\n        Helper.validate(schema, { context: { x: ['baz', 'qux'] } }, [\n            [{ foo: 'bar' }, false, {\n                message: '\"foo\" must be [ref:global:x]',\n                path: ['foo'],\n                type: 'any.only',\n                context: { value: 'bar', valids: [ref], label: 'foo', key: 'foo' }\n            }]\n        ]);\n\n        Helper.validate(schema, [\n            [{ foo: 'bar' }, false, {\n                message: '\"foo\" must be [ref:global:x]',\n                path: ['foo'],\n                type: 'any.only',\n                context: { value: 'bar', valids: [ref], label: 'foo', key: 'foo' }\n            }]\n        ]);\n    });\n\n    it('errors on unknown nested keys with the correct path', () => {\n\n        const schema = Joi.object({ a: Joi.object().keys({}) });\n        const obj = { a: { b: 'value' } };\n        Helper.validate(schema, [[obj, false, {\n            message: '\"a.b\" is not allowed',\n            path: ['a', 'b'],\n            type: 'object.unknown',\n            context: { child: 'b', label: 'a.b', key: 'b', value: 'value' }\n        }]]);\n    });\n\n    it('errors on unknown nested keys with the correct path at the root level', () => {\n\n        const schema = Joi.object({ a: Joi.object().keys({}) });\n        const obj = { c: 'hello' };\n        Helper.validate(schema, [[obj, false, {\n            message: '\"c\" is not allowed',\n            path: ['c'],\n            type: 'object.unknown',\n            context: { child: 'c', label: 'c', key: 'c', value: 'hello' }\n        }]]);\n    });\n\n    it('should work on prototype-less objects', () => {\n\n        const input = Object.create(null);\n        const schema = Joi.object().keys({\n            a: Joi.number()\n        });\n\n        input.a = 1337;\n\n        Helper.validate(schema, [[input, true]]);\n    });\n\n    it('should be able to use rename safely with a fake hasOwnProperty', () => {\n\n        const schema = Joi.object()\n            .rename('b', 'a');\n\n        const input = { b: 2, a: 1, hasOwnProperty: 'foo' };\n\n        Helper.validate(schema, [[input, false, {\n            message: '\"value\" cannot rename \"b\" because override is disabled and target \"a\" exists',\n            path: [],\n            type: 'object.rename.override',\n            context: { from: 'b', to: 'a', label: 'value', pattern: false, value: input }\n        }]]);\n    });\n\n    it('should be able to use object.with() safely with a fake hasOwnProperty', () => {\n\n        const input = { a: 1, hasOwnProperty: 'foo' };\n        const schema = Joi.object({ a: 1 }).with('a', 'b');\n\n        Helper.validate(schema, { abortEarly: false }, [[input, false, {\n            message: '\"hasOwnProperty\" is not allowed. \"a\" missing required peer \"b\"',\n            details: [\n                {\n                    message: '\"hasOwnProperty\" is not allowed',\n                    path: ['hasOwnProperty'],\n                    type: 'object.unknown',\n                    context: {\n                        child: 'hasOwnProperty',\n                        label: 'hasOwnProperty',\n                        key: 'hasOwnProperty',\n                        value: 'foo'\n                    }\n                },\n                {\n                    message: '\"a\" missing required peer \"b\"',\n                    path: [],\n                    type: 'object.with',\n                    context: {\n                        main: 'a',\n                        mainWithLabel: 'a',\n                        peer: 'b',\n                        peerWithLabel: 'b',\n                        label: 'value',\n                        value: input\n                    }\n                }\n            ]\n        }]]);\n    });\n\n    it('aborts early on unknown keys', () => {\n\n        const input = { a: 1, unknown: 2 };\n        const schema = Joi.object({ a: 1 }).with('a', 'b');\n\n        Helper.validate(schema, [[input, false, '\"unknown\" is not allowed']]);\n    });\n\n    it('applies labels with nested objects', () => {\n\n        const schema = Joi.object({\n            a: Joi.number().label('first'),\n            b: Joi.object({\n                c: Joi.string().label('second'),\n                d: Joi.number()\n            })\n        })\n            .with('a', ['b.c']);\n\n        Helper.validate(schema, [[{ a: 1, b: { d: 2 } }, false, {\n            message: '\"first\" missing required peer \"b.second\"',\n            path: [],\n            type: 'object.with',\n            context: {\n                main: 'a',\n                mainWithLabel: 'first',\n                peer: 'b.c',\n                peerWithLabel: 'b.second',\n                label: 'value',\n                value: { a: 1, b: { d: 2 } }\n            }\n        }]]);\n    });\n\n    it('errors on unknown key', () => {\n\n        const config = {\n            auth: Joi.object({\n                mode: Joi.string().valid('required', 'optional', 'try').allow(null)\n            }).allow(null)\n        };\n\n        Helper.validate(Joi.compile(config), [\n            [{ auth: { unknown: true } }, false, '\"auth.unknown\" is not allowed'],\n            [{ something: false }, false, '\"something\" is not allowed']\n        ]);\n    });\n\n    describe('dependency()', () => {\n\n        it('throws when references are passed', () => {\n\n            expect(() => Joi.object().and(Joi.ref('test'))).to.throw('and peers must be strings');\n        });\n    });\n\n    describe('and()', () => {\n\n        it('validates and()', () => {\n\n            const schema = Joi.object({\n                txt: Joi.string(),\n                upc: Joi.string().allow(null, ''),\n                code: Joi.number()\n            }).and('txt', 'upc', 'code');\n\n            const err = schema.validate({ txt: 'x' }, { abortEarly: false }).error;\n            expect(err).to.be.an.error('\"value\" contains [txt] without its required peers [upc, code]');\n            expect(err.details).to.equal([{\n                message: '\"value\" contains [txt] without its required peers [upc, code]',\n                path: [],\n                type: 'object.and',\n                context: {\n                    present: ['txt'],\n                    presentWithLabels: ['txt'],\n                    missing: ['upc', 'code'],\n                    missingWithLabels: ['upc', 'code'],\n                    label: 'value',\n                    value: { txt: 'x' }\n                }\n            }]);\n\n            Helper.validate(schema, [\n                [{}, true],\n                [{ upc: null }, false, {\n                    message: '\"value\" contains [upc] without its required peers [txt, code]',\n                    path: [],\n                    type: 'object.and',\n                    context: {\n                        present: ['upc'],\n                        presentWithLabels: ['upc'],\n                        missing: ['txt', 'code'],\n                        missingWithLabels: ['txt', 'code'],\n                        label: 'value',\n                        value: { upc: null }\n                    }\n                }],\n                [{ upc: 'test' }, false, {\n                    message: '\"value\" contains [upc] without its required peers [txt, code]',\n                    path: [],\n                    type: 'object.and',\n                    context: {\n                        present: ['upc'],\n                        presentWithLabels: ['upc'],\n                        missing: ['txt', 'code'],\n                        missingWithLabels: ['txt', 'code'],\n                        label: 'value',\n                        value: { upc: 'test' }\n                    }\n                }],\n                [{ txt: null }, false, {\n                    message: '\"txt\" must be a string',\n                    path: ['txt'],\n                    type: 'string.base',\n                    context: { value: null, label: 'txt', key: 'txt' }\n                }],\n                [{ txt: 'test' }, false, {\n                    message: '\"value\" contains [txt] without its required peers [upc, code]',\n                    path: [],\n                    type: 'object.and',\n                    context: {\n                        present: ['txt'],\n                        presentWithLabels: ['txt'],\n                        missing: ['upc', 'code'],\n                        missingWithLabels: ['upc', 'code'],\n                        label: 'value',\n                        value: { txt: 'test' }\n                    }\n                }],\n                [{ code: null }, false, {\n                    message: '\"code\" must be a number',\n                    path: ['code'],\n                    type: 'number.base',\n                    context: { label: 'code', key: 'code', value: null }\n                }],\n                [{ code: 123 }, false, {\n                    message: '\"value\" contains [code] without its required peers [txt, upc]',\n                    path: [],\n                    type: 'object.and',\n                    context: {\n                        present: ['code'],\n                        presentWithLabels: ['code'],\n                        missing: ['txt', 'upc'],\n                        missingWithLabels: ['txt', 'upc'],\n                        label: 'value',\n                        value: { code: 123 }\n                    }\n                }],\n                [{ txt: 'test', upc: null }, false, {\n                    message: '\"value\" contains [txt, upc] without its required peers [code]',\n                    path: [],\n                    type: 'object.and',\n                    context: {\n                        present: ['txt', 'upc'],\n                        presentWithLabels: ['txt', 'upc'],\n                        missing: ['code'],\n                        missingWithLabels: ['code'],\n                        label: 'value',\n                        value: { txt: 'test', upc: null }\n                    }\n                }],\n                [{ txt: 'test', upc: '' }, false, {\n                    message: '\"value\" contains [txt, upc] without its required peers [code]',\n                    path: [],\n                    type: 'object.and',\n                    context: {\n                        present: ['txt', 'upc'],\n                        presentWithLabels: ['txt', 'upc'],\n                        missing: ['code'],\n                        missingWithLabels: ['code'],\n                        label: 'value',\n                        value: { txt: 'test', upc: '' }\n                    }\n                }],\n                [{ txt: '', upc: 'test' }, false, {\n                    message: '\"txt\" is not allowed to be empty',\n                    path: ['txt'],\n                    type: 'string.empty',\n                    context: { value: '', label: 'txt', key: 'txt' }\n                }],\n                [{ txt: null, upc: 'test' }, false, {\n                    message: '\"txt\" must be a string',\n                    path: ['txt'],\n                    type: 'string.base',\n                    context: { value: null, label: 'txt', key: 'txt' }\n                }],\n                [{ txt: undefined, upc: 'test' }, false, {\n                    message: '\"value\" contains [upc] without its required peers [txt, code]',\n                    path: [],\n                    type: 'object.and',\n                    context: {\n                        present: ['upc'],\n                        presentWithLabels: ['upc'],\n                        missing: ['txt', 'code'],\n                        missingWithLabels: ['txt', 'code'],\n                        label: 'value',\n                        value: { txt: undefined, upc: 'test' }\n                    }\n                }],\n                [{ txt: 'test', upc: undefined }, false, {\n                    message: '\"value\" contains [txt] without its required peers [upc, code]',\n                    path: [],\n                    type: 'object.and',\n                    context: {\n                        present: ['txt'],\n                        presentWithLabels: ['txt'],\n                        missing: ['upc', 'code'],\n                        missingWithLabels: ['upc', 'code'],\n                        label: 'value',\n                        value: { txt: 'test', upc: undefined }\n                    }\n                }],\n                [{ txt: 'test', upc: '' }, false, {\n                    message: '\"value\" contains [txt, upc] without its required peers [code]',\n                    path: [],\n                    type: 'object.and',\n                    context: {\n                        present: ['txt', 'upc'],\n                        presentWithLabels: ['txt', 'upc'],\n                        missing: ['code'],\n                        missingWithLabels: ['code'],\n                        label: 'value',\n                        value: { txt: 'test', upc: '' }\n                    }\n                }],\n                [{ txt: 'test', upc: null }, false, {\n                    message: '\"value\" contains [txt, upc] without its required peers [code]',\n                    path: [],\n                    type: 'object.and',\n                    context: {\n                        present: ['txt', 'upc'],\n                        presentWithLabels: ['txt', 'upc'],\n                        missing: ['code'],\n                        missingWithLabels: ['code'],\n                        label: 'value',\n                        value: { txt: 'test', upc: null }\n                    }\n                }],\n                [{ txt: '', upc: undefined }, false, {\n                    message: '\"txt\" is not allowed to be empty',\n                    path: ['txt'],\n                    type: 'string.empty',\n                    context: { value: '', label: 'txt', key: 'txt' }\n                }],\n                [{ txt: '', upc: undefined, code: 999 }, false, {\n                    message: '\"txt\" is not allowed to be empty',\n                    path: ['txt'],\n                    type: 'string.empty',\n                    context: { value: '', label: 'txt', key: 'txt' }\n                }],\n                [{ txt: '', upc: undefined, code: undefined }, false, {\n                    message: '\"txt\" is not allowed to be empty',\n                    path: ['txt'],\n                    type: 'string.empty',\n                    context: { value: '', label: 'txt', key: 'txt' }\n                }],\n                [{ txt: '', upc: '' }, false, {\n                    message: '\"txt\" is not allowed to be empty',\n                    path: ['txt'],\n                    type: 'string.empty',\n                    context: { value: '', label: 'txt', key: 'txt' }\n                }],\n                [{ txt: 'test', upc: 'test' }, false, {\n                    message: '\"value\" contains [txt, upc] without its required peers [code]',\n                    path: [],\n                    type: 'object.and',\n                    context: {\n                        present: ['txt', 'upc'],\n                        presentWithLabels: ['txt', 'upc'],\n                        missing: ['code'],\n                        missingWithLabels: ['code'],\n                        label: 'value',\n                        value: { txt: 'test', upc: 'test' }\n                    }\n                }],\n                [{ txt: 'test', upc: 'test', code: 322 }, true],\n                [{ txt: 'test', upc: null, code: 322 }, true]\n            ]);\n        });\n\n        it('applies labels', () => {\n\n            const schema = Joi.object({\n                a: Joi.number().label('first'),\n                b: Joi.string().label('second')\n            }).and('a', 'b');\n            Helper.validate(schema, [[{ a: 1 }, false, {\n                message: '\"value\" contains [first] without its required peers [second]',\n                path: [],\n                type: 'object.and',\n                context: {\n                    present: ['a'],\n                    presentWithLabels: ['first'],\n                    missing: ['b'],\n                    missingWithLabels: ['second'],\n                    label: 'value',\n                    value: { a: 1 }\n                }\n            }]]);\n        });\n\n        it('allows nested objects', () => {\n\n            const schema = Joi.object({\n                a: Joi.string(),\n                b: Joi.object({ c: Joi.string(), d: Joi.number() }),\n                d: Joi.number()\n            }).and('a', 'b.c');\n\n            const sampleObject = { a: 'test', b: { c: 'test2' } };\n            const sampleObject2 = { a: 'test', b: { d: 80 } };\n\n            Helper.validate(schema, [\n                [sampleObject, true],\n                [sampleObject2, false, {\n                    message: '\"value\" contains [a] without its required peers [b.c]',\n                    path: [],\n                    type: 'object.and',\n                    context: {\n                        present: ['a'],\n                        presentWithLabels: ['a'],\n                        missing: ['b.c'],\n                        missingWithLabels: ['b.c'],\n                        label: 'value',\n                        value: sampleObject2\n                    }\n                }]\n            ]);\n        });\n\n        it('allows nested keys in functions', () => {\n\n            const schema = Joi.object({\n                a: Joi.string(),\n                b: Joi.function().keys({ c: Joi.string(), d: Joi.number() }),\n                d: Joi.number()\n            })\n                .and('a', 'b.c');\n\n            Helper.validate(schema, [\n                [{ a: 'test', b: Object.assign(() => { }, { c: 'test2' }) }, true, Helper.skip],\n                [{ a: 'test', b: Object.assign(() => { }, { d: 80 }) }, false, '\"value\" contains [a] without its required peers [b.c]']\n            ]);\n        });\n\n        it('applies labels with nested objects', () => {\n\n            const schema = Joi.object({\n                a: Joi.number().label('first'),\n                b: Joi.object({\n                    c: Joi.string().label('second'),\n                    d: Joi.number()\n                })\n            })\n                .and('a', 'b.c');\n\n            const error = schema.validate({ a: 1 }).error;\n            expect(error).to.be.an.error('\"value\" contains [first] without its required peers [b.second]');\n            expect(error.details).to.equal([{\n                message: '\"value\" contains [first] without its required peers [b.second]',\n                path: [],\n                type: 'object.and',\n                context: {\n                    present: ['a'],\n                    presentWithLabels: ['first'],\n                    missing: ['b.c'],\n                    missingWithLabels: ['b.second'],\n                    label: 'value',\n                    value: { a: 1 }\n                }\n            }]);\n        });\n\n        it('applies labels with invalid nested peers', () => {\n\n            const schema = Joi.object({\n                a: Joi.number().label('first'),\n                b: Joi.object({\n                    c: Joi.string().label('second'),\n                    d: Joi.number()\n                })\n            })\n                .and('a', 'c.d');\n\n            Helper.validate(schema, [[{ a: 1, b: { d: 1 } }, false, {\n                message: '\"value\" contains [first] without its required peers [c.d]',\n                path: [],\n                type: 'object.and',\n                context: {\n                    present: ['a'],\n                    presentWithLabels: ['first'],\n                    missing: ['c.d'],\n                    missingWithLabels: ['c.d'],\n                    label: 'value',\n                    value: { a: 1, b: { d: 1 } }\n                }\n            }]]);\n        });\n    });\n\n    describe('append()', () => {\n\n        it('should append schema', () => {\n\n            const schema = Joi.object()\n                .keys({ a: Joi.string() })\n                .append({ b: Joi.string() });\n\n            Helper.validate(schema, [[{ a: 'x', b: 'y' }, true]]);\n        });\n\n        it('should not change schema if it is null', () => {\n\n            const schema = Joi.object()\n                .keys({ a: Joi.string() })\n                .append(null);\n\n            Helper.validate(schema, [[{ a: 'x' }, true]]);\n        });\n\n        it('should not change schema if it is undefined', () => {\n\n            const schema = Joi.object()\n                .keys({ a: Joi.string() })\n                .append(undefined);\n\n            Helper.validate(schema, [[{ a: 'x' }, true]]);\n        });\n\n        it('should not change schema if it is empty-object', () => {\n\n            const schema = Joi.object()\n                .keys({ a: Joi.string() })\n                .append({});\n\n            Helper.validate(schema, [[{ a: 'x' }, true]]);\n        });\n    });\n\n    describe('assert()', () => {\n\n        it('shows path to errors in schema', () => {\n\n            expect(() => {\n\n                Joi.object().assert('.a.b', {\n                    a: {\n                        b: {\n                            c: {\n                                d: undefined\n                            }\n                        }\n                    }\n                });\n            }).to.throw('Invalid undefined schema (a.b.c.d)');\n        });\n\n        it('shows errors in schema', () => {\n\n            expect(() => {\n\n                Joi.object().assert('.a.b', undefined);\n            }).to.throw('Invalid undefined schema');\n        });\n\n        it('validates upwards reference', () => {\n\n            const schema = Joi.object({\n                a: {\n                    b: Joi.string(),\n                    c: Joi.number()\n                },\n                d: {\n                    e: Joi.any()\n                }\n            })\n                .assert(Joi.ref('/d/e', { separator: '/' }), Joi.ref('a.c'), 'equal to a/c');\n\n            Helper.validate(schema, [[{ a: { b: 'x', c: 5 }, d: { e: 6 } }, false, '\"value\" is invalid because \"d/e\" failed to equal to a/c']]);\n\n            Helper.validate(schema, [\n                [{ a: { b: 'x', c: 5 }, d: { e: 5 } }, true]\n            ]);\n        });\n\n        it('validates upwards reference with implicit context', () => {\n\n            const ref = Joi.ref('.d.e');\n            const schema = Joi.object({\n                a: {\n                    b: Joi.string(),\n                    c: Joi.number()\n                },\n                d: {\n                    e: Joi.any()\n                }\n            })\n                .assert(ref, Joi.ref('a.c'), 'equal to a.c');\n\n            Helper.validate(schema, [\n                [{ a: { b: 'x', c: 5 }, d: { e: 6 } }, false, {\n                    message: '\"value\" is invalid because \"d.e\" failed to equal to a.c',\n                    path: [],\n                    type: 'object.assert',\n                    context: {\n                        subject: ref,\n                        message: 'equal to a.c',\n                        label: 'value',\n                        value: { a: { b: 'x', c: 5 }, d: { e: 6 } }\n                    }\n                }],\n                [{ a: { b: 'x', c: 5 }, d: { e: 5 } }, true]\n            ]);\n        });\n\n        it('support own keys', () => {\n\n            const subject = Joi.ref('.a');\n            const schema = Joi.object({\n                a: Joi.number(),\n                d: {\n                    e: Joi.any()\n                }\n            })\n                .assert(subject, Joi.ref('d.e'), 'equal to d.e');\n\n            Helper.validate(schema, [\n                [{ a: 5, d: { e: 5 } }, true],\n                [{ a: 6, d: { e: 5 } }, false, {\n                    message: '\"value\" is invalid because \"a\" failed to equal to d.e',\n                    path: [],\n                    type: 'object.assert',\n                    context: {\n                        subject,\n                        message: 'equal to d.e',\n                        label: 'value',\n                        value: { a: 6, d: { e: 5 } }\n                    }\n                }]\n            ]);\n        });\n\n        it('allows root level context ref', () => {\n\n            expect(() => {\n\n                Joi.object({\n                    a: {\n                        b: Joi.string(),\n                        c: Joi.number()\n                    },\n                    d: {\n                        e: Joi.any()\n                    }\n                })\n                    .assert('$a', Joi.ref('d.e'), 'equal to d.e');\n            }).to.not.throw();\n        });\n\n        it('provides a default message for failed assertions', () => {\n\n            const ref = Joi.ref('.d.e');\n            const schema = Joi.object({\n                a: {\n                    b: Joi.string(),\n                    c: Joi.number()\n                },\n                d: {\n                    e: Joi.any()\n                }\n            }).assert(ref, Joi.boolean());\n\n            Helper.validate(schema, [[{ d: { e: [] } }, false, {\n                message: '\"value\" is invalid because \"d.e\" failed to pass the assertion test',\n                path: [],\n                type: 'object.assert',\n                context: {\n                    subject: ref,\n                    message: undefined,\n                    label: 'value',\n                    value: { d: { e: [] } }\n                }\n            }]]);\n        });\n\n        it('works with keys()', () => {\n\n            const schema = Joi.object({ a: { b: Joi.any() } })\n                .min(2)\n                .assert('.a.b', Joi.number())\n                .keys({ b: { c: Joi.any() } })\n                .assert('.b.c', Joi.number());\n\n            Helper.validate(schema, [[{ a: { b: 1 }, b: { c: 2 } }, true]]);\n        });\n\n        it('uses templates', () => {\n\n            const subject = Joi.x('{.a || .b || .c}');\n            const schema = Joi.object({\n                a: Joi.boolean(),\n                b: Joi.boolean(),\n                c: Joi.boolean()\n            })\n                .assert(subject, true, 'at least one key must be true');\n\n            Helper.validate(schema, [\n                [undefined, true],\n                [{ a: true, b: true, c: true }, true],\n                [{ a: true, b: false, c: false }, true],\n                [{ a: false, b: true, c: false }, true],\n                [{ a: false, b: false, c: true }, true],\n                [{ a: false, b: false, c: false }, false, {\n                    message: '\"value\" is invalid because at least one key must be true',\n                    path: [],\n                    type: 'object.assert',\n                    context: {\n                        subject,\n                        message: 'at least one key must be true',\n                        label: 'value',\n                        value: { a: false, b: false, c: false }\n                    }\n                }]\n            ]);\n        });\n    });\n\n    describe('cast()', () => {\n\n        it('casts value to map', () => {\n\n            const schema = Joi.object({ a: Joi.number(), b: Joi.number() }).cast('map');\n            expect(schema.validate({ a: '1', b: '2' }).value).to.equal(new Map([['a', 1], ['b', 2]]));\n        });\n\n        it('ignores null', () => {\n\n            const schema = Joi.object({ a: Joi.number(), b: Joi.number() }).allow(null).cast('map');\n            Helper.validate(schema, [[null, true, null]]);\n        });\n\n        it('ignores string', () => {\n\n            const schema = Joi.object({ a: Joi.number(), b: Joi.number() }).allow('x').cast('map');\n            Helper.validate(schema, [['x', true, 'x']]);\n        });\n\n        it('does not leak casts to any', () => {\n\n            expect(() => Joi.any().cast('map')).to.throw('Type any does not support casting to map');\n        });\n    });\n\n    describe('describe()', () => {\n\n        it('return empty description when no schema defined', () => {\n\n            const schema = Joi.object();\n            const desc = schema.describe();\n            expect(desc).to.equal({\n                type: 'object'\n            });\n        });\n\n        it('describes patterns', () => {\n\n            const schema = Joi.object({\n                a: Joi.string()\n            }).pattern(/\\w\\d/i, Joi.boolean());\n\n            expect(schema.describe()).to.equal({\n                type: 'object',\n                keys: {\n                    a: {\n                        type: 'string'\n                    }\n                },\n                patterns: [\n                    {\n                        regex: '/\\\\w\\\\d/i',\n                        rule: {\n                            type: 'boolean'\n                        }\n                    }\n                ]\n            });\n        });\n\n        it('describes patterns with schema', () => {\n\n            const schema = Joi.object({\n                a: Joi.string()\n            }).pattern(Joi.string().uuid({ version: 'uuidv4' }), Joi.boolean());\n\n            expect(schema.describe()).to.equal({\n                type: 'object',\n                keys: {\n                    a: {\n                        type: 'string'\n                    }\n                },\n                patterns: [\n                    {\n                        schema: {\n                            rules: [{\n                                args: { options: { version: 'uuidv4' } },\n                                name: 'guid'\n                            }],\n                            type: 'string'\n                        },\n                        rule: {\n                            type: 'boolean'\n                        }\n                    }\n                ]\n            });\n        });\n    });\n\n    describe('instance()', () => {\n\n        it('uses constructor name for default type name', () => {\n\n            const Foo = function Foo() {\n            };\n\n            const schema = Joi.object().instance(Foo);\n            Helper.validate(schema, [[{}, false, {\n                message: '\"value\" must be an instance of \"Foo\"',\n                path: [],\n                type: 'object.instance',\n                context: { type: 'Foo', label: 'value', value: {} }\n            }]]);\n        });\n\n        it('uses custom type name if supplied', () => {\n\n            const Foo = function () {\n            };\n\n            const schema = Joi.object().instance(Foo, 'Bar');\n            Helper.validate(schema, [[{}, false, {\n                message: '\"value\" must be an instance of \"Bar\"',\n                path: [],\n                type: 'object.instance',\n                context: { type: 'Bar', label: 'value', value: {} }\n            }]]);\n        });\n\n        it('overrides constructor name with custom name', () => {\n\n            const Foo = function Foo() {\n            };\n\n            const schema = Joi.object().instance(Foo, 'Bar');\n            Helper.validate(schema, [[{}, false, {\n                message: '\"value\" must be an instance of \"Bar\"',\n                path: [],\n                type: 'object.instance',\n                context: { type: 'Bar', label: 'value', value: {} }\n            }]]);\n        });\n\n        it('throws when constructor is not a function', () => {\n\n            expect(() => Joi.object().instance('')).to.throw('constructor must be a function');\n        });\n\n        it('uses the constructor name in the schema description', () => {\n\n            const description = Joi.object().instance(RegExp).describe();\n\n            expect(description.rules[0]).to.equal({ name: 'instance', args: { name: 'RegExp', constructor: RegExp } });\n        });\n\n        it('uses the constructor reference in the schema description', () => {\n\n            const Foo = function Foo() { };\n\n            const description = Joi.object().instance(Foo).describe();\n\n            expect(new Foo()).to.be.an.instanceof(description.rules[0].args.constructor);\n        });\n    });\n\n    describe('keys()', () => {\n\n        it('allows any key', () => {\n\n            const a = Joi.object({ a: 4 });\n            const b = a.keys();\n            Helper.validate(a, [[{ b: 3 }, false, {\n                message: '\"b\" is not allowed',\n                path: ['b'],\n                type: 'object.unknown',\n                context: { child: 'b', label: 'b', key: 'b', value: 3 }\n            }]]);\n\n            Helper.validate(b, [[{ b: 3 }, true]]);\n        });\n\n        it('forbids all keys', () => {\n\n            const a = Joi.object();\n            const b = a.keys({});\n            Helper.validate(a, [[{ b: 3 }, true]]);\n            Helper.validate(b, [[{ b: 3 }, false, {\n                message: '\"b\" is not allowed',\n                path: ['b'],\n                type: 'object.unknown',\n                context: { child: 'b', label: 'b', key: 'b', value: 3 }\n            }]]);\n        });\n\n        it('adds to existing keys', () => {\n\n            const a = Joi.object({ a: 1 });\n            const b = a.keys({ b: 2 });\n            Helper.validate(a, [[{ a: 1, b: 2 }, false, {\n                message: '\"b\" is not allowed',\n                path: ['b'],\n                type: 'object.unknown',\n                context: { child: 'b', label: 'b', key: 'b', value: 2 }\n            }]]);\n\n            Helper.validate(b, [[{ a: 1, b: 2 }, true]]);\n        });\n\n        it('overrides existing keys', () => {\n\n            const a = Joi.object({ a: Joi.number().valid(1) });\n            const b = a.keys({ a: Joi.string() });\n\n            Helper.validate(a, [\n                [{ a: 1 }, true, { a: 1 }],\n                [{ a: '1' }, true, { a: 1 }],\n                [{ a: '2' }, false, {\n                    message: '\"a\" must be [1]',\n                    path: ['a'],\n                    type: 'any.only',\n                    context: { value: 2, valids: [1], label: 'a', key: 'a' }\n                }]\n            ]);\n\n            Helper.validate(b, [\n                [{ a: 1 }, false, {\n                    message: '\"a\" must be a string',\n                    path: ['a'],\n                    type: 'string.base',\n                    context: { value: 1, label: 'a', key: 'a' }\n                }],\n                [{ a: '1' }, true, { a: '1' }]\n            ]);\n        });\n\n        it('strips keys flagged with strip', () => {\n\n            const schema = Joi.object({\n                a: Joi.string().strip(),\n                b: Joi.string()\n            });\n\n            Helper.validate(schema, [[{ a: 'test', b: 'test' }, true, { b: 'test' }]]);\n        });\n\n        it('strips keys after validation', () => {\n\n            const schema = Joi.object({\n                a: Joi.string().strip(),\n                b: Joi.string().default(Joi.ref('a'))\n            });\n\n            Helper.validate(schema, [[{ a: 'test' }, true, { b: 'test' }]]);\n        });\n\n        it('strips keys while preserving transformed values', () => {\n\n            const ref = Joi.ref('a');\n            const schema = Joi.object({\n                a: Joi.number().strip(),\n                b: Joi.number().min(ref)\n            });\n\n            Helper.validate(schema, [\n                [{ a: '1', b: '2' }, true, { b: 2 }],\n                [{ a: '1', b: '0' }, false, {\n                    message: '\"b\" must be greater than or equal to ref:a',\n                    path: ['b'],\n                    type: 'number.min',\n                    context: { limit: ref, value: 0, label: 'b', key: 'b' }\n                }]\n            ]);\n        });\n\n        it('does not alter the original object when stripping keys', () => {\n\n            const schema = Joi.object({\n                a: Joi.string().strip(),\n                b: Joi.string()\n            });\n\n            const valid = {\n                a: 'test',\n                b: 'test'\n            };\n\n            expect(schema.validate(valid)).to.equal({ value: { b: 'test' } });\n            expect(valid.a).to.equal('test');\n            expect(valid.b).to.equal('test');\n        });\n\n        it('should strip from an alternative', () => {\n\n            const schema = Joi.object({\n                a: [Joi.boolean().strip()]\n            });\n\n            Helper.validate(schema, [[{ a: true }, true, {}]]);\n        });\n\n        it('keeps keys in ref order', () => {\n\n            const schema = Joi.object({\n                type: Joi.string().required(),\n\n                set: Joi.boolean()\n                    .when('flag', { is: true, then: false }),\n\n                flag: Joi.boolean()\n            })\n                .when('.type', [\n                    { is: 'a', then: Joi.object({ flag: false }) }\n                ]);\n\n            Helper.validate(schema, [\n                [{ flag: true }, false, '\"type\" is required'],\n                [{ flag: true }, false, '\"type\" is required'],\n                [{ type: 'a', flag: true }, false, '\"flag\" must be [false]'],\n                [{ type: 'a', set: true, flag: true }, false, '\"flag\" must be [false]']\n            ]);\n        });\n    });\n\n    describe('length()', () => {\n\n        it('throws when length is not a number', () => {\n\n            expect(() => {\n\n                Joi.object().length('a');\n            }).to.throw('limit must be a positive integer or reference');\n        });\n    });\n\n    describe('max()', () => {\n\n        it('throws when limit is not a number', () => {\n\n            expect(() => {\n\n                Joi.object().max('a');\n            }).to.throw('limit must be a positive integer or reference');\n        });\n    });\n\n    describe('min()', () => {\n\n        it('throws when limit is not a number', () => {\n\n            expect(() => {\n\n                Joi.object().min('a');\n            }).to.throw('limit must be a positive integer or reference');\n        });\n    });\n\n    describe('nand()', () => {\n\n        it('validates nand()', () => {\n\n            const schema = Joi.object({\n                txt: Joi.string(),\n                upc: Joi.string().allow(null, ''),\n                code: Joi.number()\n            })\n                .nand('txt', 'upc', 'code');\n\n            Helper.validate(schema, { abortEarly: false }, [[{ txt: 'x', upc: 'y', code: 123 }, false, {\n                message: '\"txt\" must not exist simultaneously with [upc, code]',\n                details: [{\n                    message: '\"txt\" must not exist simultaneously with [upc, code]',\n                    path: [],\n                    type: 'object.nand',\n                    context: {\n                        main: 'txt',\n                        mainWithLabel: 'txt',\n                        peers: ['upc', 'code'],\n                        peersWithLabels: ['upc', 'code'],\n                        label: 'value',\n                        value: { txt: 'x', upc: 'y', code: 123 }\n                    }\n                }]\n            }]]);\n\n            Helper.validate(schema, [\n                [{}, true],\n                [{ upc: null }, true],\n                [{ upc: 'test' }, true],\n                [{ txt: 'test' }, true],\n                [{ code: 123 }, true],\n                [{ txt: 'test', upc: null }, true],\n                [{ txt: 'test', upc: '' }, true],\n                [{ txt: undefined, upc: 'test' }, true],\n                [{ txt: 'test', upc: undefined }, true],\n                [{ txt: 'test', upc: '' }, true],\n                [{ txt: 'test', upc: null }, true],\n                [{ txt: 'test', upc: undefined, code: 999 }, true],\n                [{ txt: 'test', upc: 'test' }, true],\n                [{ txt: 'test', upc: 'test', code: 322 }, false, {\n                    message: '\"txt\" must not exist simultaneously with [upc, code]',\n                    path: [],\n                    type: 'object.nand',\n                    context: {\n                        main: 'txt',\n                        mainWithLabel: 'txt',\n                        peers: ['upc', 'code'],\n                        peersWithLabels: ['upc', 'code'],\n                        label: 'value',\n                        value: { txt: 'test', upc: 'test', code: 322 }\n                    }\n                }],\n                [{ txt: 'test', upc: null, code: 322 }, false, {\n                    message: '\"txt\" must not exist simultaneously with [upc, code]',\n                    path: [],\n                    type: 'object.nand',\n                    context: {\n                        main: 'txt',\n                        mainWithLabel: 'txt',\n                        peers: ['upc', 'code'],\n                        peersWithLabels: ['upc', 'code'],\n                        label: 'value',\n                        value: { txt: 'test', upc: null, code: 322 }\n                    }\n                }]\n            ]);\n        });\n\n        it('applies labels', () => {\n\n            const schema = Joi.object({\n                a: Joi.number().label('first'),\n                b: Joi.string().label('second')\n            })\n                .nand('a', 'b');\n\n            Helper.validate(schema, [[{ a: 1, b: 'b' }, false, {\n                message: '\"first\" must not exist simultaneously with [second]',\n                path: [],\n                type: 'object.nand',\n                context: {\n                    main: 'a',\n                    mainWithLabel: 'first',\n                    peers: ['b'],\n                    peersWithLabels: ['second'],\n                    label: 'value',\n                    value: { a: 1, b: 'b' }\n                }\n            }]]);\n        });\n\n        it('allows nested objects', () => {\n\n            const schema = Joi.object({\n                a: Joi.string(),\n                b: Joi.object({ c: Joi.string(), d: Joi.number() }),\n                d: Joi.number()\n            })\n                .nand('a', 'b.c');\n\n            Helper.validate(schema, [\n                [{ a: 'test', b: { d: 80 } }, true],\n                [{ a: 'test', b: { c: 'test2' } }, false, {\n                    message: '\"a\" must not exist simultaneously with [b.c]',\n                    path: [],\n                    type: 'object.nand',\n                    context: {\n                        main: 'a',\n                        mainWithLabel: 'a',\n                        peers: ['b.c'],\n                        peersWithLabels: ['b.c'],\n                        label: 'value',\n                        value: { a: 'test', b: { c: 'test2' } }\n                    }\n                }]\n            ]);\n        });\n\n        it('allows nested keys in functions', () => {\n\n            const schema = Joi.object({\n                a: Joi.string(),\n                b: Joi.function().keys({ c: Joi.string(), d: Joi.number() }),\n                d: Joi.number()\n            })\n                .nand('a', 'b.c');\n\n            Helper.validate(schema, [\n                [{ a: 'test', b: Object.assign(() => { }, { d: 80 }) }, true, Helper.skip],\n                [{ a: 'test', b: Object.assign(() => { }, { c: 'test2' }) }, false, '\"a\" must not exist simultaneously with [b.c]']\n            ]);\n        });\n\n        it('applies labels with nested objects', () => {\n\n            const schema = Joi.object({\n                a: Joi.number().label('first'),\n                b: Joi.object({\n                    c: Joi.string().label('second'),\n                    d: Joi.number()\n                })\n            })\n                .nand('a', 'b.c');\n\n            const error = schema.validate({ a: 1, b: { c: 'c' } }).error;\n            expect(error).to.be.an.error('\"first\" must not exist simultaneously with [b.second]');\n            expect(error.details).to.equal([{\n                message: '\"first\" must not exist simultaneously with [b.second]',\n                path: [],\n                type: 'object.nand',\n                context: {\n                    main: 'a',\n                    mainWithLabel: 'first',\n                    peers: ['b.c'],\n                    peersWithLabels: ['b.second'],\n                    label: 'value',\n                    value: { a: 1, b: { c: 'c' } }\n                }\n            }]);\n        });\n    });\n\n    describe('optional()', () => {\n\n        it('does not require optional numbers', () => {\n\n            const config = {\n                position: Joi.number(),\n                suggestion: Joi.string()\n            };\n\n            Helper.validate(Joi.compile(config), [\n                [{ suggestion: 'something' }, true],\n                [{ position: 1 }, true]\n            ]);\n        });\n\n        it('does not require optional objects', () => {\n\n            const config = {\n                position: Joi.number(),\n                suggestion: Joi.object()\n            };\n\n            Helper.validate(Joi.compile(config), [\n                [{ suggestion: {} }, true],\n                [{ position: 1 }, true]\n            ]);\n        });\n    });\n\n    describe('or()', () => {\n\n        it('validates or()', () => {\n\n            const schema = Joi.object({\n                txt: Joi.string(),\n                upc: Joi.string().allow(null, ''),\n                code: Joi.number()\n            }).or('txt', 'upc', 'code');\n\n            Helper.validate(schema, { abortEarly: false }, [[{}, false, {\n                message: '\"value\" must contain at least one of [txt, upc, code]',\n                details: [{\n                    message: '\"value\" must contain at least one of [txt, upc, code]',\n                    path: [],\n                    type: 'object.missing',\n                    context: {\n                        peers: ['txt', 'upc', 'code'],\n                        peersWithLabels: ['txt', 'upc', 'code'],\n                        label: 'value',\n                        value: {}\n                    }\n                }]\n            }]]);\n\n            Helper.validate(schema, [\n                [{ upc: null }, true],\n                [{ upc: 'test' }, true],\n                [{ txt: null }, false, {\n                    message: '\"txt\" must be a string',\n                    path: ['txt'],\n                    type: 'string.base',\n                    context: { value: null, label: 'txt', key: 'txt' }\n                }],\n                [{ txt: 'test' }, true],\n                [{ code: null }, false, {\n                    message: '\"code\" must be a number',\n                    path: ['code'],\n                    type: 'number.base',\n                    context: { label: 'code', key: 'code', value: null }\n                }],\n                [{ code: 123 }, true],\n                [{ txt: 'test', upc: null }, true],\n                [{ txt: 'test', upc: '' }, true],\n                [{ txt: '', upc: 'test' }, false, {\n                    message: '\"txt\" is not allowed to be empty',\n                    path: ['txt'],\n                    type: 'string.empty',\n                    context: { value: '', label: 'txt', key: 'txt' }\n                }],\n                [{ txt: null, upc: 'test' }, false, {\n                    message: '\"txt\" must be a string',\n                    path: ['txt'],\n                    type: 'string.base',\n                    context: { value: null, label: 'txt', key: 'txt' }\n                }],\n                [{ txt: undefined, upc: 'test' }, true],\n                [{ txt: 'test', upc: undefined }, true],\n                [{ txt: 'test', upc: '' }, true],\n                [{ txt: 'test', upc: null }, true],\n                [{ txt: '', upc: undefined }, false, {\n                    message: '\"txt\" is not allowed to be empty',\n                    path: ['txt'],\n                    type: 'string.empty',\n                    context: { value: '', label: 'txt', key: 'txt' }\n                }],\n                [{ txt: '', upc: undefined, code: 999 }, false, {\n                    message: '\"txt\" is not allowed to be empty',\n                    path: ['txt'],\n                    type: 'string.empty',\n                    context: { value: '', label: 'txt', key: 'txt' }\n                }],\n                [{ txt: '', upc: undefined, code: undefined }, false, {\n                    message: '\"txt\" is not allowed to be empty',\n                    path: ['txt'],\n                    type: 'string.empty',\n                    context: { value: '', label: 'txt', key: 'txt' }\n                }],\n                [{ txt: '', upc: '' }, false, {\n                    message: '\"txt\" is not allowed to be empty',\n                    path: ['txt'],\n                    type: 'string.empty',\n                    context: { value: '', label: 'txt', key: 'txt' }\n                }],\n                [{ txt: 'test', upc: 'test' }, true],\n                [{ txt: 'test', upc: 'test', code: 322 }, true]\n            ]);\n        });\n\n        it('errors when a parameter is not a string', () => {\n\n            let error;\n            try {\n                Joi.object().or({});\n                error = false;\n            }\n            catch {\n                error = true;\n            }\n\n            expect(error).to.equal(true);\n\n            try {\n                Joi.object().or(123);\n                error = false;\n            }\n            catch {\n                error = true;\n            }\n\n            expect(error).to.equal(true);\n        });\n\n        it('errors multiple levels deep', () => {\n\n            const schema = Joi.object({\n                a: {\n                    b: Joi.object().or('x', 'y')\n                }\n            });\n\n            Helper.validate(schema, [[{ a: { b: { c: 1 } } }, false, {\n                message: '\"a.b\" must contain at least one of [x, y]',\n                path: ['a', 'b'],\n                type: 'object.missing',\n                context: {\n                    peers: ['x', 'y'],\n                    peersWithLabels: ['x', 'y'],\n                    label: 'a.b',\n                    key: 'b',\n                    value: { c: 1 }\n                }\n            }]]);\n        });\n\n        it('applies labels', () => {\n\n            const schema = Joi.object({\n                a: Joi.number().label('first'),\n                b: Joi.string().label('second')\n            }).or('a', 'b');\n            Helper.validate(schema, [[{}, false, {\n                message: '\"value\" must contain at least one of [first, second]',\n                path: [],\n                type: 'object.missing',\n                context: {\n                    peers: ['a', 'b'],\n                    peersWithLabels: ['first', 'second'],\n                    label: 'value',\n                    value: {}\n                }\n            }]]);\n        });\n\n        it('allows nested objects', () => {\n\n            const schema = Joi.object({\n                a: Joi.string(),\n                b: Joi.object({ c: Joi.string() }),\n                d: Joi.number()\n            }).or('a', 'b.c');\n\n            const sampleObject = { b: { c: 'bc' } };\n            const sampleObject2 = { d: 90 };\n\n            Helper.validate(schema, [\n                [sampleObject, true],\n                [sampleObject2, false, {\n                    message: '\"value\" must contain at least one of [a, b.c]',\n                    path: [],\n                    type: 'object.missing',\n                    context: {\n                        peers: ['a', 'b.c'],\n                        peersWithLabels: ['a', 'b.c'],\n                        label: 'value',\n                        value: sampleObject2\n                    }\n                }]\n            ]);\n        });\n\n        it('allows nested keys in functions', () => {\n\n            const schema = Joi.object({\n                a: Joi.string(),\n                b: Joi.function().keys({ c: Joi.string() }),\n                d: Joi.number()\n            })\n                .or('a', 'b.c');\n\n            Helper.validate(schema, [\n                [{ b: Object.assign(() => { }, { c: 'bc' }) }, true, Helper.skip],\n                [{ d: 90 }, false, {\n                    message: '\"value\" must contain at least one of [a, b.c]',\n                    path: [],\n                    type: 'object.missing',\n                    context: {\n                        peers: ['a', 'b.c'],\n                        peersWithLabels: ['a', 'b.c'],\n                        label: 'value',\n                        value: { d: 90 }\n                    }\n                }]\n            ]);\n        });\n\n        it('applies labels with nested objects', () => {\n\n            const schema = Joi.object({\n                a: Joi.number().label('first'),\n                b: Joi.object({\n                    c: Joi.string().label('second'),\n                    d: Joi.number()\n                })\n            })\n                .or('a', 'b.c');\n\n            Helper.validate(schema, [[{}, false, {\n                message: '\"value\" must contain at least one of [first, b.second]',\n                path: [],\n                type: 'object.missing',\n                context: {\n                    peers: ['a', 'b.c'],\n                    peersWithLabels: ['first', 'b.second'],\n                    label: 'value',\n                    value: {}\n                }\n            }]]);\n        });\n    });\n\n    describe('oxor()', () => {\n\n        it('errors when a parameter is not a string', () => {\n\n            let error;\n            try {\n                Joi.object().oxor({});\n                error = false;\n            }\n            catch {\n                error = true;\n            }\n\n            expect(error).to.equal(true);\n\n            try {\n                Joi.object().oxor(123);\n                error = false;\n            }\n            catch {\n                error = true;\n            }\n\n            expect(error).to.equal(true);\n        });\n\n        it('allows none of optional peers', () => {\n\n            const schema = Joi.object({\n                a: Joi.number(),\n                b: Joi.string()\n            }).oxor('a', 'b');\n\n            Helper.validate(schema, [[{}, true]]);\n        });\n\n        it('applies labels with too many peers', () => {\n\n            const schema = Joi.object({\n                a: Joi.number().label('first'),\n                b: Joi.string().label('second')\n            })\n                .oxor('a', 'b');\n\n            Helper.validate(schema, [[{ a: 1, b: 'b' }, false, {\n                message: '\"value\" contains a conflict between optional exclusive peers [first, second]',\n                path: [],\n                type: 'object.oxor',\n                context: {\n                    peers: ['a', 'b'],\n                    peersWithLabels: ['first', 'second'],\n                    present: ['a', 'b'],\n                    presentWithLabels: ['first', 'second'],\n                    label: 'value',\n                    value: { a: 1, b: 'b' }\n                }\n            }]]);\n        });\n\n        it('allows nested objects', () => {\n\n            const schema = Joi.object({\n                a: Joi.string(),\n                b: Joi.object({ c: Joi.string(), d: Joi.number() }),\n                d: Joi.number()\n            })\n                .oxor('a', 'b.c');\n\n            Helper.validate(schema, [\n                [{ a: 'test', b: { d: 80 } }, true],\n                [{ a: 'test', b: { c: 'test2' } }, false, {\n                    message: '\"value\" contains a conflict between optional exclusive peers [a, b.c]',\n                    path: [],\n                    type: 'object.oxor',\n                    context: {\n                        peers: ['a', 'b.c'],\n                        peersWithLabels: ['a', 'b.c'],\n                        present: ['a', 'b.c'],\n                        presentWithLabels: ['a', 'b.c'],\n                        label: 'value',\n                        value: { a: 'test', b: { c: 'test2' } }\n                    }\n                }]\n            ]);\n        });\n\n        it('allows nested keys in functions', () => {\n\n            const schema = Joi.object({\n                a: Joi.string(),\n                b: Joi.function().keys({ c: Joi.string(), d: Joi.number() }),\n                d: Joi.number()\n            })\n                .oxor('a', 'b.c');\n\n            Helper.validate(schema, [\n                [{ a: 'test', b: Object.assign(() => { }, { d: 80 }) }, true, Helper.skip],\n                [{ a: 'test', b: Object.assign(() => { }, { c: 'test2' }) }, false, '\"value\" contains a conflict between optional exclusive peers [a, b.c]']\n            ]);\n        });\n\n        it('allows setting custom isPresent function', () => {\n\n            const schema = Joi.object({\n                'a': Joi.string().allow(null),\n                'b': Joi.string().allow(null)\n            })\n                .oxor('a', 'b', { isPresent: (value) => value !== undefined && value !== null });\n\n            Helper.validate(schema, [\n                [{ a: null, b: null }, true],\n                [{}, true],\n                [{ a: 'foo', b: 'bar' }, false, '\"value\" contains a conflict between optional exclusive peers [a, b]']\n            ]);\n        });\n    });\n\n    describe('pattern()', () => {\n\n        it('shows path to errors in schema', () => {\n\n            expect(() => {\n\n                Joi.object().pattern(/.*/, {\n                    a: {\n                        b: {\n                            c: {\n                                d: undefined\n                            }\n                        }\n                    }\n                });\n            }).to.throw('Invalid undefined schema (a.b.c.d)');\n\n            expect(() => {\n\n                Joi.object().pattern(/.*/, Symbol('x'));\n            }).to.throw('Invalid schema content: symbol');\n\n        });\n\n        it('validates unknown keys using a regex pattern', () => {\n\n            const schema = Joi.object({\n                a: Joi.number()\n            }).pattern(/\\d+/, Joi.boolean()).pattern(/\\w\\w+/, 'x');\n\n            Helper.validate(schema, { abortEarly: false }, [[{ bb: 'y', 5: 'x' }, false, {\n                message: '\"5\" must be a boolean. \"bb\" must be [x]',\n                details: [\n                    {\n                        message: '\"5\" must be a boolean',\n                        path: ['5'],\n                        type: 'boolean.base',\n                        context: { label: '5', key: '5', value: 'x' }\n                    },\n                    {\n                        message: '\"bb\" must be [x]',\n                        path: ['bb'],\n                        type: 'any.only',\n                        context: { value: 'y', valids: ['x'], label: 'bb', key: 'bb' }\n                    }\n                ]\n            }]]);\n\n            Helper.validate(schema, [\n                [{ a: 5 }, true],\n                [{ a: 'x' }, false, {\n                    message: '\"a\" must be a number',\n                    path: ['a'],\n                    type: 'number.base',\n                    context: { label: 'a', key: 'a', value: 'x' }\n                }],\n                [{ b: 'x' }, false, {\n                    message: '\"b\" is not allowed',\n                    path: ['b'],\n                    type: 'object.unknown',\n                    context: { child: 'b', label: 'b', key: 'b', value: 'x' }\n                }],\n                [{ bb: 'x' }, true],\n                [{ 5: 'x' }, false, {\n                    message: '\"5\" must be a boolean',\n                    path: ['5'],\n                    type: 'boolean.base',\n                    context: { label: '5', key: '5', value: 'x' }\n                }],\n                [{ 5: false }, true],\n                [{ 5: undefined }, true]\n            ]);\n        });\n\n        it('validates unknown keys using a schema pattern', () => {\n\n            const schema = Joi.object({\n                a: Joi.number()\n            }).pattern(Joi.number().positive(), Joi.boolean())\n                .pattern(Joi.string().length(2), 'x');\n\n            Helper.validate(schema, { abortEarly: false }, [[{ bb: 'y', 5: 'x' }, false, {\n                message: '\"5\" must be a boolean. \"bb\" must be [x]',\n                details: [\n                    {\n                        message: '\"5\" must be a boolean',\n                        path: ['5'],\n                        type: 'boolean.base',\n                        context: { label: '5', key: '5', value: 'x' }\n                    },\n                    {\n                        message: '\"bb\" must be [x]',\n                        path: ['bb'],\n                        type: 'any.only',\n                        context: { value: 'y', valids: ['x'], label: 'bb', key: 'bb' }\n                    }\n                ]\n            }]]);\n\n            Helper.validate(schema, [\n                [{ a: 5 }, true],\n                [{ a: 'x' }, false, {\n                    message: '\"a\" must be a number',\n                    path: ['a'],\n                    type: 'number.base',\n                    context: { label: 'a', key: 'a', value: 'x' }\n                }],\n                [{ b: 'x' }, false, {\n                    message: '\"b\" is not allowed',\n                    path: ['b'],\n                    type: 'object.unknown',\n                    context: { child: 'b', label: 'b', key: 'b', value: 'x' }\n                }],\n                [{ bb: 'x' }, true],\n                [{ 5: 'x' }, false, {\n                    message: '\"5\" must be a boolean',\n                    path: ['5'],\n                    type: 'boolean.base',\n                    context: { label: '5', key: '5', value: 'x' }\n                }],\n                [{ 5: false }, true],\n                [{ 5: undefined }, true]\n            ]);\n        });\n\n        it('validates unknown keys using a schema pattern with a reference', () => {\n\n            const schema = Joi.object({\n                a: Joi.string(),\n                b: Joi.object().pattern(Joi.valid(Joi.ref('a')), Joi.boolean())\n            });\n\n            Helper.validate(schema, [\n                [{ a: 'x' }, true],\n                [{ a: 5 }, false, {\n                    message: '\"a\" must be a string',\n                    path: ['a'],\n                    type: 'string.base',\n                    context: { label: 'a', key: 'a', value: 5 }\n                }],\n                [{ b: 'x' }, false, {\n                    message: '\"b\" must be of type object',\n                    path: ['b'],\n                    type: 'object.base',\n                    context: { label: 'b', key: 'b', value: 'x', type: 'object' }\n                }],\n                [{ b: {} }, true],\n                [{ b: { foo: true } }, false, {\n                    message: '\"b.foo\" is not allowed',\n                    path: ['b', 'foo'],\n                    type: 'object.unknown',\n                    context: { child: 'foo', value: true, key: 'foo', label: 'b.foo' }\n                }],\n                [{ a: 'x', b: { foo: true } }, false, {\n                    message: '\"b.foo\" is not allowed',\n                    path: ['b', 'foo'],\n                    type: 'object.unknown',\n                    context: { child: 'foo', value: true, key: 'foo', label: 'b.foo' }\n                }],\n                [{ a: 'x', b: { x: 'y' } }, false, {\n                    message: '\"b.x\" must be a boolean',\n                    path: ['b', 'x'],\n                    type: 'boolean.base',\n                    context: { value: 'y', key: 'x', label: 'b.x' }\n                }]\n            ]);\n        });\n\n        it('validates unknown keys using a pattern (nested)', () => {\n\n            const schema = Joi.object({\n                x: Joi.object({\n                    a: Joi.number()\n                }).pattern(/\\d+/, Joi.boolean()).pattern(/\\w\\w+/, 'x')\n            });\n\n            Helper.validate(schema, { abortEarly: false }, [[{ x: { bb: 'y', 5: 'x' } }, false, {\n                message: '\"x.5\" must be a boolean. \"x.bb\" must be [x]',\n                details: [\n                    {\n                        message: '\"x.5\" must be a boolean',\n                        path: ['x', '5'],\n                        type: 'boolean.base',\n                        context: { label: 'x.5', key: '5', value: 'x' }\n                    },\n                    {\n                        message: '\"x.bb\" must be [x]',\n                        path: ['x', 'bb'],\n                        type: 'any.only',\n                        context: { value: 'y', valids: ['x'], label: 'x.bb', key: 'bb' }\n                    }\n                ]\n            }]]);\n        });\n\n        it('validates unknown keys using a pattern (nested)', () => {\n\n            const schema = Joi.object({\n                x: Joi.object({\n                    a: Joi.number()\n                }).pattern(Joi.number().positive(), Joi.boolean()).pattern(Joi.string().length(2), 'x')\n            });\n\n            Helper.validate(schema, { abortEarly: false }, [[{\n                x: {\n                    bb: 'y',\n                    5: 'x'\n                }\n            }, false, {\n                message: '\"x.5\" must be a boolean. \"x.bb\" must be [x]',\n                details: [\n                    {\n                        message: '\"x.5\" must be a boolean',\n                        path: ['x', '5'],\n                        type: 'boolean.base',\n                        context: { label: 'x.5', key: '5', value: 'x' }\n                    },\n                    {\n                        message: '\"x.bb\" must be [x]',\n                        path: ['x', 'bb'],\n                        type: 'any.only',\n                        context: { value: 'y', valids: ['x'], label: 'x.bb', key: 'bb' }\n                    }\n                ]\n            }]]);\n        });\n\n        it('ensures keys are also present in another object', () => {\n\n            const matches = Joi.in('....b');    // .match .matches .a .object\n            const schema = Joi.object({\n                a: Joi.object()\n                    .pattern(/.*/, Joi.number(), { matches }),\n                b: Joi.object()\n                    .pattern(/.*/, Joi.string())\n            });\n\n            Helper.validate(schema, [\n                [{ a: { x: 1 }, b: { x: 'a' } }, true],\n                [{ a: { v: 1 }, b: { x: 'a', v: 'b' } }, true],\n                [{ a: { x: 1 }, b: { y: 'a' } }, false, {\n                    message: '\"a\" keys failed to match pattern requirements',\n                    path: ['a'],\n                    type: 'object.pattern.match',\n                    context: {\n                        details: [\n                            {\n                                message: '\"a[0]\" must be [ref:....b]',\n                                path: ['a', 0],\n                                type: 'any.only',\n                                context: {\n                                    key: 0,\n                                    label: 'a[0]',\n                                    valids: [matches],\n                                    value: 'x'\n                                }\n                            }\n                        ],\n                        key: 'a',\n                        label: 'a',\n                        matches: ['x'],\n                        message: '\"a[0]\" must be [ref:....b]',\n                        value: { x: 1 }\n                    }\n                }]\n            ]);\n        });\n\n        it('errors when using a pattern on empty schema with unknown(false) and regex pattern mismatch', () => {\n\n            const schema = Joi.object().pattern(/\\d/, Joi.number()).unknown(false);\n\n            Helper.validate(schema, { abortEarly: false }, [[{ a: 5 }, false, {\n                message: '\"a\" is not allowed',\n                details: [{\n                    message: '\"a\" is not allowed',\n                    path: ['a'],\n                    type: 'object.unknown',\n                    context: { child: 'a', label: 'a', key: 'a', value: 5 }\n                }]\n            }]]);\n        });\n\n        it('errors when using a pattern on empty schema with unknown(false) and schema pattern mismatch', () => {\n\n            const schema = Joi.object().pattern(Joi.number().positive(), Joi.number()).unknown(false);\n\n            Helper.validate(schema, { abortEarly: false }, [[{ a: 5 }, false, {\n                message: '\"a\" is not allowed',\n                details: [{\n                    message: '\"a\" is not allowed',\n                    path: ['a'],\n                    type: 'object.unknown',\n                    context: { child: 'a', label: 'a', key: 'a', value: 5 }\n                }]\n            }]]);\n        });\n\n        it('reject global and sticky flags from patterns', () => {\n\n            expect(() => Joi.object().pattern(/a/g, Joi.number())).to.throw('pattern should not use global or sticky mode');\n            expect(() => Joi.object().pattern(/a/y, Joi.number())).to.throw('pattern should not use global or sticky mode');\n        });\n\n        it('allows using empty() on values', () => {\n\n            const schema = Joi.object().pattern(/a/, Joi.any().empty(null));\n            Helper.validate(schema, [[{ a1: undefined, a2: null, a3: 'test' }, true, { a1: undefined, a2: undefined, a3: 'test' }]]);\n        });\n\n        it('compiles if pattern is not regex or schema', () => {\n\n            const schema = Joi.object().pattern('x', Joi.boolean());\n            Helper.validate(schema, [\n                [{ x: true }, true],\n                [{ y: true }, false, '\"y\" is not allowed']\n            ]);\n        });\n\n        it('allows using refs in .valid() schema pattern', () => {\n\n            const schema = Joi.object().pattern(Joi.string().valid(Joi.in('$keys')), Joi.any());\n            expect(schema.validate({ a: 'test' }, { context: { keys: ['a'] } })).to.equal({ value: { a: 'test' } });\n        });\n\n        it('enforces pattern matches rule', () => {\n\n            const ref1 = Joi.ref('a');              // .matches ..object\n            const ref2 = Joi.x('{a - 1}');\n\n            const schema = Joi.object({\n                a: Joi.number().required()\n            })\n                .pattern(/^x\\d+$/, Joi.boolean(), { matches: Joi.array().length(ref1) })\n                .pattern(/^z\\w+$/, Joi.number())\n                .pattern(/^x\\w+$/, Joi.number(), { matches: Joi.array().max(ref2) });\n\n            Helper.validate(schema, [\n                [{ a: 1, x1: true }, true],\n                [{ a: 2, x1: true, x2: true, xx: 1 }, true],\n                [{ a: 3, x1: true, x2: true, x3: false, xx: 1 }, true],\n                [{ a: 0, x1: true }, false, {\n                    message: '\"value\" keys failed to match pattern requirements',\n                    path: [],\n                    type: 'object.pattern.match',\n                    context: {\n                        message: '\"value\" must contain ref:a items',\n                        label: 'value',\n                        value: { a: 0, x1: true },\n                        matches: ['x1'],\n                        details: [\n                            {\n                                context: {\n                                    label: 'value',\n                                    limit: ref1,\n                                    value: ['x1']\n                                },\n                                message: '\"value\" must contain ref:a items',\n                                path: [],\n                                type: 'array.length'\n                            }\n                        ]\n                    }\n                }],\n                [{ a: 1 }, false, '\"value\" keys failed to match pattern requirements']\n            ]);\n\n            const description = schema.describe();\n            expect(description).to.equal({\n                type: 'object',\n                keys: {\n                    a: {\n                        type: 'number',\n                        flags: {\n                            presence: 'required'\n                        }\n                    }\n                },\n                patterns: [\n                    {\n                        rule: {\n                            type: 'boolean'\n                        },\n                        regex: '/^x\\\\d+$/',\n                        matches: {\n                            type: 'array',\n                            rules: [\n                                {\n                                    name: 'length',\n                                    args: {\n                                        limit: {\n                                            ref: {\n                                                path: ['a']\n                                            }\n                                        }\n                                    }\n                                }\n                            ]\n                        }\n                    },\n                    {\n                        rule: {\n                            type: 'number'\n                        },\n                        regex: '/^z\\\\w+$/'\n                    },\n                    {\n                        rule: {\n                            type: 'number'\n                        },\n                        regex: '/^x\\\\w+$/',\n                        matches: {\n                            type: 'array',\n                            rules: [\n                                {\n                                    name: 'max',\n                                    args: {\n                                        limit: {\n                                            template: '{a - 1}'\n                                        }\n                                    }\n                                }\n                            ]\n                        }\n                    }\n                ]\n            });\n        });\n\n        it('enforces pattern matches rule (abortEarly false)', () => {\n\n            const schema = Joi.object({\n                a: Joi.number().required()\n            })\n                .pattern(/^x\\d+$/, Joi.boolean(), { matches: Joi.array().length(Joi.ref('a')) })\n                .pattern(/^x\\w+$/, Joi.number(), { matches: Joi.array().max(Joi.x('{a - 1}')) });\n\n            const err = schema.validate({ a: 0, x1: true, xx: 1 }, { abortEarly: false }).error;\n            expect(err).to.be.an.error('\"value\" keys failed to match pattern requirements');\n            expect(err.details).to.have.length(2);\n        });\n\n        it('matches matching keys to grandparents', () => {\n\n            const hasMatchingGrandparent = (value, { message, state }) => {\n\n                // 0: [ 'b' ]\n                // 1: { b: true }\n                // 2: { match: { b: true } }\n                // 3: { a: { match: { b: true } }, b: { match: { a: true } } }\n\n                if (state.ancestors[3][value] === undefined) {\n                    return message('{{#label}} does not have a matching grandparent');\n                }\n\n                return value;\n            };\n\n            const schema = Joi.object()\n                .pattern(/.*/, Joi.object({\n                    match: Joi.object()\n                        .pattern(/.*/, Joi.boolean(), { matches: hasMatchingGrandparent })\n                }));\n\n            Helper.validate(schema, [\n                [{ a: { match: { b: true } }, b: { match: { a: true } } }, true],\n                [{ a: { match: { b: true } } }, false, {\n                    message: '\"a.match\" keys failed to match pattern requirements',\n                    path: ['a', 'match'],\n                    type: 'object.pattern.match',\n                    context: {\n                        key: 'match',\n                        label: 'a.match',\n                        matches: ['b'],\n                        message: '\"a.match[0]\" does not have a matching grandparent',\n                        value: { b: true },\n                        details: [\n                            {\n                                context: { key: 0, label: 'a.match[0]', value: 'b' },\n                                message: '\"a.match[0]\" does not have a matching grandparent',\n                                path: ['a', 'match', 0],\n                                type: 'custom'\n                            }\n                        ]\n                    }\n                }]\n            ]);\n        });\n\n        it('works with keys()', () => {\n\n            const schema = Joi.object()\n                .pattern(/a/, Joi.any())\n                .keys({ b: Joi.any() });\n\n            expect(schema.validate({ a: { b: 1 }, b: { c: 2 } }).error).to.not.exist();\n        });\n\n        it('supports overlapping patterns', () => {\n\n            const schema = Joi.object()\n                .pattern(/^x/, Joi.number().min(1), { fallthrough: true })\n                .pattern(/^x/, Joi.number().max(10));\n\n            Helper.validate(schema, [\n                [{ x1: 1, x2: 2 }, true],\n                [{ x1: 11 }, false, {\n                    message: '\"x1\" must be less than or equal to 10',\n                    path: ['x1'],\n                    type: 'number.max',\n                    context: { limit: 10, value: 11, label: 'x1', key: 'x1' }\n                }]\n            ]);\n        });\n\n        it('ignores overlapping patterns', () => {\n\n            const schema = Joi.object()\n                .pattern(/^x/, Joi.number().min(1))\n                .pattern(/^x/, Joi.number().max(10));\n\n            Helper.validate(schema, [\n                [{ x1: 1, x2: 2 }, true],\n                [{ x1: 11 }, true]\n            ]);\n        });\n    });\n\n    describe('ref()', () => {\n\n        it('validates references', () => {\n\n            const schema = Joi.object().ref();\n\n            Helper.validate(schema, [\n                [{}, false, {\n                    message: '\"value\" must be a Joi reference',\n                    path: [],\n                    type: 'object.refType',\n                    context: { label: 'value', value: {} }\n                }],\n                [Joi.ref('a.b'), true]\n            ]);\n        });\n    });\n\n    describe('regex()', () => {\n\n        it('validates regular expressions', () => {\n\n            const schema = Joi.object().regex();\n\n            Helper.validate(schema, [\n                [{}, false, {\n                    message: '\"value\" must be a RegExp object',\n                    path: [],\n                    type: 'object.regex',\n                    context: { label: 'value', value: {} }\n                }],\n                [/a/, true]\n            ]);\n        });\n    });\n\n    describe('rename()', () => {\n\n        it('allows renaming multiple times with multiple enabled', () => {\n\n            const schema = Joi.object({\n                test: Joi.string()\n            }).rename('test1', 'test').rename('test2', 'test', { multiple: true });\n\n            expect(Joi.compile(schema).validate({ test1: 'a', test2: 'b' }).error).to.not.exist();\n        });\n\n        it('errors renaming multiple times with multiple disabled', () => {\n\n            const schema = Joi.object({\n                test: Joi.string()\n            }).rename('test1', 'test').rename('test2', 'test');\n\n            Helper.validate(Joi.compile(schema), [[{ test1: 'a', test2: 'b' }, false, {\n                message: '\"value\" cannot rename \"test2\" because multiple renames are disabled and another key was already renamed to \"test\"',\n                path: [],\n                type: 'object.rename.multiple',\n                context: { from: 'test2', to: 'test', label: 'value', pattern: false, value: { test: 'a', test2: 'b' } }\n            }]]);\n        });\n\n        it('errors multiple times when abortEarly is false', () => {\n\n            const schema = Joi.object()\n                .rename('a', 'b')\n                .rename('c', 'b')\n                .rename('d', 'b')\n                .prefs({ abortEarly: false });\n\n            Helper.validate(schema, [[{ a: 1, c: 1, d: 1 }, false, {\n                message: '\"value\" cannot rename \"c\" because multiple renames are disabled and another key was already renamed to \"b\". \"value\" cannot rename \"d\" because multiple renames are disabled and another key was already renamed to \"b\"',\n                details: [\n                    {\n                        message: '\"value\" cannot rename \"c\" because multiple renames are disabled and another key was already renamed to \"b\"',\n                        path: [],\n                        type: 'object.rename.multiple',\n                        context: { from: 'c', to: 'b', label: 'value', pattern: false, value: { b: 1 } }\n                    },\n                    {\n                        message: '\"value\" cannot rename \"d\" because multiple renames are disabled and another key was already renamed to \"b\"',\n                        path: [],\n                        type: 'object.rename.multiple',\n                        context: { from: 'd', to: 'b', label: 'value', pattern: false, value: { b: 1 } }\n                    }\n                ]\n            }]]);\n        });\n\n        it('aliases a key', () => {\n\n            const schema = Joi.object({\n                a: Joi.number(),\n                b: Joi.number()\n            }).rename('a', 'b', { alias: true });\n\n            const obj = { a: 10 };\n            Helper.validate(Joi.compile(schema), [[obj, true, { a: 10, b: 10 }]]);\n        });\n\n        it('with override disabled should not allow overwriting existing value', () => {\n\n            const schema = Joi.object({\n                test1: Joi.string()\n            }).rename('test', 'test1');\n\n            Helper.validate(schema, [[{ test: 'b', test1: 'a' }, false, {\n                message: '\"value\" cannot rename \"test\" because override is disabled and target \"test1\" exists',\n                path: [],\n                type: 'object.rename.override',\n                context: { from: 'test', to: 'test1', label: 'value', pattern: false, value: { test: 'b', test1: 'a' } }\n            }]]);\n        });\n\n        it('with override enabled should allow overwriting existing value', () => {\n\n            const schema = Joi.object({\n                test1: Joi.string()\n            }).rename('test', 'test1', { override: true });\n\n            Helper.validate(schema, [\n                [{ test: 'b', test1: 'a' }, true, { test1: 'b' }]\n            ]);\n        });\n\n        it('renames when data is nested in an array via items', () => {\n\n            const schema = {\n                arr: Joi.array().items(Joi.object({\n                    one: Joi.string(),\n                    two: Joi.string()\n                }).rename('uno', 'one').rename('dos', 'two'))\n            };\n\n            const data = { arr: [{ uno: '1', dos: '2' }] };\n            Helper.validate(Joi.object(schema), [[data, true, { arr: [{ one: '1', two: '2' }] }]]);\n        });\n\n        it('applies rename and validation in the correct order regardless of key order', () => {\n\n            const schema1 = Joi.object({\n                a: Joi.number()\n            }).rename('b', 'a');\n\n            const input1 = { b: '5' };\n            Helper.validate(schema1, [[input1, true, { a: 5 }]]);\n\n            const schema2 = Joi.object({ a: Joi.number(), b: Joi.any() }).rename('b', 'a');\n            const input2 = { b: '5' };\n            Helper.validate(schema2, [[input2, true, { a: 5 }]]);\n        });\n\n        it('sets the default value after key is renamed', () => {\n\n            const schema = Joi.object({\n                foo2: Joi.string().default('test')\n            }).rename('foo', 'foo2');\n\n            const input = {};\n            Helper.validate(schema, [[input, true, { foo2: 'test' }]]);\n        });\n\n        it('renames keys that are empty strings', () => {\n\n            const schema = Joi.object().rename('', 'notEmpty');\n            const input = {\n                '': 'something'\n            };\n\n            Helper.validate(schema, [[input, true, { notEmpty: 'something' }]]);\n        });\n\n        it('should not create new keys when the key in question does not exist', () => {\n\n            const schema = Joi.object()\n                .rename('b', '_b');\n\n            const input = {\n                a: 'something'\n            };\n\n            Helper.validate(schema, [[input, true, input]]);\n        });\n\n        it('ignores a key with ignoredUndefined if from does not exist', () => {\n\n            const schema = Joi.object().rename('b', 'a', { ignoreUndefined: true });\n\n            const input = {\n                a: 'something'\n            };\n\n            Helper.validate(schema, [[input, true, { a: 'something' }]]);\n        });\n\n        it('deletes a key with override and ignoredUndefined if from exists', () => {\n\n            const schema = Joi.object()\n                .rename('b', 'a', { ignoreUndefined: true, override: true });\n\n            const input = {\n                a: 'something',\n                b: 'something else'\n            };\n\n            Helper.validate(schema, [[input, true, { a: 'something else' }]]);\n        });\n\n        it('deletes a key with override if present and undefined', () => {\n\n            const schema = Joi.object()\n                .rename('b', 'a', { override: true });\n\n            const input = {\n                a: 'something',\n                b: undefined\n            };\n\n            Helper.validate(schema, [[input, true, {}]]);\n        });\n\n        it('leaves target if source is present and undefined and ignoreUndefined is set', () => {\n\n            const schema = Joi.object()\n                .rename('b', 'a', { override: true, ignoreUndefined: true });\n\n            const input = {\n                a: 'something',\n                b: undefined\n            };\n\n            Helper.validate(schema, [[input, true, input]]);\n        });\n\n        it('should fulfill describe() with defaults', () => {\n\n            const schema = Joi.object().rename('b', 'a');\n            const desc = schema.describe();\n\n            expect(desc).to.equal({\n                type: 'object',\n                renames: [{\n                    from: 'b',\n                    to: 'a',\n                    options: {\n                        alias: false,\n                        multiple: false,\n                        override: false\n                    }\n                }]\n            });\n        });\n\n        it('should fulfill describe() with non-defaults', () => {\n\n            const schema = Joi.object().rename('b', 'a', { alias: true, multiple: true, override: true });\n            const desc = schema.describe();\n\n            expect(desc).to.equal({\n                type: 'object',\n                renames: [{\n                    from: 'b',\n                    to: 'a',\n                    options: {\n                        alias: true,\n                        multiple: true,\n                        override: true\n                    }\n                }]\n            });\n        });\n\n        it('should leave key if from does not exist regardless of override', () => {\n\n            const schema = Joi.object()\n                .rename('b', 'a', { override: true });\n\n            const input = {\n                a: 'something'\n            };\n\n            Helper.validate(schema, [[input, true, input]]);\n        });\n\n        describe('using regex', () => {\n\n            it('renames using a regular expression', () => {\n\n                const regex = /foobar/i;\n\n                const schema = Joi.object({\n                    fooBar: Joi.string()\n                }).rename(regex, 'fooBar');\n\n                Helper.validate(Joi.compile(schema), [[{ FOOBAR: 'a' }, true, { fooBar: 'a' }]]);\n            });\n\n            it('aliases a key', () => {\n\n                const regex = /^a$/i;\n\n                const schema = Joi.object({\n                    other: Joi.any(),\n                    A: Joi.number(),\n                    b: Joi.number(),\n                    c: Joi.number()\n                }).rename(regex, 'b', { alias: true });\n\n                Helper.validate(Joi.compile(schema), [[{ other: 'here', A: 100, c: 50 }, true, { other: 'here', A: 100, b: 100, c: 50 }]]);\n            });\n\n            it('uses template', () => {\n\n                const schema = Joi.object()\n                    .rename(/^(\\d+)$/, Joi.x('x{#1}x'))\n                    .pattern(/^x\\d+x$/, Joi.any());\n\n                const input = {\n                    123: 'x',\n                    1: 'y',\n                    0: 'z',\n                    x4x: 'test'\n                };\n\n                Helper.validate(Joi.compile(schema), [[input, true, {\n                    x123x: 'x',\n                    x1x: 'y',\n                    x0x: 'z',\n                    x4x: 'test'\n                }]]);\n\n                expect(schema.describe()).to.equal({\n                    type: 'object',\n                    patterns: [{\n                        regex: '/^x\\\\d+x$/',\n                        rule: { type: 'any' }\n                    }],\n                    renames: [{\n                        from: { regex: '/^(\\\\d+)$/' },\n                        to: {\n                            template: 'x{#1}x'\n                        },\n                        options: {\n                            alias: false,\n                            multiple: false,\n                            override: false\n                        }\n                    }]\n                });\n            });\n\n            it('uses template with prefix override', () => {\n\n                const schema = Joi.object()\n                    .rename(/^(\\d+)$/, Joi.x('x{@1}x', { prefix: { local: '@' } }))\n                    .pattern(/^x\\d+x$/, Joi.any());\n\n                const input = {\n                    123: 'x',\n                    1: 'y',\n                    0: 'z',\n                    x4x: 'test'\n                };\n\n                Helper.validate(Joi.compile(schema), [[input, true, {\n                    x123x: 'x',\n                    x1x: 'y',\n                    x0x: 'z',\n                    x4x: 'test'\n                }]]);\n\n                expect(schema.describe()).to.equal({\n                    type: 'object',\n                    patterns: [{\n                        regex: '/^x\\\\d+x$/',\n                        rule: { type: 'any' }\n                    }],\n                    renames: [{\n                        from: { regex: '/^(\\\\d+)$/' },\n                        to: {\n                            template: 'x{@1}x',\n                            options: { prefix: { local: '@' } }\n                        },\n                        options: {\n                            alias: false,\n                            multiple: false,\n                            override: false\n                        }\n                    }]\n                });\n            });\n\n            it('uses template that references another sibling key', () => {\n\n                const schema = Joi.object({\n                    prefix: Joi.string().lowercase().required()\n                })\n                    .rename(/^(\\d+)$/, Joi.x('{.prefix}{#1}'))\n                    .unknown();\n\n                const input = {\n                    123: 'x',\n                    1: 'y',\n                    0: 'z',\n                    prefix: 'TEST'\n                };\n\n                Helper.validate(Joi.compile(schema), [[input, true, {\n                    TEST123: 'x',\n                    TEST1: 'y',\n                    TEST0: 'z',\n                    prefix: 'test'\n                }]]);\n            });\n\n            it('uses template that references peer key', () => {\n\n                const schema = Joi.object({\n                    a: Joi.object()\n                        .rename(/^(\\d+)$/, Joi.x('{b.prefix}{#1}'))\n                        .unknown(),\n                    b: {\n                        prefix: Joi.string().lowercase()\n                    }\n                });\n\n                Helper.validate(schema, [\n                    [{ a: { 5: 'x' }, b: { prefix: 'p' } }, true, { a: { p5: 'x' }, b: { prefix: 'p' } }],\n                    [{ a: { 5: 'x' }, b: { prefix: 'P' } }, true, { a: { p5: 'x' }, b: { prefix: 'p' } }],\n                    [{ b: { prefix: 'P' }, a: { 5: 'x' } }, true, { a: { p5: 'x' }, b: { prefix: 'p' } }],\n                    [{ b: {}, a: { 5: 'x' } }, true, { a: { 5: 'x' }, b: {} }],\n                    [{ a: { 5: 'x' } }, true, { a: { 5: 'x' } }]\n                ]);\n            });\n\n            it('uses template without refs', () => {\n\n                const schema = Joi.object()\n                    .rename(/^(\\d+)$/, Joi.x('x'))\n                    .unknown();\n\n                Helper.validate(Joi.compile(schema), [[{ 1: 'x' }, true, { x: 'x' }]]);\n            });\n\n            it('deletes a key with override if present and undefined', () => {\n\n                const schema = Joi.object()\n                    .rename(/b/, 'a', { override: true });\n\n                const input = {\n                    a: 'something',\n                    b: undefined\n                };\n\n                Helper.validate(schema, [[input, true, {}]]);\n            });\n\n            it('with override disabled it should not allow overwriting existing value', () => {\n\n                const schema = Joi.object({\n                    test1: Joi.string()\n                })\n                    .rename(/^test1$/i, 'test');\n\n                const item = {\n                    test: 'b',\n                    test1: 'a'\n                };\n\n                Helper.validate(Joi.compile(schema), [[item, false, {\n                    message: '\"value\" cannot rename \"test1\" because override is disabled and target \"test\" exists',\n                    path: [],\n                    type: 'object.rename.override',\n                    context: { from: 'test1', to: 'test', label: 'value', pattern: true, value: item }\n                }]]);\n            });\n\n            it('with override enabled should allow overwriting existing value', () => {\n\n                const regex = /^test$/i;\n\n                const schema = Joi.object({\n                    test1: Joi.string()\n                }).rename(regex, 'test1', { override: true });\n\n                Helper.validate(schema, [[{ test: 'b', test1: 'a' }, true, { test1: 'b' }]]);\n            });\n\n            it('renames when data is nested in an array via items', () => {\n\n                const regex1 = /^uno$/i;\n                const regex2 = /^dos$/i;\n\n                const schema = {\n                    arr: Joi.array().items(Joi.object({\n                        one: Joi.string(),\n                        two: Joi.string()\n                    }).rename(regex1, 'one').rename(regex2, 'two'))\n                };\n\n                const data = { arr: [{ uno: '1', dos: '2' }] };\n                Helper.validate(Joi.object(schema), [[data, true, { arr: [{ one: '1', two: '2' }] }]]);\n            });\n\n            it('skips when existing name matches', () => {\n\n                const regex = /^abc$/i;\n\n                const schema = Joi.object({ abc: Joi.string() }).rename(regex, 'abc', { override: true });\n\n                Helper.validate(schema, [\n                    [{ ABC: 'x' }, true, { abc: 'x' }],\n                    [{ abc: 'x' }, true, { abc: 'x' }]\n                ]);\n            });\n\n            it('applies rename and validation in the correct order regardless of key order', () => {\n\n                const regex = /^b$/i;\n\n                const schema1 = Joi.object({\n                    a: Joi.number()\n                }).rename(regex, 'a');\n\n                const input1 = { b: '5' };\n                Helper.validate(schema1, [[input1, true, { a: 5 }]]);\n\n                const schema2 = Joi.object({ a: Joi.number(), b: Joi.any() }).rename('b', 'a');\n                const input2 = { b: '5' };\n                Helper.validate(schema2, [[input2, true, { a: 5 }]]);\n            });\n\n            it('sets the default value after key is renamed', () => {\n\n                const regex = /^foo$/i;\n\n                const schema = Joi.object({\n                    foo2: Joi.string().default('test')\n                }).rename(regex, 'foo2');\n\n                const input = {};\n                Helper.validate(schema, [[input, true, { foo2: 'test' }]]);\n            });\n\n            it('should not create new keys when the key in question does not exist', () => {\n\n                const schema = Joi.object()\n                    .rename(/^b$/i, '_b');\n\n                const input = {\n                    a: 'something'\n                };\n\n                Helper.validate(schema, [[input, true, { a: 'something' }]]);\n            });\n\n            it('should leave key if from does not exist regardless of override', () => {\n\n                const schema = Joi.object()\n                    .rename(/^b$/i, 'a', { override: true });\n\n                const input = {\n                    a: 'something'\n                };\n\n                Helper.validate(schema, [[input, true, input]]);\n            });\n\n            it('skips when all matches are undefined and ignoredUndefined is true', () => {\n\n                const schema = Joi.object().keys({\n                    a: Joi.any(),\n                    b: Joi.any()\n                })\n                    .rename(/^b$/i, 'a', { ignoreUndefined: true });\n\n                const input = {\n                    b: undefined\n                };\n\n                Helper.validate(schema, [[input, true, { b: undefined }]]);\n            });\n\n            it('deletes a key with override and ignoredUndefined if from exists', () => {\n\n                const schema = Joi.object().keys({\n                    c: Joi.any(),\n                    a: Joi.any()\n                })\n                    .rename(/^b$/, 'a', { ignoreUndefined: true, override: true });\n\n                const input = {\n                    a: 'something',\n                    b: 'something else'\n                };\n\n                Helper.validate(schema, [[input, true, { a: 'something else' }]]);\n            });\n\n            it('should fulfill describe() with non-defaults', () => {\n\n                const regex = /^b$/i;\n\n                const schema = Joi.object().rename(regex, 'a', { alias: true, multiple: true, override: true });\n                const desc = schema.describe();\n\n                expect(desc).to.equal({\n                    type: 'object',\n                    renames: [{\n                        from: { regex: regex.toString() },\n                        to: 'a',\n                        options: {\n                            alias: true,\n                            multiple: true,\n                            override: true\n                        }\n                    }]\n                });\n            });\n\n            it('should fulfill describe() with defaults', () => {\n\n                const regex = /^b$/i;\n\n                const schema = Joi.object().rename(regex, 'a');\n                const desc = schema.describe();\n\n                expect(desc).to.equal({\n                    type: 'object',\n                    renames: [{\n                        from: { regex: regex.toString() },\n                        to: 'a',\n                        options: {\n                            alias: false,\n                            multiple: false,\n                            override: false\n                        }\n                    }]\n                });\n            });\n\n            it('allows renaming multiple times with multiple enabled', () => {\n\n                const schema = Joi.object({\n                    fooBar: Joi.string()\n                }).rename(/foobar/i, 'fooBar', { multiple: true });\n\n                Helper.validate(Joi.compile(schema), [[{ FOOBAR: 'a', FooBar: 'b' }, true, { fooBar: 'b' }]]);\n            });\n\n            it('errors renaming multiple times with multiple disabled', () => {\n\n                const schema = Joi.object({\n                    fooBar: Joi.string()\n                })\n                    .rename(/foobar/i, 'fooBar')\n                    .rename(/foobar/i, 'fooBar');\n\n                Helper.validate(Joi.compile(schema), [[{ FOOBAR: 'a', FooBar: 'b' }, false, {\n                    message: '\"value\" cannot rename \"FooBar\" because multiple renames are disabled and another key was already renamed to \"fooBar\"',\n                    path: [],\n                    type: 'object.rename.multiple',\n                    context: { from: 'FooBar', to: 'fooBar', label: 'value', pattern: true, value: { FooBar: 'b', fooBar: 'a' } }\n                }]]);\n            });\n\n            it('errors multiple times when abortEarly is false', () => {\n\n                const schema = Joi.object({\n                    z: Joi.string()\n                })\n                    .rename(/a/i, 'b')\n                    .rename(/c/i, 'b')\n                    .rename(/z/i, 'z')\n                    .prefs({ abortEarly: false });\n\n                Helper.validate(schema, [[{ a: 1, c: 1, d: 1, z: 1 }, false, {\n                    message: '\"value\" cannot rename \"c\" because multiple renames are disabled and another key was already renamed to \"b\". \"z\" must be a string. \"d\" is not allowed. \"b\" is not allowed',\n                    details: [\n                        {\n                            message: '\"value\" cannot rename \"c\" because multiple renames are disabled and another key was already renamed to \"b\"',\n                            path: [],\n                            type: 'object.rename.multiple',\n                            context: { from: 'c', to: 'b', label: 'value', pattern: true, value: { b: 1, d: 1, z: 1 } }\n                        },\n                        {\n                            message: '\"z\" must be a string',\n                            path: ['z'],\n                            type: 'string.base',\n                            context: { value: 1, key: 'z', label: 'z' }\n                        },\n                        {\n                            message: '\"d\" is not allowed',\n                            path: ['d'],\n                            type: 'object.unknown',\n                            context: { child: 'd', key: 'd', label: 'd', value: 1 }\n                        },\n                        {\n                            message: '\"b\" is not allowed',\n                            path: ['b'],\n                            type: 'object.unknown',\n                            context: { child: 'b', key: 'b', label: 'b', value: 1 }\n                        }\n                    ]\n                }]]);\n            });\n        });\n    });\n\n    describe('schema()', () => {\n\n        it('should detect joi instances', () => {\n\n            const schema = Joi.object().schema();\n            Helper.validate(schema, [\n                [{}, false, {\n                    message: '\"value\" must be a Joi schema of any type',\n                    path: [],\n                    type: 'object.schema',\n                    context: { label: 'value', type: 'any', value: {} }\n                }],\n                [{ isJoi: true }, false, {\n                    message: '\"value\" must be a Joi schema of any type',\n                    path: [],\n                    type: 'object.schema',\n                    context: { label: 'value', type: 'any', value: { isJoi: true } }\n                }],\n                [Joi.number().max(2), true]\n            ]);\n        });\n\n        it('validated schema type', () => {\n\n            const schema = Joi.object().schema('number');\n            Helper.validate(schema, [\n                [Joi.number().max(2), true],\n                [{}, false, {\n                    message: '\"value\" must be a Joi schema of number type',\n                    path: [],\n                    type: 'object.schema',\n                    context: { label: 'value', type: 'number', value: {} }\n                }],\n                [{ isJoi: true }, false, {\n                    message: '\"value\" must be a Joi schema of number type',\n                    path: [],\n                    type: 'object.schema',\n                    context: { label: 'value', type: 'number', value: { isJoi: true } }\n                }],\n                [Joi.string(), false, {\n                    message: '\"value\" must be a Joi schema of number type',\n                    path: [],\n                    type: 'object.schema',\n                    context: { label: 'value', type: 'number', value: Joi.string() }\n                }]\n            ]);\n        });\n    });\n\n    describe('tailor()', () => {\n\n        it('customizes schema', () => {\n\n            const alterations = {\n                x: (s) => s.min(10),\n                y: (s) => s.max(50),\n                z: (s) => s.integer()\n            };\n\n            const before = Joi.object({\n                a: {\n                    b: Joi.number().alter(alterations)\n                },\n                b: Joi.object()\n                    .pattern(/.*/, Joi.number().alter(alterations)),\n                c: Joi.object({\n                    x: Joi.number(),\n                    y: Joi.number()\n                })\n                    .assert('.c.x', Joi.number().alter(alterations))\n            });\n\n            const bd = before.describe();\n\n            const first = before.tailor('x');\n\n            const c = Joi.object({\n                x: Joi.number(),\n                y: Joi.number()\n            })\n                .assert('.c.x', Joi.number().min(10).alter(alterations));\n\n            const after1 = Joi.object({\n                a: {\n                    b: Joi.number().min(10).alter(alterations)\n                },\n                b: Joi.object()\n                    .pattern(/.*/, Joi.number().min(10).alter(alterations)),\n                c\n            });\n\n            expect(first.describe()).to.equal(after1.describe());\n            expect(before.describe()).to.equal(bd);\n        });\n\n        it('customizes schema on object and keys', () => {\n\n            const alterations = {\n                x: (s) => s.min(10),\n                y: (s) => s.max(50),\n                z: (s) => s.integer()\n            };\n\n            const before = Joi.object({\n                a: {\n                    b: Joi.number().alter(alterations)\n                },\n                b: Joi.object()\n                    .pattern(/.*/, Joi.number().alter(alterations)),\n                c: Joi.object({\n                    x: Joi.number(),\n                    y: Joi.number()\n                })\n                    .assert('.c.x', Joi.number().alter(alterations))\n                    .alter(alterations)\n            });\n\n            const bd = before.describe();\n\n            const first = before.tailor('x');\n\n            const after1 = Joi.object({\n                a: {\n                    b: Joi.number().min(10).alter(alterations)\n                },\n                b: Joi.object()\n                    .pattern(/.*/, Joi.number().min(10).alter(alterations)),\n                c: Joi.object({\n                    x: Joi.number(),\n                    y: Joi.number()\n                })\n                    .alter(alterations)\n                    .assert('.c.x', Joi.number().min(10).alter(alterations))\n                    .min(10)\n            });\n\n            expect(first.describe()).to.equal(after1.describe());\n            Helper.equal(first, after1);\n            expect(before.describe()).to.equal(bd);\n        });\n    });\n\n    describe('unknown()', () => {\n\n        it('avoids unnecessary cloning when called twice', () => {\n\n            const schema = Joi.object().unknown();\n            expect(schema.unknown()).to.shallow.equal(schema);\n        });\n\n        it('allows local unknown without applying to keys', () => {\n\n            const schema = Joi.object({\n                a: {\n                    b: Joi.number()\n                }\n            }).unknown();\n\n            Helper.validate(schema, [\n                [{ a: { b: 5 } }, true],\n                [{ a: { b: 'x' } }, false, {\n                    message: '\"a.b\" must be a number',\n                    path: ['a', 'b'],\n                    type: 'number.base',\n                    context: { label: 'a.b', key: 'b', value: 'x' }\n                }],\n                [{ a: { b: 5 }, c: 'ignore' }, true],\n                [{ a: { b: 5, c: 'ignore' } }, false, {\n                    message: '\"a.c\" is not allowed',\n                    path: ['a', 'c'],\n                    type: 'object.unknown',\n                    context: { child: 'c', label: 'a.c', key: 'c', value: 'ignore' }\n                }]\n            ]);\n        });\n\n        it('forbids local unknown without applying to keys', () => {\n\n            const schema = Joi.object({\n                a: Joi.object({\n                    b: Joi.number()\n                }).unknown()\n            }).prefs({ allowUnknown: false });\n\n            Helper.validate(schema, [\n                [{ a: { b: 5 } }, true],\n                [{ a: { b: 'x' } }, false, {\n                    message: '\"a.b\" must be a number',\n                    path: ['a', 'b'],\n                    type: 'number.base',\n                    context: { label: 'a.b', key: 'b', value: 'x' }\n                }],\n                [{ a: { b: 5 }, c: 'ignore' }, false, {\n                    message: '\"c\" is not allowed',\n                    path: ['c'],\n                    type: 'object.unknown',\n                    context: { child: 'c', label: 'c', key: 'c', value: 'ignore' }\n                }],\n                [{ a: { b: 5, c: 'ignore' } }, true]\n            ]);\n        });\n\n        it('overrides stripUnknown at a local level', () => {\n\n            const schema = Joi.object({\n                a: Joi.object({\n                    b: Joi.number(),\n                    c: Joi.object({\n                        d: Joi.number()\n                    })\n                }).unknown()\n            }).prefs({ allowUnknown: false, stripUnknown: true });\n\n            Helper.validate(schema, [\n                [{ a: { b: 5 } }, true, { a: { b: 5 } }],\n                [{ a: { b: 'x' } }, false, {\n                    message: '\"a.b\" must be a number',\n                    path: ['a', 'b'],\n                    type: 'number.base',\n                    context: { label: 'a.b', key: 'b', value: 'x' }\n                }],\n                [{ a: { b: 5 }, d: 'ignore' }, true, { a: { b: 5 } }],\n                [{ a: { b: 5, d: 'ignore' } }, true, { a: { b: 5, d: 'ignore' } }],\n                [{ a: { b: 5, c: { e: 'ignore' } } }, true, { a: { b: 5, c: {} } }]\n            ]);\n        });\n    });\n\n    describe('with()', () => {\n\n        it('validated with', () => {\n\n            const schema = Joi.object({\n                txt: Joi.string(),\n                upc: Joi.string()\n            }).with('txt', 'upc');\n\n            Helper.validate(schema, { abortEarly: false }, [[{ txt: 'a' }, false, {\n                message: '\"txt\" missing required peer \"upc\"',\n                details: [{\n                    message: '\"txt\" missing required peer \"upc\"',\n                    path: [],\n                    type: 'object.with',\n                    context: {\n                        main: 'txt',\n                        mainWithLabel: 'txt',\n                        peer: 'upc',\n                        peerWithLabel: 'upc',\n                        label: 'value',\n                        value: { txt: 'a' }\n                    }\n                }]\n            }]]);\n\n            Helper.validate(schema, [\n                [{ upc: 'test' }, true],\n                [{ txt: 'test' }, false, {\n                    message: '\"txt\" missing required peer \"upc\"',\n                    path: [],\n                    type: 'object.with',\n                    context: {\n                        main: 'txt',\n                        mainWithLabel: 'txt',\n                        peer: 'upc',\n                        peerWithLabel: 'upc',\n                        label: 'value',\n                        value: { txt: 'test' }\n                    }\n                }],\n                [{ txt: 'test', upc: null }, false, {\n                    message: '\"upc\" must be a string',\n                    path: ['upc'],\n                    type: 'string.base',\n                    context: { value: null, label: 'upc', key: 'upc' }\n                }],\n                [{ txt: 'test', upc: '' }, false, {\n                    message: '\"upc\" is not allowed to be empty',\n                    path: ['upc'],\n                    type: 'string.empty',\n                    context: { value: '', label: 'upc', key: 'upc' }\n                }],\n                [{ txt: 'test', upc: undefined }, false, {\n                    message: '\"txt\" missing required peer \"upc\"',\n                    path: [],\n                    type: 'object.with',\n                    context: {\n                        main: 'txt',\n                        mainWithLabel: 'txt',\n                        peer: 'upc',\n                        peerWithLabel: 'upc',\n                        label: 'value',\n                        value: { txt: 'test', upc: undefined }\n                    }\n                }],\n                [{ txt: 'test', upc: 'test' }, true]\n            ]);\n        });\n\n        it('errors when a parameter is not a string', () => {\n\n            let error;\n            try {\n                Joi.object().with({});\n                error = false;\n            }\n            catch {\n                error = true;\n            }\n\n            expect(error).to.equal(true);\n\n            try {\n                Joi.object().with(123);\n                error = false;\n            }\n            catch {\n                error = true;\n            }\n\n            expect(error).to.equal(true);\n        });\n\n        it('validates when key is an empty string', () => {\n\n            const schema = Joi.object().with('', 'b');\n            Helper.validate(schema, [\n                [{ c: 'hi', d: 'there' }, true]\n            ]);\n        });\n\n        it('applies labels', () => {\n\n            const schema = Joi.object({\n                a: Joi.number().label('first'),\n                b: Joi.string().label('second')\n            }).with('a', ['b']);\n            Helper.validate(schema, [[{ a: 1 }, false, {\n                message: '\"first\" missing required peer \"second\"',\n                path: [],\n                type: 'object.with',\n                context: {\n                    main: 'a',\n                    mainWithLabel: 'first',\n                    peer: 'b',\n                    peerWithLabel: 'second',\n                    label: 'value',\n                    value: { a: 1 }\n                }\n            }]]);\n        });\n\n        it('allows nested objects', () => {\n\n            const schema = Joi.object({\n                a: Joi.string(),\n                b: Joi.object({ c: Joi.string(), d: Joi.number() }),\n                d: Joi.number()\n            }).with('a', 'b.c');\n\n            Helper.validate(schema, [\n                [{ a: 'test', b: { c: 'test2' } }, true],\n                [{ a: 'test', b: { d: 80 } }, false, {\n                    message: '\"a\" missing required peer \"b.c\"',\n                    path: [],\n                    type: 'object.with',\n                    context: {\n                        main: 'a',\n                        mainWithLabel: 'a',\n                        peer: 'b.c',\n                        peerWithLabel: 'b.c',\n                        label: 'value',\n                        value: { a: 'test', b: { d: 80 } }\n                    }\n                }]\n            ]);\n\n            const schema2 = Joi.object({\n                a: Joi.object({ b: Joi.string() }),\n                b: Joi.object({ c: Joi.string() })\n            }).with('a.b', 'b.c');\n\n            Helper.validate(schema2, [\n                [{ a: { b: 'test' }, b: { c: 'test2' } }, true],\n                [{ a: { b: 'test' }, b: {} }, false, {\n                    message: '\"a.b\" missing required peer \"b.c\"',\n                    path: [],\n                    type: 'object.with',\n                    context: {\n                        main: 'a.b',\n                        mainWithLabel: 'a.b',\n                        peer: 'b.c',\n                        peerWithLabel: 'b.c',\n                        label: 'value',\n                        value: { a: { b: 'test' }, b: {} }\n                    }\n                }]\n            ]);\n        });\n\n        it('allows nested keys in functions', () => {\n\n            const schema = Joi.object({\n                a: Joi.string(),\n                b: Joi.function().keys({ c: Joi.string(), d: Joi.number() }),\n                d: Joi.number()\n            })\n                .with('a', 'b.c');\n\n            Helper.validate(schema, [\n                [{ a: 'test', b: Object.assign(() => { }, { c: 'test2' }) }, true, Helper.skip],\n                [{ a: 'test', b: Object.assign(() => { }, { d: 80 }) }, false, '\"a\" missing required peer \"b.c\"']\n            ]);\n        });\n\n        it('applies labels with nested objects', () => {\n\n            const schema = Joi.object({\n                a: Joi.number().label('first'),\n                b: Joi.object({\n                    c: Joi.string().label('second'),\n                    d: Joi.number()\n                })\n            })\n                .with('a', ['b.c']);\n\n            Helper.validate(schema, [[{ a: 1, b: { d: 2 } }, false, {\n                message: '\"first\" missing required peer \"b.second\"',\n                path: [],\n                type: 'object.with',\n                context: {\n                    main: 'a',\n                    mainWithLabel: 'first',\n                    peer: 'b.c',\n                    peerWithLabel: 'b.second',\n                    label: 'value',\n                    value: { a: 1, b: { d: 2 } }\n                }\n            }]]);\n\n            const schema2 = Joi.object({\n                a: Joi.object({\n                    b: Joi.string().label('first')\n                }),\n                b: Joi.object({\n                    c: Joi.string().label('second')\n                })\n            })\n                .with('a.b', ['b.c']);\n\n            Helper.validate(schema2, [[{ a: { b: 'test' }, b: {} }, false, {\n                message: '\"a.first\" missing required peer \"b.second\"',\n                path: [],\n                type: 'object.with',\n                context: {\n                    main: 'a.b',\n                    mainWithLabel: 'a.first',\n                    peer: 'b.c',\n                    peerWithLabel: 'b.second',\n                    label: 'value',\n                    value: { a: { b: 'test' }, b: {} }\n                }\n            }]]);\n        });\n\n        it('handles period in key names', () => {\n\n            const schema = Joi.object({\n                'x.from': Joi.string().lowercase().email(),\n                'x.url': Joi.string().uri({ scheme: ['https'] })\n            })\n                .with('x.from', 'x.url', { separator: false });\n\n            const test = { 'x.url': 'https://example.com', 'x.from': 'test@example.com' };\n            Helper.validate(schema, [[test, true, test]]);\n        });\n    });\n\n    describe('without()', () => {\n\n        it('validated without', () => {\n\n            const schema = Joi.object({\n                txt: Joi.string(),\n                upc: Joi.string()\n            }).without('txt', 'upc');\n\n            Helper.validate(schema, { abortEarly: false }, [[{ txt: 'a', upc: 'b' }, false, {\n                message: '\"txt\" conflict with forbidden peer \"upc\"',\n                details: [{\n                    message: '\"txt\" conflict with forbidden peer \"upc\"',\n                    path: [],\n                    type: 'object.without',\n                    context: {\n                        main: 'txt',\n                        mainWithLabel: 'txt',\n                        peer: 'upc',\n                        peerWithLabel: 'upc',\n                        label: 'value',\n                        value: { txt: 'a', upc: 'b' }\n                    }\n                }]\n            }]]);\n\n            Helper.validate(schema, [\n                [{ upc: 'test' }, true],\n                [{ txt: 'test' }, true],\n                [{ txt: 'test', upc: null }, false, {\n                    message: '\"upc\" must be a string',\n                    path: ['upc'],\n                    type: 'string.base',\n                    context: { value: null, label: 'upc', key: 'upc' }\n                }],\n                [{ txt: 'test', upc: '' }, false, {\n                    message: '\"upc\" is not allowed to be empty',\n                    path: ['upc'],\n                    type: 'string.empty',\n                    context: { value: '', label: 'upc', key: 'upc' }\n                }],\n                [{ txt: 'test', upc: undefined }, true],\n                [{ txt: 'test', upc: 'test' }, false, {\n                    message: '\"txt\" conflict with forbidden peer \"upc\"',\n                    path: [],\n                    type: 'object.without',\n                    context: {\n                        main: 'txt',\n                        mainWithLabel: 'txt',\n                        peer: 'upc',\n                        peerWithLabel: 'upc',\n                        label: 'value',\n                        value: { txt: 'test', upc: 'test' }\n                    }\n                }]\n            ]);\n        });\n\n        it('errors when a parameter is not a string', () => {\n\n            let error;\n            try {\n                Joi.object().without({});\n                error = false;\n            }\n            catch {\n                error = true;\n            }\n\n            expect(error).to.equal(true);\n\n            try {\n                Joi.object().without(123);\n                error = false;\n            }\n            catch {\n                error = true;\n            }\n\n            expect(error).to.equal(true);\n        });\n\n        it('validates when key is an empty string', () => {\n\n            const schema = Joi.object().without('', 'b');\n            Helper.validate(schema, [\n                [{ a: 'hi', b: 'there' }, true]\n            ]);\n        });\n\n        it('validates when key is stripped', () => {\n\n            const schema = Joi.object({\n                a: Joi.any().strip(),\n                b: Joi.any()\n            }).without('a', 'b');\n\n            Helper.validate(schema, [\n                [{ a: 'hi', b: 'there' }, true, { b: 'there' }]\n            ]);\n        });\n\n        it('applies labels', () => {\n\n            const schema = Joi.object({\n                a: Joi.number().label('first'),\n                b: Joi.string().label('second')\n            }).without('a', ['b']);\n            Helper.validate(schema, [[{ a: 1, b: 'b' }, false, {\n                message: '\"first\" conflict with forbidden peer \"second\"',\n                path: [],\n                type: 'object.without',\n                context: {\n                    main: 'a',\n                    mainWithLabel: 'first',\n                    peer: 'b',\n                    peerWithLabel: 'second',\n                    label: 'value',\n                    value: { a: 1, b: 'b' }\n                }\n            }]]);\n        });\n\n        it('allows nested objects', () => {\n\n            const schema = Joi.object({\n                a: Joi.string(),\n                b: Joi.object({ c: Joi.string(), d: Joi.number() }),\n                d: Joi.number()\n            }).without('a', ['b.c', 'b.d']);\n\n            const sampleObject = { a: 'test', d: 9000 };\n            const sampleObject2 = { a: 'test', b: { d: 80 } };\n\n            Helper.validate(schema, [\n                [sampleObject, true],\n                [sampleObject2, false, {\n                    message: '\"a\" conflict with forbidden peer \"b.d\"',\n                    path: [],\n                    type: 'object.without',\n                    context: {\n                        main: 'a',\n                        mainWithLabel: 'a',\n                        peer: 'b.d',\n                        peerWithLabel: 'b.d',\n                        label: 'value',\n                        value: sampleObject2\n                    }\n                }]\n            ]);\n        });\n\n        it('allows nested keys in functions', () => {\n\n            const schema = Joi.object({\n                a: Joi.string(),\n                b: Joi.function().keys({ c: Joi.string(), d: Joi.number() }),\n                d: Joi.number()\n            })\n                .without('a', ['b.c', 'b.d']);\n\n            const sampleObject = { a: 'test', d: 9000 };\n            const sampleObject2 = { a: 'test', b: Object.assign(() => { }, { d: 80 }) };\n\n            Helper.validate(schema, [[sampleObject, true]]);\n\n            const error2 = schema.validate(sampleObject2).error;\n            expect(error2).to.be.an.error('\"a\" conflict with forbidden peer \"b.d\"');\n            expect(error2.details).to.equal([{\n                message: '\"a\" conflict with forbidden peer \"b.d\"',\n                path: [],\n                type: 'object.without',\n                context: {\n                    main: 'a',\n                    mainWithLabel: 'a',\n                    peer: 'b.d',\n                    peerWithLabel: 'b.d',\n                    label: 'value',\n                    value: error2.details[0].context.value\n                }\n            }]);\n        });\n\n        it('applies labels with nested objects', () => {\n\n            const schema = Joi.object({\n                a: Joi.number().label('first'),\n                b: Joi.object({\n                    c: Joi.string().label('second'),\n                    d: Joi.number()\n                })\n            })\n                .without('a', ['b.c']);\n\n            Helper.validate(schema, [[{ a: 1, b: { c: 'c' } }, false, {\n                message: '\"first\" conflict with forbidden peer \"b.second\"',\n                path: [],\n                type: 'object.without',\n                context: {\n                    main: 'a',\n                    mainWithLabel: 'first',\n                    peer: 'b.c',\n                    peerWithLabel: 'b.second',\n                    label: 'value',\n                    value: { a: 1, b: { c: 'c' } }\n                }\n            }]]);\n        });\n\n        it('validates keys with prefix characters', () => {\n\n            const schema = Joi.object({\n                $a: Joi.number(),\n                '#b': Joi.number(),\n                '/c': Joi.number()\n            })\n                .without('$a', ['#b', '/c']);\n\n            Helper.validate(schema, [\n                [{ $a: 1 }, true],\n                [{ '#b': 1 }, true],\n                [{ '/c': 1 }, true],\n                [{ $a: 1, '/c': 1 }, false, '\"$a\" conflict with forbidden peer \"/c\"']\n            ]);\n        });\n    });\n\n    describe('xor()', () => {\n\n        it('validates xor', () => {\n\n            const schema = Joi.object({\n                txt: Joi.string(),\n                upc: Joi.string()\n            }).xor('txt', 'upc');\n\n            Helper.validate(schema, { abortEarly: false }, [[{}, false, {\n                message: '\"value\" must contain at least one of [txt, upc]',\n                details: [{\n                    message: '\"value\" must contain at least one of [txt, upc]',\n                    path: [],\n                    type: 'object.missing',\n                    context: {\n                        peers: ['txt', 'upc'],\n                        peersWithLabels: ['txt', 'upc'],\n                        label: 'value',\n                        value: {}\n                    }\n                }]\n            }]]);\n\n            Helper.validate(schema, [\n                [{ upc: null }, false, {\n                    message: '\"upc\" must be a string',\n                    path: ['upc'],\n                    type: 'string.base',\n                    context: { value: null, label: 'upc', key: 'upc' }\n                }],\n                [{ upc: 'test' }, true],\n                [{ txt: null }, false, {\n                    message: '\"txt\" must be a string',\n                    path: ['txt'],\n                    type: 'string.base',\n                    context: { value: null, label: 'txt', key: 'txt' }\n                }],\n                [{ txt: 'test' }, true],\n                [{ txt: 'test', upc: null }, false, {\n                    message: '\"upc\" must be a string',\n                    path: ['upc'],\n                    type: 'string.base',\n                    context: { value: null, label: 'upc', key: 'upc' }\n                }],\n                [{ txt: 'test', upc: '' }, false, {\n                    message: '\"upc\" is not allowed to be empty',\n                    path: ['upc'],\n                    type: 'string.empty',\n                    context: { value: '', label: 'upc', key: 'upc' }\n                }],\n                [{ txt: '', upc: 'test' }, false, {\n                    message: '\"txt\" is not allowed to be empty',\n                    path: ['txt'],\n                    type: 'string.empty',\n                    context: { value: '', label: 'txt', key: 'txt' }\n                }],\n                [{ txt: null, upc: 'test' }, false, {\n                    message: '\"txt\" must be a string',\n                    path: ['txt'],\n                    type: 'string.base',\n                    context: { value: null, label: 'txt', key: 'txt' }\n                }],\n                [{ txt: undefined, upc: 'test' }, true],\n                [{ txt: 'test', upc: undefined }, true],\n                [{ txt: '', upc: undefined }, false, {\n                    message: '\"txt\" is not allowed to be empty',\n                    path: ['txt'],\n                    type: 'string.empty',\n                    context: { value: '', label: 'txt', key: 'txt' }\n                }],\n                [{ txt: '', upc: '' }, false, {\n                    message: '\"txt\" is not allowed to be empty',\n                    path: ['txt'],\n                    type: 'string.empty',\n                    context: { value: '', label: 'txt', key: 'txt' }\n                }],\n                [{ txt: 'test', upc: 'test' }, false, {\n                    message: '\"value\" contains a conflict between exclusive peers [txt, upc]',\n                    path: [],\n                    type: 'object.xor',\n                    context: {\n                        peers: ['txt', 'upc'],\n                        peersWithLabels: ['txt', 'upc'],\n                        present: ['txt', 'upc'],\n                        presentWithLabels: ['txt', 'upc'],\n                        label: 'value',\n                        value: { txt: 'test', upc: 'test' }\n                    }\n                }]\n            ]);\n        });\n\n        it('validates multiple peers xor', () => {\n\n            const schema = Joi.object({\n                txt: Joi.string(),\n                upc: Joi.string(),\n                code: Joi.string()\n            }).xor('txt', 'upc', 'code');\n\n            Helper.validate(schema, [\n                [{ upc: 'test' }, true],\n                [{ txt: 'test' }, true],\n                [{}, false, {\n                    message: '\"value\" must contain at least one of [txt, upc, code]',\n                    path: [],\n                    type: 'object.missing',\n                    context: {\n                        peers: ['txt', 'upc', 'code'],\n                        peersWithLabels: ['txt', 'upc', 'code'],\n                        label: 'value',\n                        value: {}\n                    }\n                }]\n            ]);\n        });\n\n        it('validates xor with number types', () => {\n\n            const schema = Joi.object({\n                code: Joi.number(),\n                upc: Joi.number()\n            }).xor('code', 'upc');\n\n            Helper.validate(schema, [\n                [{ upc: 123 }, true],\n                [{ code: 456 }, true],\n                [{ code: 456, upc: 123 }, false, {\n                    message: '\"value\" contains a conflict between exclusive peers [code, upc]',\n                    path: [],\n                    type: 'object.xor',\n                    context: {\n                        peers: ['code', 'upc'],\n                        peersWithLabels: ['code', 'upc'],\n                        present: ['code', 'upc'],\n                        presentWithLabels: ['code', 'upc'],\n                        label: 'value',\n                        value: { code: 456, upc: 123 }\n                    }\n                }],\n                [{}, false, {\n                    message: '\"value\" must contain at least one of [code, upc]',\n                    path: [],\n                    type: 'object.missing',\n                    context: {\n                        peers: ['code', 'upc'],\n                        peersWithLabels: ['code', 'upc'],\n                        label: 'value',\n                        value: {}\n                    }\n                }]\n            ]);\n        });\n\n        it('validates xor when empty value of peer allowed', () => {\n\n            const schema = Joi.object({\n                code: Joi.string(),\n                upc: Joi.string().allow('')\n            })\n                .xor('code', 'upc');\n\n            Helper.validate(schema, [\n                [{ upc: '' }, true],\n                [{ upc: '123' }, true],\n                [{ code: '456' }, true],\n                [{ code: '456', upc: '' }, false, {\n                    message: '\"value\" contains a conflict between exclusive peers [code, upc]',\n                    path: [],\n                    type: 'object.xor',\n                    context: {\n                        peers: ['code', 'upc'],\n                        peersWithLabels: ['code', 'upc'],\n                        present: ['code', 'upc'],\n                        presentWithLabels: ['code', 'upc'],\n                        label: 'value',\n                        value: { code: '456', upc: '' }\n                    }\n                }],\n                [{}, false, {\n                    message: '\"value\" must contain at least one of [code, upc]',\n                    path: [],\n                    type: 'object.missing',\n                    context: {\n                        peers: ['code', 'upc'],\n                        peersWithLabels: ['code', 'upc'],\n                        label: 'value',\n                        value: {}\n                    }\n                }]\n            ]);\n        });\n\n        it('errors when a parameter is not a string', () => {\n\n            expect(() => Joi.object().xor({})).to.throw();\n            expect(() => Joi.object().xor(123)).to.throw();\n        });\n\n        it('applies labels without any peer', () => {\n\n            const schema = Joi.object({\n                a: Joi.number().label('first'),\n                b: Joi.string().label('second')\n            })\n                .xor('a', 'b');\n\n            Helper.validate(schema, [[{}, false, {\n                message: '\"value\" must contain at least one of [first, second]',\n                path: [],\n                type: 'object.missing',\n                context: {\n                    peers: ['a', 'b'],\n                    peersWithLabels: ['first', 'second'],\n                    label: 'value',\n                    value: {}\n                }\n            }]]);\n        });\n\n        it('applies labels with too many peers', () => {\n\n            const schema = Joi.object({\n                a: Joi.number().label('first'),\n                b: Joi.string().label('second')\n            })\n                .xor('a', 'b');\n\n            Helper.validate(schema, [[{ a: 1, b: 'b' }, false, {\n                message: '\"value\" contains a conflict between exclusive peers [first, second]',\n                path: [],\n                type: 'object.xor',\n                context: {\n                    peers: ['a', 'b'],\n                    peersWithLabels: ['first', 'second'],\n                    present: ['a', 'b'],\n                    presentWithLabels: ['first', 'second'],\n                    label: 'value',\n                    value: { a: 1, b: 'b' }\n                }\n            }]]);\n        });\n\n        it('applies labels with too many peers', () => {\n\n            const schema = Joi.object({\n                a: Joi.number().label('first'),\n                b: Joi.string().label('second'),\n                c: Joi.string().label('third'),\n                d: Joi.string().label('fourth')\n            })\n                .xor('a', 'b', 'c', 'd');\n\n            Helper.validate(schema, [[{ a: 1, b: 'b', d: 'd' }, false, {\n                message: '\"value\" contains a conflict between exclusive peers [first, second, third, fourth]',\n                path: [],\n                type: 'object.xor',\n                context: {\n                    peers: ['a', 'b', 'c', 'd'],\n                    peersWithLabels: ['first', 'second', 'third', 'fourth'],\n                    present: ['a', 'b', 'd'],\n                    presentWithLabels: ['first', 'second', 'fourth'],\n                    label: 'value',\n                    value: { a: 1, b: 'b', d: 'd' }\n                }\n            }]]);\n        });\n\n        it('allows nested objects', () => {\n\n            const schema = Joi.object({\n                a: Joi.string(),\n                b: Joi.object({ c: Joi.string(), d: Joi.number() }),\n                d: Joi.number()\n            })\n                .xor('a', 'b.c');\n\n            Helper.validate(schema, [\n                [{ a: 'test', b: { d: 80 } }, true],\n                [{ a: 'test', b: { c: 'test2' } }, false, '\"value\" contains a conflict between exclusive peers [a, b.c]']\n            ]);\n        });\n\n        it('allows nested keys in functions', () => {\n\n            const schema = Joi.object({\n                a: Joi.string(),\n                b: Joi.function().keys({ c: Joi.string(), d: Joi.number() }),\n                d: Joi.number()\n            })\n                .xor('a', 'b.c');\n\n            Helper.validate(schema, [\n                [{ a: 'test', b: Object.assign(() => { }, { d: 80 }) }, true, Helper.skip],\n                [{ a: 'test', b: Object.assign(() => { }, { c: 'test2' }) }, false, '\"value\" contains a conflict between exclusive peers [a, b.c]']\n            ]);\n        });\n\n        it('applies labels without any nested peers', () => {\n\n            const schema = Joi.object({\n                a: Joi.number().label('first'),\n                b: Joi.object({\n                    c: Joi.string().label('second'),\n                    d: Joi.number()\n                })\n            })\n                .xor('a', 'b.c');\n\n            Helper.validate(schema, [[{}, false, {\n                message: '\"value\" must contain at least one of [first, b.second]',\n                path: [],\n                type: 'object.missing',\n                context: {\n                    peers: ['a', 'b.c'],\n                    peersWithLabels: ['first', 'b.second'],\n                    label: 'value',\n                    value: {}\n                }\n            }]]);\n        });\n\n        it('applies labels with too many nested peers', () => {\n\n            const schema = Joi.object({\n                a: Joi.number().label('first'),\n                b: Joi.object({\n                    c: Joi.string().label('second'),\n                    d: Joi.number()\n                })\n            })\n                .xor('a', 'b.c');\n\n            Helper.validate(schema, [[{ a: 1, b: { c: 'c' } }, false, {\n                message: '\"value\" contains a conflict between exclusive peers [first, b.second]',\n                path: [],\n                type: 'object.xor',\n                context: {\n                    peers: ['a', 'b.c'],\n                    peersWithLabels: ['first', 'b.second'],\n                    present: ['a', 'b.c'],\n                    presentWithLabels: ['first', 'b.second'],\n                    label: 'value',\n                    value: { a: 1, b: { c: 'c' } }\n                }\n            }]]);\n        });\n\n        it('handles period in key names', () => {\n\n            const schema = Joi.object({\n                'x.from': Joi.string().lowercase().email(),\n                'x.url': Joi.string().uri({ scheme: ['https'] })\n            })\n                .xor('x.from', 'x.url', { separator: false });\n\n            const test = { 'x.url': 'https://example.com' };\n            Helper.validate(schema, [[test, true, test]]);\n        });\n    });\n});\n"
  },
  {
    "path": "test/types/string.js",
    "content": "'use strict';\n\nconst Code = require('@hapi/code');\nconst Lab = require('@hapi/lab');\nconst Joi = require('../..');\n\nconst Helper = require('../helper');\n\n\nconst internals = {};\n\n\nconst { describe, it } = exports.lab = Lab.script();\nconst { expect } = Code;\n\n\nprocess.env.TZ = 'utc'; // Needed for timezone sensitive tests\n\n\ndescribe('string', () => {\n\n    it('should throw an exception if arguments were passed.', () => {\n\n        expect(() => Joi.string('invalid argument.')).to.throw('The string type does not allow arguments');\n    });\n\n    it('blocks empty strings by default', () => {\n\n        Helper.validate(Joi.string(), [['', false, '\"value\" is not allowed to be empty']]);\n        Helper.validate(Joi.string().allow('x'), [['', false, '\"value\" is not allowed to be empty']]);\n        Helper.validate(Joi.string().allow(''), [['', true]]);\n    });\n\n    it('fails on boolean', () => {\n\n        const schema = Joi.string();\n        Helper.validate(schema, [\n            [true, false, {\n                message: '\"value\" must be a string',\n                path: [],\n                type: 'string.base',\n                context: { value: true, label: 'value' }\n            }],\n            [false, false, {\n                message: '\"value\" must be a string',\n                path: [],\n                type: 'string.base',\n                context: { value: false, label: 'value' }\n            }]\n        ]);\n    });\n\n    it('fails on integer', () => {\n\n        const schema = Joi.string();\n        Helper.validate(schema, [\n            [123, false, {\n                message: '\"value\" must be a string',\n                path: [],\n                type: 'string.base',\n                context: { value: 123, label: 'value' }\n            }],\n            [0, false, {\n                message: '\"value\" must be a string',\n                path: [],\n                type: 'string.base',\n                context: { value: 0, label: 'value' }\n            }],\n            ['123', true],\n            ['0', true]\n        ]);\n    });\n\n    it('allows undefined, deny empty string', () => {\n\n        Helper.validate(Joi.string(), [\n            [undefined, true],\n            ['', false, {\n                message: '\"value\" is not allowed to be empty',\n                path: [],\n                type: 'string.empty',\n                context: { value: '', label: 'value' }\n            }]\n        ]);\n    });\n\n    it('validates null', () => {\n\n        Helper.validate(Joi.string(), [[null, false, {\n            message: '\"value\" must be a string',\n            path: [],\n            type: 'string.base',\n            context: { value: null, label: 'value' }\n        }]]);\n\n        expect(Joi.string().validate(null).error.annotate()).to.equal('\"value\" must be a string');\n    });\n\n    it('supports own properties references', () => {\n\n        const schema = Joi.string()\n            .when('.length', { is: 3, then: 'abc', break: true })\n            .when('.0', { is: 'a', then: 'axxx' });\n\n        Helper.validate(schema, [\n            ['abc', true],\n            ['axxx', true]\n        ]);\n    });\n\n    describe('allow()', () => {\n\n        it('validates combination of allow(\\'\\') and min', () => {\n\n            const rule = Joi.string().allow('').min(3);\n            Helper.validate(rule, [\n                ['x', false, {\n                    message: '\"value\" length must be at least 3 characters long',\n                    path: [],\n                    type: 'string.min',\n                    context: {\n                        limit: 3,\n                        value: 'x',\n                        encoding: undefined,\n                        label: 'value'\n                    }\n                }],\n                ['123', true],\n                ['1234', true],\n                ['', true],\n                [null, false, {\n                    message: '\"value\" must be a string',\n                    path: [],\n                    type: 'string.base',\n                    context: { value: null, label: 'value' }\n                }]\n            ]);\n        });\n\n        it('validates combination of allow(\\'\\') and max', () => {\n\n            const rule = Joi.string().allow('').max(3);\n            Helper.validate(rule, [\n                ['x', true],\n                ['123', true],\n                ['1234', false, {\n                    message: '\"value\" length must be less than or equal to 3 characters long',\n                    path: [],\n                    type: 'string.max',\n                    context: {\n                        limit: 3,\n                        value: '1234',\n                        encoding: undefined,\n                        label: 'value'\n                    }\n                }],\n                ['', true],\n                [null, false, {\n                    message: '\"value\" must be a string',\n                    path: [],\n                    type: 'string.base',\n                    context: { value: null, label: 'value' }\n                }]\n            ]);\n        });\n\n        it('validates combination of null allowed and max', () => {\n\n            const rule = Joi.string().allow(null).max(3);\n            Helper.validate(rule, [\n                ['x', true],\n                ['123', true],\n                ['1234', false, {\n                    message: '\"value\" length must be less than or equal to 3 characters long',\n                    path: [],\n                    type: 'string.max',\n                    context: {\n                        limit: 3,\n                        value: '1234',\n                        encoding: undefined,\n                        label: 'value'\n                    }\n                }],\n                ['', false, {\n                    message: '\"value\" is not allowed to be empty',\n                    path: [],\n                    type: 'string.empty',\n                    context: { value: '', label: 'value' }\n                }],\n                [null, true]\n            ]);\n        });\n\n        it('validates null with allow(null)', () => {\n\n            Helper.validate(Joi.string().allow(null), [\n                [null, true]\n            ]);\n        });\n\n        it('validates \"\" (empty string) with allow(\\'\\')', () => {\n\n            Helper.validate(Joi.string().allow(''), [\n                ['', true],\n                ['', true]\n            ]);\n        });\n    });\n\n    describe('alphanum()', () => {\n\n        it('validates alphanum', () => {\n\n            const schema = Joi.string().alphanum();\n            Helper.validate(schema, [\n                ['w0rld of w4lm4rtl4bs', false, {\n                    message: '\"value\" must only contain alpha-numeric characters',\n                    path: [],\n                    type: 'string.alphanum',\n                    context: { value: 'w0rld of w4lm4rtl4bs', label: 'value' }\n                }],\n                ['w0rldofw4lm4rtl4bs', true],\n                ['abcd#f?h1j orly?', false, {\n                    message: '\"value\" must only contain alpha-numeric characters',\n                    path: [],\n                    type: 'string.alphanum',\n                    context: { value: 'abcd#f?h1j orly?', label: 'value' }\n                }]\n            ]);\n        });\n\n        it('validates combination of min, max, and alphanum', () => {\n\n            const rule = Joi.string().min(2).max(3).alphanum();\n            Helper.validate(rule, [\n                ['x', false, {\n                    message: '\"value\" length must be at least 2 characters long',\n                    path: [],\n                    type: 'string.min',\n                    context: {\n                        limit: 2,\n                        value: 'x',\n                        encoding: undefined,\n                        label: 'value'\n                    }\n                }],\n                ['123', true],\n                ['1234', false, {\n                    message: '\"value\" length must be less than or equal to 3 characters long',\n                    path: [],\n                    type: 'string.max',\n                    context: {\n                        limit: 3,\n                        value: '1234',\n                        encoding: undefined,\n                        label: 'value'\n                    }\n                }],\n                ['12', true],\n                ['ab', true],\n                ['abc', true],\n                ['abcd', false, {\n                    message: '\"value\" length must be less than or equal to 3 characters long',\n                    path: [],\n                    type: 'string.max',\n                    context: {\n                        limit: 3,\n                        value: 'abcd',\n                        encoding: undefined,\n                        label: 'value'\n                    }\n                }],\n                ['*ab', false, {\n                    message: '\"value\" must only contain alpha-numeric characters',\n                    path: [],\n                    type: 'string.alphanum',\n                    context: { value: '*ab', label: 'value' }\n                }],\n                ['', false, {\n                    message: '\"value\" is not allowed to be empty',\n                    path: [],\n                    type: 'string.empty',\n                    context: { value: '', label: 'value' }\n                }],\n                [null, false, {\n                    message: '\"value\" must be a string',\n                    path: [],\n                    type: 'string.base',\n                    context: { value: null, label: 'value' }\n                }]\n            ]);\n        });\n\n        it('validates combination of min, max, alphanum, and allow(\\'\\')', () => {\n\n            const rule = Joi.string().min(2).max(3).alphanum().allow('');\n            Helper.validate(rule, [\n                ['x', false, {\n                    message: '\"value\" length must be at least 2 characters long',\n                    path: [],\n                    type: 'string.min',\n                    context: {\n                        limit: 2,\n                        value: 'x',\n                        encoding: undefined,\n                        label: 'value'\n                    }\n                }],\n                ['123', true],\n                ['1234', false, {\n                    message: '\"value\" length must be less than or equal to 3 characters long',\n                    path: [],\n                    type: 'string.max',\n                    context: {\n                        limit: 3,\n                        value: '1234',\n                        encoding: undefined,\n                        label: 'value'\n                    }\n                }],\n                ['12', true],\n                ['ab', true],\n                ['abc', true],\n                ['abcd', false, {\n                    message: '\"value\" length must be less than or equal to 3 characters long',\n                    path: [],\n                    type: 'string.max',\n                    context: {\n                        limit: 3,\n                        value: 'abcd',\n                        encoding: undefined,\n                        label: 'value'\n                    }\n                }],\n                ['*ab', false, {\n                    message: '\"value\" must only contain alpha-numeric characters',\n                    path: [],\n                    type: 'string.alphanum',\n                    context: { value: '*ab', label: 'value' }\n                }],\n                ['', true],\n                [null, false, {\n                    message: '\"value\" must be a string',\n                    path: [],\n                    type: 'string.base',\n                    context: { value: null, label: 'value' }\n                }]\n            ]);\n        });\n\n        it('validates combination of min, max, alphanum, and required', () => {\n\n            const rule = Joi.string().min(2).max(3).alphanum().required();\n            Helper.validate(rule, [\n                ['x', false, {\n                    message: '\"value\" length must be at least 2 characters long',\n                    path: [],\n                    type: 'string.min',\n                    context: {\n                        limit: 2,\n                        value: 'x',\n                        encoding: undefined,\n                        label: 'value'\n                    }\n                }],\n                ['123', true],\n                ['1234', false, {\n                    message: '\"value\" length must be less than or equal to 3 characters long',\n                    path: [],\n                    type: 'string.max',\n                    context: {\n                        limit: 3,\n                        value: '1234',\n                        encoding: undefined,\n                        label: 'value'\n                    }\n                }],\n                ['12', true],\n                ['ab', true],\n                ['abc', true],\n                ['abcd', false, {\n                    message: '\"value\" length must be less than or equal to 3 characters long',\n                    path: [],\n                    type: 'string.max',\n                    context: {\n                        limit: 3,\n                        value: 'abcd',\n                        encoding: undefined,\n                        label: 'value'\n                    }\n                }],\n                ['*ab', false, {\n                    message: '\"value\" must only contain alpha-numeric characters',\n                    path: [],\n                    type: 'string.alphanum',\n                    context: { value: '*ab', label: 'value' }\n                }],\n                ['', false, {\n                    message: '\"value\" is not allowed to be empty',\n                    path: [],\n                    type: 'string.empty',\n                    context: { value: '', label: 'value' }\n                }],\n                [null, false, {\n                    message: '\"value\" must be a string',\n                    path: [],\n                    type: 'string.base',\n                    context: { value: null, label: 'value' }\n                }]\n            ]);\n        });\n\n        it('validates combination of min, max, alphanum, and regex', () => {\n\n            const rule = Joi.string().min(2).max(3).alphanum().regex(/^a/);\n            Helper.validate(rule, [\n                ['x', false, {\n                    message: '\"value\" length must be at least 2 characters long',\n                    path: [],\n                    type: 'string.min',\n                    context: {\n                        limit: 2,\n                        value: 'x',\n                        encoding: undefined,\n                        label: 'value'\n                    }\n                }],\n                ['123', false, {\n                    message: '\"value\" with value \"123\" fails to match the required pattern: /^a/',\n                    path: [],\n                    type: 'string.pattern.base',\n                    context: {\n                        name: undefined,\n                        regex: /^a/,\n                        value: '123',\n                        label: 'value'\n                    }\n                }],\n                ['1234', false, {\n                    message: '\"value\" length must be less than or equal to 3 characters long',\n                    path: [],\n                    type: 'string.max',\n                    context: {\n                        limit: 3,\n                        value: '1234',\n                        encoding: undefined,\n                        label: 'value'\n                    }\n                }],\n                ['12', false, {\n                    message: '\"value\" with value \"12\" fails to match the required pattern: /^a/',\n                    path: [],\n                    type: 'string.pattern.base',\n                    context: {\n                        name: undefined,\n                        regex: /^a/,\n                        value: '12',\n                        label: 'value'\n                    }\n                }],\n                ['ab', true],\n                ['abc', true],\n                ['a2c', true],\n                ['abcd', false, {\n                    message: '\"value\" length must be less than or equal to 3 characters long',\n                    path: [],\n                    type: 'string.max',\n                    context: {\n                        limit: 3,\n                        value: 'abcd',\n                        encoding: undefined,\n                        label: 'value'\n                    }\n                }],\n                ['*ab', false, {\n                    message: '\"value\" must only contain alpha-numeric characters',\n                    path: [],\n                    type: 'string.alphanum',\n                    context: { value: '*ab', label: 'value' }\n                }],\n                ['', false, {\n                    message: '\"value\" is not allowed to be empty',\n                    path: [],\n                    type: 'string.empty',\n                    context: { value: '', label: 'value' }\n                }],\n                [null, false, {\n                    message: '\"value\" must be a string',\n                    path: [],\n                    type: 'string.base',\n                    context: { value: null, label: 'value' }\n                }]\n            ]);\n        });\n\n        it('validates combination of min, max, alphanum, required, and regex', () => {\n\n            const rule = Joi.string().min(2).max(3).alphanum().required().regex(/^a/);\n            Helper.validate(rule, [\n                ['x', false, {\n                    message: '\"value\" length must be at least 2 characters long',\n                    path: [],\n                    type: 'string.min',\n                    context: {\n                        limit: 2,\n                        value: 'x',\n                        encoding: undefined,\n                        label: 'value'\n                    }\n                }],\n                ['123', false, {\n                    message: '\"value\" with value \"123\" fails to match the required pattern: /^a/',\n                    path: [],\n                    type: 'string.pattern.base',\n                    context: {\n                        name: undefined,\n                        regex: /^a/,\n                        value: '123',\n                        label: 'value'\n                    }\n                }],\n                ['1234', false, {\n                    message: '\"value\" length must be less than or equal to 3 characters long',\n                    path: [],\n                    type: 'string.max',\n                    context: {\n                        limit: 3,\n                        value: '1234',\n                        encoding: undefined,\n                        label: 'value'\n                    }\n                }],\n                ['12', false, {\n                    message: '\"value\" with value \"12\" fails to match the required pattern: /^a/',\n                    path: [],\n                    type: 'string.pattern.base',\n                    context: {\n                        name: undefined,\n                        regex: /^a/,\n                        value: '12',\n                        label: 'value'\n                    }\n                }],\n                ['ab', true],\n                ['abc', true],\n                ['a2c', true],\n                ['abcd', false, {\n                    message: '\"value\" length must be less than or equal to 3 characters long',\n                    path: [],\n                    type: 'string.max',\n                    context: {\n                        limit: 3,\n                        value: 'abcd',\n                        encoding: undefined,\n                        label: 'value'\n                    }\n                }],\n                ['*ab', false, {\n                    message: '\"value\" must only contain alpha-numeric characters',\n                    path: [],\n                    type: 'string.alphanum',\n                    context: { value: '*ab', label: 'value' }\n                }],\n                ['', false, {\n                    message: '\"value\" is not allowed to be empty',\n                    path: [],\n                    type: 'string.empty',\n                    context: { value: '', label: 'value' }\n                }],\n                [null, false, {\n                    message: '\"value\" must be a string',\n                    path: [],\n                    type: 'string.base',\n                    context: { value: null, label: 'value' }\n                }]\n            ]);\n        });\n\n        it('validates combination of min, max, alphanum, allow(\\'\\'), and regex', () => {\n\n            const rule = Joi.string().min(2).max(3).alphanum().allow('').regex(/^a/);\n            Helper.validate(rule, [\n                ['x', false, {\n                    message: '\"value\" length must be at least 2 characters long',\n                    path: [],\n                    type: 'string.min',\n                    context: {\n                        limit: 2,\n                        value: 'x',\n                        encoding: undefined,\n                        label: 'value'\n                    }\n                }],\n                ['123', false, {\n                    message: '\"value\" with value \"123\" fails to match the required pattern: /^a/',\n                    path: [],\n                    type: 'string.pattern.base',\n                    context: {\n                        name: undefined,\n                        regex: /^a/,\n                        value: '123',\n                        label: 'value'\n                    }\n                }],\n                ['1234', false, {\n                    message: '\"value\" length must be less than or equal to 3 characters long',\n                    path: [],\n                    type: 'string.max',\n                    context: {\n                        limit: 3,\n                        value: '1234',\n                        encoding: undefined,\n                        label: 'value'\n                    }\n                }],\n                ['12', false, {\n                    message: '\"value\" with value \"12\" fails to match the required pattern: /^a/',\n                    path: [],\n                    type: 'string.pattern.base',\n                    context: {\n                        name: undefined,\n                        regex: /^a/,\n                        value: '12',\n                        label: 'value'\n                    }\n                }],\n                ['ab', true],\n                ['abc', true],\n                ['a2c', true],\n                ['abcd', false, {\n                    message: '\"value\" length must be less than or equal to 3 characters long',\n                    path: [],\n                    type: 'string.max',\n                    context: {\n                        limit: 3,\n                        value: 'abcd',\n                        encoding: undefined,\n                        label: 'value'\n                    }\n                }],\n                ['*ab', false, {\n                    message: '\"value\" must only contain alpha-numeric characters',\n                    path: [],\n                    type: 'string.alphanum',\n                    context: { value: '*ab', label: 'value' }\n                }],\n                ['', true],\n                [null, false, {\n                    message: '\"value\" must be a string',\n                    path: [],\n                    type: 'string.base',\n                    context: { value: null, label: 'value' }\n                }]\n            ]);\n        });\n    });\n\n    describe('base64()', () => {\n\n        it('validates the base64 options', () => {\n\n            expect(() => Joi.string().base64('a')).to.throw('Options must be of type object');\n            expect(() => Joi.string().base64({ paddingRequired: 'a' })).to.throw('paddingRequired must be boolean');\n            expect(() => Joi.string().base64({ urlSafe: 'a' })).to.throw('urlSafe must be boolean');\n        });\n\n        it('validates a base64 string with no options', () => {\n\n            const rule = Joi.string().base64();\n            Helper.validate(rule, [\n                ['YW55IGNhcm5hbCBwbGVhc3VyZS4=', true],\n                ['YW55IGNh+/5hbCBwbGVhc3VyZS4=', true],\n                ['=YW55IGNhcm5hbCBwbGVhc3VyZS4', false, {\n                    message: '\"value\" must be a valid base64 string',\n                    path: [],\n                    type: 'string.base64',\n                    context: {\n                        value: '=YW55IGNhcm5hbCBwbGVhc3VyZS4',\n                        label: 'value'\n                    }\n                }],\n                ['YW55IGNhcm5hb-_wbGVhc3VyZS4=', false, {\n                    message: '\"value\" must be a valid base64 string',\n                    path: [],\n                    type: 'string.base64',\n                    context: {\n                        value: 'YW55IGNhcm5hb-_wbGVhc3VyZS4=',\n                        label: 'value'\n                    }\n                }],\n                ['YW55IGNhcm5hbCBwbGVhc3VyZS4==', false, {\n                    message: '\"value\" must be a valid base64 string',\n                    path: [],\n                    type: 'string.base64',\n                    context: {\n                        value: 'YW55IGNhcm5hbCBwbGVhc3VyZS4==',\n                        label: 'value'\n                    }\n                }],\n                ['YW55IGNhcm5hbCBwbGVhc3VyZS4', false, {\n                    message: '\"value\" must be a valid base64 string',\n                    path: [],\n                    type: 'string.base64',\n                    context: {\n                        value: 'YW55IGNhcm5hbCBwbGVhc3VyZS4',\n                        label: 'value'\n                    }\n                }],\n                ['Y=', false, {\n                    message: '\"value\" must be a valid base64 string',\n                    path: [],\n                    type: 'string.base64',\n                    context: {\n                        value: 'Y=',\n                        label: 'value'\n                    }\n                }],\n                ['Y===', false, {\n                    message: '\"value\" must be a valid base64 string',\n                    path: [],\n                    type: 'string.base64',\n                    context: {\n                        value: 'Y===',\n                        label: 'value'\n                    }\n                }],\n                ['YW', false, {\n                    message: '\"value\" must be a valid base64 string',\n                    path: [],\n                    type: 'string.base64',\n                    context: {\n                        value: 'YW',\n                        label: 'value'\n                    }\n                }],\n                ['YW==', true],\n                ['YW5', false, {\n                    message: '\"value\" must be a valid base64 string',\n                    path: [],\n                    type: 'string.base64',\n                    context: {\n                        value: 'YW5',\n                        label: 'value'\n                    }\n                }],\n                ['YW5=', true],\n                ['$#%#$^$^)(*&^%', false, {\n                    message: '\"value\" must be a valid base64 string',\n                    path: [],\n                    type: 'string.base64',\n                    context: {\n                        value: '$#%#$^$^)(*&^%',\n                        label: 'value'\n                    }\n                }]\n            ]);\n        });\n\n        it('validates a base64 string with padding explicitly required', () => {\n\n            const rule = Joi.string().base64({ paddingRequired: true });\n            Helper.validate(rule, [\n                ['YW55IGNhcm5hbCBwbGVhc3VyZS4=', true],\n                ['=YW55IGNhcm5hbCBwbGVhc3VyZS4', false, {\n                    message: '\"value\" must be a valid base64 string',\n                    path: [],\n                    type: 'string.base64',\n                    context: {\n                        value: '=YW55IGNhcm5hbCBwbGVhc3VyZS4',\n                        label: 'value'\n                    }\n                }],\n                ['YW55IGNhcm5hbCBwbGVhc3VyZS4==', false, {\n                    message: '\"value\" must be a valid base64 string',\n                    path: [],\n                    type: 'string.base64',\n                    context: {\n                        value: 'YW55IGNhcm5hbCBwbGVhc3VyZS4==',\n                        label: 'value'\n                    }\n                }],\n                ['YW55IGNhcm5hbCBwbGVhc3VyZS4', false, {\n                    message: '\"value\" must be a valid base64 string',\n                    path: [],\n                    type: 'string.base64',\n                    context: {\n                        value: 'YW55IGNhcm5hbCBwbGVhc3VyZS4',\n                        label: 'value'\n                    }\n                }],\n                ['Y=', false, {\n                    message: '\"value\" must be a valid base64 string',\n                    path: [],\n                    type: 'string.base64',\n                    context: {\n                        value: 'Y=',\n                        label: 'value'\n                    }\n                }],\n                ['Y===', false, {\n                    message: '\"value\" must be a valid base64 string',\n                    path: [],\n                    type: 'string.base64',\n                    context: {\n                        value: 'Y===',\n                        label: 'value'\n                    }\n                }],\n                ['YW', false, {\n                    message: '\"value\" must be a valid base64 string',\n                    path: [],\n                    type: 'string.base64',\n                    context: {\n                        value: 'YW',\n                        label: 'value'\n                    }\n                }],\n                ['YW==', true],\n                ['YW5', false, {\n                    message: '\"value\" must be a valid base64 string',\n                    path: [],\n                    type: 'string.base64',\n                    context: {\n                        value: 'YW5',\n                        label: 'value'\n                    }\n                }],\n                ['YW5=', true],\n                ['$#%#$^$^)(*&^%', false, {\n                    message: '\"value\" must be a valid base64 string',\n                    path: [],\n                    type: 'string.base64',\n                    context: {\n                        value: '$#%#$^$^)(*&^%',\n                        label: 'value'\n                    }\n                }]\n            ]);\n        });\n\n        it('validates a base64 string with padding not required', () => {\n\n            const rule = Joi.string().base64({ paddingRequired: false });\n            Helper.validate(rule, [\n                ['YW55IGNhcm5hbCBwbGVhc3VyZS4=', true],\n                ['YW55IGNhcm5hbCBwbGVhc3VyZS4==', false, {\n                    message: '\"value\" must be a valid base64 string',\n                    path: [],\n                    type: 'string.base64',\n                    context: {\n                        value: 'YW55IGNhcm5hbCBwbGVhc3VyZS4==',\n                        label: 'value'\n                    }\n                }],\n                ['YW55IGNhcm5hbCBwbGVhc3VyZS4', true],\n                ['=YW55IGNhcm5hbCBwbGVhc3VyZS4', false, {\n                    message: '\"value\" must be a valid base64 string',\n                    path: [],\n                    type: 'string.base64',\n                    context: {\n                        value: '=YW55IGNhcm5hbCBwbGVhc3VyZS4',\n                        label: 'value'\n                    }\n                }],\n                ['YW55IGNhcm5hbCBwbGVhc3VyZS4==', false, {\n                    message: '\"value\" must be a valid base64 string',\n                    path: [],\n                    type: 'string.base64',\n                    context: {\n                        value: 'YW55IGNhcm5hbCBwbGVhc3VyZS4==',\n                        label: 'value'\n                    }\n                }],\n                ['YW55IG==cm5hbCBwbGVhc3VyZS4=', false, {\n                    message: '\"value\" must be a valid base64 string',\n                    path: [],\n                    type: 'string.base64',\n                    context: {\n                        value: 'YW55IG==cm5hbCBwbGVhc3VyZS4=',\n                        label: 'value'\n                    }\n                }],\n                ['Y$', false, {\n                    message: '\"value\" must be a valid base64 string',\n                    path: [],\n                    type: 'string.base64',\n                    context: {\n                        value: 'Y$',\n                        label: 'value'\n                    }\n                }],\n                ['Y', false, {\n                    message: '\"value\" must be a valid base64 string',\n                    path: [],\n                    type: 'string.base64',\n                    context: {\n                        value: 'Y',\n                        label: 'value'\n                    }\n                }],\n                ['Y===', false, {\n                    message: '\"value\" must be a valid base64 string',\n                    path: [],\n                    type: 'string.base64',\n                    context: {\n                        value: 'Y===',\n                        label: 'value'\n                    }\n                }],\n                ['YW', true],\n                ['YW==', true],\n                ['YW5', true],\n                ['YW5=', true],\n                ['$#%#$^$^)(*&^%', false, {\n                    message: '\"value\" must be a valid base64 string',\n                    path: [],\n                    type: 'string.base64',\n                    context: {\n                        value: '$#%#$^$^)(*&^%',\n                        label: 'value'\n                    }\n                }]\n            ]);\n        });\n\n        it('validates a url-safe base64 string with padding explicitly required', () => {\n\n            const rule = Joi.string().base64({ urlSafe: true, paddingRequired: true });\n            Helper.validate(rule, [\n                ['YW55IGNhcm5hb-_wbGVhc3VyZS4=', true],\n                ['=YW55IGNhcm5-_CBwbGVhc3VyZS4', false, {\n                    message: '\"value\" must be a valid base64 string',\n                    path: [],\n                    type: 'string.base64',\n                    context: {\n                        value: '=YW55IGNhcm5-_CBwbGVhc3VyZS4',\n                        label: 'value'\n                    }\n                }],\n                ['YW55IGNhcm5+/CBwbGVhc3VyZS4=', false, {\n                    message: '\"value\" must be a valid base64 string',\n                    path: [],\n                    type: 'string.base64',\n                    context: {\n                        value: 'YW55IGNhcm5+/CBwbGVhc3VyZS4=',\n                        label: 'value'\n                    }\n                }]\n            ]);\n        });\n\n        it('validates a url-safe base64 string with padding not required', () => {\n\n            const rule = Joi.string().base64({ urlSafe: true, paddingRequired: false });\n            Helper.validate(rule, [\n                ['YW55IGNhcm5hbCBwbGVhc3VyZS4=', true],\n                ['YW55IGNhcm-_bCBwbGVhc3VyZS4=', true],\n                ['YW55IGNhcm5hbCBwbGVhc3VyZS4', true],\n                ['YW55IGNhc-_hbCBwbGVhc3VyZS4', true],\n                ['YW55IGNhcm5hbCBwbGVhc3VyZS4==', false, {\n                    message: '\"value\" must be a valid base64 string',\n                    path: [],\n                    type: 'string.base64',\n                    context: {\n                        value: 'YW55IGNhcm5hbCBwbGVhc3VyZS4==',\n                        label: 'value'\n                    }\n                }],\n                ['YW55IGNhcm-_bCBwbGVhc3VyZS4==', false, {\n                    message: '\"value\" must be a valid base64 string',\n                    path: [],\n                    type: 'string.base64',\n                    context: {\n                        value: 'YW55IGNhcm-_bCBwbGVhc3VyZS4==',\n                        label: 'value'\n                    }\n                }]\n            ]);\n        });\n    });\n\n    describe('creditCard()', () => {\n\n        it('should validate credit card', () => {\n\n            const t = Joi.string().creditCard();\n\n            Helper.validate(t, [\n                ['378734493671000', true],  // american express\n                ['371449635398431', true],  // american express\n                ['378282246310005', true],  // american express\n                ['341111111111111', true],  // american express\n                ['5610591081018250', true], // australian bank\n                ['5019717010103742', true], // dankort pbs\n                ['38520000023237', true],   // diners club\n                ['30569309025904', true],   // diners club\n                ['6011000990139424', true], // discover\n                ['6011111111111117', true], // discover\n                ['6011601160116611', true], // discover\n                ['3566002020360505', true], // jbc\n                ['3530111333300000', true], // jbc\n                ['5105105105105100', true], // mastercard\n                ['5555555555554444', true], // mastercard\n                ['5431111111111111', true], // mastercard\n                ['6331101999990016', true], // switch/solo paymentech\n                ['4222222222222', true],    // visa\n                ['4012888888881881', true], // visa\n                ['4111111111111111', true], // visa\n                ['4111111111111112', false, {\n                    message: '\"value\" must be a credit card',\n                    path: [],\n                    type: 'string.creditCard',\n                    context: { value: '4111111111111112', label: 'value' }\n                }],\n                ['411111111111111X', false, {\n                    message: '\"value\" must be a credit card',\n                    path: [],\n                    type: 'string.creditCard',\n                    context: { value: '411111111111111X', label: 'value' }\n                }],\n                [null, false, {\n                    message: '\"value\" must be a string',\n                    path: [],\n                    type: 'string.base',\n                    context: { value: null, label: 'value' }\n                }]\n            ]);\n        });\n    });\n\n    describe('dataUri()', () => {\n\n        it('validates a dataUri string', () => {\n\n            const rule = Joi.string().dataUri();\n            Helper.validate(rule, [\n                ['data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAIAAAD8GO2jAABJRU5ErkJggg==', true],\n                ['ata:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAIAAAD8GO2jAABJRU5ErkJggg==', false, {\n                    message: '\"value\" must be a valid dataUri string',\n                    path: [],\n                    type: 'string.dataUri',\n                    context: {\n                        value: 'ata:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAIAAAD8GO2jAABJRU5ErkJggg==',\n                        label: 'value'\n                    }\n                }],\n                ['data:image/png;iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAIAAAD8GO2jAABJRU5ErkJggg==', true],\n                ['base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAIAAAD8GO2jAABJRU5ErkJggg==', false, {\n                    message: '\"value\" must be a valid dataUri string',\n                    path: [],\n                    type: 'string.dataUri',\n                    context: {\n                        value: 'base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAIAAAD8GO2jAABJRU5ErkJggg==',\n                        label: 'value'\n                    }\n                }],\n                ['data:base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAIAAAD8GO2jAABJRU5ErkJggg==', false, {\n                    message: '\"value\" must be a valid dataUri string',\n                    path: [],\n                    type: 'string.dataUri',\n                    context: {\n                        value: 'data:base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAIAAAD8GO2jAABJRU5ErkJggg==',\n                        label: 'value'\n                    }\n                }],\n                ['data:image/png;base64,=YW55IGNhcm5hbCBwbGVhc3VyZS4', false, {\n                    message: '\"value\" must be a valid dataUri string',\n                    path: [],\n                    type: 'string.dataUri',\n                    context: {\n                        value: 'data:image/png;base64,=YW55IGNhcm5hbCBwbGVhc3VyZS4',\n                        label: 'value'\n                    }\n                }],\n                ['data:image/png;base64,YW55IGNhcm5hbCBwbGVhc3VyZS4=', true],\n                ['data:image/png;charset=utf-8,=YW55IGNhcm5hbCBwbGVhc3VyZS', true],\n                ['data:text/x-script.python;charset=utf-8,=YW55IGNhcm5hbCBwbGVhc3VyZS', true]\n            ]);\n        });\n\n        it('validates a dataUri string with padding explicitly required', () => {\n\n            const rule = Joi.string().dataUri({ paddingRequired: true });\n            Helper.validate(rule, [\n                ['data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAIAAAD8GO2jAABJRU5ErkJggg==', true],\n                ['ata:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAIAAAD8GO2jAABJRU5ErkJggg==', false, {\n                    message: '\"value\" must be a valid dataUri string',\n                    path: [],\n                    type: 'string.dataUri',\n                    context: {\n                        value: 'ata:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAIAAAD8GO2jAABJRU5ErkJggg==',\n                        label: 'value'\n                    }\n                }],\n                ['data:image/png;iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAIAAAD8GO2jAABJRU5ErkJggg==', true],\n                ['base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAIAAAD8GO2jAABJRU5ErkJggg==', false, {\n                    message: '\"value\" must be a valid dataUri string',\n                    path: [],\n                    type: 'string.dataUri',\n                    context: {\n                        value: 'base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAIAAAD8GO2jAABJRU5ErkJggg==',\n                        label: 'value'\n                    }\n                }],\n                ['data:base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAIAAAD8GO2jAABJRU5ErkJggg==', false, {\n                    message: '\"value\" must be a valid dataUri string',\n                    path: [],\n                    type: 'string.dataUri',\n                    context: {\n                        value: 'data:base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAIAAAD8GO2jAABJRU5ErkJggg==',\n                        label: 'value'\n                    }\n                }],\n                ['data:image/png;base64,=YW55IGNhcm5hbCBwbGVhc3VyZS4', false, {\n                    message: '\"value\" must be a valid dataUri string',\n                    path: [],\n                    type: 'string.dataUri',\n                    context: {\n                        value: 'data:image/png;base64,=YW55IGNhcm5hbCBwbGVhc3VyZS4',\n                        label: 'value'\n                    }\n                }],\n                ['data:image/png;base64,YW55IGNhcm5hbCBwbGVhc3VyZS4=', true],\n                ['data:image/png;charset=utf-8,=YW55IGNhcm5hbCBwbGVhc3VyZS', true]\n            ]);\n\n        });\n\n        it('validates a dataUri string with padding not required', () => {\n\n            const rule = Joi.string().dataUri({ paddingRequired: false });\n            Helper.validate(rule, [\n                ['data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAIAAAD8GO2jAABJRU5ErkJggg==', true],\n                ['ata:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAIAAAD8GO2jAABJRU5ErkJggg==', false, {\n                    message: '\"value\" must be a valid dataUri string',\n                    path: [],\n                    type: 'string.dataUri',\n                    context: {\n                        value: 'ata:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAIAAAD8GO2jAABJRU5ErkJggg==',\n                        label: 'value'\n                    }\n                }],\n                ['data:image/png;iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAIAAAD8GO2jAABJRU5ErkJggg==', true],\n                ['base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAIAAAD8GO2jAABJRU5ErkJggg==', false, {\n                    message: '\"value\" must be a valid dataUri string',\n                    path: [],\n                    type: 'string.dataUri',\n                    context: {\n                        value: 'base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAIAAAD8GO2jAABJRU5ErkJggg==',\n                        label: 'value'\n                    }\n                }],\n                ['data:base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAIAAAD8GO2jAABJRU5ErkJggg==', false, {\n                    message: '\"value\" must be a valid dataUri string',\n                    path: [],\n                    type: 'string.dataUri',\n                    context: {\n                        value: 'data:base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAIAAAD8GO2jAABJRU5ErkJggg==',\n                        label: 'value'\n                    }\n                }],\n                ['data:image/png;base64,=YW55IGNhcm5hbCBwbGVhc3VyZS4', false, {\n                    message: '\"value\" must be a valid dataUri string',\n                    path: [],\n                    type: 'string.dataUri',\n                    context: {\n                        value: 'data:image/png;base64,=YW55IGNhcm5hbCBwbGVhc3VyZS4',\n                        label: 'value'\n                    }\n                }],\n                ['data:image/png;base64,YW55IGNhcm5hbCBwbGVhc3VyZS4=', true],\n                ['data:image/png;charset=utf-8,=YW55IGNhcm5hbCBwbGVhc3VyZS', true]\n            ]);\n\n        });\n    });\n\n    describe('describe()', () => {\n\n        it('describes various versions of a guid', () => {\n\n            const schema = Joi.string().guid({ version: ['uuidv1', 'uuidv3', 'uuidv5', 'uuidv8'] });\n\n            expect(schema.describe()).to.equal({\n                type: 'string',\n                rules: [\n                    {\n                        name: 'guid',\n                        args: {\n                            options: {\n                                version: ['uuidv1', 'uuidv3', 'uuidv5', 'uuidv8']\n                            }\n                        }\n                    }\n                ]\n            });\n        });\n\n        it('describes invert regex pattern', () => {\n\n            const schema = Joi.string().regex(/[a-z]/, { invert: true });\n\n            expect(schema.describe()).to.equal({\n                type: 'string',\n                rules: [\n                    {\n                        name: 'pattern',\n                        args: {\n                            regex: '/[a-z]/',\n                            options: {\n                                invert: true\n                            }\n                        }\n                    }\n                ]\n            });\n        });\n\n        it('describes a hex string', () => {\n\n            expect(Joi.string().hex().describe()).to.equal({\n                type: 'string',\n                rules: [{\n                    name: 'hex',\n                    args: {\n                        options: {\n                            byteAligned: false,\n                            prefix: false\n                        }\n                    }\n                }]\n            });\n            expect(Joi.string().hex({ byteAligned: true }).describe()).to.equal({\n                type: 'string',\n                rules: [{\n                    name: 'hex',\n                    args: {\n                        options: {\n                            byteAligned: true,\n                            prefix: false\n                        }\n                    }\n                }]\n            });\n            expect(Joi.string().hex({ prefix: true }).describe()).to.equal({\n                type: 'string',\n                rules: [{\n                    name: 'hex',\n                    args: {\n                        options: {\n                            byteAligned: false,\n                            prefix: true\n                        }\n                    }\n                }]\n            });\n            expect(Joi.string().hex({ prefix: 'optional' }).describe()).to.equal({\n                type: 'string',\n                rules: [{\n                    name: 'hex',\n                    args: {\n                        options: {\n                            byteAligned: false,\n                            prefix: 'optional'\n                        }\n                    }\n                }]\n            });\n        });\n    });\n\n    describe('domain()', () => {\n\n        it('validates options', () => {\n\n            expect(() => Joi.string().domain({ minDomainSegments: 1 })).to.not.throw();\n            expect(() => Joi.string().domain({ minDomainSegments: '1' })).to.throw('minDomainSegments must be a positive integer');\n            expect(() => Joi.string().domain({ minDomainSegments: 0 })).to.throw('minDomainSegments must be a positive integer');\n            expect(() => Joi.string().domain({ minDomainSegments: -1 })).to.throw('minDomainSegments must be a positive integer');\n            expect(() => Joi.string().domain({ minDomainSegments: 2.3 })).to.throw('minDomainSegments must be a positive integer');\n\n            expect(() => Joi.string().domain({ maxDomainSegments: 4 })).to.not.throw();\n            expect(() => Joi.string().domain({ maxDomainSegments: '1' })).to.throw('maxDomainSegments must be a positive integer');\n            expect(() => Joi.string().domain({ maxDomainSegments: 0 })).to.throw('maxDomainSegments must be a positive integer');\n            expect(() => Joi.string().domain({ maxDomainSegments: -1 })).to.throw('maxDomainSegments must be a positive integer');\n            expect(() => Joi.string().domain({ maxDomainSegments: 2.3 })).to.throw('maxDomainSegments must be a positive integer');\n\n            expect(() => Joi.string().domain({ tlds: false })).to.not.throw();\n            expect(() => Joi.string().domain({ tlds: true })).to.not.throw();\n            expect(() => Joi.string().domain({ tlds: {} })).to.not.throw();\n\n            expect(() => Joi.string().domain({ tlds: { allow: true } })).to.not.throw();\n            expect(() => Joi.string().domain({ tlds: { allow: false } })).to.not.throw();\n            expect(() => Joi.string().domain({ tlds: { allow: ['com'] } })).to.not.throw();\n            expect(() => Joi.string().domain({ tlds: { allow: new Set(['com']) } })).to.not.throw();\n            expect(() => Joi.string().domain({ tlds: { allow: 'com' } })).to.throw('tlds.allow must be an array, Set, or boolean');\n            expect(() => Joi.string().domain({ tlds: { allow: { com: true } } })).to.throw('tlds.allow must be an array, Set, or boolean');\n            expect(() => Joi.string().domain({ tlds: { allow: ['co.uk'] } })).to.throw('tlds.allow must contain valid top level domain names');\n\n            expect(() => Joi.string().domain({ tlds: { deny: ['com'] } })).to.not.throw();\n            expect(() => Joi.string().domain({ tlds: { deny: false } })).to.not.throw();\n            expect(() => Joi.string().domain({ tlds: { deny: new Set(['com']) } })).to.not.throw();\n            expect(() => Joi.string().domain({ tlds: { deny: true } })).to.throw('tlds.deny must be an array, Set, or boolean');\n            expect(() => Joi.string().domain({ tlds: { deny: 'com' } })).to.throw('tlds.deny must be an array, Set, or boolean');\n            expect(() => Joi.string().domain({ tlds: { deny: { com: true } } })).to.throw('tlds.deny must be an array, Set, or boolean');\n            expect(() => Joi.string().domain({ tlds: { deny: ['co.uk'] } })).to.throw('tlds.deny must contain valid top level domain names');\n        });\n\n        it('validates domain', () => {\n\n            const schema = Joi.string().domain();\n            Helper.validate(schema, [\n                ['example.com', true],\n                ['com', false, {\n                    message: '\"value\" must contain a valid domain name',\n                    path: [],\n                    type: 'string.domain',\n                    context: { value: 'com', label: 'value' }\n                }],\n                ['\"example.com', false, {\n                    message: '\"value\" must contain a valid domain name',\n                    path: [],\n                    type: 'string.domain',\n                    context: { value: '\"example.com', label: 'value' }\n                }],\n                ['mail@example.com', false, '\"value\" must contain a valid domain name'],\n                ['joi.dev.whatevertldiwant', false, '\"value\" must contain a valid domain name']\n            ]);\n        });\n\n        it('validates domain with tlds.allow', () => {\n\n            const schema = Joi.string().domain({ tlds: { allow: ['com', 'org'] } });\n            Helper.validate(schema, [\n                ['example.com', true],\n                ['example.org', true],\n                ['example.edu', false, {\n                    message: '\"value\" must contain a valid domain name',\n                    path: [],\n                    type: 'string.domain',\n                    context: { value: 'example.edu', label: 'value' }\n                }]\n            ]);\n        });\n\n        it('validates domain with minDomainSegments', () => {\n\n            const schema = Joi.string().domain({ minDomainSegments: 4 });\n            Helper.validate(schema, [\n                ['example.com', false, {\n                    message: '\"value\" must contain a valid domain name',\n                    path: [],\n                    type: 'string.domain',\n                    context: { value: 'example.com', label: 'value' }\n                }],\n                ['www.example.com', false, {\n                    message: '\"value\" must contain a valid domain name',\n                    path: [],\n                    type: 'string.domain',\n                    context: { value: 'www.example.com', label: 'value' }\n                }],\n                ['sub.www.example.com', true]\n            ]);\n        });\n\n        it('validates domain with maxDomainSegments', () => {\n\n            const schema = Joi.string().domain({ maxDomainSegments: 4 });\n            Helper.validate(schema, [\n                ['x.y.z.example.com', false, {\n                    message: '\"value\" must contain a valid domain name',\n                    path: [],\n                    type: 'string.domain',\n                    context: { value: 'x.y.z.example.com', label: 'value' }\n                }],\n                ['sub.www.example.com', true]\n            ]);\n        });\n\n        it('validates domain', () => {\n\n            const schema = { item: Joi.string().domain() };\n            Helper.validate(Joi.compile(schema), [[{ item: 'something' }, false, {\n                message: '\"item\" must contain a valid domain name',\n                path: ['item'],\n                type: 'string.domain',\n                context: { value: 'something', label: 'item', key: 'item' }\n            }]]);\n        });\n\n        it('validates domain with underscores', () => {\n\n            const validSchema = Joi.string().domain({ allowUnderscore: true });\n            Helper.validate(validSchema, [\n                ['_acme-challenge.example.com', true],\n                ['_abc.example.com', true]\n            ]);\n\n            const invalidSchema = Joi.string().domain();\n            Helper.validate(invalidSchema, [\n                ['_acme-challenge.example.com', false, {\n                    context: {\n                        label: 'value',\n                        value: '_acme-challenge.example.com'\n                    },\n                    message: '\"value\" must contain a valid domain name',\n                    path: [],\n                    type: 'string.domain'\n                }],\n                ['_abc.example.com', false, {\n                    context: {\n                        label: 'value',\n                        value: '_abc.example.com'\n                    },\n                    message: '\"value\" must contain a valid domain name',\n                    path: [],\n                    type: 'string.domain'\n                }]\n            ]);\n        });\n    });\n\n    describe('email()', () => {\n\n        it('validates options', () => {\n\n            expect(() => Joi.string().email({})).to.not.throw();\n            expect(() => Joi.string().email({ minDomainSegments: 1 })).to.not.throw();\n            expect(() => Joi.string().email({ minDomainSegments: '1' })).to.throw('minDomainSegments must be a positive integer');\n            expect(() => Joi.string().email({ minDomainSegments: 0 })).to.throw('minDomainSegments must be a positive integer');\n            expect(() => Joi.string().email({ minDomainSegments: -1 })).to.throw('minDomainSegments must be a positive integer');\n            expect(() => Joi.string().email({ minDomainSegments: 2.3 })).to.throw('minDomainSegments must be a positive integer');\n\n            expect(() => Joi.string().email({ tlds: false })).to.not.throw();\n            expect(() => Joi.string().email({ tlds: true })).to.not.throw();\n            expect(() => Joi.string().email({ tlds: {} })).to.not.throw();\n\n            expect(() => Joi.string().email({ tlds: { allow: true } })).to.not.throw();\n            expect(() => Joi.string().email({ tlds: { allow: false } })).to.not.throw();\n            expect(() => Joi.string().email({ tlds: { allow: ['com'] } })).to.not.throw();\n            expect(() => Joi.string().email({ tlds: { allow: new Set(['com']) } })).to.not.throw();\n            expect(() => Joi.string().email({ tlds: { allow: 'com' } })).to.throw('tlds.allow must be an array, Set, or boolean');\n            expect(() => Joi.string().email({ tlds: { allow: { com: true } } })).to.throw('tlds.allow must be an array, Set, or boolean');\n\n            expect(() => Joi.string().email({ tlds: { deny: ['com'] } })).to.not.throw();\n            expect(() => Joi.string().email({ tlds: { deny: new Set(['com']) } })).to.not.throw();\n            expect(() => Joi.string().email({ tlds: { deny: false } })).to.not.throw();\n            expect(() => Joi.string().email({ tlds: { deny: true } })).to.throw('tlds.deny must be an array, Set, or boolean');\n            expect(() => Joi.string().email({ tlds: { deny: 'com' } })).to.throw('tlds.deny must be an array, Set, or boolean');\n            expect(() => Joi.string().email({ tlds: { deny: { com: true } } })).to.throw('tlds.deny must be an array, Set, or boolean');\n\n            expect(() => Joi.string().email({ multiple: true })).to.not.throw();\n            expect(() => Joi.string().email({ multiple: false })).to.not.throw();\n            expect(() => Joi.string().email({ multiple: {} })).to.throw('multiple option must be an boolean');\n            expect(() => Joi.string().email({ multiple: 'abc' })).to.throw('multiple option must be an boolean');\n        });\n\n        it('validates email', () => {\n\n            const schema = Joi.string().email();\n            Helper.validate(schema, [\n                ['joe@example.com', true],\n                ['êjness@something.com', true],\n                ['\"joe\"@example.com', false, {\n                    message: '\"value\" must be a valid email',\n                    path: [],\n                    type: 'string.email',\n                    context: { value: '\"joe\"@example.com', invalids: ['\"joe\"@example.com'], label: 'value' }\n                }],\n                ['example@io', false, {\n                    message: '\"value\" must be a valid email',\n                    path: [],\n                    type: 'string.email',\n                    context: { value: 'example@io', invalids: ['example@io'], label: 'value' }\n                }],\n                ['@iaminvalid.com', false, {\n                    message: '\"value\" must be a valid email',\n                    path: [],\n                    type: 'string.email',\n                    context: { value: '@iaminvalid.com', invalids: ['@iaminvalid.com'], label: 'value' }\n                }],\n                ['joe@[IPv6:2a00:1450:4001:c02::1b]', false, {\n                    message: '\"value\" must be a valid email',\n                    path: [],\n                    type: 'string.email',\n                    context: { value: 'joe@[IPv6:2a00:1450:4001:c02::1b]', invalids: ['joe@[IPv6:2a00:1450:4001:c02::1b]'], label: 'value' }\n                }],\n                ['12345678901234567890123456789012345678901234567890123456789012345@example.com', false, {\n                    message: '\"value\" must be a valid email',\n                    path: [],\n                    type: 'string.email',\n                    context: { value: '12345678901234567890123456789012345678901234567890123456789012345@example.com', invalids: ['12345678901234567890123456789012345678901234567890123456789012345@example.com'], label: 'value' }\n                }],\n                ['123456789012345678901234567890123456789012345678901234567890@12345678901234567890123456789012345678901234567890123456789.12345678901234567890123456789012345678901234567890123456789.12345678901234567890123456789012345678901234567890123456789.12345.toolong.com', false, {\n                    message: '\"value\" must be a valid email',\n                    path: [],\n                    type: 'string.email',\n                    context: { value: '123456789012345678901234567890123456789012345678901234567890@12345678901234567890123456789012345678901234567890123456789.12345678901234567890123456789012345678901234567890123456789.12345678901234567890123456789012345678901234567890123456789.12345.toolong.com', invalids: ['123456789012345678901234567890123456789012345678901234567890@12345678901234567890123456789012345678901234567890123456789.12345678901234567890123456789012345678901234567890123456789.12345678901234567890123456789012345678901234567890123456789.12345.toolong.com'], label: 'value' }\n                }],\n                ['foo@bar%2ecom', false, '\"value\" must be a valid email'],\n                ['invalid_tlds@email.ccc', false, '\"value\" must be a valid email']\n            ]);\n        });\n\n        it('allows long emails', () => {\n\n            const schema = Joi.string().email({ ignoreLength: true });\n            Helper.validate(schema, [\n                ['12345678901234567890123456789012345678901234567890123456789012345@example.com', true],\n                ['123456789012345678901234567890123456789012345678901234567890@12345678901234567890123456789012345678901234567890123456789.12345678901234567890123456789012345678901234567890123456789.12345678901234567890123456789012345678901234567890123456789.12345.toolong.com', true]\n            ]);\n        });\n\n        it('validates email with tlds.allow', () => {\n\n            const schema = Joi.string().email({ tlds: { allow: ['com', 'org'] } });\n            Helper.validate(schema, [\n                ['joe@example.com', true],\n                ['joe@example.org', true],\n                ['joe@example.edu', false, {\n                    message: '\"value\" must be a valid email',\n                    path: [],\n                    type: 'string.email',\n                    context: { value: 'joe@example.edu', invalids: ['joe@example.edu'], label: 'value' }\n                }]\n            ]);\n        });\n\n        it('validates email with tlds false', () => {\n\n            const schema = Joi.string().email({ tlds: false });\n            Helper.validate(schema, [\n                ['joe@example.no-such-domain', true],\n                ['joe@example.org', true]\n            ]);\n        });\n\n        it('validates email with minDomainSegments', () => {\n\n            const schema = Joi.string().email({ minDomainSegments: 4 });\n            Helper.validate(schema, [\n                ['joe@example.com', false, {\n                    message: '\"value\" must be a valid email',\n                    path: [],\n                    type: 'string.email',\n                    context: { value: 'joe@example.com', invalids: ['joe@example.com'], label: 'value' }\n                }],\n                ['joe@www.example.com', false, {\n                    message: '\"value\" must be a valid email',\n                    path: [],\n                    type: 'string.email',\n                    context: { value: 'joe@www.example.com', invalids: ['joe@www.example.com'], label: 'value' }\n                }],\n                ['joe@sub.www.example.com', true]\n            ]);\n        });\n\n        it('validates email with multiple', () => {\n\n            const schema = Joi.string().email({ multiple: true });\n            Helper.validate(schema, [\n                ['joe@example.com', true],\n                ['joe@example.com, joe@example.org, joe@example.com', true],\n                ['joe@example.com , joe@example.org ,joe@example.com', true],\n                ['joe@example.com  , joe@example.org ,  joe@example.com', true],\n                ['joe@example.com, joe@example, joe@example.org, joe@com', false, {\n                    message: '\"value\" must be a valid email',\n                    path: [],\n                    type: 'string.email',\n                    context: { value: 'joe@example.com, joe@example, joe@example.org, joe@com', invalids: ['joe@example', 'joe@com'], label: 'value' }\n                }]\n            ]);\n        });\n\n        it('validates email with multiple (separator)', () => {\n\n            const schema = Joi.string().email({ multiple: true, separator: ';' });\n            Helper.validate(schema, [\n                ['joe@example.com', true],\n                ['joe@example.com; joe@example.org; joe@example.com', true],\n                ['joe@example.com ; joe@example.org ;joe@example.com', true],\n                ['joe@example.com  ; joe@example.org ;  joe@example.com', true],\n                ['joe@example.com; joe@example; joe@example.org; joe@com', false, {\n                    message: '\"value\" must be a valid email',\n                    path: [],\n                    type: 'string.email',\n                    context: { value: 'joe@example.com; joe@example; joe@example.org; joe@com', invalids: ['joe@example', 'joe@com'], label: 'value' }\n                }]\n            ]);\n        });\n\n        it('validates email', () => {\n\n            const schema = { item: Joi.string().email() };\n            Helper.validate(Joi.compile(schema), [[{ item: 'something' }, false, {\n                message: '\"item\" must be a valid email',\n                path: ['item'],\n                type: 'string.email',\n                context: { value: 'something', invalids: ['something'], label: 'item', key: 'item' }\n            }]]);\n        });\n\n        it('validates combination of email and min', () => {\n\n            const rule = Joi.string().email().min(8);\n            Helper.validate(rule, [\n                ['x@x.com', false, {\n                    message: '\"value\" length must be at least 8 characters long',\n                    path: [],\n                    type: 'string.min',\n                    context: {\n                        limit: 8,\n                        value: 'x@x.com',\n                        encoding: undefined,\n                        label: 'value'\n                    }\n                }],\n                ['123@x.com', true],\n                ['', false, {\n                    message: '\"value\" is not allowed to be empty',\n                    path: [],\n                    type: 'string.empty',\n                    context: { value: '', label: 'value' }\n                }],\n                [null, false, {\n                    message: '\"value\" must be a string',\n                    path: [],\n                    type: 'string.base',\n                    context: { value: null, label: 'value' }\n                }]\n            ]);\n        });\n\n        it('validates combination of email, min, and max', () => {\n\n            const rule = Joi.string().email().min(8).max(10);\n            Helper.validate(rule, [\n                ['x@x.com', false, {\n                    message: '\"value\" length must be at least 8 characters long',\n                    path: [],\n                    type: 'string.min',\n                    context: {\n                        limit: 8,\n                        value: 'x@x.com',\n                        encoding: undefined,\n                        label: 'value'\n                    }\n                }],\n                ['123@x.com', true],\n                ['1234@x.com', true],\n                ['12345@x.com', false, {\n                    message: '\"value\" length must be less than or equal to 10 characters long',\n                    path: [],\n                    type: 'string.max',\n                    context: {\n                        limit: 10,\n                        value: '12345@x.com',\n                        encoding: undefined,\n                        label: 'value'\n                    }\n                }],\n                ['', false, {\n                    message: '\"value\" is not allowed to be empty',\n                    path: [],\n                    type: 'string.empty',\n                    context: { value: '', label: 'value' }\n                }],\n                [null, false, {\n                    message: '\"value\" must be a string',\n                    path: [],\n                    type: 'string.base',\n                    context: { value: null, label: 'value' }\n                }]\n            ]);\n        });\n\n        it('validates combination of email, min, max, and invalid', () => {\n\n            const rule = Joi.string().email().min(8).max(10).invalid('123@x.com');\n            Helper.validate(rule, [\n                ['x@x.com', false, {\n                    message: '\"value\" length must be at least 8 characters long',\n                    path: [],\n                    type: 'string.min',\n                    context: {\n                        limit: 8,\n                        value: 'x@x.com',\n                        encoding: undefined,\n                        label: 'value'\n                    }\n                }],\n                ['123@x.com', false, {\n                    message: '\"value\" contains an invalid value',\n                    path: [],\n                    type: 'any.invalid',\n                    context: { value: '123@x.com', invalids: ['123@x.com'], label: 'value' }\n                }],\n                ['1234@x.com', true],\n                ['12345@x.com', false, {\n                    message: '\"value\" length must be less than or equal to 10 characters long',\n                    path: [],\n                    type: 'string.max',\n                    context: {\n                        limit: 10,\n                        value: '12345@x.com',\n                        encoding: undefined,\n                        label: 'value'\n                    }\n                }],\n                ['', false, {\n                    message: '\"value\" is not allowed to be empty',\n                    path: [],\n                    type: 'string.empty',\n                    context: { value: '', label: 'value' }\n                }],\n                [null, false, {\n                    message: '\"value\" must be a string',\n                    path: [],\n                    type: 'string.base',\n                    context: { value: null, label: 'value' }\n                }]\n            ]);\n        });\n\n        it('validates combination of email, min, max, and allow', () => {\n\n            const rule = Joi.string().email().min(8).max(10).allow('x@x.com');\n            Helper.validate(rule, [\n                ['x@x.com', true],\n                ['123@x.com', true],\n                ['1234@x.com', true],\n                ['12345@x.com', false, {\n                    message: '\"value\" length must be less than or equal to 10 characters long',\n                    path: [],\n                    type: 'string.max',\n                    context: {\n                        limit: 10,\n                        value: '12345@x.com',\n                        encoding: undefined,\n                        label: 'value'\n                    }\n                }],\n                ['', false, {\n                    message: '\"value\" is not allowed to be empty',\n                    path: [],\n                    type: 'string.empty',\n                    context: { value: '', label: 'value' }\n                }],\n                [null, false, {\n                    message: '\"value\" must be a string',\n                    path: [],\n                    type: 'string.base',\n                    context: { value: null, label: 'value' }\n                }]\n            ]);\n        });\n\n        it('validates combination of email, min, max, allow, and invalid', () => {\n\n            const rule = Joi.string().email().min(8).max(10).allow('x@x.com').invalid('123@x.com');\n            Helper.validate(rule, [\n                ['x@x.com', true],\n                ['123@x.com', false, {\n                    message: '\"value\" contains an invalid value',\n                    path: [],\n                    type: 'any.invalid',\n                    context: { value: '123@x.com', invalids: ['123@x.com'], label: 'value' }\n                }],\n                ['1234@x.com', true],\n                ['12345@x.com', false, {\n                    message: '\"value\" length must be less than or equal to 10 characters long',\n                    path: [],\n                    type: 'string.max',\n                    context: {\n                        limit: 10,\n                        value: '12345@x.com',\n                        encoding: undefined,\n                        label: 'value'\n                    }\n                }],\n                ['', false, {\n                    message: '\"value\" is not allowed to be empty',\n                    path: [],\n                    type: 'string.empty',\n                    context: { value: '', label: 'value' }\n                }],\n                [null, false, {\n                    message: '\"value\" must be a string',\n                    path: [],\n                    type: 'string.base',\n                    context: { value: null, label: 'value' }\n                }]\n            ]);\n        });\n\n        it('validates combination of email, min, max, allow, invalid, and allow(\\'\\')', () => {\n\n            const rule = Joi.string().email().min(8).max(10).allow('x@x.com').invalid('123@x.com').allow('');\n            Helper.validate(rule, [\n                ['x@x.com', true],\n                ['123@x.com', false, {\n                    message: '\"value\" contains an invalid value',\n                    path: [],\n                    type: 'any.invalid',\n                    context: { value: '123@x.com', invalids: ['123@x.com'], label: 'value' }\n                }],\n                ['1234@x.com', true],\n                ['12345@x.com', false, {\n                    message: '\"value\" length must be less than or equal to 10 characters long',\n                    path: [],\n                    type: 'string.max',\n                    context: {\n                        limit: 10,\n                        value: '12345@x.com',\n                        encoding: undefined,\n                        label: 'value'\n                    }\n                }],\n                ['', true],\n                [null, false, {\n                    message: '\"value\" must be a string',\n                    path: [],\n                    type: 'string.base',\n                    context: { value: null, label: 'value' }\n                }]\n            ]);\n        });\n\n        it('validates combination of email, min, max, allow, and allow(\\'\\')', () => {\n\n            const rule = Joi.string().email().min(8).max(10).allow('x@x.com').allow('');\n            Helper.validate(rule, [\n                ['x@x.com', true],\n                ['123@x.com', true],\n                ['1234@x.com', true],\n                ['12345@x.com', false, {\n                    message: '\"value\" length must be less than or equal to 10 characters long',\n                    path: [],\n                    type: 'string.max',\n                    context: {\n                        limit: 10,\n                        value: '12345@x.com',\n                        encoding: undefined,\n                        label: 'value'\n                    }\n                }],\n                ['', true],\n                [null, false, {\n                    message: '\"value\" must be a string',\n                    path: [],\n                    type: 'string.base',\n                    context: { value: null, label: 'value' }\n                }]\n            ]);\n        });\n\n        it('validates combination of email, min, max, allow, invalid, and regex', () => {\n\n            const rule = Joi.string().email().min(8).max(10).allow('x@x.com').invalid('123@x.com').regex(/^1/);\n            Helper.validate(rule, [\n                ['x@x.com', true],\n                ['123@x.com', false, {\n                    message: '\"value\" contains an invalid value',\n                    path: [],\n                    type: 'any.invalid',\n                    context: { value: '123@x.com', invalids: ['123@x.com'], label: 'value' }\n                }],\n                ['1234@x.com', true],\n                ['12345@x.com', false, {\n                    message: '\"value\" length must be less than or equal to 10 characters long',\n                    path: [],\n                    type: 'string.max',\n                    context: {\n                        limit: 10,\n                        value: '12345@x.com',\n                        encoding: undefined,\n                        label: 'value'\n                    }\n                }],\n                ['', false, {\n                    message: '\"value\" is not allowed to be empty',\n                    path: [],\n                    type: 'string.empty',\n                    context: { value: '', label: 'value' }\n                }],\n                [null, false, {\n                    message: '\"value\" must be a string',\n                    path: [],\n                    type: 'string.base',\n                    context: { value: null, label: 'value' }\n                }]\n            ]);\n        });\n\n        it('validates combination of email, min, max, allow, invalid, regex, and allow(\\'\\')', () => {\n\n            const rule = Joi.string().email().min(8).max(10).allow('x@x.com').invalid('123@x.com').regex(/^1/).allow('');\n            Helper.validate(rule, [\n                ['x@x.com', true],\n                ['123@x.com', false, {\n                    message: '\"value\" contains an invalid value',\n                    path: [],\n                    type: 'any.invalid',\n                    context: { value: '123@x.com', invalids: ['123@x.com'], label: 'value' }\n                }],\n                ['1234@x.com', true],\n                ['12345@x.com', false, {\n                    message: '\"value\" length must be less than or equal to 10 characters long',\n                    path: [],\n                    type: 'string.max',\n                    context: {\n                        limit: 10,\n                        value: '12345@x.com',\n                        encoding: undefined,\n                        label: 'value'\n                    }\n                }],\n                ['', true],\n                [null, false, {\n                    message: '\"value\" must be a string',\n                    path: [],\n                    type: 'string.base',\n                    context: { value: null, label: 'value' }\n                }]\n            ]);\n        });\n\n        it('validates combination of email, min, max, and allow(\\'\\')', () => {\n\n            const rule = Joi.string().email().min(8).max(10).allow('');\n            Helper.validate(rule, [\n                ['x@x.com', false, {\n                    message: '\"value\" length must be at least 8 characters long',\n                    path: [],\n                    type: 'string.min',\n                    context: {\n                        limit: 8,\n                        value: 'x@x.com',\n                        encoding: undefined,\n                        label: 'value'\n                    }\n                }],\n                ['123@x.com', true],\n                ['1234@x.com', true],\n                ['12345@x.com', false, {\n                    message: '\"value\" length must be less than or equal to 10 characters long',\n                    path: [],\n                    type: 'string.max',\n                    context: {\n                        limit: 10,\n                        value: '12345@x.com',\n                        encoding: undefined,\n                        label: 'value'\n                    }\n                }],\n                ['', true],\n                [null, false, {\n                    message: '\"value\" must be a string',\n                    path: [],\n                    type: 'string.base',\n                    context: { value: null, label: 'value' }\n                }]\n            ]);\n        });\n\n        it('validates combination of email, min, max, and regex', () => {\n\n            const rule = Joi.string().email().min(8).max(10).regex(/^1234/);\n            Helper.validate(rule, [\n                ['x@x.com', false, {\n                    message: '\"value\" length must be at least 8 characters long',\n                    path: [],\n                    type: 'string.min',\n                    context: {\n                        limit: 8,\n                        value: 'x@x.com',\n                        encoding: undefined,\n                        label: 'value'\n                    }\n                }],\n                ['123@x.com', false, {\n                    message: '\"value\" with value \"123@x.com\" fails to match the required pattern: /^1234/',\n                    path: [],\n                    type: 'string.pattern.base',\n                    context: {\n                        name: undefined,\n                        regex: /^1234/,\n                        value: '123@x.com',\n                        label: 'value'\n                    }\n                }],\n                ['1234@x.com', true],\n                ['12345@x.com', false, {\n                    message: '\"value\" length must be less than or equal to 10 characters long',\n                    path: [],\n                    type: 'string.max',\n                    context: {\n                        limit: 10,\n                        value: '12345@x.com',\n                        encoding: undefined,\n                        label: 'value'\n                    }\n                }],\n                ['', false, {\n                    message: '\"value\" is not allowed to be empty',\n                    path: [],\n                    type: 'string.empty',\n                    context: { value: '', label: 'value' }\n                }],\n                [null, false, {\n                    message: '\"value\" must be a string',\n                    path: [],\n                    type: 'string.base',\n                    context: { value: null, label: 'value' }\n                }]\n            ]);\n        });\n\n        it('validates combination of email, min, max, regex, and allow(\\'\\')', () => {\n\n            const rule = Joi.string().email().min(8).max(10).regex(/^1234/).allow('');\n            Helper.validate(rule, [\n                ['x@x.com', false, {\n                    message: '\"value\" length must be at least 8 characters long',\n                    path: [],\n                    type: 'string.min',\n                    context: {\n                        limit: 8,\n                        value: 'x@x.com',\n                        encoding: undefined,\n                        label: 'value'\n                    }\n                }],\n                ['123@x.com', false, {\n                    message: '\"value\" with value \"123@x.com\" fails to match the required pattern: /^1234/',\n                    path: [],\n                    type: 'string.pattern.base',\n                    context: {\n                        name: undefined,\n                        regex: /^1234/,\n                        value: '123@x.com',\n                        label: 'value'\n                    }\n                }],\n                ['1234@x.com', true],\n                ['12345@x.com', false, {\n                    message: '\"value\" length must be less than or equal to 10 characters long',\n                    path: [],\n                    type: 'string.max',\n                    context: {\n                        limit: 10,\n                        value: '12345@x.com',\n                        encoding: undefined,\n                        label: 'value'\n                    }\n                }],\n                ['', true],\n                [null, false, {\n                    message: '\"value\" must be a string',\n                    path: [],\n                    type: 'string.base',\n                    context: { value: null, label: 'value' }\n                }]\n            ]);\n        });\n\n        it('validates combination of email, min, max, regex, and required', () => {\n\n            const rule = Joi.string().email().min(8).max(10).regex(/^1234/).required();\n            Helper.validate(rule, [\n                ['x@x.com', false, {\n                    message: '\"value\" length must be at least 8 characters long',\n                    path: [],\n                    type: 'string.min',\n                    context: {\n                        limit: 8,\n                        value: 'x@x.com',\n                        encoding: undefined,\n                        label: 'value'\n                    }\n                }],\n                ['123@x.com', false, {\n                    message: '\"value\" with value \"123@x.com\" fails to match the required pattern: /^1234/',\n                    path: [],\n                    type: 'string.pattern.base',\n                    context: {\n                        name: undefined,\n                        regex: /^1234/,\n                        value: '123@x.com',\n                        label: 'value'\n                    }\n                }],\n                ['1234@x.com', true],\n                ['12345@x.com', false, {\n                    message: '\"value\" length must be less than or equal to 10 characters long',\n                    path: [],\n                    type: 'string.max',\n                    context: {\n                        limit: 10,\n                        value: '12345@x.com',\n                        encoding: undefined,\n                        label: 'value'\n                    }\n                }],\n                ['', false, {\n                    message: '\"value\" is not allowed to be empty',\n                    path: [],\n                    type: 'string.empty',\n                    context: { value: '', label: 'value' }\n                }],\n                [null, false, {\n                    message: '\"value\" must be a string',\n                    path: [],\n                    type: 'string.base',\n                    context: { value: null, label: 'value' }\n                }]\n            ]);\n        });\n    });\n\n    describe('guid()', () => {\n\n        it('throws when options.version is invalid', () => {\n\n            expect(() => Joi.string().guid({ version: 42 })).to.throw('version at position 0 must be a string');\n            expect(() => Joi.string().guid({ version: '42' })).to.throw('version at position 0 must be one of uuidv1, uuidv2, uuidv3, uuidv4, uuidv5, uuidv6, uuidv7, uuidv8');\n        });\n\n        it('throws when options.separator is invalid', () => {\n\n            expect(() => Joi.string().guid({ separator: 42 })).to.throw('separator must be one of true, false, \"-\", or \":\"');\n        });\n\n        it('validates guid', () => {\n\n            Helper.validate(Joi.string().guid(), [\n                ['{D1A5279D-B27D-4CD4-A05E-EFDD53D08E8D}', true],\n                ['{B59511BD6A5F4DF09ECF562A108D8A2E}', true],\n                ['69593D62-71EA-4548-85E4-04FC71357423', true],\n                ['69593D62:71EA:4548:85E4:04FC71357423', true],\n                ['677E2553DD4D43B09DA77414DB1EB8EA', true],\n                ['{5ba3bba3-729a-4717-88c1-b7c4b7ba80db}', true],\n                ['{7e9081b59a6d4cc1a8c347f69fb4198d}', true],\n                ['0c74f13f-fa83-4c48-9b33-68921dd72463', true],\n                ['b4b2fb69c6244e5eb0698e0c6ec66618', true],\n                ['{D1A5279D-B27D-0CD4-005E-EFDD53D08E8D}', true],\n                ['{283B67B2-430F-4E6F-97E6-19041992-C1B0}', false, {\n                    message: '\"value\" must be a valid GUID',\n                    path: [],\n                    type: 'string.guid',\n                    context: {\n                        value: '{283B67B2-430F-4E6F-97E6-19041992-C1B0}',\n                        label: 'value'\n                    }\n                }],\n                ['{D1A5279D-B27D-4CD4-A05E-EFDD53D08E8D', false, {\n                    message: '\"value\" must be a valid GUID',\n                    path: [],\n                    type: 'string.guid',\n                    context: {\n                        value: '{D1A5279D-B27D-4CD4-A05E-EFDD53D08E8D',\n                        label: 'value'\n                    }\n                }],\n                ['{D1A5279D-B27D-4CD4-A05E-EFDD53D08E8D]', false, {\n                    message: '\"value\" must be a valid GUID',\n                    path: [],\n                    type: 'string.guid',\n                    context: {\n                        value: '{D1A5279D-B27D-4CD4-A05E-EFDD53D08E8D]',\n                        label: 'value'\n                    }\n                }],\n                ['D1A5279D-B27D-4CD4-A05E-EFDD53D08E8D}', false, {\n                    message: '\"value\" must be a valid GUID',\n                    path: [],\n                    type: 'string.guid',\n                    context: {\n                        value: 'D1A5279D-B27D-4CD4-A05E-EFDD53D08E8D}',\n                        label: 'value'\n                    }\n                }],\n                ['{D1A5279D:B27D-4CD4-A05E-EFDD53D08E8D}', false, {\n                    message: '\"value\" must be a valid GUID',\n                    path: [],\n                    type: 'string.guid',\n                    context: {\n                        value: '{D1A5279D:B27D-4CD4-A05E-EFDD53D08E8D}',\n                        label: 'value'\n                    }\n                }],\n                ['{D1A5279D-B27D:4CD4-A05E-EFDD53D08E8D}', false, {\n                    message: '\"value\" must be a valid GUID',\n                    path: [],\n                    type: 'string.guid',\n                    context: {\n                        value: '{D1A5279D-B27D:4CD4-A05E-EFDD53D08E8D}',\n                        label: 'value'\n                    }\n                }],\n                ['{D1A5279D-B27D-4CD4:A05E-EFDD53D08E8D}', false, {\n                    message: '\"value\" must be a valid GUID',\n                    path: [],\n                    type: 'string.guid',\n                    context: {\n                        value: '{D1A5279D-B27D-4CD4:A05E-EFDD53D08E8D}',\n                        label: 'value'\n                    }\n                }],\n                ['{D1A5279D-B27D-4CD4-A05E:EFDD53D08E8D}', false, {\n                    message: '\"value\" must be a valid GUID',\n                    path: [],\n                    type: 'string.guid',\n                    context: {\n                        value: '{D1A5279D-B27D-4CD4-A05E:EFDD53D08E8D}',\n                        label: 'value'\n                    }\n                }]\n            ]);\n        });\n\n        it('validates uuidv1', () => {\n\n            Helper.validate(Joi.string().guid({ version: ['uuidv1'] }), [\n                ['{D1A5279D-B27D-1CD4-A05E-EFDD53D08E8D}', true],\n                ['{B59511BD6A5F1DF09ECF562A108D8A2E}', true],\n                ['69593D62-71EA-1548-85E4-04FC71357423', true],\n                ['677E2553DD4D13B09DA77414DB1EB8EA', true],\n                ['{5ba3bba3-729a-1717-88c1-b7c4b7ba80db}', true],\n                ['{7e9081b59a6d1cc1a8c347f69fb4198d}', true],\n                ['0c74f13f-fa83-1c48-9b33-68921dd72463', true],\n                ['b4b2fb69c6241e5eb0698e0c6ec66618', true],\n                ['{D1A5279D-B27D-4CD4-A05E-EFDD53D08E8D}', false, {\n                    message: '\"value\" must be a valid GUID',\n                    path: [],\n                    type: 'string.guid',\n                    context: {\n                        value: '{D1A5279D-B27D-4CD4-A05E-EFDD53D08E8D}',\n                        label: 'value'\n                    }\n                }],\n                ['{D1A5279D-B27D-1CD4-C05E-EFDD53D08E8D}', false, {\n                    message: '\"value\" must be a valid GUID',\n                    path: [],\n                    type: 'string.guid',\n                    context: {\n                        value: '{D1A5279D-B27D-1CD4-C05E-EFDD53D08E8D}',\n                        label: 'value'\n                    }\n                }],\n                ['{283B67B2-430F-1E6F-97E6-19041992-C1B0}', false, {\n                    message: '\"value\" must be a valid GUID',\n                    path: [],\n                    type: 'string.guid',\n                    context: {\n                        value: '{283B67B2-430F-1E6F-97E6-19041992-C1B0}',\n                        label: 'value'\n                    }\n                }],\n                ['{D1A5279D-B27D-1CD4-A05E-EFDD53D08E8D', false, {\n                    message: '\"value\" must be a valid GUID',\n                    path: [],\n                    type: 'string.guid',\n                    context: {\n                        value: '{D1A5279D-B27D-1CD4-A05E-EFDD53D08E8D',\n                        label: 'value'\n                    }\n                }],\n                ['{D1A5279D-B27D-1CD4-A05E-EFDD53D08E8D]', false, {\n                    message: '\"value\" must be a valid GUID',\n                    path: [],\n                    type: 'string.guid',\n                    context: {\n                        value: '{D1A5279D-B27D-1CD4-A05E-EFDD53D08E8D]',\n                        label: 'value'\n                    }\n                }],\n                ['D1A5279D-B27D-1CD4-A05E-EFDD53D08E8D}', false, {\n                    message: '\"value\" must be a valid GUID',\n                    path: [],\n                    type: 'string.guid',\n                    context: {\n                        value: 'D1A5279D-B27D-1CD4-A05E-EFDD53D08E8D}',\n                        label: 'value'\n                    }\n                }],\n                ['{D1A5279D:B27D-1CD4-A05E-EFDD53D08E8D}', false, {\n                    message: '\"value\" must be a valid GUID',\n                    path: [],\n                    type: 'string.guid',\n                    context: {\n                        value: '{D1A5279D:B27D-1CD4-A05E-EFDD53D08E8D}',\n                        label: 'value'\n                    }\n                }],\n                ['{D1A5279D-B27D:1CD4-A05E-EFDD53D08E8D}', false, {\n                    message: '\"value\" must be a valid GUID',\n                    path: [],\n                    type: 'string.guid',\n                    context: {\n                        value: '{D1A5279D-B27D:1CD4-A05E-EFDD53D08E8D}',\n                        label: 'value'\n                    }\n                }],\n                ['{D1A5279D-B27D-1CD4:A05E-EFDD53D08E8D}', false, {\n                    message: '\"value\" must be a valid GUID',\n                    path: [],\n                    type: 'string.guid',\n                    context: {\n                        value: '{D1A5279D-B27D-1CD4:A05E-EFDD53D08E8D}',\n                        label: 'value'\n                    }\n                }],\n                ['{D1A5279D-B27D-1CD4-A05E:EFDD53D08E8D}', false, {\n                    message: '\"value\" must be a valid GUID',\n                    path: [],\n                    type: 'string.guid',\n                    context: {\n                        value: '{D1A5279D-B27D-1CD4-A05E:EFDD53D08E8D}',\n                        label: 'value'\n                    }\n                }]\n            ]);\n        });\n\n        it('validates uuidv2', () => {\n\n            Helper.validate(Joi.string().guid({ version: ['uuidv2'] }), [\n                ['{D1A5279D-B27D-2CD4-A05E-EFDD53D08E8D}', true],\n                ['{B59511BD6A5F2DF09ECF562A108D8A2E}', true],\n                ['69593D62-71EA-2548-85E4-04FC71357423', true],\n                ['677E2553DD4D23B09DA77414DB1EB8EA', true],\n                ['{5ba3bba3-729a-2717-88c1-b7c4b7ba80db}', true],\n                ['{7e9081b59a6d2cc1a8c347f69fb4198d}', true],\n                ['0c74f13f-fa83-2c48-9b33-68921dd72463', true],\n                ['b4b2fb69c6242e5eb0698e0c6ec66618', true],\n                ['{D1A5279D-B27D-4CD4-A05E-EFDD53D08E8D}', false, {\n                    message: '\"value\" must be a valid GUID',\n                    path: [],\n                    type: 'string.guid',\n                    context: {\n                        value: '{D1A5279D-B27D-4CD4-A05E-EFDD53D08E8D}',\n                        label: 'value'\n                    }\n                }],\n                ['{D1A5279D-B27D-2CD4-C05E-EFDD53D08E8D}', false, {\n                    message: '\"value\" must be a valid GUID',\n                    path: [],\n                    type: 'string.guid',\n                    context: {\n                        value: '{D1A5279D-B27D-2CD4-C05E-EFDD53D08E8D}',\n                        label: 'value'\n                    }\n                }],\n                ['{283B67B2-430F-2E6F-97E6-19041992-C1B0}', false, {\n                    message: '\"value\" must be a valid GUID',\n                    path: [],\n                    type: 'string.guid',\n                    context: {\n                        value: '{283B67B2-430F-2E6F-97E6-19041992-C1B0}',\n                        label: 'value'\n                    }\n                }],\n                ['{D1A5279D-B27D-2CD4-A05E-EFDD53D08E8D', false, {\n                    message: '\"value\" must be a valid GUID',\n                    path: [],\n                    type: 'string.guid',\n                    context: {\n                        value: '{D1A5279D-B27D-2CD4-A05E-EFDD53D08E8D',\n                        label: 'value'\n                    }\n                }],\n                ['{D1A5279D-B27D-2CD4-A05E-EFDD53D08E8D]', false, {\n                    message: '\"value\" must be a valid GUID',\n                    path: [],\n                    type: 'string.guid',\n                    context: {\n                        value: '{D1A5279D-B27D-2CD4-A05E-EFDD53D08E8D]',\n                        label: 'value'\n                    }\n                }],\n                ['D1A5279D-B27D-2CD4-A05E-EFDD53D08E8D}', false, {\n                    message: '\"value\" must be a valid GUID',\n                    path: [],\n                    type: 'string.guid',\n                    context: {\n                        value: 'D1A5279D-B27D-2CD4-A05E-EFDD53D08E8D}',\n                        label: 'value'\n                    }\n                }],\n                ['{D1A5279D:B27D-2CD4-A05E-EFDD53D08E8D}', false, {\n                    message: '\"value\" must be a valid GUID',\n                    path: [],\n                    type: 'string.guid',\n                    context: {\n                        value: '{D1A5279D:B27D-2CD4-A05E-EFDD53D08E8D}',\n                        label: 'value'\n                    }\n                }],\n                ['{D1A5279D-B27D:2CD4-A05E-EFDD53D08E8D}', false, {\n                    message: '\"value\" must be a valid GUID',\n                    path: [],\n                    type: 'string.guid',\n                    context: {\n                        value: '{D1A5279D-B27D:2CD4-A05E-EFDD53D08E8D}',\n                        label: 'value'\n                    }\n                }],\n                ['{D1A5279D-B27D-2CD4:A05E-EFDD53D08E8D}', false, {\n                    message: '\"value\" must be a valid GUID',\n                    path: [],\n                    type: 'string.guid',\n                    context: {\n                        value: '{D1A5279D-B27D-2CD4:A05E-EFDD53D08E8D}',\n                        label: 'value'\n                    }\n                }],\n                ['{D1A5279D-B27D-2CD4-A05E:EFDD53D08E8D}', false, {\n                    message: '\"value\" must be a valid GUID',\n                    path: [],\n                    type: 'string.guid',\n                    context: {\n                        value: '{D1A5279D-B27D-2CD4-A05E:EFDD53D08E8D}',\n                        label: 'value'\n                    }\n                }]\n            ]);\n        });\n\n        it('validates uuidv3', () => {\n\n            Helper.validate(Joi.string().guid({ version: ['uuidv3'] }), [\n                ['{D1A5279D-B27D-3CD4-A05E-EFDD53D08E8D}', true],\n                ['{B59511BD6A5F3DF09ECF562A108D8A2E}', true],\n                ['69593D62-71EA-3548-85E4-04FC71357423', true],\n                ['677E2553DD4D33B09DA77414DB1EB8EA', true],\n                ['{5ba3bba3-729a-3717-88c1-b7c4b7ba80db}', true],\n                ['{7e9081b59a6d3cc1a8c347f69fb4198d}', true],\n                ['0c74f13f-fa83-3c48-9b33-68921dd72463', true],\n                ['b4b2fb69c6243e5eb0698e0c6ec66618', true],\n                ['{D1A5279D-B27D-4CD4-A05E-EFDD53D08E8D}', false, {\n                    message: '\"value\" must be a valid GUID',\n                    path: [],\n                    type: 'string.guid',\n                    context: {\n                        value: '{D1A5279D-B27D-4CD4-A05E-EFDD53D08E8D}',\n                        label: 'value'\n                    }\n                }],\n                ['{D1A5279D-B27D-3CD4-C05E-EFDD53D08E8D}', false, {\n                    message: '\"value\" must be a valid GUID',\n                    path: [],\n                    type: 'string.guid',\n                    context: {\n                        value: '{D1A5279D-B27D-3CD4-C05E-EFDD53D08E8D}',\n                        label: 'value'\n                    }\n                }],\n                ['{283B67B2-430F-3E6F-97E6-19041992-C1B0}', false, {\n                    message: '\"value\" must be a valid GUID',\n                    path: [],\n                    type: 'string.guid',\n                    context: {\n                        value: '{283B67B2-430F-3E6F-97E6-19041992-C1B0}',\n                        label: 'value'\n                    }\n                }],\n                ['{D1A5279D-B27D-3CD4-A05E-EFDD53D08E8D', false, {\n                    message: '\"value\" must be a valid GUID',\n                    path: [],\n                    type: 'string.guid',\n                    context: {\n                        value: '{D1A5279D-B27D-3CD4-A05E-EFDD53D08E8D',\n                        label: 'value'\n                    }\n                }],\n                ['{D1A5279D-B27D-3CD4-A05E-EFDD53D08E8D]', false, {\n                    message: '\"value\" must be a valid GUID',\n                    path: [],\n                    type: 'string.guid',\n                    context: {\n                        value: '{D1A5279D-B27D-3CD4-A05E-EFDD53D08E8D]',\n                        label: 'value'\n                    }\n                }],\n                ['D1A5279D-B27D-3CD4-A05E-EFDD53D08E8D}', false, {\n                    message: '\"value\" must be a valid GUID',\n                    path: [],\n                    type: 'string.guid',\n                    context: {\n                        value: 'D1A5279D-B27D-3CD4-A05E-EFDD53D08E8D}',\n                        label: 'value'\n                    }\n                }],\n                ['{D1A5279D:B27D-3CD4-A05E-EFDD53D08E8D}', false, {\n                    message: '\"value\" must be a valid GUID',\n                    path: [],\n                    type: 'string.guid',\n                    context: {\n                        value: '{D1A5279D:B27D-3CD4-A05E-EFDD53D08E8D}',\n                        label: 'value'\n                    }\n                }],\n                ['{D1A5279D-B27D:3CD4-A05E-EFDD53D08E8D}', false, {\n                    message: '\"value\" must be a valid GUID',\n                    path: [],\n                    type: 'string.guid',\n                    context: {\n                        value: '{D1A5279D-B27D:3CD4-A05E-EFDD53D08E8D}',\n                        label: 'value'\n                    }\n                }],\n                ['{D1A5279D-B27D-3CD4:A05E-EFDD53D08E8D}', false, {\n                    message: '\"value\" must be a valid GUID',\n                    path: [],\n                    type: 'string.guid',\n                    context: {\n                        value: '{D1A5279D-B27D-3CD4:A05E-EFDD53D08E8D}',\n                        label: 'value'\n                    }\n                }],\n                ['{D1A5279D-B27D-3CD4-A05E:EFDD53D08E8D}', false, {\n                    message: '\"value\" must be a valid GUID',\n                    path: [],\n                    type: 'string.guid',\n                    context: {\n                        value: '{D1A5279D-B27D-3CD4-A05E:EFDD53D08E8D}',\n                        label: 'value'\n                    }\n                }]\n            ]);\n        });\n\n        it('validates uuidv4', () => {\n\n            Helper.validate(Joi.string().guid({ version: ['uuidv4'] }), [\n                ['{D1A5279D-B27D-4CD4-A05E-EFDD53D08E8D}', true],\n                ['{B59511BD6A5F4DF09ECF562A108D8A2E}', true],\n                ['69593D62-71EA-4548-85E4-04FC71357423', true],\n                ['677E2553DD4D43B09DA77414DB1EB8EA', true],\n                ['{5ba3bba3-729a-4717-88c1-b7c4b7ba80db}', true],\n                ['{7e9081b59a6d4cc1a8c347f69fb4198d}', true],\n                ['0c74f13f-fa83-4c48-9b33-68921dd72463', true],\n                ['b4b2fb69c6244e5eb0698e0c6ec66618', true],\n                ['{D1A5279D-B27D-5CD4-A05E-EFDD53D08E8D}', false, {\n                    message: '\"value\" must be a valid GUID',\n                    path: [],\n                    type: 'string.guid',\n                    context: {\n                        value: '{D1A5279D-B27D-5CD4-A05E-EFDD53D08E8D}',\n                        label: 'value'\n                    }\n                }],\n                ['{D1A5279D-B27D-4CD4-C05E-EFDD53D08E8D}', false, {\n                    message: '\"value\" must be a valid GUID',\n                    path: [],\n                    type: 'string.guid',\n                    context: {\n                        value: '{D1A5279D-B27D-4CD4-C05E-EFDD53D08E8D}',\n                        label: 'value'\n                    }\n                }],\n                ['{283B67B2-430F-4E6F-97E6-19041992-C1B0}', false, {\n                    message: '\"value\" must be a valid GUID',\n                    path: [],\n                    type: 'string.guid',\n                    context: {\n                        value: '{283B67B2-430F-4E6F-97E6-19041992-C1B0}',\n                        label: 'value'\n                    }\n                }],\n                ['{D1A5279D-B27D-4CD4-A05E-EFDD53D08E8D', false, {\n                    message: '\"value\" must be a valid GUID',\n                    path: [],\n                    type: 'string.guid',\n                    context: {\n                        value: '{D1A5279D-B27D-4CD4-A05E-EFDD53D08E8D',\n                        label: 'value'\n                    }\n                }],\n                ['{D1A5279D-B27D-4CD4-A05E-EFDD53D08E8D]', false, {\n                    message: '\"value\" must be a valid GUID',\n                    path: [],\n                    type: 'string.guid',\n                    context: {\n                        value: '{D1A5279D-B27D-4CD4-A05E-EFDD53D08E8D]',\n                        label: 'value'\n                    }\n                }],\n                ['D1A5279D-B27D-4CD4-A05E-EFDD53D08E8D}', false, {\n                    message: '\"value\" must be a valid GUID',\n                    path: [],\n                    type: 'string.guid',\n                    context: {\n                        value: 'D1A5279D-B27D-4CD4-A05E-EFDD53D08E8D}',\n                        label: 'value'\n                    }\n                }],\n                ['{D1A5279D:B27D-4CD4-A05E-EFDD53D08E8D}', false, {\n                    message: '\"value\" must be a valid GUID',\n                    path: [],\n                    type: 'string.guid',\n                    context: {\n                        value: '{D1A5279D:B27D-4CD4-A05E-EFDD53D08E8D}',\n                        label: 'value'\n                    }\n                }],\n                ['{D1A5279D-B27D:4CD4-A05E-EFDD53D08E8D}', false, {\n                    message: '\"value\" must be a valid GUID',\n                    path: [],\n                    type: 'string.guid',\n                    context: {\n                        value: '{D1A5279D-B27D:4CD4-A05E-EFDD53D08E8D}',\n                        label: 'value'\n                    }\n                }],\n                ['{D1A5279D-B27D-4CD4:A05E-EFDD53D08E8D}', false, {\n                    message: '\"value\" must be a valid GUID',\n                    path: [],\n                    type: 'string.guid',\n                    context: {\n                        value: '{D1A5279D-B27D-4CD4:A05E-EFDD53D08E8D}',\n                        label: 'value'\n                    }\n                }],\n                ['{D1A5279D-B27D-4CD4-A05E:EFDD53D08E8D}', false, {\n                    message: '\"value\" must be a valid GUID',\n                    path: [],\n                    type: 'string.guid',\n                    context: {\n                        value: '{D1A5279D-B27D-4CD4-A05E:EFDD53D08E8D}',\n                        label: 'value'\n                    }\n                }]\n            ]);\n        });\n\n        it('validates uuidv5', () => {\n\n            Helper.validate(Joi.string().guid({ version: ['uuidv5'] }), [\n                ['{D1A5279D-B27D-5CD4-A05E-EFDD53D08E8D}', true],\n                ['{B59511BD6A5F5DF09ECF562A108D8A2E}', true],\n                ['69593D62-71EA-5548-85E4-04FC71357423', true],\n                ['677E2553DD4D53B09DA77414DB1EB8EA', true],\n                ['{5ba3bba3-729a-5717-88c1-b7c4b7ba80db}', true],\n                ['{7e9081b59a6d5cc1a8c347f69fb4198d}', true],\n                ['0c74f13f-fa83-5c48-9b33-68921dd72463', true],\n                ['b4b2fb69c6245e5eb0698e0c6ec66618', true],\n                ['{D1A5279D-B27D-4CD4-A05E-EFDD53D08E8D}', false, {\n                    message: '\"value\" must be a valid GUID',\n                    path: [],\n                    type: 'string.guid',\n                    context: {\n                        value: '{D1A5279D-B27D-4CD4-A05E-EFDD53D08E8D}',\n                        label: 'value'\n                    }\n                }],\n                ['{D1A5279D-B27D-5CD4-C05E-EFDD53D08E8D}', false, {\n                    message: '\"value\" must be a valid GUID',\n                    path: [],\n                    type: 'string.guid',\n                    context: {\n                        value: '{D1A5279D-B27D-5CD4-C05E-EFDD53D08E8D}',\n                        label: 'value'\n                    }\n                }],\n                ['{283B67B2-430F-5E6F-97E6-19041992-C1B0}', false, {\n                    message: '\"value\" must be a valid GUID',\n                    path: [],\n                    type: 'string.guid',\n                    context: {\n                        value: '{283B67B2-430F-5E6F-97E6-19041992-C1B0}',\n                        label: 'value'\n                    }\n                }],\n                ['{D1A5279D-B27D-5CD4-A05E-EFDD53D08E8D', false, {\n                    message: '\"value\" must be a valid GUID',\n                    path: [],\n                    type: 'string.guid',\n                    context: {\n                        value: '{D1A5279D-B27D-5CD4-A05E-EFDD53D08E8D',\n                        label: 'value'\n                    }\n                }],\n                ['{D1A5279D-B27D-5CD4-A05E-EFDD53D08E8D]', false, {\n                    message: '\"value\" must be a valid GUID',\n                    path: [],\n                    type: 'string.guid',\n                    context: {\n                        value: '{D1A5279D-B27D-5CD4-A05E-EFDD53D08E8D]',\n                        label: 'value'\n                    }\n                }],\n                ['D1A5279D-B27D-5CD4-A05E-EFDD53D08E8D}', false, {\n                    message: '\"value\" must be a valid GUID',\n                    path: [],\n                    type: 'string.guid',\n                    context: {\n                        value: 'D1A5279D-B27D-5CD4-A05E-EFDD53D08E8D}',\n                        label: 'value'\n                    }\n                }],\n                ['{D1A5279D:B27D-5CD4-A05E-EFDD53D08E8D}', false, {\n                    message: '\"value\" must be a valid GUID',\n                    path: [],\n                    type: 'string.guid',\n                    context: {\n                        value: '{D1A5279D:B27D-5CD4-A05E-EFDD53D08E8D}',\n                        label: 'value'\n                    }\n                }],\n                ['{D1A5279D-B27D:5CD4-A05E-EFDD53D08E8D}', false, {\n                    message: '\"value\" must be a valid GUID',\n                    path: [],\n                    type: 'string.guid',\n                    context: {\n                        value: '{D1A5279D-B27D:5CD4-A05E-EFDD53D08E8D}',\n                        label: 'value'\n                    }\n                }],\n                ['{D1A5279D-B27D-5CD4:A05E-EFDD53D08E8D}', false, {\n                    message: '\"value\" must be a valid GUID',\n                    path: [],\n                    type: 'string.guid',\n                    context: {\n                        value: '{D1A5279D-B27D-5CD4:A05E-EFDD53D08E8D}',\n                        label: 'value'\n                    }\n                }],\n                ['{D1A5279D-B27D-5CD4-A05E:EFDD53D08E8D}', false, {\n                    message: '\"value\" must be a valid GUID',\n                    path: [],\n                    type: 'string.guid',\n                    context: {\n                        value: '{D1A5279D-B27D-5CD4-A05E:EFDD53D08E8D}',\n                        label: 'value'\n                    }\n                }]\n            ]);\n        });\n\n        it('validates uuidv6', () => {\n\n            Helper.validate(Joi.string().guid({ version: ['uuidv6'] }), [\n                ['{D1A5279D-B27D-6CD4-A05E-EFDD53D08E8D}', true],\n                ['{B59511BD6A5F6DF09ECF562A108D8A2E}', true],\n                ['69593D62-71EA-6548-85E4-04FC71357423', true],\n                ['677E2553DD4D63B09DA77414DB1EB8EA', true],\n                ['{5ba3bba3-729a-6717-88c1-b7c4b7ba80db}', true],\n                ['{7e9081b59a6d6cc1a8c347f69fb4198d}', true],\n                ['0c74f13f-fa83-6c48-9b33-68921dd72463', true],\n                ['b4b2fb69c6246e5eb0698e0c6ec66618', true],\n                ['{D1A5279D-B27D-4CD4-A05E-EFDD53D08E8D}', false, {\n                    message: '\"value\" must be a valid GUID',\n                    path: [],\n                    type: 'string.guid',\n                    context: {\n                        value: '{D1A5279D-B27D-4CD4-A05E-EFDD53D08E8D}',\n                        label: 'value'\n                    }\n                }],\n                ['{D1A5279D-B27D-6CD4-C05E-EFDD53D08E8D}', false, {\n                    message: '\"value\" must be a valid GUID',\n                    path: [],\n                    type: 'string.guid',\n                    context: {\n                        value: '{D1A5279D-B27D-6CD4-C05E-EFDD53D08E8D}',\n                        label: 'value'\n                    }\n                }],\n                ['{283B67B2-430F-6E6F-97E6-19041992-C1B0}', false, {\n                    message: '\"value\" must be a valid GUID',\n                    path: [],\n                    type: 'string.guid',\n                    context: {\n                        value: '{283B67B2-430F-6E6F-97E6-19041992-C1B0}',\n                        label: 'value'\n                    }\n                }],\n                ['{D1A5279D-B27D-6CD4-A05E-EFDD53D08E8D', false, {\n                    message: '\"value\" must be a valid GUID',\n                    path: [],\n                    type: 'string.guid',\n                    context: {\n                        value: '{D1A5279D-B27D-6CD4-A05E-EFDD53D08E8D',\n                        label: 'value'\n                    }\n                }],\n                ['{D1A5279D-B27D-6CD4-A05E-EFDD53D08E8D]', false, {\n                    message: '\"value\" must be a valid GUID',\n                    path: [],\n                    type: 'string.guid',\n                    context: {\n                        value: '{D1A5279D-B27D-6CD4-A05E-EFDD53D08E8D]',\n                        label: 'value'\n                    }\n                }],\n                ['D1A5279D-B27D-6CD4-A05E-EFDD53D08E8D}', false, {\n                    message: '\"value\" must be a valid GUID',\n                    path: [],\n                    type: 'string.guid',\n                    context: {\n                        value: 'D1A5279D-B27D-6CD4-A05E-EFDD53D08E8D}',\n                        label: 'value'\n                    }\n                }],\n                ['{D1A5279D:B27D-6CD4-A05E-EFDD53D08E8D}', false, {\n                    message: '\"value\" must be a valid GUID',\n                    path: [],\n                    type: 'string.guid',\n                    context: {\n                        value: '{D1A5279D:B27D-6CD4-A05E-EFDD53D08E8D}',\n                        label: 'value'\n                    }\n                }],\n                ['{D1A5279D-B27D:6CD4-A05E-EFDD53D08E8D}', false, {\n                    message: '\"value\" must be a valid GUID',\n                    path: [],\n                    type: 'string.guid',\n                    context: {\n                        value: '{D1A5279D-B27D:6CD4-A05E-EFDD53D08E8D}',\n                        label: 'value'\n                    }\n                }],\n                ['{D1A5279D-B27D-6CD4:A05E-EFDD53D08E8D}', false, {\n                    message: '\"value\" must be a valid GUID',\n                    path: [],\n                    type: 'string.guid',\n                    context: {\n                        value: '{D1A5279D-B27D-6CD4:A05E-EFDD53D08E8D}',\n                        label: 'value'\n                    }\n                }],\n                ['{D1A5279D-B27D-6CD4-A05E:EFDD53D08E8D}', false, {\n                    message: '\"value\" must be a valid GUID',\n                    path: [],\n                    type: 'string.guid',\n                    context: {\n                        value: '{D1A5279D-B27D-6CD4-A05E:EFDD53D08E8D}',\n                        label: 'value'\n                    }\n                }]\n            ]);\n        });\n\n        it('validates uuidv7', () => {\n\n            Helper.validate(Joi.string().guid({ version: ['uuidv7'] }), [\n                ['{D1A5279D-B27D-7CD4-A05E-EFDD53D08E8D}', true],\n                ['{B59511BD6A5F7DF09ECF562A108D8A2E}', true],\n                ['69593D62-71EA-7548-85E4-04FC71357423', true],\n                ['677E2553DD4D73B09DA77414DB1EB8EA', true],\n                ['{5ba3bba3-729a-7717-88c1-b7c4b7ba80db}', true],\n                ['{7e9081b59a6d7cc1a8c347f69fb4198d}', true],\n                ['0c74f13f-fa83-7c48-9b33-68921dd72463', true],\n                ['b4b2fb69c6247e5eb0698e0c6ec66618', true],\n                ['{D1A5279D-B27D-4CD4-A05E-EFDD53D08E8D}', false, {\n                    message: '\"value\" must be a valid GUID',\n                    path: [],\n                    type: 'string.guid',\n                    context: {\n                        value: '{D1A5279D-B27D-4CD4-A05E-EFDD53D08E8D}',\n                        label: 'value'\n                    }\n                }],\n                ['{D1A5279D-B27D-7CD4-C05E-EFDD53D08E8D}', false, {\n                    message: '\"value\" must be a valid GUID',\n                    path: [],\n                    type: 'string.guid',\n                    context: {\n                        value: '{D1A5279D-B27D-7CD4-C05E-EFDD53D08E8D}',\n                        label: 'value'\n                    }\n                }],\n                ['{283B67B2-430F-7E6F-97E6-19041992-C1B0}', false, {\n                    message: '\"value\" must be a valid GUID',\n                    path: [],\n                    type: 'string.guid',\n                    context: {\n                        value: '{283B67B2-430F-7E6F-97E6-19041992-C1B0}',\n                        label: 'value'\n                    }\n                }],\n                ['{D1A5279D-B27D-7CD4-A05E-EFDD53D08E8D', false, {\n                    message: '\"value\" must be a valid GUID',\n                    path: [],\n                    type: 'string.guid',\n                    context: {\n                        value: '{D1A5279D-B27D-7CD4-A05E-EFDD53D08E8D',\n                        label: 'value'\n                    }\n                }],\n                ['{D1A5279D-B27D-7CD4-A05E-EFDD53D08E8D]', false, {\n                    message: '\"value\" must be a valid GUID',\n                    path: [],\n                    type: 'string.guid',\n                    context: {\n                        value: '{D1A5279D-B27D-7CD4-A05E-EFDD53D08E8D]',\n                        label: 'value'\n                    }\n                }],\n                ['D1A5279D-B27D-7CD4-A05E-EFDD53D08E8D}', false, {\n                    message: '\"value\" must be a valid GUID',\n                    path: [],\n                    type: 'string.guid',\n                    context: {\n                        value: 'D1A5279D-B27D-7CD4-A05E-EFDD53D08E8D}',\n                        label: 'value'\n                    }\n                }],\n                ['{D1A5279D:B27D-7CD4-A05E-EFDD53D08E8D}', false, {\n                    message: '\"value\" must be a valid GUID',\n                    path: [],\n                    type: 'string.guid',\n                    context: {\n                        value: '{D1A5279D:B27D-7CD4-A05E-EFDD53D08E8D}',\n                        label: 'value'\n                    }\n                }],\n                ['{D1A5279D-B27D:7CD4-A05E-EFDD53D08E8D}', false, {\n                    message: '\"value\" must be a valid GUID',\n                    path: [],\n                    type: 'string.guid',\n                    context: {\n                        value: '{D1A5279D-B27D:7CD4-A05E-EFDD53D08E8D}',\n                        label: 'value'\n                    }\n                }],\n                ['{D1A5279D-B27D-7CD4:A05E-EFDD53D08E8D}', false, {\n                    message: '\"value\" must be a valid GUID',\n                    path: [],\n                    type: 'string.guid',\n                    context: {\n                        value: '{D1A5279D-B27D-7CD4:A05E-EFDD53D08E8D}',\n                        label: 'value'\n                    }\n                }],\n                ['{D1A5279D-B27D-7CD4-A05E:EFDD53D08E8D}', false, {\n                    message: '\"value\" must be a valid GUID',\n                    path: [],\n                    type: 'string.guid',\n                    context: {\n                        value: '{D1A5279D-B27D-7CD4-A05E:EFDD53D08E8D}',\n                        label: 'value'\n                    }\n                }]\n            ]);\n        });\n\n        it('validates uuidv8', () => {\n\n            Helper.validate(Joi.string().guid({ version: ['uuidv8'] }), [\n                ['{D1A5279D-B27D-8CD4-A05E-EFDD53D08E8D}', true],\n                ['{B59511BD6A5F8DF09ECF562A108D8A2E}', true],\n                ['69593D62-71EA-8548-85E4-04FC71357423', true],\n                ['677E2553DD4D83B09DA77414DB1EB8EA', true],\n                ['{5ba3bba3-729a-8717-88c1-b7c4b7ba80db}', true],\n                ['{7e9081b59a6d8cc1a8c347f69fb4198d}', true],\n                ['0c74f13f-fa83-8c48-9b33-68921dd72463', true],\n                ['b4b2fb69c6248e5eb0698e0c6ec66618', true],\n                ['{D1A5279D-B27D-4CD4-A05E-EFDD53D08E8D}', false, {\n                    message: '\"value\" must be a valid GUID',\n                    path: [],\n                    type: 'string.guid',\n                    context: {\n                        value: '{D1A5279D-B27D-4CD4-A05E-EFDD53D08E8D}',\n                        label: 'value'\n                    }\n                }],\n                ['{D1A5279D-B27D-8CD4-C05E-EFDD53D08E8D}', false, {\n                    message: '\"value\" must be a valid GUID',\n                    path: [],\n                    type: 'string.guid',\n                    context: {\n                        value: '{D1A5279D-B27D-8CD4-C05E-EFDD53D08E8D}',\n                        label: 'value'\n                    }\n                }],\n                ['{283B67B2-430F-8E6F-97E6-19041992-C1B0}', false, {\n                    message: '\"value\" must be a valid GUID',\n                    path: [],\n                    type: 'string.guid',\n                    context: {\n                        value: '{283B67B2-430F-8E6F-97E6-19041992-C1B0}',\n                        label: 'value'\n                    }\n                }],\n                ['{D1A5279D-B27D-8CD4-A05E-EFDD53D08E8D', false, {\n                    message: '\"value\" must be a valid GUID',\n                    path: [],\n                    type: 'string.guid',\n                    context: {\n                        value: '{D1A5279D-B27D-8CD4-A05E-EFDD53D08E8D',\n                        label: 'value'\n                    }\n                }],\n                ['{D1A5279D-B27D-8CD4-A05E-EFDD53D08E8D]', false, {\n                    message: '\"value\" must be a valid GUID',\n                    path: [],\n                    type: 'string.guid',\n                    context: {\n                        value: '{D1A5279D-B27D-8CD4-A05E-EFDD53D08E8D]',\n                        label: 'value'\n                    }\n                }],\n                ['D1A5279D-B27D-8CD4-A05E-EFDD53D08E8D}', false, {\n                    message: '\"value\" must be a valid GUID',\n                    path: [],\n                    type: 'string.guid',\n                    context: {\n                        value: 'D1A5279D-B27D-8CD4-A05E-EFDD53D08E8D}',\n                        label: 'value'\n                    }\n                }],\n                ['{D1A5279D:B27D-8CD4-A05E-EFDD53D08E8D}', false, {\n                    message: '\"value\" must be a valid GUID',\n                    path: [],\n                    type: 'string.guid',\n                    context: {\n                        value: '{D1A5279D:B27D-8CD4-A05E-EFDD53D08E8D}',\n                        label: 'value'\n                    }\n                }],\n                ['{D1A5279D-B27D:8CD4-A05E-EFDD53D08E8D}', false, {\n                    message: '\"value\" must be a valid GUID',\n                    path: [],\n                    type: 'string.guid',\n                    context: {\n                        value: '{D1A5279D-B27D:8CD4-A05E-EFDD53D08E8D}',\n                        label: 'value'\n                    }\n                }],\n                ['{D1A5279D-B27D-8CD4:A05E-EFDD53D08E8D}', false, {\n                    message: '\"value\" must be a valid GUID',\n                    path: [],\n                    type: 'string.guid',\n                    context: {\n                        value: '{D1A5279D-B27D-8CD4:A05E-EFDD53D08E8D}',\n                        label: 'value'\n                    }\n                }],\n                ['{D1A5279D-B27D-8CD4-A05E:EFDD53D08E8D}', false, {\n                    message: '\"value\" must be a valid GUID',\n                    path: [],\n                    type: 'string.guid',\n                    context: {\n                        value: '{D1A5279D-B27D-8CD4-A05E:EFDD53D08E8D}',\n                        label: 'value'\n                    }\n                }]\n            ]);\n        });\n\n        it('validates multiple uuid versions (1,3,5,7)', () => {\n\n            Helper.validate(Joi.string().guid({ version: ['uuidv1', 'uuidv3', 'uuidv5', 'uuidv7'] }), [\n                ['{D1A5279D-B27D-1CD4-805E-EFDD53D08E8D}', true],\n                ['{D1A5279D-B27D-3CD4-905E-EFDD53D08E8D}', true],\n                ['{D1A5279D-B27D-5CD4-A05E-EFDD53D08E8D}', true],\n                ['{D1A5279D-B27D-7CD4-A05E-EFDD53D08E8D}', true],\n                ['{B59511BD6A5F5DF09ECF562A108D8A2E}', true],\n                ['69593D62-71EA-5548-85E4-04FC71357423', true],\n                ['677E2553DD4D53B09DA77414DB1EB8EA', true],\n                ['{5ba3bba3-729a-5717-88c1-b7c4b7ba80db}', true],\n                ['{7e9081b59a6d5cc1a8c347f69fb4198d}', true],\n                ['0c74f13f-fa83-5c48-9b33-68921dd72463', true],\n                ['b4b2fb69c6245e5eb0698e0c6ec66618', true],\n                ['{D1A5279D-B27D-4CD4-A05E-EFDD53D08E8D}', false, {\n                    message: '\"value\" must be a valid GUID',\n                    path: [],\n                    type: 'string.guid',\n                    context: {\n                        value: '{D1A5279D-B27D-4CD4-A05E-EFDD53D08E8D}',\n                        label: 'value'\n                    }\n                }],\n                ['{D1A5279D-B27D-5CD4-C05E-EFDD53D08E8D}', false, {\n                    message: '\"value\" must be a valid GUID',\n                    path: [],\n                    type: 'string.guid',\n                    context: {\n                        value: '{D1A5279D-B27D-5CD4-C05E-EFDD53D08E8D}',\n                        label: 'value'\n                    }\n                }],\n                ['{283B67B2-430F-5E6F-97E6-19041992-C1B0}', false, {\n                    message: '\"value\" must be a valid GUID',\n                    path: [],\n                    type: 'string.guid',\n                    context: {\n                        value: '{283B67B2-430F-5E6F-97E6-19041992-C1B0}',\n                        label: 'value'\n                    }\n                }],\n                ['{D1A5279D-B27D-5CD4-A05E-EFDD53D08E8D', false, {\n                    message: '\"value\" must be a valid GUID',\n                    path: [],\n                    type: 'string.guid',\n                    context: {\n                        value: '{D1A5279D-B27D-5CD4-A05E-EFDD53D08E8D',\n                        label: 'value'\n                    }\n                }],\n                ['{D1A5279D-B27D-5CD4-A05E-EFDD53D08E8D]', false, {\n                    message: '\"value\" must be a valid GUID',\n                    path: [],\n                    type: 'string.guid',\n                    context: {\n                        value: '{D1A5279D-B27D-5CD4-A05E-EFDD53D08E8D]',\n                        label: 'value'\n                    }\n                }],\n                ['D1A5279D-B27D-5CD4-A05E-EFDD53D08E8D}', false, {\n                    message: '\"value\" must be a valid GUID',\n                    path: [],\n                    type: 'string.guid',\n                    context: {\n                        value: 'D1A5279D-B27D-5CD4-A05E-EFDD53D08E8D}',\n                        label: 'value'\n                    }\n                }],\n                ['{D1A5279D:B27D-5CD4-A05E-EFDD53D08E8D}', false, {\n                    message: '\"value\" must be a valid GUID',\n                    path: [],\n                    type: 'string.guid',\n                    context: {\n                        value: '{D1A5279D:B27D-5CD4-A05E-EFDD53D08E8D}',\n                        label: 'value'\n                    }\n                }],\n                ['{D1A5279D-B27D:5CD4-A05E-EFDD53D08E8D}', false, {\n                    message: '\"value\" must be a valid GUID',\n                    path: [],\n                    type: 'string.guid',\n                    context: {\n                        value: '{D1A5279D-B27D:5CD4-A05E-EFDD53D08E8D}',\n                        label: 'value'\n                    }\n                }],\n                ['{D1A5279D-B27D-5CD4:A05E-EFDD53D08E8D}', false, {\n                    message: '\"value\" must be a valid GUID',\n                    path: [],\n                    type: 'string.guid',\n                    context: {\n                        value: '{D1A5279D-B27D-5CD4:A05E-EFDD53D08E8D}',\n                        label: 'value'\n                    }\n                }],\n                ['{D1A5279D-B27D-5CD4-A05E:EFDD53D08E8D}', false, {\n                    message: '\"value\" must be a valid GUID',\n                    path: [],\n                    type: 'string.guid',\n                    context: {\n                        value: '{D1A5279D-B27D-5CD4-A05E:EFDD53D08E8D}',\n                        label: 'value'\n                    }\n                }]\n            ]);\n        });\n\n        it('validates guid', () => {\n\n            const schema = { item: Joi.string().guid() };\n            Helper.validate(Joi.compile(schema), [[{ item: 'something' }, false, {\n                message: '\"item\" must be a valid GUID',\n                path: ['item'],\n                type: 'string.guid',\n                context: { value: 'something', label: 'item', key: 'item' }\n            }]]);\n        });\n\n        it('validates separator', () => {\n\n            Helper.validate(Joi.string().guid({ separator: '-' }), [\n                ['69593D62-71EA-4548-85E4-04FC71357423', true],\n                ['69593D62:71EA:4548:85E4:04FC71357423', false, '\"value\" must be a valid GUID'],\n                ['b4b2fb69c6241e5eb0698e0c6ec66618', false, '\"value\" must be a valid GUID']\n            ]);\n\n            Helper.validate(Joi.string().guid({ separator: ':' }), [\n                ['69593D62:71EA:4548:85E4:04FC71357423', true],\n                ['69593D62-71EA-4548-85E4-04FC71357423', false, '\"value\" must be a valid GUID'],\n                ['b4b2fb69c6241e5eb0698e0c6ec66618', false, '\"value\" must be a valid GUID']\n            ]);\n\n            Helper.validate(Joi.string().guid({ separator: true }), [\n                ['69593D62-71EA-4548-85E4-04FC71357423', true],\n                ['69593D62:71EA:4548:85E4:04FC71357423', true],\n                ['b4b2fb69c6241e5eb0698e0c6ec66618', false, '\"value\" must be a valid GUID']\n            ]);\n\n            Helper.validate(Joi.string().guid({ separator: false }), [\n                ['69593D62-71EA-4548-85E4-04FC71357423', false, '\"value\" must be a valid GUID'],\n                ['69593D62:71EA:4548:85E4:04FC71357423', false, '\"value\" must be a valid GUID'],\n                ['b4b2fb69c6241e5eb0698e0c6ec66618', true]\n            ]);\n        });\n\n        it('validates wrapper', () => {\n\n            // Wthout wrapper all are valid GUIDs\n            Helper.validate(Joi.string().guid(), [\n                ['69593D62-71EA-4548-85E4-04FC71357423', true],\n                ['{69593D62-71EA-4548-85E4-04FC71357423}', true],\n                ['[69593D62-71EA-4548-85E4-04FC71357423]', true],\n                ['(69593D62-71EA-4548-85E4-04FC71357423)', true]\n            ]);\n\n            // Wrapper false means no wrapper is allowed\n            Helper.validate(Joi.string().guid({ wrapper: false }), [\n                ['69593D62-71EA-4548-85E4-04FC71357423', true],\n                ['{69593D62-71EA-4548-85E4-04FC71357423}', false, '\"value\" must be a valid GUID'],\n                ['[69593D62-71EA-4548-85E4-04FC71357423]', false, '\"value\" must be a valid GUID'],\n                ['(69593D62-71EA-4548-85E4-04FC71357423)', false, '\"value\" must be a valid GUID']\n            ]);\n\n            // Wrapper true means a wrapper is enforced\n            Helper.validate(Joi.string().guid({ wrapper: true }), [\n                ['69593D62-71EA-4548-85E4-04FC71357423', false, '\"value\" must be a valid GUID'],\n                ['{69593D62-71EA-4548-85E4-04FC71357423}', true],\n                ['[69593D62-71EA-4548-85E4-04FC71357423]', true],\n                ['(69593D62-71EA-4548-85E4-04FC71357423)', true]\n            ]);\n\n            // Wrapper can be set to a specific value\n            Helper.validate(Joi.string().guid({ wrapper: '{' }), [\n                ['69593D62-71EA-4548-85E4-04FC71357423', false, '\"value\" must be a valid GUID'],\n                ['{69593D62-71EA-4548-85E4-04FC71357423}', true],\n                ['[69593D62-71EA-4548-85E4-04FC71357423]', false, '\"value\" must be a valid GUID'],\n                ['(69593D62-71EA-4548-85E4-04FC71357423)', false, '\"value\" must be a valid GUID']\n            ]);\n\n            // Wrapper can be set to a specific value\n            Helper.validate(Joi.string().guid({ wrapper: '(' }), [\n                ['69593D62-71EA-4548-85E4-04FC71357423', false, '\"value\" must be a valid GUID'],\n                ['{69593D62-71EA-4548-85E4-04FC71357423}', false, '\"value\" must be a valid GUID'],\n                ['[69593D62-71EA-4548-85E4-04FC71357423]', false, '\"value\" must be a valid GUID'],\n                ['(69593D62-71EA-4548-85E4-04FC71357423)', true]\n            ]);\n\n            // Wrapper can be set to a specific value\n            Helper.validate(Joi.string().guid({ wrapper: '[' }), [\n                ['69593D62-71EA-4548-85E4-04FC71357423', false, '\"value\" must be a valid GUID'],\n                ['{69593D62-71EA-4548-85E4-04FC71357423}', false, '\"value\" must be a valid GUID'],\n                ['[69593D62-71EA-4548-85E4-04FC71357423]', true],\n                ['(69593D62-71EA-4548-85E4-04FC71357423)', false, '\"value\" must be a valid GUID']\n            ]);\n\n        });\n\n        it('errors on invalid wrapper', () => {\n\n            expect(() => {\n\n                Joi.string().guid({ wrapper: 1 });\n            }).to.throw('\"wrapper\" must be true, false, or one of \"{\", \"[\", \"(\"');\n\n            expect(() => {\n\n                Joi.string().guid({ wrapper: '|' });\n            }).to.throw('\"wrapper\" must be true, false, or one of \"{\", \"[\", \"(\"');\n        });\n\n        it('validates combination of guid and min', () => {\n\n            const rule = Joi.string().guid().min(36);\n            Helper.validate(rule, [\n                ['{D1A5279D-B27D-4CD4-A05E-EFDD53D08E8D}', true],\n                ['{B59511BD6A5F4DF09ECF562A108D8A2E}', false, {\n                    message: '\"value\" length must be at least 36 characters long',\n                    path: [],\n                    type: 'string.min',\n                    context: {\n                        limit: 36,\n                        value: '{B59511BD6A5F4DF09ECF562A108D8A2E}',\n                        encoding: undefined,\n                        label: 'value'\n                    }\n                }],\n                ['69593D62-71EA-4548-85E4-04FC71357423', true],\n                ['677E2553DD4D43B09DA77414DB1EB8EA', false, {\n                    message: '\"value\" length must be at least 36 characters long',\n                    path: [],\n                    type: 'string.min',\n                    context: {\n                        limit: 36,\n                        value: '677E2553DD4D43B09DA77414DB1EB8EA',\n                        encoding: undefined,\n                        label: 'value'\n                    }\n                }],\n                ['{5ba3bba3-729a-4717-88c1-b7c4b7ba80db}', true],\n                ['{7e9081b59a6d4cc1a8c347f69fb4198d}', false, {\n                    message: '\"value\" length must be at least 36 characters long',\n                    path: [],\n                    type: 'string.min',\n                    context: {\n                        limit: 36,\n                        value: '{7e9081b59a6d4cc1a8c347f69fb4198d}',\n                        encoding: undefined,\n                        label: 'value'\n                    }\n                }],\n                ['0c74f13f-fa83-4c48-9b33-68921dd72463', true],\n                ['b4b2fb69c6244e5eb0698e0c6ec66618', false, {\n                    message: '\"value\" length must be at least 36 characters long',\n                    path: [],\n                    type: 'string.min',\n                    context: {\n                        limit: 36,\n                        value: 'b4b2fb69c6244e5eb0698e0c6ec66618',\n                        encoding: undefined,\n                        label: 'value'\n                    }\n                }],\n                ['{283B67B2-430F-4E6F-97E6-19041992-C1B0}', false, {\n                    message: '\"value\" must be a valid GUID',\n                    path: [],\n                    type: 'string.guid',\n                    context: {\n                        value: '{283B67B2-430F-4E6F-97E6-19041992-C1B0}',\n                        label: 'value'\n                    }\n                }],\n                ['{D1A5279D-B27D-4CD4-A05E-EFDD53D08E8D', false, {\n                    message: '\"value\" must be a valid GUID',\n                    path: [],\n                    type: 'string.guid',\n                    context: {\n                        value: '{D1A5279D-B27D-4CD4-A05E-EFDD53D08E8D',\n                        label: 'value'\n                    }\n                }],\n                ['D1A5279D-B27D-4CD4-A05E-EFDD53D08E8D}', false, {\n                    message: '\"value\" must be a valid GUID',\n                    path: [],\n                    type: 'string.guid',\n                    context: {\n                        value: 'D1A5279D-B27D-4CD4-A05E-EFDD53D08E8D}',\n                        label: 'value'\n                    }\n                }],\n                ['', false, {\n                    message: '\"value\" is not allowed to be empty',\n                    path: [],\n                    type: 'string.empty',\n                    context: { value: '', label: 'value' }\n                }],\n                [null, false, {\n                    message: '\"value\" must be a string',\n                    path: [],\n                    type: 'string.base',\n                    context: { value: null, label: 'value' }\n                }]\n            ]);\n        });\n\n        it('validates combination of guid, min and max', () => {\n\n            const rule = Joi.string().guid().min(32).max(34);\n            Helper.validate(rule, [\n                ['{D1A5279D-B27D-4CD4-A05E-EFDD53D08E8D}', false, {\n                    message: '\"value\" length must be less than or equal to 34 characters long',\n                    path: [],\n                    type: 'string.max',\n                    context: {\n                        limit: 34,\n                        value: '{D1A5279D-B27D-4CD4-A05E-EFDD53D08E8D}',\n                        encoding: undefined,\n                        label: 'value'\n                    }\n                }],\n                ['{B59511BD6A5F4DF09ECF562A108D8A2E}', true],\n                ['69593D62-71EA-4548-85E4-04FC71357423', false, {\n                    message: '\"value\" length must be less than or equal to 34 characters long',\n                    path: [],\n                    type: 'string.max',\n                    context: {\n                        limit: 34,\n                        value: '69593D62-71EA-4548-85E4-04FC71357423',\n                        encoding: undefined,\n                        label: 'value'\n                    }\n                }],\n                ['677E2553DD4D43B09DA77414DB1EB8EA', true],\n                ['{5ba3bba3-729a-4717-88c1-b7c4b7ba80db}', false, {\n                    message: '\"value\" length must be less than or equal to 34 characters long',\n                    path: [],\n                    type: 'string.max',\n                    context: {\n                        limit: 34,\n                        value: '{5ba3bba3-729a-4717-88c1-b7c4b7ba80db}',\n                        encoding: undefined,\n                        label: 'value'\n                    }\n                }],\n                ['{7e9081b59a6d4cc1a8c347f69fb4198d}', true],\n                ['0c74f13f-fa83-4c48-9b33-68921dd72463', false, {\n                    message: '\"value\" length must be less than or equal to 34 characters long',\n                    path: [],\n                    type: 'string.max',\n                    context: {\n                        limit: 34,\n                        value: '0c74f13f-fa83-4c48-9b33-68921dd72463',\n                        encoding: undefined,\n                        label: 'value'\n                    }\n                }],\n                ['b4b2fb69c6244e5eb0698e0c6ec66618', true],\n                ['{283B67B2-430F-4E6F-97E6-19041992-C1B0}', false, {\n                    message: '\"value\" must be a valid GUID',\n                    path: [],\n                    type: 'string.guid',\n                    context: {\n                        value: '{283B67B2-430F-4E6F-97E6-19041992-C1B0}',\n                        label: 'value'\n                    }\n                }],\n                ['{D1A5279D-B27D-4CD4-A05E-EFDD53D08E8D', false, {\n                    message: '\"value\" must be a valid GUID',\n                    path: [],\n                    type: 'string.guid',\n                    context: {\n                        value: '{D1A5279D-B27D-4CD4-A05E-EFDD53D08E8D',\n                        label: 'value'\n                    }\n                }],\n                ['D1A5279D-B27D-4CD4-A05E-EFDD53D08E8D}', false, {\n                    message: '\"value\" must be a valid GUID',\n                    path: [],\n                    type: 'string.guid',\n                    context: {\n                        value: 'D1A5279D-B27D-4CD4-A05E-EFDD53D08E8D}',\n                        label: 'value'\n                    }\n                }],\n                ['', false, {\n                    message: '\"value\" is not allowed to be empty',\n                    path: [],\n                    type: 'string.empty',\n                    context: { value: '', label: 'value' }\n                }],\n                [null, false, {\n                    message: '\"value\" must be a string',\n                    path: [],\n                    type: 'string.base',\n                    context: { value: null, label: 'value' }\n                }]\n            ]);\n        });\n\n        it('validates combination of guid, min, max and invalid', () => {\n\n            const rule = Joi.string().guid().min(32).max(34).invalid('b4b2fb69c6244e5eb0698e0c6ec66618');\n            Helper.validate(rule, [\n                ['{D1A5279D-B27D-4CD4-A05E-EFDD53D08E8D}', false, {\n                    message: '\"value\" length must be less than or equal to 34 characters long',\n                    path: [],\n                    type: 'string.max',\n                    context: {\n                        limit: 34,\n                        value: '{D1A5279D-B27D-4CD4-A05E-EFDD53D08E8D}',\n                        encoding: undefined,\n                        label: 'value'\n                    }\n                }],\n                ['{B59511BD6A5F4DF09ECF562A108D8A2E}', true],\n                ['69593D62-71EA-4548-85E4-04FC71357423', false, {\n                    message: '\"value\" length must be less than or equal to 34 characters long',\n                    path: [],\n                    type: 'string.max',\n                    context: {\n                        limit: 34,\n                        value: '69593D62-71EA-4548-85E4-04FC71357423',\n                        encoding: undefined,\n                        label: 'value'\n                    }\n                }],\n                ['677E2553DD4D43B09DA77414DB1EB8EA', true],\n                ['{5ba3bba3-729a-4717-88c1-b7c4b7ba80db}', false, {\n                    message: '\"value\" length must be less than or equal to 34 characters long',\n                    path: [],\n                    type: 'string.max',\n                    context: {\n                        limit: 34,\n                        value: '{5ba3bba3-729a-4717-88c1-b7c4b7ba80db}',\n                        encoding: undefined,\n                        label: 'value'\n                    }\n                }],\n                ['{7e9081b59a6d4cc1a8c347f69fb4198d}', true],\n                ['0c74f13f-fa83-4c48-9b33-68921dd72463', false, {\n                    message: '\"value\" length must be less than or equal to 34 characters long',\n                    path: [],\n                    type: 'string.max',\n                    context: {\n                        limit: 34,\n                        value: '0c74f13f-fa83-4c48-9b33-68921dd72463',\n                        encoding: undefined,\n                        label: 'value'\n                    }\n                }],\n                ['b4b2fb69c6244e5eb0698e0c6ec66618', false, {\n                    message: '\"value\" contains an invalid value',\n                    path: [],\n                    type: 'any.invalid',\n                    context: { value: 'b4b2fb69c6244e5eb0698e0c6ec66618', invalids: ['b4b2fb69c6244e5eb0698e0c6ec66618'], label: 'value' }\n                }],\n                ['{283B67B2-430F-4E6F-97E6-19041992-C1B0}', false, {\n                    message: '\"value\" must be a valid GUID',\n                    path: [],\n                    type: 'string.guid',\n                    context: {\n                        value: '{283B67B2-430F-4E6F-97E6-19041992-C1B0}',\n                        label: 'value'\n                    }\n                }],\n                ['{D1A5279D-B27D-4CD4-A05E-EFDD53D08E8D', false, {\n                    message: '\"value\" must be a valid GUID',\n                    path: [],\n                    type: 'string.guid',\n                    context: {\n                        value: '{D1A5279D-B27D-4CD4-A05E-EFDD53D08E8D',\n                        label: 'value'\n                    }\n                }],\n                ['D1A5279D-B27D-4CD4-A05E-EFDD53D08E8D}', false, {\n                    message: '\"value\" must be a valid GUID',\n                    path: [],\n                    type: 'string.guid',\n                    context: {\n                        value: 'D1A5279D-B27D-4CD4-A05E-EFDD53D08E8D}',\n                        label: 'value'\n                    }\n                }],\n                ['', false, {\n                    message: '\"value\" is not allowed to be empty',\n                    path: [],\n                    type: 'string.empty',\n                    context: { value: '', label: 'value' }\n                }],\n                [null, false, {\n                    message: '\"value\" must be a string',\n                    path: [],\n                    type: 'string.base',\n                    context: { value: null, label: 'value' }\n                }]\n            ]);\n        });\n\n        it('validates combination of guid, min, max and allow', () => {\n\n            const rule = Joi.string().guid().min(32).max(34).allow('{D1A5279D-B27D-4CD4-A05E-EFDD53D08E8D');\n            Helper.validate(rule, [\n                ['{D1A5279D-B27D-4CD4-A05E-EFDD53D08E8D}', false, {\n                    message: '\"value\" length must be less than or equal to 34 characters long',\n                    path: [],\n                    type: 'string.max',\n                    context: {\n                        limit: 34,\n                        value: '{D1A5279D-B27D-4CD4-A05E-EFDD53D08E8D}',\n                        encoding: undefined,\n                        label: 'value'\n                    }\n                }],\n                ['{B59511BD6A5F4DF09ECF562A108D8A2E}', true],\n                ['69593D62-71EA-4548-85E4-04FC71357423', false, {\n                    message: '\"value\" length must be less than or equal to 34 characters long',\n                    path: [],\n                    type: 'string.max',\n                    context: {\n                        limit: 34,\n                        value: '69593D62-71EA-4548-85E4-04FC71357423',\n                        encoding: undefined,\n                        label: 'value'\n                    }\n                }],\n                ['677E2553DD4D43B09DA77414DB1EB8EA', true],\n                ['{5ba3bba3-729a-4717-88c1-b7c4b7ba80db}', false, {\n                    message: '\"value\" length must be less than or equal to 34 characters long',\n                    path: [],\n                    type: 'string.max',\n                    context: {\n                        limit: 34,\n                        value: '{5ba3bba3-729a-4717-88c1-b7c4b7ba80db}',\n                        encoding: undefined,\n                        label: 'value'\n                    }\n                }],\n                ['{7e9081b59a6d4cc1a8c347f69fb4198d}', true],\n                ['0c74f13f-fa83-4c48-9b33-68921dd72463', false, {\n                    message: '\"value\" length must be less than or equal to 34 characters long',\n                    path: [],\n                    type: 'string.max',\n                    context: {\n                        limit: 34,\n                        value: '0c74f13f-fa83-4c48-9b33-68921dd72463',\n                        encoding: undefined,\n                        label: 'value'\n                    }\n                }],\n                ['b4b2fb69c6244e5eb0698e0c6ec66618', true],\n                ['{283B67B2-430F-4E6F-97E6-19041992-C1B0}', false, {\n                    message: '\"value\" must be a valid GUID',\n                    path: [],\n                    type: 'string.guid',\n                    context: {\n                        value: '{283B67B2-430F-4E6F-97E6-19041992-C1B0}',\n                        label: 'value'\n                    }\n                }],\n                ['{D1A5279D-B27D-4CD4-A05E-EFDD53D08E8D', true],\n                ['D1A5279D-B27D-4CD4-A05E-EFDD53D08E8D}', false, {\n                    message: '\"value\" must be a valid GUID',\n                    path: [],\n                    type: 'string.guid',\n                    context: {\n                        value: 'D1A5279D-B27D-4CD4-A05E-EFDD53D08E8D}',\n                        label: 'value'\n                    }\n                }],\n                ['', false, {\n                    message: '\"value\" is not allowed to be empty',\n                    path: [],\n                    type: 'string.empty',\n                    context: { value: '', label: 'value' }\n                }],\n                [null, false, {\n                    message: '\"value\" must be a string',\n                    path: [],\n                    type: 'string.base',\n                    context: { value: null, label: 'value' }\n                }]\n            ]);\n        });\n\n        it('validates combination of guid, min, max, allow and invalid', () => {\n\n            const rule = Joi.string().guid().min(32).max(34).allow('{D1A5279D-B27D-4CD4-A05E-EFDD53D08E8D').invalid('b4b2fb69c6244e5eb0698e0c6ec66618');\n            Helper.validate(rule, [\n                ['{D1A5279D-B27D-4CD4-A05E-EFDD53D08E8D}', false, {\n                    message: '\"value\" length must be less than or equal to 34 characters long',\n                    path: [],\n                    type: 'string.max',\n                    context: {\n                        limit: 34,\n                        value: '{D1A5279D-B27D-4CD4-A05E-EFDD53D08E8D}',\n                        encoding: undefined,\n                        label: 'value'\n                    }\n                }],\n                ['{B59511BD6A5F4DF09ECF562A108D8A2E}', true],\n                ['69593D62-71EA-4548-85E4-04FC71357423', false, {\n                    message: '\"value\" length must be less than or equal to 34 characters long',\n                    path: [],\n                    type: 'string.max',\n                    context: {\n                        limit: 34,\n                        value: '69593D62-71EA-4548-85E4-04FC71357423',\n                        encoding: undefined,\n                        label: 'value'\n                    }\n                }],\n                ['677E2553DD4D43B09DA77414DB1EB8EA', true],\n                ['{5ba3bba3-729a-4717-88c1-b7c4b7ba80db}', false, {\n                    message: '\"value\" length must be less than or equal to 34 characters long',\n                    path: [],\n                    type: 'string.max',\n                    context: {\n                        limit: 34,\n                        value: '{5ba3bba3-729a-4717-88c1-b7c4b7ba80db}',\n                        encoding: undefined,\n                        label: 'value'\n                    }\n                }],\n                ['{7e9081b59a6d4cc1a8c347f69fb4198d}', true],\n                ['0c74f13f-fa83-4c48-9b33-68921dd72463', false, {\n                    message: '\"value\" length must be less than or equal to 34 characters long',\n                    path: [],\n                    type: 'string.max',\n                    context: {\n                        limit: 34,\n                        value: '0c74f13f-fa83-4c48-9b33-68921dd72463',\n                        encoding: undefined,\n                        label: 'value'\n                    }\n                }],\n                ['b4b2fb69c6244e5eb0698e0c6ec66618', false, {\n                    message: '\"value\" contains an invalid value',\n                    path: [],\n                    type: 'any.invalid',\n                    context: { value: 'b4b2fb69c6244e5eb0698e0c6ec66618', invalids: ['b4b2fb69c6244e5eb0698e0c6ec66618'], label: 'value' }\n                }],\n                ['{283B67B2-430F-4E6F-97E6-19041992-C1B0}', false, {\n                    message: '\"value\" must be a valid GUID',\n                    path: [],\n                    type: 'string.guid',\n                    context: {\n                        value: '{283B67B2-430F-4E6F-97E6-19041992-C1B0}',\n                        label: 'value'\n                    }\n                }],\n                ['{D1A5279D-B27D-4CD4-A05E-EFDD53D08E8D', true],\n                ['D1A5279D-B27D-4CD4-A05E-EFDD53D08E8D}', false, {\n                    message: '\"value\" must be a valid GUID',\n                    path: [],\n                    type: 'string.guid',\n                    context: {\n                        value: 'D1A5279D-B27D-4CD4-A05E-EFDD53D08E8D}',\n                        label: 'value'\n                    }\n                }],\n                ['', false, {\n                    message: '\"value\" is not allowed to be empty',\n                    path: [],\n                    type: 'string.empty',\n                    context: { value: '', label: 'value' }\n                }],\n                [null, false, {\n                    message: '\"value\" must be a string',\n                    path: [],\n                    type: 'string.base',\n                    context: { value: null, label: 'value' }\n                }]\n            ]);\n        });\n\n        it('validates combination of guid, min, max, allow, invalid and allow(\\'\\')', () => {\n\n            const rule = Joi.string().guid().min(32).max(34).allow('{D1A5279D-B27D-4CD4-A05E-EFDD53D08E8D').invalid('b4b2fb69c6244e5eb0698e0c6ec66618').allow('');\n            Helper.validate(rule, [\n                ['{D1A5279D-B27D-4CD4-A05E-EFDD53D08E8D}', false, {\n                    message: '\"value\" length must be less than or equal to 34 characters long',\n                    path: [],\n                    type: 'string.max',\n                    context: {\n                        limit: 34,\n                        value: '{D1A5279D-B27D-4CD4-A05E-EFDD53D08E8D}',\n                        encoding: undefined,\n                        label: 'value'\n                    }\n                }],\n                ['{B59511BD6A5F4DF09ECF562A108D8A2E}', true],\n                ['69593D62-71EA-4548-85E4-04FC71357423', false, {\n                    message: '\"value\" length must be less than or equal to 34 characters long',\n                    path: [],\n                    type: 'string.max',\n                    context: {\n                        limit: 34,\n                        value: '69593D62-71EA-4548-85E4-04FC71357423',\n                        encoding: undefined,\n                        label: 'value'\n                    }\n                }],\n                ['677E2553DD4D43B09DA77414DB1EB8EA', true],\n                ['{5ba3bba3-729a-4717-88c1-b7c4b7ba80db}', false, {\n                    message: '\"value\" length must be less than or equal to 34 characters long',\n                    path: [],\n                    type: 'string.max',\n                    context: {\n                        limit: 34,\n                        value: '{5ba3bba3-729a-4717-88c1-b7c4b7ba80db}',\n                        encoding: undefined,\n                        label: 'value'\n                    }\n                }],\n                ['{7e9081b59a6d4cc1a8c347f69fb4198d}', true],\n                ['0c74f13f-fa83-4c48-9b33-68921dd72463', false, {\n                    message: '\"value\" length must be less than or equal to 34 characters long',\n                    path: [],\n                    type: 'string.max',\n                    context: {\n                        limit: 34,\n                        value: '0c74f13f-fa83-4c48-9b33-68921dd72463',\n                        encoding: undefined,\n                        label: 'value'\n                    }\n                }],\n                ['b4b2fb69c6244e5eb0698e0c6ec66618', false, {\n                    message: '\"value\" contains an invalid value',\n                    path: [],\n                    type: 'any.invalid',\n                    context: { value: 'b4b2fb69c6244e5eb0698e0c6ec66618', invalids: ['b4b2fb69c6244e5eb0698e0c6ec66618'], label: 'value' }\n                }],\n                ['{283B67B2-430F-4E6F-97E6-19041992-C1B0}', false, {\n                    message: '\"value\" must be a valid GUID',\n                    path: [],\n                    type: 'string.guid',\n                    context: {\n                        value: '{283B67B2-430F-4E6F-97E6-19041992-C1B0}',\n                        label: 'value'\n                    }\n                }],\n                ['{D1A5279D-B27D-4CD4-A05E-EFDD53D08E8D', true],\n                ['D1A5279D-B27D-4CD4-A05E-EFDD53D08E8D}', false, {\n                    message: '\"value\" must be a valid GUID',\n                    path: [],\n                    type: 'string.guid',\n                    context: {\n                        value: 'D1A5279D-B27D-4CD4-A05E-EFDD53D08E8D}',\n                        label: 'value'\n                    }\n                }],\n                ['', true],\n                [null, false, {\n                    message: '\"value\" must be a string',\n                    path: [],\n                    type: 'string.base',\n                    context: { value: null, label: 'value' }\n                }]\n            ]);\n        });\n\n        it('validates combination of guid, min, max, allow and allow(\\'\\')', () => {\n\n            const rule = Joi.string().guid().min(32).max(34).allow('{D1A5279D-B27D-4CD4-A05E-EFDD53D08E8D').allow('');\n            Helper.validate(rule, [\n                ['{D1A5279D-B27D-4CD4-A05E-EFDD53D08E8D}', false, {\n                    message: '\"value\" length must be less than or equal to 34 characters long',\n                    path: [],\n                    type: 'string.max',\n                    context: {\n                        limit: 34,\n                        value: '{D1A5279D-B27D-4CD4-A05E-EFDD53D08E8D}',\n                        encoding: undefined,\n                        label: 'value'\n                    }\n                }],\n                ['{B59511BD6A5F4DF09ECF562A108D8A2E}', true],\n                ['69593D62-71EA-4548-85E4-04FC71357423', false, {\n                    message: '\"value\" length must be less than or equal to 34 characters long',\n                    path: [],\n                    type: 'string.max',\n                    context: {\n                        limit: 34,\n                        value: '69593D62-71EA-4548-85E4-04FC71357423',\n                        encoding: undefined,\n                        label: 'value'\n                    }\n                }],\n                ['677E2553DD4D43B09DA77414DB1EB8EA', true],\n                ['{5ba3bba3-729a-4717-88c1-b7c4b7ba80db}', false, {\n                    message: '\"value\" length must be less than or equal to 34 characters long',\n                    path: [],\n                    type: 'string.max',\n                    context: {\n                        limit: 34,\n                        value: '{5ba3bba3-729a-4717-88c1-b7c4b7ba80db}',\n                        encoding: undefined,\n                        label: 'value'\n                    }\n                }],\n                ['{7e9081b59a6d4cc1a8c347f69fb4198d}', true],\n                ['0c74f13f-fa83-4c48-9b33-68921dd72463', false, {\n                    message: '\"value\" length must be less than or equal to 34 characters long',\n                    path: [],\n                    type: 'string.max',\n                    context: {\n                        limit: 34,\n                        value: '0c74f13f-fa83-4c48-9b33-68921dd72463',\n                        encoding: undefined,\n                        label: 'value'\n                    }\n                }],\n                ['b4b2fb69c6244e5eb0698e0c6ec66618', true],\n                ['{283B67B2-430F-4E6F-97E6-19041992-C1B0}', false, {\n                    message: '\"value\" must be a valid GUID',\n                    path: [],\n                    type: 'string.guid',\n                    context: {\n                        value: '{283B67B2-430F-4E6F-97E6-19041992-C1B0}',\n                        label: 'value'\n                    }\n                }],\n                ['{D1A5279D-B27D-4CD4-A05E-EFDD53D08E8D', true],\n                ['D1A5279D-B27D-4CD4-A05E-EFDD53D08E8D}', false, {\n                    message: '\"value\" must be a valid GUID',\n                    path: [],\n                    type: 'string.guid',\n                    context: {\n                        value: 'D1A5279D-B27D-4CD4-A05E-EFDD53D08E8D}',\n                        label: 'value'\n                    }\n                }],\n                ['', true],\n                [null, false, {\n                    message: '\"value\" must be a string',\n                    path: [],\n                    type: 'string.base',\n                    context: { value: null, label: 'value' }\n                }]\n            ]);\n        });\n\n        it('validates combination of guid, min, max, allow, invalid and regex', () => {\n\n            const rule = Joi.string().guid().min(32).max(34).allow('{D1A5279D-B27D-4CD4-A05E-EFDD53D08').invalid('b4b2fb69c6244e5eb0698e0c6ec66618').regex(/^{7e908/);\n            Helper.validate(rule, [\n                ['{D1A5279D-B27D-4CD4-A05E-EFDD53D08E8D}', false, {\n                    message: '\"value\" length must be less than or equal to 34 characters long',\n                    path: [],\n                    type: 'string.max',\n                    context: {\n                        limit: 34,\n                        value: '{D1A5279D-B27D-4CD4-A05E-EFDD53D08E8D}',\n                        encoding: undefined,\n                        label: 'value'\n                    }\n                }],\n                ['{B59511BD6A5F4DF09ECF562A108D8A2E}', false, {\n                    message: '\"value\" with value \"{B59511BD6A5F4DF09ECF562A108D8A2E}\" fails to match the required pattern: /^{7e908/',\n                    path: [],\n                    type: 'string.pattern.base',\n                    context: {\n                        name: undefined,\n                        regex: /^{7e908/,\n                        value: '{B59511BD6A5F4DF09ECF562A108D8A2E}',\n                        label: 'value'\n                    }\n                }],\n                ['69593D62-71EA-4548-85E4-04FC71357423', false, {\n                    message: '\"value\" length must be less than or equal to 34 characters long',\n                    path: [],\n                    type: 'string.max',\n                    context: {\n                        limit: 34,\n                        value: '69593D62-71EA-4548-85E4-04FC71357423',\n                        encoding: undefined,\n                        label: 'value'\n                    }\n                }],\n                ['677E2553DD4D43B09DA77414DB1EB8EA', false, {\n                    message: '\"value\" with value \"677E2553DD4D43B09DA77414DB1EB8EA\" fails to match the required pattern: /^{7e908/',\n                    path: [],\n                    type: 'string.pattern.base',\n                    context: {\n                        name: undefined,\n                        regex: /^{7e908/,\n                        value: '677E2553DD4D43B09DA77414DB1EB8EA',\n                        label: 'value'\n                    }\n                }],\n                ['{5ba3bba3-729a-4717-88c1-b7c4b7ba80db}', false, {\n                    message: '\"value\" length must be less than or equal to 34 characters long',\n                    path: [],\n                    type: 'string.max',\n                    context: {\n                        limit: 34,\n                        value: '{5ba3bba3-729a-4717-88c1-b7c4b7ba80db}',\n                        encoding: undefined,\n                        label: 'value'\n                    }\n                }],\n                ['{7e9081b59a6d4cc1a8c347f69fb4198d}', true],\n                ['0c74f13f-fa83-4c48-9b33-68921dd72463', false, {\n                    message: '\"value\" length must be less than or equal to 34 characters long',\n                    path: [],\n                    type: 'string.max',\n                    context: {\n                        limit: 34,\n                        value: '0c74f13f-fa83-4c48-9b33-68921dd72463',\n                        encoding: undefined,\n                        label: 'value'\n                    }\n                }],\n                ['b4b2fb69c6244e5eb0698e0c6ec66618', false, {\n                    message: '\"value\" contains an invalid value',\n                    path: [],\n                    type: 'any.invalid',\n                    context: { value: 'b4b2fb69c6244e5eb0698e0c6ec66618', invalids: ['b4b2fb69c6244e5eb0698e0c6ec66618'], label: 'value' }\n                }],\n                ['{283B67B2-430F-4E6F-97E6-19041992-C1B0}', false, {\n                    message: '\"value\" must be a valid GUID',\n                    path: [],\n                    type: 'string.guid',\n                    context: {\n                        value: '{283B67B2-430F-4E6F-97E6-19041992-C1B0}',\n                        label: 'value'\n                    }\n                }],\n                ['{D1A5279D-B27D-4CD4-A05E-EFDD53D08', true],\n                ['D1A5279D-B27D-4CD4-A05E-EFDD53D08E8D}', false, {\n                    message: '\"value\" must be a valid GUID',\n                    path: [],\n                    type: 'string.guid',\n                    context: {\n                        value: 'D1A5279D-B27D-4CD4-A05E-EFDD53D08E8D}',\n                        label: 'value'\n                    }\n                }],\n                ['', false, {\n                    message: '\"value\" is not allowed to be empty',\n                    path: [],\n                    type: 'string.empty',\n                    context: { value: '', label: 'value' }\n                }],\n                [null, false, {\n                    message: '\"value\" must be a string',\n                    path: [],\n                    type: 'string.base',\n                    context: { value: null, label: 'value' }\n                }]\n            ]);\n        });\n\n        it('validates combination of guid, min, max, allow, invalid, regex and allow(\\'\\')', () => {\n\n            const rule = Joi.string().guid().min(32).max(34).allow('{D1A5279D-B27D-4CD4-A05E-EFDD53D08').invalid('b4b2fb69c6244e5eb0698e0c6ec66618').regex(/^{7e908/).allow('');\n            Helper.validate(rule, [\n                ['{D1A5279D-B27D-4CD4-A05E-EFDD53D08E8D}', false, {\n                    message: '\"value\" length must be less than or equal to 34 characters long',\n                    path: [],\n                    type: 'string.max',\n                    context: {\n                        limit: 34,\n                        value: '{D1A5279D-B27D-4CD4-A05E-EFDD53D08E8D}',\n                        encoding: undefined,\n                        label: 'value'\n                    }\n                }],\n                ['{B59511BD6A5F4DF09ECF562A108D8A2E}', false, {\n                    message: '\"value\" with value \"{B59511BD6A5F4DF09ECF562A108D8A2E}\" fails to match the required pattern: /^{7e908/',\n                    path: [],\n                    type: 'string.pattern.base',\n                    context: {\n                        name: undefined,\n                        regex: /^{7e908/,\n                        value: '{B59511BD6A5F4DF09ECF562A108D8A2E}',\n                        label: 'value'\n                    }\n                }],\n                ['69593D62-71EA-4548-85E4-04FC71357423', false, {\n                    message: '\"value\" length must be less than or equal to 34 characters long',\n                    path: [],\n                    type: 'string.max',\n                    context: {\n                        limit: 34,\n                        value: '69593D62-71EA-4548-85E4-04FC71357423',\n                        encoding: undefined,\n                        label: 'value'\n                    }\n                }],\n                ['677E2553DD4D43B09DA77414DB1EB8EA', false, {\n                    message: '\"value\" with value \"677E2553DD4D43B09DA77414DB1EB8EA\" fails to match the required pattern: /^{7e908/',\n                    path: [],\n                    type: 'string.pattern.base',\n                    context: {\n                        name: undefined,\n                        regex: /^{7e908/,\n                        value: '677E2553DD4D43B09DA77414DB1EB8EA',\n                        label: 'value'\n                    }\n                }],\n                ['{5ba3bba3-729a-4717-88c1-b7c4b7ba80db}', false, {\n                    message: '\"value\" length must be less than or equal to 34 characters long',\n                    path: [],\n                    type: 'string.max',\n                    context: {\n                        limit: 34,\n                        value: '{5ba3bba3-729a-4717-88c1-b7c4b7ba80db}',\n                        encoding: undefined,\n                        label: 'value'\n                    }\n                }],\n                ['{7e9081b59a6d4cc1a8c347f69fb4198d}', true],\n                ['0c74f13f-fa83-4c48-9b33-68921dd72463', false, {\n                    message: '\"value\" length must be less than or equal to 34 characters long',\n                    path: [],\n                    type: 'string.max',\n                    context: {\n                        limit: 34,\n                        value: '0c74f13f-fa83-4c48-9b33-68921dd72463',\n                        encoding: undefined,\n                        label: 'value'\n                    }\n                }],\n                ['b4b2fb69c6244e5eb0698e0c6ec66618', false, {\n                    message: '\"value\" contains an invalid value',\n                    path: [],\n                    type: 'any.invalid',\n                    context: { value: 'b4b2fb69c6244e5eb0698e0c6ec66618', invalids: ['b4b2fb69c6244e5eb0698e0c6ec66618'], label: 'value' }\n                }],\n                ['{283B67B2-430F-4E6F-97E6-19041992-C1B0}', false, {\n                    message: '\"value\" must be a valid GUID',\n                    path: [],\n                    type: 'string.guid',\n                    context: {\n                        value: '{283B67B2-430F-4E6F-97E6-19041992-C1B0}',\n                        label: 'value'\n                    }\n                }],\n                ['{D1A5279D-B27D-4CD4-A05E-EFDD53D08', true],\n                ['D1A5279D-B27D-4CD4-A05E-EFDD53D08E8D}', false, {\n                    message: '\"value\" must be a valid GUID',\n                    path: [],\n                    type: 'string.guid',\n                    context: {\n                        value: 'D1A5279D-B27D-4CD4-A05E-EFDD53D08E8D}',\n                        label: 'value'\n                    }\n                }],\n                ['', true],\n                [null, false, {\n                    message: '\"value\" must be a string',\n                    path: [],\n                    type: 'string.base',\n                    context: { value: null, label: 'value' }\n                }]\n            ]);\n        });\n\n        it('validates combination of guid, min, max and allow(\\'\\')', () => {\n\n            const rule = Joi.string().guid().min(32).max(34).allow('');\n            Helper.validate(rule, [\n                ['{D1A5279D-B27D-4CD4-A05E-EFDD53D08E8D}', false, {\n                    message: '\"value\" length must be less than or equal to 34 characters long',\n                    path: [],\n                    type: 'string.max',\n                    context: {\n                        limit: 34,\n                        value: '{D1A5279D-B27D-4CD4-A05E-EFDD53D08E8D}',\n                        encoding: undefined,\n                        label: 'value'\n                    }\n                }],\n                ['{B59511BD6A5F4DF09ECF562A108D8A2E}', true],\n                ['69593D62-71EA-4548-85E4-04FC71357423', false, {\n                    message: '\"value\" length must be less than or equal to 34 characters long',\n                    path: [],\n                    type: 'string.max',\n                    context: {\n                        limit: 34,\n                        value: '69593D62-71EA-4548-85E4-04FC71357423',\n                        encoding: undefined,\n                        label: 'value'\n                    }\n                }],\n                ['677E2553DD4D43B09DA77414DB1EB8EA', true],\n                ['{5ba3bba3-729a-4717-88c1-b7c4b7ba80db}', false, {\n                    message: '\"value\" length must be less than or equal to 34 characters long',\n                    path: [],\n                    type: 'string.max',\n                    context: {\n                        limit: 34,\n                        value: '{5ba3bba3-729a-4717-88c1-b7c4b7ba80db}',\n                        encoding: undefined,\n                        label: 'value'\n                    }\n                }],\n                ['{7e9081b59a6d4cc1a8c347f69fb4198d}', true],\n                ['0c74f13f-fa83-4c48-9b33-68921dd72463', false, {\n                    message: '\"value\" length must be less than or equal to 34 characters long',\n                    path: [],\n                    type: 'string.max',\n                    context: {\n                        limit: 34,\n                        value: '0c74f13f-fa83-4c48-9b33-68921dd72463',\n                        encoding: undefined,\n                        label: 'value'\n                    }\n                }],\n                ['b4b2fb69c6244e5eb0698e0c6ec66618', true],\n                ['{283B67B2-430F-4E6F-97E6-19041992-C1B0}', false, {\n                    message: '\"value\" must be a valid GUID',\n                    path: [],\n                    type: 'string.guid',\n                    context: {\n                        value: '{283B67B2-430F-4E6F-97E6-19041992-C1B0}',\n                        label: 'value'\n                    }\n                }],\n                ['{D1A5279D-B27D-4CD4-A05E-EFDD53D08E8D', false, {\n                    message: '\"value\" must be a valid GUID',\n                    path: [],\n                    type: 'string.guid',\n                    context: {\n                        value: '{D1A5279D-B27D-4CD4-A05E-EFDD53D08E8D',\n                        label: 'value'\n                    }\n                }],\n                ['D1A5279D-B27D-4CD4-A05E-EFDD53D08E8D}', false, {\n                    message: '\"value\" must be a valid GUID',\n                    path: [],\n                    type: 'string.guid',\n                    context: {\n                        value: 'D1A5279D-B27D-4CD4-A05E-EFDD53D08E8D}',\n                        label: 'value'\n                    }\n                }],\n                ['', true],\n                [null, false, {\n                    message: '\"value\" must be a string',\n                    path: [],\n                    type: 'string.base',\n                    context: { value: null, label: 'value' }\n                }]\n            ]);\n        });\n\n        it('validates combination of guid, min, max and regex', () => {\n\n            const rule = Joi.string().guid().min(32).max(34).regex(/^{7e9081/i);\n            Helper.validate(rule, [\n                ['{D1A5279D-B27D-4CD4-A05E-EFDD53D08E8D}', false, {\n                    message: '\"value\" length must be less than or equal to 34 characters long',\n                    path: [],\n                    type: 'string.max',\n                    context: {\n                        limit: 34,\n                        value: '{D1A5279D-B27D-4CD4-A05E-EFDD53D08E8D}',\n                        encoding: undefined,\n                        label: 'value'\n                    }\n                }],\n                ['{B59511BD6A5F4DF09ECF562A108D8A2E}', false, {\n                    message: '\"value\" with value \"{B59511BD6A5F4DF09ECF562A108D8A2E}\" fails to match the required pattern: /^{7e9081/i',\n                    path: [],\n                    type: 'string.pattern.base',\n                    context: {\n                        name: undefined,\n                        regex: /^{7e9081/i,\n                        value: '{B59511BD6A5F4DF09ECF562A108D8A2E}',\n                        label: 'value'\n                    }\n                }],\n                ['69593D62-71EA-4548-85E4-04FC71357423', false, {\n                    message: '\"value\" length must be less than or equal to 34 characters long',\n                    path: [],\n                    type: 'string.max',\n                    context: {\n                        limit: 34,\n                        value: '69593D62-71EA-4548-85E4-04FC71357423',\n                        encoding: undefined,\n                        label: 'value'\n                    }\n                }],\n                ['677E2553DD4D43B09DA77414DB1EB8EA', false, {\n                    message: '\"value\" with value \"677E2553DD4D43B09DA77414DB1EB8EA\" fails to match the required pattern: /^{7e9081/i',\n                    path: [],\n                    type: 'string.pattern.base',\n                    context: {\n                        name: undefined,\n                        regex: /^{7e9081/i,\n                        value: '677E2553DD4D43B09DA77414DB1EB8EA',\n                        label: 'value'\n                    }\n                }],\n                ['{5ba3bba3-729a-4717-88c1-b7c4b7ba80db}', false, {\n                    message: '\"value\" length must be less than or equal to 34 characters long',\n                    path: [],\n                    type: 'string.max',\n                    context: {\n                        limit: 34,\n                        value: '{5ba3bba3-729a-4717-88c1-b7c4b7ba80db}',\n                        encoding: undefined,\n                        label: 'value'\n                    }\n                }],\n                ['{7e9081b59a6d4cc1a8c347f69fb4198d}', true],\n                ['0c74f13f-fa83-4c48-9b33-68921dd72463', false, {\n                    message: '\"value\" length must be less than or equal to 34 characters long',\n                    path: [],\n                    type: 'string.max',\n                    context: {\n                        limit: 34,\n                        value: '0c74f13f-fa83-4c48-9b33-68921dd72463',\n                        encoding: undefined,\n                        label: 'value'\n                    }\n                }],\n                ['b4b2fb69c6244e5eb0698e0c6ec66618', false, {\n                    message: '\"value\" with value \"b4b2fb69c6244e5eb0698e0c6ec66618\" fails to match the required pattern: /^{7e9081/i',\n                    path: [],\n                    type: 'string.pattern.base',\n                    context: {\n                        name: undefined,\n                        regex: /^{7e9081/i,\n                        value: 'b4b2fb69c6244e5eb0698e0c6ec66618',\n                        label: 'value'\n                    }\n                }],\n                ['{283B67B2-430F-4E6F-97E6-19041992-C1B0}', false, {\n                    message: '\"value\" must be a valid GUID',\n                    path: [],\n                    type: 'string.guid',\n                    context: {\n                        value: '{283B67B2-430F-4E6F-97E6-19041992-C1B0}',\n                        label: 'value'\n                    }\n                }],\n                ['{D1A5279D-B27D-4CD4-A05E-EFDD53D08E8D', false, {\n                    message: '\"value\" must be a valid GUID',\n                    path: [],\n                    type: 'string.guid',\n                    context: {\n                        value: '{D1A5279D-B27D-4CD4-A05E-EFDD53D08E8D',\n                        label: 'value'\n                    }\n                }],\n                ['D1A5279D-B27D-4CD4-A05E-EFDD53D08E8D}', false, {\n                    message: '\"value\" must be a valid GUID',\n                    path: [],\n                    type: 'string.guid',\n                    context: {\n                        value: 'D1A5279D-B27D-4CD4-A05E-EFDD53D08E8D}',\n                        label: 'value'\n                    }\n                }],\n                ['', false, {\n                    message: '\"value\" is not allowed to be empty',\n                    path: [],\n                    type: 'string.empty',\n                    context: { value: '', label: 'value' }\n                }],\n                [null, false, {\n                    message: '\"value\" must be a string',\n                    path: [],\n                    type: 'string.base',\n                    context: { value: null, label: 'value' }\n                }]\n            ]);\n        });\n\n        it('validates combination of guid, min, max, regex and allow(\\'\\')', () => {\n\n            const rule = Joi.string().guid().min(32).max(34).regex(/^{7e9081/i).allow('');\n            Helper.validate(rule, [\n                ['{D1A5279D-B27D-4CD4-A05E-EFDD53D08E8D}', false, {\n                    message: '\"value\" length must be less than or equal to 34 characters long',\n                    path: [],\n                    type: 'string.max',\n                    context: {\n                        limit: 34,\n                        value: '{D1A5279D-B27D-4CD4-A05E-EFDD53D08E8D}',\n                        encoding: undefined,\n                        label: 'value'\n                    }\n                }],\n                ['{B59511BD6A5F4DF09ECF562A108D8A2E}', false, {\n                    message: '\"value\" with value \"{B59511BD6A5F4DF09ECF562A108D8A2E}\" fails to match the required pattern: /^{7e9081/i',\n                    path: [],\n                    type: 'string.pattern.base',\n                    context: {\n                        name: undefined,\n                        regex: /^{7e9081/i,\n                        value: '{B59511BD6A5F4DF09ECF562A108D8A2E}',\n                        label: 'value'\n                    }\n                }],\n                ['69593D62-71EA-4548-85E4-04FC71357423', false, {\n                    message: '\"value\" length must be less than or equal to 34 characters long',\n                    path: [],\n                    type: 'string.max',\n                    context: {\n                        limit: 34,\n                        value: '69593D62-71EA-4548-85E4-04FC71357423',\n                        encoding: undefined,\n                        label: 'value'\n                    }\n                }],\n                ['677E2553DD4D43B09DA77414DB1EB8EA', false, {\n                    message: '\"value\" with value \"677E2553DD4D43B09DA77414DB1EB8EA\" fails to match the required pattern: /^{7e9081/i',\n                    path: [],\n                    type: 'string.pattern.base',\n                    context: {\n                        name: undefined,\n                        regex: /^{7e9081/i,\n                        value: '677E2553DD4D43B09DA77414DB1EB8EA',\n                        label: 'value'\n                    }\n                }],\n                ['{5ba3bba3-729a-4717-88c1-b7c4b7ba80db}', false, {\n                    message: '\"value\" length must be less than or equal to 34 characters long',\n                    path: [],\n                    type: 'string.max',\n                    context: {\n                        limit: 34,\n                        value: '{5ba3bba3-729a-4717-88c1-b7c4b7ba80db}',\n                        encoding: undefined,\n                        label: 'value'\n                    }\n                }],\n                ['{7e9081b59a6d4cc1a8c347f69fb4198d}', true],\n                ['0c74f13f-fa83-4c48-9b33-68921dd72463', false, {\n                    message: '\"value\" length must be less than or equal to 34 characters long',\n                    path: [],\n                    type: 'string.max',\n                    context: {\n                        limit: 34,\n                        value: '0c74f13f-fa83-4c48-9b33-68921dd72463',\n                        encoding: undefined,\n                        label: 'value'\n                    }\n                }],\n                ['b4b2fb69c6244e5eb0698e0c6ec66618', false, {\n                    message: '\"value\" with value \"b4b2fb69c6244e5eb0698e0c6ec66618\" fails to match the required pattern: /^{7e9081/i',\n                    path: [],\n                    type: 'string.pattern.base',\n                    context: {\n                        name: undefined,\n                        regex: /^{7e9081/i,\n                        value: 'b4b2fb69c6244e5eb0698e0c6ec66618',\n                        label: 'value'\n                    }\n                }],\n                ['{283B67B2-430F-4E6F-97E6-19041992-C1B0}', false, {\n                    message: '\"value\" must be a valid GUID',\n                    path: [],\n                    type: 'string.guid',\n                    context: {\n                        value: '{283B67B2-430F-4E6F-97E6-19041992-C1B0}',\n                        label: 'value'\n                    }\n                }],\n                ['{D1A5279D-B27D-4CD4-A05E-EFDD53D08E8D', false, {\n                    message: '\"value\" must be a valid GUID',\n                    path: [],\n                    type: 'string.guid',\n                    context: {\n                        value: '{D1A5279D-B27D-4CD4-A05E-EFDD53D08E8D',\n                        label: 'value'\n                    }\n                }],\n                ['D1A5279D-B27D-4CD4-A05E-EFDD53D08E8D}', false, {\n                    message: '\"value\" must be a valid GUID',\n                    path: [],\n                    type: 'string.guid',\n                    context: {\n                        value: 'D1A5279D-B27D-4CD4-A05E-EFDD53D08E8D}',\n                        label: 'value'\n                    }\n                }],\n                ['', true],\n                [null, false, {\n                    message: '\"value\" must be a string',\n                    path: [],\n                    type: 'string.base',\n                    context: { value: null, label: 'value' }\n                }]\n            ]);\n        });\n\n        it('validates combination of guid, min, max, regex and required', () => {\n\n            const rule = Joi.string().guid().min(32).max(34).regex(/^{7e9081/i).required();\n            Helper.validate(rule, [\n                ['{D1A5279D-B27D-4CD4-A05E-EFDD53D08E8D}', false, {\n                    message: '\"value\" length must be less than or equal to 34 characters long',\n                    path: [],\n                    type: 'string.max',\n                    context: {\n                        limit: 34,\n                        value: '{D1A5279D-B27D-4CD4-A05E-EFDD53D08E8D}',\n                        encoding: undefined,\n                        label: 'value'\n                    }\n                }],\n                ['{B59511BD6A5F4DF09ECF562A108D8A2E}', false, {\n                    message: '\"value\" with value \"{B59511BD6A5F4DF09ECF562A108D8A2E}\" fails to match the required pattern: /^{7e9081/i',\n                    path: [],\n                    type: 'string.pattern.base',\n                    context: {\n                        name: undefined,\n                        regex: /^{7e9081/i,\n                        value: '{B59511BD6A5F4DF09ECF562A108D8A2E}',\n                        label: 'value'\n                    }\n                }],\n                ['69593D62-71EA-4548-85E4-04FC71357423', false, {\n                    message: '\"value\" length must be less than or equal to 34 characters long',\n                    path: [],\n                    type: 'string.max',\n                    context: {\n                        limit: 34,\n                        value: '69593D62-71EA-4548-85E4-04FC71357423',\n                        encoding: undefined,\n                        label: 'value'\n                    }\n                }],\n                ['677E2553DD4D43B09DA77414DB1EB8EA', false, {\n                    message: '\"value\" with value \"677E2553DD4D43B09DA77414DB1EB8EA\" fails to match the required pattern: /^{7e9081/i',\n                    path: [],\n                    type: 'string.pattern.base',\n                    context: {\n                        name: undefined,\n                        regex: /^{7e9081/i,\n                        value: '677E2553DD4D43B09DA77414DB1EB8EA',\n                        label: 'value'\n                    }\n                }],\n                ['{5ba3bba3-729a-4717-88c1-b7c4b7ba80db}', false, {\n                    message: '\"value\" length must be less than or equal to 34 characters long',\n                    path: [],\n                    type: 'string.max',\n                    context: {\n                        limit: 34,\n                        value: '{5ba3bba3-729a-4717-88c1-b7c4b7ba80db}',\n                        encoding: undefined,\n                        label: 'value'\n                    }\n                }],\n                ['{7e9081b59a6d4cc1a8c347f69fb4198d}', true],\n                ['0c74f13f-fa83-4c48-9b33-68921dd72463', false, {\n                    message: '\"value\" length must be less than or equal to 34 characters long',\n                    path: [],\n                    type: 'string.max',\n                    context: {\n                        limit: 34,\n                        value: '0c74f13f-fa83-4c48-9b33-68921dd72463',\n                        encoding: undefined,\n                        label: 'value'\n                    }\n                }],\n                ['b4b2fb69c6244e5eb0698e0c6ec66618', false, {\n                    message: '\"value\" with value \"b4b2fb69c6244e5eb0698e0c6ec66618\" fails to match the required pattern: /^{7e9081/i',\n                    path: [],\n                    type: 'string.pattern.base',\n                    context: {\n                        name: undefined,\n                        regex: /^{7e9081/i,\n                        value: 'b4b2fb69c6244e5eb0698e0c6ec66618',\n                        label: 'value'\n                    }\n                }],\n                ['{283B67B2-430F-4E6F-97E6-19041992-C1B0}', false, {\n                    message: '\"value\" must be a valid GUID',\n                    path: [],\n                    type: 'string.guid',\n                    context: {\n                        value: '{283B67B2-430F-4E6F-97E6-19041992-C1B0}',\n                        label: 'value'\n                    }\n                }],\n                ['{D1A5279D-B27D-4CD4-A05E-EFDD53D08E8D', false, {\n                    message: '\"value\" must be a valid GUID',\n                    path: [],\n                    type: 'string.guid',\n                    context: {\n                        value: '{D1A5279D-B27D-4CD4-A05E-EFDD53D08E8D',\n                        label: 'value'\n                    }\n                }],\n                ['D1A5279D-B27D-4CD4-A05E-EFDD53D08E8D}', false, {\n                    message: '\"value\" must be a valid GUID',\n                    path: [],\n                    type: 'string.guid',\n                    context: {\n                        value: 'D1A5279D-B27D-4CD4-A05E-EFDD53D08E8D}',\n                        label: 'value'\n                    }\n                }],\n                ['', false, {\n                    message: '\"value\" is not allowed to be empty',\n                    path: [],\n                    type: 'string.empty',\n                    context: { value: '', label: 'value' }\n                }],\n                [null, false, {\n                    message: '\"value\" must be a string',\n                    path: [],\n                    type: 'string.base',\n                    context: { value: null, label: 'value' }\n                }]\n            ]);\n        });\n    });\n\n    describe('hex()', () => {\n\n        it('validates the hexadecimal options', () => {\n\n            expect(() => {\n\n                Joi.string().hex('a');\n            }).to.throw('Options must be of type object');\n\n            expect(() => {\n\n                Joi.string().hex({ byteAligned: 'a' });\n            }).to.throw('byteAligned must be boolean');\n        });\n\n        it('validates an hexadecimal string with no options', () => {\n\n            const rule = Joi.string().hex();\n            Helper.validate(rule, [\n                ['123456789abcdef', true],\n                ['123456789AbCdEf', true],\n                ['123afg', false, {\n                    message: '\"value\" must only contain hexadecimal characters',\n                    path: [],\n                    type: 'string.hex',\n                    context: { value: '123afg', label: 'value' }\n                }],\n                ['0x123456789AbCdEf', false, '\"value\" must only contain hexadecimal characters']\n            ]);\n        });\n\n        it('validates an hexadecimal string with byte align explicitly required', () => {\n\n            const rule = Joi.string().hex({ byteAligned: true }).strict();\n            Helper.validate(rule, [\n                ['0123456789abcdef', true],\n                ['123456789abcdef', false, {\n                    message: '\"value\" hex decoded representation must be byte aligned',\n                    path: [],\n                    type: 'string.hexAlign',\n                    context: { value: '123456789abcdef', label: 'value' }\n                }],\n                ['0123afg', false, {\n                    message: '\"value\" must only contain hexadecimal characters',\n                    path: [],\n                    type: 'string.hex',\n                    context: { value: '0123afg', label: 'value' }\n                }]\n            ]);\n        });\n\n        it('converts an hexadecimal string with byte align explicitly required', () => {\n\n            const rule = Joi.string().hex({ byteAligned: true });\n            Helper.validate(rule, [\n                ['0123456789abcdef', true, '0123456789abcdef'],\n                ['123456789abcdef', true, '0123456789abcdef'],\n                ['0123afg', false, {\n                    message: '\"value\" must only contain hexadecimal characters',\n                    path: [],\n                    type: 'string.hex',\n                    context: { value: '00123afg', label: 'value' }\n                }],\n                ['00123afg', false, {\n                    message: '\"value\" must only contain hexadecimal characters',\n                    path: [],\n                    type: 'string.hex',\n                    context: { value: '00123afg', label: 'value' }\n                }]\n            ]);\n        });\n\n        it('validates an hexadecimal string with prefix explicitly required', () => {\n\n            const rule = Joi.string().hex({ prefix: true }).strict();\n            Helper.validate(rule, [\n                ['0123456789abcdef', false, {\n                    message: '\"value\" must only contain hexadecimal characters',\n                    path: [],\n                    type: 'string.hex',\n                    context: { value: '0123456789abcdef', label: 'value' }\n                }],\n                ['0x0123456789abcdef', true],\n                ['0X0123456789abcdef', true]\n            ]);\n        });\n\n        it('validates an hexadecimal string with optional prefix', () => {\n\n            const rule = Joi.string().hex({ prefix: 'optional' }).strict();\n            Helper.validate(rule, [\n                ['0123456789abcdef', true],\n                ['0x0123456789abcdef', true],\n                ['0X0123456789abcdef', true],\n                ['0123456789abcdefg', false, {\n                    message: '\"value\" must only contain hexadecimal characters',\n                    path: [],\n                    type: 'string.hex',\n                    context: { value: '0123456789abcdefg', label: 'value' }\n                }],\n                ['0x0123456789abcdefg', false, {\n                    message: '\"value\" must only contain hexadecimal characters',\n                    path: [],\n                    type: 'string.hex',\n                    context: { value: '0x0123456789abcdefg', label: 'value' }\n                }],\n                ['0X0123456789abcdefg', false, {\n                    message: '\"value\" must only contain hexadecimal characters',\n                    path: [],\n                    type: 'string.hex',\n                    context: { value: '0X0123456789abcdefg', label: 'value' }\n                }]\n            ]);\n        });\n    });\n\n    describe('hostname()', () => {\n\n        it('validates hostnames', () => {\n\n            const schema = Joi.string().hostname();\n            Helper.validate(schema, [\n                ['www.example.com', true],\n                ['domain.local', true],\n                ['3domain.local', true],\n                ['hostname', true],\n                ['host:name', false, {\n                    message: '\"value\" must be a valid hostname',\n                    path: [],\n                    type: 'string.hostname',\n                    context: { value: 'host:name', label: 'value' }\n                }],\n                ['-', false, {\n                    message: '\"value\" must be a valid hostname',\n                    path: [],\n                    type: 'string.hostname',\n                    context: { value: '-', label: 'value' }\n                }],\n                ['2387628', false, '\"value\" must be a valid hostname'],\n                ['01234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789', false, {\n                    message: '\"value\" must be a valid hostname',\n                    path: [],\n                    type: 'string.hostname',\n                    context: { value: '01234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789', label: 'value' }\n                }],\n                ['::1', true],\n                ['0:0:0:0:0:0:0:1', true],\n                ['0:?:0:0:0:0:0:1', false, {\n                    message: '\"value\" must be a valid hostname',\n                    path: [],\n                    type: 'string.hostname',\n                    context: { value: '0:?:0:0:0:0:0:1', label: 'value' }\n                }],\n                ['10.10.10.10/24', false, {\n                    message: '\"value\" must be a valid hostname',\n                    path: [],\n                    type: 'string.hostname',\n                    context: { value: '10.10.10.10/24', label: 'value' }\n                }],\n                ['2001:db8::/48', false, {\n                    message: '\"value\" must be a valid hostname',\n                    path: [],\n                    type: 'string.hostname',\n                    context: { value: '2001:db8::/48', label: 'value' }\n                }]\n            ]);\n        });\n    });\n\n    describe('insensitive', () => {\n\n        it('avoids unnecessary cloning when called twice', () => {\n\n            const schema = Joi.string().insensitive();\n            expect(schema.insensitive()).to.shallow.equal(schema);\n        });\n\n        it('sets right values with valid', () => {\n\n            const simpleSchema = Joi.string().insensitive().valid('A');\n            Helper.validate(simpleSchema, [['a', true, 'A']]);\n\n            const refSchema = Joi.string().insensitive().valid(Joi.ref('$v'));\n            Helper.validate(refSchema, { context: { v: 'A' } }, [['a', true, 'A']]);\n\n            const refArraySchema = Joi.string().insensitive().valid(Joi.in('$v'));\n            Helper.validate(refArraySchema, { context: { v: ['B', 'A'] } }, [['a', true, 'A']]);\n\n            const strictSchema = Joi.string().insensitive().valid('A').strict();\n            Helper.validate(strictSchema, [['a', true, 'a']]);\n        });\n    });\n\n    describe('invalid()', () => {\n\n        it('should return false for denied value', () => {\n\n            const text = Joi.string().invalid('joi');\n            Helper.validate(text, [['joi', false, '\"value\" contains an invalid value']]);\n        });\n\n        it('validates invalid values', () => {\n\n            const schema = Joi.string().invalid('a', 'b', 'c');\n            Helper.validate(schema, [\n                ['x', true],\n                ['a', false, {\n                    message: '\"value\" contains an invalid value',\n                    path: [],\n                    type: 'any.invalid',\n                    context: { value: 'a', invalids: ['a', 'b', 'c'], label: 'value' }\n                }],\n                ['c', false, {\n                    message: '\"value\" contains an invalid value',\n                    path: [],\n                    type: 'any.invalid',\n                    context: { value: 'c', invalids: ['a', 'b', 'c'], label: 'value' }\n                }]\n            ]);\n        });\n\n        it('should invert invalid values', () => {\n\n            const schema = Joi.string().valid('a', 'b', 'c');\n            Helper.validate(schema, [\n                ['x', false, {\n                    message: '\"value\" must be one of [a, b, c]',\n                    path: [],\n                    type: 'any.only',\n                    context: { value: 'x', valids: ['a', 'b', 'c'], label: 'value' }\n                }],\n                ['a', true],\n                ['c', true]\n            ]);\n        });\n\n        it('inverts case sensitive values', () => {\n\n            Helper.validate(Joi.string().invalid('a', 'b'), [\n                ['a', false, {\n                    message: '\"value\" contains an invalid value',\n                    path: [],\n                    type: 'any.invalid',\n                    context: { value: 'a', invalids: ['a', 'b'], label: 'value' }\n                }],\n                ['b', false, {\n                    message: '\"value\" contains an invalid value',\n                    path: [],\n                    type: 'any.invalid',\n                    context: { value: 'b', invalids: ['a', 'b'], label: 'value' }\n                }],\n                ['A', true],\n                ['B', true]\n            ]);\n        });\n\n        it('inverts case insensitive values', () => {\n\n            Helper.validate(Joi.string().invalid('a', 'b').insensitive(), [\n                ['a', false, {\n                    message: '\"value\" contains an invalid value',\n                    path: [],\n                    type: 'any.invalid',\n                    context: { value: 'a', invalids: ['a', 'b'], label: 'value' }\n                }],\n                ['b', false, {\n                    message: '\"value\" contains an invalid value',\n                    path: [],\n                    type: 'any.invalid',\n                    context: { value: 'b', invalids: ['a', 'b'], label: 'value' }\n                }],\n                ['A', false, {\n                    message: '\"value\" contains an invalid value',\n                    path: [],\n                    type: 'any.invalid',\n                    context: { value: 'A', invalids: ['a', 'b'], label: 'value' }\n                }],\n                ['B', false, {\n                    message: '\"value\" contains an invalid value',\n                    path: [],\n                    type: 'any.invalid',\n                    context: { value: 'B', invalids: ['a', 'b'], label: 'value' }\n                }]\n            ]);\n        });\n    });\n\n    describe('ip()', () => {\n\n        const prepareIps = function (ips) {\n\n            return function (success, message = '', version = ['ipv4', 'ipv6']) {\n\n                return ips.map((ip) => [ip, success, !success && message ? {\n                    message,\n                    path: [],\n                    type: /versions/.test(message) ? 'string.ipVersion' : 'string.ip',\n                    context: (() => {\n\n                        const context = {\n                            value: ip,\n                            cidr: /(\\w+) CIDR/.exec(message)[1],\n                            label: 'value'\n                        };\n\n                        if (/versions/.test(message)) {\n                            context.version = version;\n                        }\n\n                        return context;\n                    })()\n                } : ip]);\n            };\n        };\n\n        const invalidIPs = function (message, version) {\n\n            return prepareIps([\n                'ASDF',\n                '192.0.2.16:80/30',\n                '192.0.2.16a',\n                'qwerty',\n                '127.0.0.1:8000',\n                'ftp://www.example.com',\n                'Bananas in pajamas are coming down the stairs'\n            ])(false, message, version);\n        };\n\n        const invalidIPv4s = function (message, version) {\n\n            return prepareIps([\n                '0.0.0.0/33',\n                '256.0.0.0/0',\n                '255.255.255.256/32',\n                '255.255.255.255/64',\n                '255.255.255.255/128',\n                '255.255.255.255/255',\n                '256.0.0.0',\n                '255.255.255.256'\n            ])(false, message, version);\n        };\n\n        const invalidIPv6s = function (message, version) {\n\n            return prepareIps([\n                '1080:0:0:0:8:800:200C:417G/33',\n                '1080:0:0:0:8:800:200C:417G',\n                'FEDC:BA98:7654:3210:FEDC:BA98:7654:3210/129',\n                'FEDC:BA98:7654:3210:FEDC:BA98:7654:3210/255'\n            ])(false, message, version);\n        };\n\n        const invalidIPvFutures = function (message, version) {\n\n            return prepareIps([\n                'v1.09#/33',\n                'v1.09#',\n                'v1.09azAZ-._~!$&\\'()*+,;=:/129',\n                'v1.09azAZ-._~!$&\\'()*+,;=:/255'\n            ])(false, message, version);\n        };\n\n        const validIPv4sWithCidr = prepareIps([\n            '0.0.0.0/32',\n            '255.255.255.255/0',\n            '127.0.0.1/0',\n            '192.168.2.1/0',\n            '0.0.0.3/2',\n            '0.0.0.7/3',\n            '0.0.0.15/4',\n            '0.0.0.31/5',\n            '0.0.0.63/6',\n            '0.0.0.127/7',\n            '01.020.030.100/7',\n            '0.0.0.0/0',\n            '00.00.00.00/0',\n            '000.000.000.000/32'\n        ]);\n\n        const validIPv4sWithoutCidr = prepareIps([\n            '0.0.0.0',\n            '255.255.255.255',\n            '127.0.0.1',\n            '192.168.2.1',\n            '0.0.0.3',\n            '0.0.0.7',\n            '0.0.0.15',\n            '0.0.0.31',\n            '0.0.0.63',\n            '0.0.0.127',\n            '01.020.030.100',\n            '0.0.0.0',\n            '00.00.00.00',\n            '000.000.000.000'\n        ]);\n\n        const validIPv6sWithCidr = prepareIps([\n            '2001:db8::7/32',\n            'a:b:c:d:e::1.2.3.4/13',\n            'a:b:c:d:e::1.2.3.4/64',\n            'FEDC:BA98:7654:3210:FEDC:BA98:7654:3210/0',\n            'FEDC:BA98:7654:3210:FEDC:BA98:7654:3210/32',\n            'FEDC:BA98:7654:3210:FEDC:BA98:7654:3210/128',\n            '1080:0:0:0:8:800:200C:417A/27'\n        ]);\n\n        const validIPv6sWithoutCidr = prepareIps([\n            '2001:db8::7',\n            'a:b:c:d:e::1.2.3.4',\n            'FEDC:BA98:7654:3210:FEDC:BA98:7654:3210',\n            'FEDC:BA98:7654:3210:FEDC:BA98:7654:3210',\n            '1080:0:0:0:8:800:200C:417A',\n            '::1:2:3:4:5:6:7',\n            '::1:2:3:4:5:6',\n            '1::1:2:3:4:5:6',\n            '::1:2:3:4:5',\n            '1::1:2:3:4:5',\n            '2:1::1:2:3:4:5',\n            '::1:2:3:4',\n            '1::1:2:3:4',\n            '2:1::1:2:3:4',\n            '3:2:1::1:2:3:4',\n            '::1:2:3',\n            '1::1:2:3',\n            '2:1::1:2:3',\n            '3:2:1::1:2:3',\n            '4:3:2:1::1:2:3',\n            '::1:2',\n            '1::1:2',\n            '2:1::1:2',\n            '3:2:1::1:2',\n            '4:3:2:1::1:2',\n            '5:4:3:2:1::1:2',\n            '::1',\n            '1::1',\n            '2:1::1',\n            '3:2:1::1',\n            '4:3:2:1::1',\n            '5:4:3:2:1::1',\n            '6:5:4:3:2:1::1',\n            '::',\n            '1::',\n            '2:1::',\n            '3:2:1::',\n            '4:3:2:1::',\n            '5:4:3:2:1::',\n            '6:5:4:3:2:1::',\n            '7:6:5:4:3:2:1::'\n        ]);\n\n        const validIPvFuturesWithCidr = prepareIps(['v1.09azAZ-._~!$&\\'()*+,;=:/32', 'v1.09azAZ-._~!$&\\'()*+,;=:/128']);\n\n        const validIPvFuturesWithoutCidr = prepareIps(['v1.09azAZ-._~!$&\\'()*+,;=:']);\n\n        it('should validate all ip addresses with optional CIDR by default', () => {\n\n            const schema = Joi.string().ip();\n            const message = '\"value\" must be a valid ip address with a optional CIDR';\n            Helper.validate(schema, []\n                .concat(validIPv4sWithCidr(true))\n                .concat(validIPv4sWithoutCidr(true))\n                .concat(validIPv6sWithCidr(true))\n                .concat(validIPv6sWithoutCidr(true))\n                .concat(validIPvFuturesWithCidr(true))\n                .concat(validIPvFuturesWithoutCidr(true))\n                .concat(invalidIPs(message))\n                .concat(invalidIPv4s(message))\n                .concat(invalidIPv6s(message))\n                .concat(invalidIPvFutures(message)));\n        });\n\n        it('should validate all ip addresses with an optional CIDR', () => {\n\n            const schema = Joi.string().ip({ cidr: 'optional' });\n            const message = '\"value\" must be a valid ip address with a optional CIDR';\n            Helper.validate(schema, []\n                .concat(validIPv4sWithCidr(true))\n                .concat(validIPv4sWithoutCidr(true))\n                .concat(validIPv6sWithCidr(true))\n                .concat(validIPv6sWithoutCidr(true))\n                .concat(validIPvFuturesWithCidr(true))\n                .concat(validIPvFuturesWithoutCidr(true))\n                .concat(invalidIPs(message))\n                .concat(invalidIPv4s(message))\n                .concat(invalidIPv6s(message))\n                .concat(invalidIPvFutures(message)));\n        });\n\n        it('should validate all ip addresses with a required CIDR', () => {\n\n            const schema = Joi.string().ip({ cidr: 'required' });\n            const message = '\"value\" must be a valid ip address with a required CIDR';\n            Helper.validate(schema, []\n                .concat(validIPv4sWithCidr(true))\n                .concat(validIPv4sWithoutCidr(false, message))\n                .concat(validIPv6sWithCidr(true))\n                .concat(validIPv6sWithoutCidr(false, message))\n                .concat(validIPvFuturesWithCidr(true))\n                .concat(validIPvFuturesWithoutCidr(false, message))\n                .concat(invalidIPs(message))\n                .concat(invalidIPv4s(message))\n                .concat(invalidIPv6s(message))\n                .concat(invalidIPvFutures(message)));\n        });\n\n        it('should validate all ip addresses with a forbidden CIDR', () => {\n\n            const schema = Joi.string().ip({ cidr: 'forbidden' });\n            const message = '\"value\" must be a valid ip address with a forbidden CIDR';\n            Helper.validate(schema, []\n                .concat(validIPv4sWithCidr(false, message))\n                .concat(validIPv4sWithoutCidr(true))\n                .concat(validIPv6sWithCidr(false, message))\n                .concat(validIPv6sWithoutCidr(true))\n                .concat(validIPvFuturesWithCidr(false, message))\n                .concat(validIPvFuturesWithoutCidr(true))\n                .concat(invalidIPs(message))\n                .concat(invalidIPv4s(message))\n                .concat(invalidIPv6s(message))\n                .concat(invalidIPvFutures(message)));\n        });\n\n        it('throws when options is not an object', () => {\n\n            expect(() => Joi.string().ip(42)).to.throw('Options must be of type object');\n        });\n\n        it('throws when options.cidr is not a string', () => {\n\n            expect(() => Joi.string().ip({ cidr: 42 })).to.throw('options.cidr must be one of required, optional, forbidden');\n        });\n\n        it('throws when options.cidr is not a valid value', () => {\n\n            expect(() => Joi.string().ip({ cidr: '42' })).to.throw('options.cidr must be one of required, optional, forbidden');\n        });\n\n        it('throws when options.version is an empty array', () => {\n\n            expect(() => Joi.string().ip({ version: [] })).to.throw('options.version must have at least 1 version specified');\n        });\n\n        it('throws when options.version is not a string', () => {\n\n            expect(() => Joi.string().ip({ version: 42 })).to.throw('options.version must be a string or an array of string');\n        });\n\n        it('throws when options.version is not a valid value', () => {\n\n            expect(() => Joi.string().ip({ version: '42' })).to.throw('options.version contains unknown version 42 - must be one of ipv4, ipv6, ipvfuture');\n        });\n\n        it('validates ip', () => {\n\n            const schema = { item: Joi.string().ip() };\n            Helper.validate(Joi.compile(schema), [[{ item: 'something' }, false, {\n                message: '\"item\" must be a valid ip address with a optional CIDR',\n                path: ['item'],\n                type: 'string.ip',\n                context: {\n                    value: 'something',\n                    cidr: 'optional',\n                    label: 'item',\n                    key: 'item'\n                }\n            }]]);\n        });\n\n        it('validates ip and cidr presence', () => {\n\n            const schema = { item: Joi.string().ip({ cidr: 'required' }) };\n            Helper.validate(Joi.compile(schema), [[{ item: 'something' }, false, {\n                message: '\"item\" must be a valid ip address with a required CIDR',\n                path: ['item'],\n                type: 'string.ip',\n                context: {\n                    value: 'something',\n                    cidr: 'required',\n                    label: 'item',\n                    key: 'item'\n                }\n            }]]);\n        });\n\n        it('validates custom ip version and cidr presence', () => {\n\n            const schema = { item: Joi.string().ip({ version: 'ipv4', cidr: 'required' }) };\n            Helper.validate(Joi.compile(schema), [[{ item: 'something' }, false, {\n                message: '\"item\" must be a valid ip address of one of the following versions [ipv4] with a required CIDR',\n                path: ['item'],\n                type: 'string.ipVersion',\n                context: {\n                    value: 'something',\n                    cidr: 'required',\n                    version: ['ipv4'],\n                    label: 'item',\n                    key: 'item'\n                }\n            }]]);\n        });\n\n        describe('ip({ version: \"ipv4\" })', () => {\n\n            it('should validate all ipv4 addresses with a default CIDR strategy', () => {\n\n                const version = 'ipv4';\n                const schema = Joi.string().ip({ version });\n                const message = '\"value\" must be a valid ip address of one of the following versions [ipv4] with a optional CIDR';\n                Helper.validate(schema, []\n                    .concat(validIPv4sWithCidr(true))\n                    .concat(validIPv4sWithoutCidr(true))\n                    .concat(validIPv6sWithCidr(false, message, [version]))\n                    .concat(validIPv6sWithoutCidr(false, message, [version]))\n                    .concat(validIPvFuturesWithCidr(false, message, [version]))\n                    .concat(validIPvFuturesWithoutCidr(false, message, [version]))\n                    .concat(invalidIPs(message, [version]))\n                    .concat(invalidIPv4s(message, [version]))\n                    .concat(invalidIPv6s(message, [version]))\n                    .concat(invalidIPvFutures(message, [version])));\n            });\n\n            it('should validate all ipv4 addresses with an optional CIDR', () => {\n\n                const version = 'ipv4';\n                const schema = Joi.string().ip({ version, cidr: 'optional' });\n                const message = '\"value\" must be a valid ip address of one of the following versions [ipv4] with a optional CIDR';\n                Helper.validate(schema, []\n                    .concat(validIPv4sWithCidr(true))\n                    .concat(validIPv4sWithoutCidr(true))\n                    .concat(validIPv6sWithCidr(false, message, [version]))\n                    .concat(validIPv6sWithoutCidr(false, message, [version]))\n                    .concat(validIPvFuturesWithCidr(false, message, [version]))\n                    .concat(validIPvFuturesWithoutCidr(false, message, [version]))\n                    .concat(invalidIPs(message, [version]))\n                    .concat(invalidIPv4s(message, [version]))\n                    .concat(invalidIPv6s(message, [version]))\n                    .concat(invalidIPvFutures(message, [version])));\n            });\n\n            it('should validate all ipv4 addresses with a required CIDR', () => {\n\n                const version = 'ipv4';\n                const schema = Joi.string().ip({ version, cidr: 'required' });\n                const message = '\"value\" must be a valid ip address of one of the following versions [ipv4] with a required CIDR';\n                Helper.validate(schema, []\n                    .concat(validIPv4sWithCidr(true))\n                    .concat(validIPv4sWithoutCidr(false, message, [version]))\n                    .concat(validIPv6sWithCidr(false, message, [version]))\n                    .concat(validIPv6sWithoutCidr(false, message, [version]))\n                    .concat(validIPvFuturesWithCidr(false, message, [version]))\n                    .concat(validIPvFuturesWithoutCidr(false, message, [version]))\n                    .concat(invalidIPs(message, [version]))\n                    .concat(invalidIPv4s(message, [version]))\n                    .concat(invalidIPv6s(message, [version]))\n                    .concat(invalidIPvFutures(message, [version])));\n            });\n\n            it('should validate all ipv4 addresses with a forbidden CIDR', () => {\n\n                const version = 'ipv4';\n                const schema = Joi.string().ip({ version, cidr: 'forbidden' });\n                const message = '\"value\" must be a valid ip address of one of the following versions [ipv4] with a forbidden CIDR';\n                Helper.validate(schema, []\n                    .concat(validIPv4sWithCidr(false, message, [version]))\n                    .concat(validIPv4sWithoutCidr(true))\n                    .concat(validIPv6sWithCidr(false, message, [version]))\n                    .concat(validIPv6sWithoutCidr(false, message, [version]))\n                    .concat(validIPvFuturesWithCidr(false, message, [version]))\n                    .concat(validIPvFuturesWithoutCidr(false, message, [version]))\n                    .concat(invalidIPs(message, [version]))\n                    .concat(invalidIPv4s(message, [version]))\n                    .concat(invalidIPv6s(message, [version]))\n                    .concat(invalidIPvFutures(message, [version])));\n            });\n        });\n\n        describe('ip({ version: \"ipv6\" })', () => {\n\n            it('should validate all ipv6 addresses with a default CIDR strategy', () => {\n\n                const version = 'ipv6';\n                const schema = Joi.string().ip({ version });\n                const message = '\"value\" must be a valid ip address of one of the following versions [ipv6] with a optional CIDR';\n                Helper.validate(schema, []\n                    .concat(validIPv4sWithCidr(false, message, [version]))\n                    .concat(validIPv4sWithoutCidr(false, message, [version]))\n                    .concat(validIPv6sWithCidr(true))\n                    .concat(validIPv6sWithoutCidr(true))\n                    .concat(validIPvFuturesWithCidr(false, message, [version]))\n                    .concat(validIPvFuturesWithoutCidr(false, message, [version]))\n                    .concat(invalidIPs(message, [version]))\n                    .concat(invalidIPv4s(message, [version]))\n                    .concat(invalidIPv6s(message, [version]))\n                    .concat(invalidIPvFutures(message, [version])));\n            });\n\n            it('should validate all ipv6 addresses with an optional CIDR', () => {\n\n                const version = 'ipv6';\n                const schema = Joi.string().ip({ version, cidr: 'optional' });\n                const message = '\"value\" must be a valid ip address of one of the following versions [ipv6] with a optional CIDR';\n                Helper.validate(schema, []\n                    .concat(validIPv4sWithCidr(false, message, [version]))\n                    .concat(validIPv4sWithoutCidr(false, message, [version]))\n                    .concat(validIPv6sWithCidr(true))\n                    .concat(validIPv6sWithoutCidr(true))\n                    .concat(validIPvFuturesWithCidr(false, message, [version]))\n                    .concat(validIPvFuturesWithoutCidr(false, message, [version]))\n                    .concat(invalidIPs(message, [version]))\n                    .concat(invalidIPv4s(message, [version]))\n                    .concat(invalidIPv6s(message, [version]))\n                    .concat(invalidIPvFutures(message, [version])));\n            });\n\n            it('should validate all ipv6 addresses with a required CIDR', () => {\n\n                const version = 'ipv6';\n                const schema = Joi.string().ip({ version, cidr: 'required' });\n                const message = '\"value\" must be a valid ip address of one of the following versions [ipv6] with a required CIDR';\n                Helper.validate(schema, []\n                    .concat(validIPv4sWithCidr(false, message, [version]))\n                    .concat(validIPv4sWithoutCidr(false, message, [version]))\n                    .concat(validIPv6sWithCidr(true))\n                    .concat(validIPv6sWithoutCidr(false, message, [version]))\n                    .concat(validIPvFuturesWithCidr(false, message, [version]))\n                    .concat(validIPvFuturesWithoutCidr(false, message, [version]))\n                    .concat(invalidIPs(message, [version]))\n                    .concat(invalidIPv4s(message, [version]))\n                    .concat(invalidIPv6s(message, [version]))\n                    .concat(invalidIPvFutures(message, [version])));\n            });\n\n            it('should validate all ipv6 addresses with a forbidden CIDR', () => {\n\n                const version = 'ipv6';\n                const schema = Joi.string().ip({ version, cidr: 'forbidden' });\n                const message = '\"value\" must be a valid ip address of one of the following versions [ipv6] with a forbidden CIDR';\n                Helper.validate(schema, []\n                    .concat(validIPv4sWithCidr(false, message, [version]))\n                    .concat(validIPv4sWithoutCidr(false, message, [version]))\n                    .concat(validIPv6sWithCidr(false, message, [version]))\n                    .concat(validIPv6sWithoutCidr(true))\n                    .concat(validIPvFuturesWithCidr(false, message, [version]))\n                    .concat(validIPvFuturesWithoutCidr(false, message, [version]))\n                    .concat(invalidIPs(message, [version]))\n                    .concat(invalidIPv4s(message, [version]))\n                    .concat(invalidIPv6s(message, [version]))\n                    .concat(invalidIPvFutures(message, [version])));\n            });\n        });\n\n        describe('ip({ version: \"ipvfuture\" })', () => {\n\n            it('should validate all ipvfuture addresses with a default CIDR strategy', () => {\n\n                const version = 'ipvfuture';\n                const schema = Joi.string().ip({ version });\n                const message = '\"value\" must be a valid ip address of one of the following versions [ipvfuture] with a optional CIDR';\n                Helper.validate(schema, []\n                    .concat(validIPv4sWithCidr(false, message, [version]))\n                    .concat(validIPv4sWithoutCidr(false, message, [version]))\n                    .concat(validIPv6sWithCidr(false, message, [version]))\n                    .concat(validIPv6sWithoutCidr(false, message, [version]))\n                    .concat(validIPvFuturesWithCidr(true))\n                    .concat(validIPvFuturesWithoutCidr(true))\n                    .concat(invalidIPs(message, [version]))\n                    .concat(invalidIPv4s(message, [version]))\n                    .concat(invalidIPv6s(message, [version]))\n                    .concat(invalidIPvFutures(message, [version])));\n            });\n\n            it('should validate all ipvfuture addresses with an optional CIDR', () => {\n\n                const version = 'ipvfuture';\n                const schema = Joi.string().ip({ version, cidr: 'optional' });\n                const message = '\"value\" must be a valid ip address of one of the following versions [ipvfuture] with a optional CIDR';\n                Helper.validate(schema, []\n                    .concat(validIPv4sWithCidr(false, message, [version]))\n                    .concat(validIPv4sWithoutCidr(false, message, [version]))\n                    .concat(validIPv6sWithCidr(false, message, [version]))\n                    .concat(validIPv6sWithoutCidr(false, message, [version]))\n                    .concat(validIPvFuturesWithCidr(true))\n                    .concat(validIPvFuturesWithoutCidr(true))\n                    .concat(invalidIPs(message, [version]))\n                    .concat(invalidIPv4s(message, [version]))\n                    .concat(invalidIPv6s(message, [version]))\n                    .concat(invalidIPvFutures(message, [version])));\n            });\n\n            it('should validate all ipvfuture addresses with a required CIDR', () => {\n\n                const version = 'ipvfuture';\n                const schema = Joi.string().ip({ version, cidr: 'required' });\n                const message = '\"value\" must be a valid ip address of one of the following versions [ipvfuture] with a required CIDR';\n                Helper.validate(schema, []\n                    .concat(validIPv4sWithCidr(false, message, [version]))\n                    .concat(validIPv4sWithoutCidr(false, message, [version]))\n                    .concat(validIPv6sWithCidr(false, message, [version]))\n                    .concat(validIPv6sWithoutCidr(false, message, [version]))\n                    .concat(validIPvFuturesWithCidr(true))\n                    .concat(validIPvFuturesWithoutCidr(false, message, [version]))\n                    .concat(invalidIPs(message, [version]))\n                    .concat(invalidIPv4s(message, [version]))\n                    .concat(invalidIPv6s(message, [version]))\n                    .concat(invalidIPvFutures(message, [version])));\n            });\n\n            it('should validate all ipvfuture addresses with a forbidden CIDR', () => {\n\n                const version = 'ipvfuture';\n                const schema = Joi.string().ip({ version, cidr: 'forbidden' });\n                const message = '\"value\" must be a valid ip address of one of the following versions [ipvfuture] with a forbidden CIDR';\n                Helper.validate(schema, []\n                    .concat(validIPv4sWithCidr(false, message, [version]))\n                    .concat(validIPv4sWithoutCidr(false, message, [version]))\n                    .concat(validIPv6sWithCidr(false, message, [version]))\n                    .concat(validIPv6sWithoutCidr(false, message, [version]))\n                    .concat(validIPvFuturesWithCidr(false, message, [version]))\n                    .concat(validIPvFuturesWithoutCidr(true))\n                    .concat(invalidIPs(message, [version]))\n                    .concat(invalidIPv4s(message, [version]))\n                    .concat(invalidIPv6s(message, [version]))\n                    .concat(invalidIPvFutures(message, [version])));\n            });\n        });\n\n        describe('ip({ version: [ \"ipv4\", \"ipv6\" ] })', () => {\n\n            it('should validate all ipv4 and ipv6 addresses with a default CIDR strategy', () => {\n\n                const schema = Joi.string().ip({ version: ['ipv4', 'ipv6'] });\n                const message = '\"value\" must be a valid ip address of one of the following versions [ipv4, ipv6] with a optional CIDR';\n                Helper.validate(schema, []\n                    .concat(validIPv4sWithCidr(true))\n                    .concat(validIPv4sWithoutCidr(true))\n                    .concat(validIPv6sWithCidr(true))\n                    .concat(validIPv6sWithoutCidr(true))\n                    .concat(validIPvFuturesWithCidr(false, message))\n                    .concat(validIPvFuturesWithoutCidr(false, message))\n                    .concat(invalidIPs(message))\n                    .concat(invalidIPv4s(message))\n                    .concat(invalidIPv6s(message))\n                    .concat(invalidIPvFutures(message)));\n            });\n\n            it('should validate all ipv4 and ipv6 addresses with an optional CIDR', () => {\n\n                const schema = Joi.string().ip({ version: ['ipv4', 'ipv6'], cidr: 'optional' });\n                const message = '\"value\" must be a valid ip address of one of the following versions [ipv4, ipv6] with a optional CIDR';\n                Helper.validate(schema, []\n                    .concat(validIPv4sWithCidr(true))\n                    .concat(validIPv4sWithoutCidr(true))\n                    .concat(validIPv6sWithCidr(true))\n                    .concat(validIPv6sWithoutCidr(true))\n                    .concat(validIPvFuturesWithCidr(false, message))\n                    .concat(validIPvFuturesWithoutCidr(false, message))\n                    .concat(invalidIPs(message))\n                    .concat(invalidIPv4s(message))\n                    .concat(invalidIPv6s(message))\n                    .concat(invalidIPvFutures(message)));\n            });\n\n            it('should validate all ipv4 and ipv6 addresses with a required CIDR', () => {\n\n                const schema = Joi.string().ip({ version: ['ipv4', 'ipv6'], cidr: 'required' });\n                const message = '\"value\" must be a valid ip address of one of the following versions [ipv4, ipv6] with a required CIDR';\n                Helper.validate(schema, []\n                    .concat(validIPv4sWithCidr(true))\n                    .concat(validIPv4sWithoutCidr(false, message))\n                    .concat(validIPv6sWithCidr(true))\n                    .concat(validIPv6sWithoutCidr(false, message))\n                    .concat(validIPvFuturesWithCidr(false, message))\n                    .concat(validIPvFuturesWithoutCidr(false, message))\n                    .concat(invalidIPs(message))\n                    .concat(invalidIPv4s(message))\n                    .concat(invalidIPv6s(message))\n                    .concat(invalidIPvFutures(message)));\n            });\n\n            it('should validate all ipv4 and ipv6 addresses with a forbidden CIDR', () => {\n\n                const schema = Joi.string().ip({ version: ['ipv4', 'ipv6'], cidr: 'forbidden' });\n                const message = '\"value\" must be a valid ip address of one of the following versions [ipv4, ipv6] with a forbidden CIDR';\n                Helper.validate(schema, []\n                    .concat(validIPv4sWithCidr(false, message))\n                    .concat(validIPv4sWithoutCidr(true))\n                    .concat(validIPv6sWithCidr(false, message))\n                    .concat(validIPv6sWithoutCidr(true))\n                    .concat(validIPvFuturesWithCidr(false, message))\n                    .concat(validIPvFuturesWithoutCidr(false, message))\n                    .concat(invalidIPs(message))\n                    .concat(invalidIPv4s(message))\n                    .concat(invalidIPv6s(message))\n                    .concat(invalidIPvFutures(message)));\n            });\n        });\n    });\n\n    describe('isoData()', () => {\n\n        it('validates isoDate', () => {\n\n            Helper.validate(Joi.string().isoDate(), { convert: false }, [\n                ['+002013-06-07T14:21:46.295Z', true],\n                ['-002013-06-07T14:21:46.295Z', true],\n                ['002013-06-07T14:21:46.295Z', false, {\n                    message: '\"value\" must be in iso format',\n                    path: [],\n                    type: 'string.isoDate',\n                    context: { value: '002013-06-07T14:21:46.295Z', label: 'value' }\n                }],\n                ['+2013-06-07T14:21:46.295Z', false, {\n                    message: '\"value\" must be in iso format',\n                    path: [],\n                    type: 'string.isoDate',\n                    context: { value: '+2013-06-07T14:21:46.295Z', label: 'value' }\n                }],\n                ['-2013-06-07T14:21:46.295Z', false, {\n                    message: '\"value\" must be in iso format',\n                    path: [],\n                    type: 'string.isoDate',\n                    context: { value: '-2013-06-07T14:21:46.295Z', label: 'value' }\n                }],\n                ['2013-06-07T14:21:46.295Z', true],\n                ['2013-06-07T14:21:46.295Z0', false, {\n                    message: '\"value\" must be in iso format',\n                    path: [],\n                    type: 'string.isoDate',\n                    context: { value: '2013-06-07T14:21:46.295Z0', label: 'value' }\n                }],\n                ['2013-06-07T14:21:46.295+07:00', true],\n                ['2013-06-07T14:21:46.295+07', true],\n                ['2013-06-07T14:21:46.295+07:000', false, {\n                    message: '\"value\" must be in iso format',\n                    path: [],\n                    type: 'string.isoDate',\n                    context: { value: '2013-06-07T14:21:46.295+07:000', label: 'value' }\n                }],\n                ['2013-06-07T14:21:46.295-07:00', true],\n                ['2013-06-07T14:21:46Z', true],\n                ['2013-06-07T14:21:46Z0', false, {\n                    message: '\"value\" must be in iso format',\n                    path: [],\n                    type: 'string.isoDate',\n                    context: { value: '2013-06-07T14:21:46Z0', label: 'value' }\n                }],\n                ['2013-06-07T14:21:46+07:00', true],\n                ['2013-06-07T14:21:46+07:000', false, {\n                    message: '\"value\" must be in iso format',\n                    path: [],\n                    type: 'string.isoDate',\n                    context: { value: '2013-06-07T14:21:46+07:000', label: 'value' }\n                }],\n                ['2013-06-07T14:21:46-07:00', true],\n                ['2013-06-07T14:21Z', true],\n                ['2013-06-07T14:21+07:00', true],\n                ['2013-06-07T14:21+07:000', false, {\n                    message: '\"value\" must be in iso format',\n                    path: [],\n                    type: 'string.isoDate',\n                    context: { value: '2013-06-07T14:21+07:000', label: 'value' }\n                }],\n                ['2013-06-07T14:21-07:00', true],\n                ['2013-06-07T14:21Z+7:00', false, {\n                    message: '\"value\" must be in iso format',\n                    path: [],\n                    type: 'string.isoDate',\n                    context: { value: '2013-06-07T14:21Z+7:00', label: 'value' }\n                }],\n                ['2013-06-07', true],\n                ['2013-06-07T', false, {\n                    message: '\"value\" must be in iso format',\n                    path: [],\n                    type: 'string.isoDate',\n                    context: { value: '2013-06-07T', label: 'value' }\n                }],\n                ['2013-06-07T14:21', true],\n                ['1-1-2013', false, {\n                    message: '\"value\" must be in iso format',\n                    path: [],\n                    type: 'string.isoDate',\n                    context: { value: '1-1-2013', label: 'value' }\n                }],\n                ['2013-06-07T14.2334,4', false, {\n                    message: '\"value\" must be in iso format',\n                    path: [],\n                    type: 'string.isoDate',\n                    context: { value: '2013-06-07T14.2334,4', label: 'value' }\n                }],\n                ['2013-06-07T14,23:34', false, {\n                    message: '\"value\" must be in iso format',\n                    path: [],\n                    type: 'string.isoDate',\n                    context: { value: '2013-06-07T14,23:34', label: 'value' }\n                }],\n                ['2013-06-07T24', false, {\n                    message: '\"value\" must be in iso format',\n                    path: [],\n                    type: 'string.isoDate',\n                    context: { value: '2013-06-07T24', label: 'value' }\n                }],\n                ['2013-06-07T24:00', true],\n                ['2013-06-07T24:21', false, {\n                    message: '\"value\" must be in iso format',\n                    path: [],\n                    type: 'string.isoDate',\n                    context: { value: '2013-06-07T24:21', label: 'value' }\n                }],\n                ['2013-06-07 146946.295', false, {\n                    message: '\"value\" must be in iso format',\n                    path: [],\n                    type: 'string.isoDate',\n                    context: { value: '2013-06-07 146946.295', label: 'value' }\n                }],\n                ['2013-06-07 1421,44', false, {\n                    message: '\"value\" must be in iso format',\n                    path: [],\n                    type: 'string.isoDate',\n                    context: { value: '2013-06-07 1421,44', label: 'value' }\n                }],\n                ['2013-W2311', false, {\n                    message: '\"value\" must be in iso format',\n                    path: [],\n                    type: 'string.isoDate',\n                    context: { value: '2013-W2311', label: 'value' }\n                }],\n                ['2013-M231', false, {\n                    message: '\"value\" must be in iso format',\n                    path: [],\n                    type: 'string.isoDate',\n                    context: { value: '2013-M231', label: 'value' }\n                }],\n                ['2013-W23-1T14:21', false, {\n                    message: '\"value\" must be in iso format',\n                    path: [],\n                    type: 'string.isoDate',\n                    context: { value: '2013-W23-1T14:21', label: 'value' }\n                }],\n                ['2013-W23-1T14:21:', false, {\n                    message: '\"value\" must be in iso format',\n                    path: [],\n                    type: 'string.isoDate',\n                    context: { value: '2013-W23-1T14:21:', label: 'value' }\n                }],\n                ['2013-W23-1T14:21:46+07:00', false, {\n                    message: '\"value\" must be in iso format',\n                    path: [],\n                    type: 'string.isoDate',\n                    context: { value: '2013-W23-1T14:21:46+07:00', label: 'value' }\n                }],\n                ['2013-W23-1T14:21:46+07:000', false, {\n                    message: '\"value\" must be in iso format',\n                    path: [],\n                    type: 'string.isoDate',\n                    context: { value: '2013-W23-1T14:21:46+07:000', label: 'value' }\n                }],\n                ['2013-W23-1T14:21:46-07:00', false, {\n                    message: '\"value\" must be in iso format',\n                    path: [],\n                    type: 'string.isoDate',\n                    context: { value: '2013-W23-1T14:21:46-07:00', label: 'value' }\n                }],\n                ['2013-184', false, {\n                    message: '\"value\" must be in iso format',\n                    path: [],\n                    type: 'string.isoDate',\n                    context: { value: '2013-184', label: 'value' }\n                }],\n                ['2013-1841', false, {\n                    message: '\"value\" must be in iso format',\n                    path: [],\n                    type: 'string.isoDate',\n                    context: { value: '2013-1841', label: 'value' }\n                }]\n            ]);\n        });\n\n        it('validates isoDate', () => {\n\n            const schema = { item: Joi.string().isoDate() };\n            Helper.validate(Joi.compile(schema), [[{ item: 'something' }, false, '\"item\" must be in iso format']]);\n            Helper.validate(Joi.compile(schema), { convert: false }, [[{ item: 'something' }, false, '\"item\" must be in iso format']]);\n        });\n\n        it('validates and formats isoDate with convert set to true (default)', () => {\n\n            const rule = Joi.string().isoDate();\n            Helper.validate(rule, { convert: true }, [\n                ['+002013-06-07T14:21:46.295Z', true, '2013-06-07T14:21:46.295Z'],\n                ['-002013-06-07T14:21:46.295Z', true, '-002013-06-07T14:21:46.295Z'],\n                ['2013-06-07T14:21:46.295Z', true, '2013-06-07T14:21:46.295Z'],\n                ['2013-06-07T14:21:46.295+07:00', true, '2013-06-07T07:21:46.295Z'],\n                ['2013-06-07T14:21:46.295-07:00', true, '2013-06-07T21:21:46.295Z'],\n                ['2013-06-07T14:21:46Z', true, '2013-06-07T14:21:46.000Z'],\n                ['2013-06-07T14:21:46+07:00', true, '2013-06-07T07:21:46.000Z'],\n                ['2013-06-07T14:21:46-07:00', true, '2013-06-07T21:21:46.000Z'],\n                ['2013-06-07T14:21Z', true, '2013-06-07T14:21:00.000Z'],\n                ['2013-06-07T14:21+07:00', true, '2013-06-07T07:21:00.000Z'],\n                ['2013-06-07T14:21-07:00', true, '2013-06-07T21:21:00.000Z'],\n                ['2013-06-07', true, '2013-06-07T00:00:00.000Z'],\n                ['2013-06-07T14:21', true, '2013-06-07T14:21:00.000Z'],\n                ['2013-184', false, {\n                    message: '\"value\" must be in iso format',\n                    path: [],\n                    type: 'string.isoDate',\n                    context: { value: '2013-184', label: 'value' }\n                }]\n            ]);\n        });\n\n        it('supports allowed values', () => {\n\n            const schema = Joi.string().isoDate().allow('x');\n            Helper.validate(schema, [['x', true]]);\n        });\n    });\n\n    describe('isoDuration()', () => {\n\n        it('validates isoDuration', () => {\n\n            Helper.validate(Joi.string().isoDuration(), [\n                ['P3Y6M4DT12H30M5S', true],\n                ['P3Y6M4DT12H30M', true],\n                ['P3Y6M4DT12H5S', true],\n                ['P3Y6M4DT30M5S', true],\n                ['P3Y6MT12H30M5S', true],\n                ['P3Y4DT12H30M5S', true],\n                ['P6M4DT12H30M5S', true],\n                ['PT10H20M5S', true],\n                ['PT40S', true],\n                ['PT0S', true],\n                ['P0D', true],\n                ['P30S', false, {\n                    message: '\"value\" must be a valid ISO 8601 duration',\n                    path: [],\n                    type: 'string.isoDuration',\n                    context: { value: 'P30S', label: 'value' }\n                }],\n                ['P30', false, {\n                    message: '\"value\" must be a valid ISO 8601 duration',\n                    path: [],\n                    type: 'string.isoDuration',\n                    context: { value: 'P30', label: 'value' }\n                }],\n                ['', false, {\n                    message: '\"value\" is not allowed to be empty',\n                    path: [],\n                    type: 'string.empty',\n                    context: { value: '', label: 'value' }\n                }],\n                [null, false, {\n                    message: '\"value\" must be a string',\n                    path: [],\n                    type: 'string.base',\n                    context: { value: null, label: 'value' }\n                }]\n            ]);\n        });\n    });\n\n    describe('length()', () => {\n\n        it('validates length requirements', () => {\n\n            const schema = Joi.string().length(3);\n            Helper.validate(schema, [\n                ['test', false, {\n                    message: '\"value\" length must be 3 characters long',\n                    path: [],\n                    type: 'string.length',\n                    context: {\n                        limit: 3,\n                        value: 'test',\n                        encoding: undefined,\n                        label: 'value'\n                    }\n                }],\n                ['0', false, {\n                    message: '\"value\" length must be 3 characters long',\n                    path: [],\n                    type: 'string.length',\n                    context: {\n                        limit: 3,\n                        value: '0',\n                        encoding: undefined,\n                        label: 'value'\n                    }\n                }],\n                [null, false, {\n                    message: '\"value\" must be a string',\n                    path: [],\n                    type: 'string.base',\n                    context: { value: null, label: 'value' }\n                }],\n                ['abc', true]\n            ]);\n        });\n\n        it('throws when limit is undefined', () => {\n\n            expect(() => {\n\n                Joi.string().length();\n            }).to.throw('limit must be a positive integer or reference');\n        });\n\n        it('throws when limit is not a number', () => {\n\n            expect(() => {\n\n                Joi.string().length('a');\n            }).to.throw('limit must be a positive integer or reference');\n        });\n\n        it('throws when limit is not an integer', () => {\n\n            expect(() => {\n\n                Joi.string().length(1.2);\n            }).to.throw('limit must be a positive integer or reference');\n        });\n\n        it('throws when limit is not a positive integer', () => {\n\n            expect(() => {\n\n                Joi.string().length(-42);\n            }).to.throw('limit must be a positive integer or reference');\n        });\n\n        it('enforces a limit using byte count', () => {\n\n            const schema = Joi.string().length(2, 'utf8');\n            Helper.validate(schema, [\n                ['\\u00bd', true],\n                ['a', false, {\n                    message: '\"value\" length must be 2 characters long',\n                    path: [],\n                    type: 'string.length',\n                    context: { limit: 2, value: 'a', encoding: 'utf8', label: 'value' }\n                }]\n            ]);\n        });\n\n        it('accepts references as length', () => {\n\n            const ref = Joi.ref('a');\n            const schema = Joi.object({ a: Joi.number(), b: Joi.string().length(ref, 'utf8') });\n            Helper.validate(schema, [\n                [{ a: 2, b: '\\u00bd' }, true],\n                [{ a: 2, b: 'a' }, false, {\n                    message: '\"b\" length must be ref:a characters long',\n                    path: ['b'],\n                    type: 'string.length',\n                    context: { limit: ref, value: 'a', encoding: 'utf8', label: 'b', key: 'b' }\n                }]\n            ]);\n        });\n\n        it('accepts context references as length', () => {\n\n            const ref = Joi.ref('$a');\n            const schema = Joi.object({ b: Joi.string().length(ref, 'utf8') });\n            Helper.validate(schema, { context: { a: 2 } }, [\n                [{ b: '\\u00bd' }, true],\n                [{ b: 'a' }, false, {\n                    message: '\"b\" length must be ref:global:a characters long',\n                    path: ['b'],\n                    type: 'string.length',\n                    context: { limit: ref, value: 'a', encoding: 'utf8', label: 'b', key: 'b' }\n                }],\n                [{ b: 'a' }, false, {\n                    message: '\"b\" length must be ref:global:a characters long',\n                    path: ['b'],\n                    type: 'string.length',\n                    context: { limit: ref, value: 'a', encoding: 'utf8', label: 'b', key: 'b' }\n                }]\n            ]);\n        });\n\n        it('errors if reference is not a number', () => {\n\n            const ref = Joi.ref('a');\n            const schema = Joi.object({ a: Joi.any(), b: Joi.string().length(ref, 'utf8') });\n\n            Helper.validate(schema, [\n                [{ a: 'Hi there', b: '\\u00bd' }, false, {\n                    message: '\"b\" limit references \"ref:a\" which must be a positive integer',\n                    path: ['b'],\n                    type: 'any.ref',\n                    context: { ref, label: 'b', key: 'b', value: 'Hi there', arg: 'limit', reason: 'must be a positive integer' }\n                }]\n            ]);\n        });\n\n        it('errors if context reference is not a number', () => {\n\n            const ref = Joi.ref('$a');\n            const schema = Joi.object({ a: Joi.any(), b: Joi.string().length(ref, 'utf8') });\n\n            Helper.validate(schema, { context: { a: 'Hi there' } }, [\n                [{ b: '\\u00bd' }, false, {\n                    message: '\"b\" limit references \"ref:global:a\" which must be a positive integer',\n                    path: ['b'],\n                    type: 'any.ref',\n                    context: { ref, label: 'b', key: 'b', value: 'Hi there', reason: 'must be a positive integer', arg: 'limit' }\n                }]\n            ]);\n        });\n    });\n\n    describe('lowercase()', () => {\n\n        it('only allows strings that are entirely lowercase', () => {\n\n            const schema = Joi.string().lowercase();\n            Helper.validate(schema, { convert: false }, [\n                ['this is all lowercase', true],\n                ['5', true],\n                ['lower\\tcase', true],\n                ['Uppercase', false, {\n                    message: '\"value\" must only contain lowercase characters',\n                    path: [],\n                    type: 'string.lowercase',\n                    context: { value: 'Uppercase', label: 'value' }\n                }],\n                ['MixEd cAsE', false, {\n                    message: '\"value\" must only contain lowercase characters',\n                    path: [],\n                    type: 'string.lowercase',\n                    context: { value: 'MixEd cAsE', label: 'value' }\n                }],\n                [1, false, {\n                    message: '\"value\" must be a string',\n                    path: [],\n                    type: 'string.base',\n                    context: { value: 1, label: 'value' }\n                }]\n            ]);\n        });\n\n        it('coerce string to lowercase before validation', () => {\n\n            const schema = Joi.string().lowercase();\n            Helper.validate(schema, [['UPPER TO LOWER', true, 'upper to lower']]);\n        });\n\n        it('should work in combination with a trim', () => {\n\n            const schema = Joi.string().lowercase().trim();\n            Helper.validate(schema, [\n                [' abc', true, 'abc'],\n                [' ABC', true, 'abc'],\n                ['ABC', true, 'abc'],\n                [1, false, {\n                    message: '\"value\" must be a string',\n                    path: [],\n                    type: 'string.base',\n                    context: { value: 1, label: 'value' }\n                }]\n            ]);\n        });\n\n        it('should work in combination with a replacement', () => {\n\n            const schema = Joi.string().lowercase().replace(/\\s+/g, ' ');\n            Helper.validate(schema, [\n                ['a\\r b\\n c', true, 'a b c'],\n                ['A\\t B  C', true, 'a b c'],\n                ['ABC', true, 'abc'],\n                [1, false, {\n                    message: '\"value\" must be a string',\n                    path: [],\n                    type: 'string.base',\n                    context: { value: 1, label: 'value' }\n                }]\n            ]);\n        });\n    });\n\n    describe('max()', () => {\n\n        it('validates maximum length when max is used', () => {\n\n            const schema = Joi.string().max(3);\n            Helper.validate(schema, [\n                ['test', false, {\n                    message: '\"value\" length must be less than or equal to 3 characters long',\n                    path: [],\n                    type: 'string.max',\n                    context: {\n                        limit: 3,\n                        value: 'test',\n                        encoding: undefined,\n                        label: 'value'\n                    }\n                }],\n                ['0', true],\n                [null, false, {\n                    message: '\"value\" must be a string',\n                    path: [],\n                    type: 'string.base',\n                    context: { value: null, label: 'value' }\n                }]\n            ]);\n        });\n\n        it('throws when limit is undefined', () => {\n\n            expect(() => {\n\n                Joi.string().max();\n            }).to.throw('limit must be a positive integer or reference');\n        });\n\n        it('throws when limit is not a number', () => {\n\n            expect(() => {\n\n                Joi.string().max('a');\n            }).to.throw('limit must be a positive integer or reference');\n        });\n\n        it('throws when limit is not an integer', () => {\n\n            expect(() => {\n\n                Joi.string().max(1.2);\n            }).to.throw('limit must be a positive integer or reference');\n        });\n\n        it('throws when limit is not a positive integer', () => {\n\n            expect(() => {\n\n                Joi.string().max(-1);\n            }).to.throw('limit must be a positive integer or reference');\n        });\n\n        it('enforces a limit using byte count', () => {\n\n            const schema = Joi.string().max(1, 'utf8');\n            Helper.validate(schema, [\n                ['\\u00bd', false, {\n                    message: '\"value\" length must be less than or equal to 1 characters long',\n                    path: [],\n                    type: 'string.max',\n                    context: { limit: 1, value: '\\u00bd', encoding: 'utf8', label: 'value' }\n                }],\n                ['a', true]\n            ]);\n        });\n\n        it('accepts references as min length', () => {\n\n            const ref = Joi.ref('a');\n            const schema = Joi.object({ a: Joi.number(), b: Joi.string().max(ref, 'utf8') });\n            Helper.validate(schema, [\n                [{ a: 2, b: '\\u00bd' }, true],\n                [{ a: 2, b: 'three' }, false, {\n                    message: '\"b\" length must be less than or equal to ref:a characters long',\n                    path: ['b'],\n                    type: 'string.max',\n                    context: {\n                        limit: ref,\n                        value: 'three',\n                        encoding: 'utf8',\n                        label: 'b',\n                        key: 'b'\n                    }\n                }]\n            ]);\n        });\n\n        it('accepts context references as min length', () => {\n\n            const ref = Joi.ref('$a');\n            const schema = Joi.object({ b: Joi.string().max(ref, 'utf8') });\n            Helper.validate(schema, { context: { a: 2 } }, [\n                [{ b: '\\u00bd' }, true],\n                [{ b: 'three' }, false, {\n                    message: '\"b\" length must be less than or equal to ref:global:a characters long',\n                    path: ['b'],\n                    type: 'string.max',\n                    context: {\n                        limit: ref,\n                        value: 'three',\n                        encoding: 'utf8',\n                        label: 'b',\n                        key: 'b'\n                    }\n                }]\n            ]);\n        });\n\n        it('errors if reference is not a number', () => {\n\n            const ref = Joi.ref('a');\n            const schema = Joi.object({ a: Joi.any(), b: Joi.string().max(ref, 'utf8') });\n\n            Helper.validate(schema, [\n                [{ a: 'Hi there', b: '\\u00bd' }, false, {\n                    message: '\"b\" limit references \"ref:a\" which must be a positive integer',\n                    path: ['b'],\n                    type: 'any.ref',\n                    context: { ref, label: 'b', key: 'b', value: 'Hi there', arg: 'limit', reason: 'must be a positive integer' }\n                }]\n            ]);\n        });\n\n        it('errors if context reference is not a number', () => {\n\n            const ref = Joi.ref('$a');\n            const schema = Joi.object({ b: Joi.string().max(ref, 'utf8') });\n\n            Helper.validate(schema, { context: { a: 'Hi there' } }, [\n                [{ b: '\\u00bd' }, false, {\n                    message: '\"b\" limit references \"ref:global:a\" which must be a positive integer',\n                    path: ['b'],\n                    type: 'any.ref',\n                    context: { ref, label: 'b', key: 'b', value: 'Hi there', arg: 'limit', reason: 'must be a positive integer' }\n                }]\n            ]);\n        });\n    });\n\n    describe('min()', () => {\n\n        it('throws when limit is undefined', () => {\n\n            expect(() => {\n\n                Joi.string().min();\n            }).to.throw('limit must be a positive integer or reference');\n        });\n\n        it('throws when limit is not a number', () => {\n\n            expect(() => {\n\n                Joi.string().min('a');\n            }).to.throw('limit must be a positive integer or reference');\n        });\n\n        it('throws when limit is not an integer', () => {\n\n            expect(() => {\n\n                Joi.string().min(1.2);\n            }).to.throw('limit must be a positive integer or reference');\n        });\n\n        it('throws when limit is not a positive integer', () => {\n\n            expect(() => {\n\n                Joi.string().min(-1);\n            }).to.throw('limit must be a positive integer or reference');\n        });\n\n        it('enforces a limit using byte count', () => {\n\n            const schema = Joi.string().min(2, 'utf8');\n            Helper.validate(schema, [\n                ['\\u00bd', true],\n                ['a', false, {\n                    message: '\"value\" length must be at least 2 characters long',\n                    path: [],\n                    type: 'string.min',\n                    context: {\n                        limit: 2,\n                        value: 'a',\n                        encoding: 'utf8',\n                        label: 'value'\n                    }\n                }]\n            ]);\n        });\n\n        it('allows empty string when min explicitly set to zero', () => {\n\n            Helper.validate(Joi.string().min(0), [\n                [undefined, true],\n                ['', true]\n            ]);\n        });\n\n        it('accepts references as min length', () => {\n\n            const ref = Joi.ref('a');\n            const schema = Joi.object({ a: Joi.number(), b: Joi.string().min(ref, 'utf8') });\n            Helper.validate(schema, [\n                [{ a: 2, b: '\\u00bd' }, true],\n                [{ a: 2, b: 'a' }, false, {\n                    message: '\"b\" length must be at least ref:a characters long',\n                    path: ['b'],\n                    type: 'string.min',\n                    context: {\n                        limit: ref,\n                        value: 'a',\n                        encoding: 'utf8',\n                        label: 'b',\n                        key: 'b'\n                    }\n                }]\n            ]);\n        });\n\n        it('accepts references as min length within a when', () => {\n\n            const schema = Joi.object({\n                a: Joi.string().required(),\n                b: Joi.number().required(),\n                c: Joi.number().required().when('a', {\n                    is: Joi.string().min(Joi.ref('b')), // a.length >= b\n                    then: Joi.number().valid(0)\n                })\n            });\n\n            Helper.validate(schema, [\n                [{ a: 'abc', b: 4, c: 42 }, true],\n                [{ a: 'abc', b: 3, c: 0 }, true],\n                [{ a: 'abc', b: 3, c: 42 }, false, {\n                    message: '\"c\" must be [0]',\n                    path: ['c'],\n                    type: 'any.only',\n                    context: { value: 42, valids: [0], label: 'c', key: 'c' }\n                }]\n            ]);\n        });\n\n        it('accepts context references as min length', () => {\n\n            const ref = Joi.ref('$a');\n            const schema = Joi.object({ b: Joi.string().min(ref, 'utf8') });\n            Helper.validate(schema, { context: { a: 2 } }, [\n                [{ b: '\\u00bd' }, true],\n                [{ b: 'a' }, false, {\n                    message: '\"b\" length must be at least ref:global:a characters long',\n                    path: ['b'],\n                    type: 'string.min',\n                    context: {\n                        limit: ref,\n                        value: 'a',\n                        encoding: 'utf8',\n                        label: 'b',\n                        key: 'b'\n                    }\n                }]\n            ]);\n        });\n\n        it('errors if reference is not a number', () => {\n\n            const ref = Joi.ref('a');\n            const schema = Joi.object({ a: Joi.any(), b: Joi.string().min(ref, 'utf8') });\n\n            Helper.validate(schema, [\n                [{ a: 'Hi there', b: '\\u00bd' }, false, {\n                    message: '\"b\" limit references \"ref:a\" which must be a positive integer',\n                    path: ['b'],\n                    type: 'any.ref',\n                    context: { ref, label: 'b', key: 'b', value: 'Hi there', arg: 'limit', reason: 'must be a positive integer' }\n                }]\n            ]);\n        });\n\n        it('errors if context reference is not a number', () => {\n\n            const ref = Joi.ref('$a');\n            const schema = Joi.object({ b: Joi.string().min(ref, 'utf8') });\n\n            Helper.validate(schema, { context: { a: 'Hi there' } }, [\n                [{ b: '\\u00bd' }, false, {\n                    message: '\"b\" limit references \"ref:global:a\" which must be a positive integer',\n                    path: ['b'],\n                    type: 'any.ref',\n                    context: { ref, label: 'b', key: 'b', value: 'Hi there', arg: 'limit', reason: 'must be a positive integer' }\n                }]\n            ]);\n        });\n\n        it('validates minimum length when min is used', () => {\n\n            const schema = Joi.string().min(3);\n            Helper.validate(schema, [\n                ['test', true],\n                ['0', false, {\n                    message: '\"value\" length must be at least 3 characters long',\n                    path: [],\n                    type: 'string.min',\n                    context: {\n                        limit: 3,\n                        value: '0',\n                        encoding: undefined,\n                        label: 'value'\n                    }\n                }],\n                [null, false, {\n                    message: '\"value\" must be a string',\n                    path: [],\n                    type: 'string.base',\n                    context: { value: null, label: 'value' }\n                }]\n            ]);\n        });\n\n        it('validates minimum length when min is 0', () => {\n\n            const schema = Joi.string().min(0).required();\n            Helper.validate(schema, [\n                ['0', true],\n                [null, false, {\n                    message: '\"value\" must be a string',\n                    path: [],\n                    type: 'string.base',\n                    context: { value: null, label: 'value' }\n                }],\n                [undefined, false, {\n                    message: '\"value\" is required',\n                    path: [],\n                    type: 'any.required',\n                    context: { label: 'value' }\n                }]\n            ]);\n        });\n\n        it('should return false with minimum length and a null value passed in', () => {\n\n            const schema = Joi.string().min(3);\n            Helper.validate(schema, [\n                [null, false, {\n                    message: '\"value\" must be a string',\n                    path: [],\n                    type: 'string.base',\n                    context: { value: null, label: 'value' }\n                }]\n            ]);\n        });\n\n        it('null allowed overrides min length requirement', () => {\n\n            const schema = Joi.string().min(3).allow(null);\n            Helper.validate(schema, [\n                [null, true]\n            ]);\n        });\n\n        it('validates combination of min and max', () => {\n\n            const rule = Joi.string().min(2).max(3);\n            Helper.validate(rule, [\n                ['x', false, {\n                    message: '\"value\" length must be at least 2 characters long',\n                    path: [],\n                    type: 'string.min',\n                    context: {\n                        limit: 2,\n                        value: 'x',\n                        encoding: undefined,\n                        label: 'value'\n                    }\n                }],\n                ['123', true],\n                ['1234', false, {\n                    message: '\"value\" length must be less than or equal to 3 characters long',\n                    path: [],\n                    type: 'string.max',\n                    context: {\n                        limit: 3,\n                        value: '1234',\n                        encoding: undefined,\n                        label: 'value'\n                    }\n                }],\n                ['12', true],\n                ['', false, {\n                    message: '\"value\" is not allowed to be empty',\n                    path: [],\n                    type: 'string.empty',\n                    context: { value: '', label: 'value' }\n                }],\n                [null, false, {\n                    message: '\"value\" must be a string',\n                    path: [],\n                    type: 'string.base',\n                    context: { value: null, label: 'value' }\n                }]\n            ]);\n        });\n\n        it('validates combination of min, max, and allow(\\'\\')', () => {\n\n            const rule = Joi.string().min(2).max(3).allow('');\n            Helper.validate(rule, [\n                ['x', false, {\n                    message: '\"value\" length must be at least 2 characters long',\n                    path: [],\n                    type: 'string.min',\n                    context: {\n                        limit: 2,\n                        value: 'x',\n                        encoding: undefined,\n                        label: 'value'\n                    }\n                }],\n                ['123', true],\n                ['1234', false, {\n                    message: '\"value\" length must be less than or equal to 3 characters long',\n                    path: [],\n                    type: 'string.max',\n                    context: {\n                        limit: 3,\n                        value: '1234',\n                        encoding: undefined,\n                        label: 'value'\n                    }\n                }],\n                ['12', true],\n                ['', true],\n                [null, false, {\n                    message: '\"value\" must be a string',\n                    path: [],\n                    type: 'string.base',\n                    context: { value: null, label: 'value' }\n                }]\n            ]);\n        });\n\n        it('validates combination of min, max, and required', () => {\n\n            const rule = Joi.string().min(2).max(3).required();\n            Helper.validate(rule, [\n                ['x', false, {\n                    message: '\"value\" length must be at least 2 characters long',\n                    path: [],\n                    type: 'string.min',\n                    context: {\n                        limit: 2,\n                        value: 'x',\n                        encoding: undefined,\n                        label: 'value'\n                    }\n                }],\n                ['123', true],\n                ['1234', false, {\n                    message: '\"value\" length must be less than or equal to 3 characters long',\n                    path: [],\n                    type: 'string.max',\n                    context: {\n                        limit: 3,\n                        value: '1234',\n                        encoding: undefined,\n                        label: 'value'\n                    }\n                }],\n                ['12', true],\n                ['', false, {\n                    message: '\"value\" is not allowed to be empty',\n                    path: [],\n                    type: 'string.empty',\n                    context: { value: '', label: 'value' }\n                }],\n                [null, false, {\n                    message: '\"value\" must be a string',\n                    path: [],\n                    type: 'string.base',\n                    context: { value: null, label: 'value' }\n                }]\n            ]);\n        });\n\n        it('validates combination of min, max, and regex', () => {\n\n            const rule = Joi.string().min(2).max(3).regex(/^a/);\n            Helper.validate(rule, [\n                ['x', false, {\n                    message: '\"value\" length must be at least 2 characters long',\n                    path: [],\n                    type: 'string.min',\n                    context: {\n                        limit: 2,\n                        value: 'x',\n                        encoding: undefined,\n                        label: 'value'\n                    }\n                }],\n                ['123', false, {\n                    message: '\"value\" with value \"123\" fails to match the required pattern: /^a/',\n                    path: [],\n                    type: 'string.pattern.base',\n                    context: {\n                        name: undefined,\n                        regex: /^a/,\n                        value: '123',\n                        label: 'value'\n                    }\n                }],\n                ['1234', false, {\n                    message: '\"value\" length must be less than or equal to 3 characters long',\n                    path: [],\n                    type: 'string.max',\n                    context: {\n                        limit: 3,\n                        value: '1234',\n                        encoding: undefined,\n                        label: 'value'\n                    }\n                }],\n                ['12', false, {\n                    message: '\"value\" with value \"12\" fails to match the required pattern: /^a/',\n                    path: [],\n                    type: 'string.pattern.base',\n                    context: {\n                        name: undefined,\n                        regex: /^a/,\n                        value: '12',\n                        label: 'value'\n                    }\n                }],\n                ['ab', true],\n                ['abc', true],\n                ['abcd', false, {\n                    message: '\"value\" length must be less than or equal to 3 characters long',\n                    path: [],\n                    type: 'string.max',\n                    context: {\n                        limit: 3,\n                        value: 'abcd',\n                        encoding: undefined,\n                        label: 'value'\n                    }\n                }],\n                ['', false, {\n                    message: '\"value\" is not allowed to be empty',\n                    path: [],\n                    type: 'string.empty',\n                    context: { value: '', label: 'value' }\n                }],\n                [null, false, {\n                    message: '\"value\" must be a string',\n                    path: [],\n                    type: 'string.base',\n                    context: { value: null, label: 'value' }\n                }]\n            ]);\n        });\n\n        it('validates combination of min, max, regex, and allow(\\'\\')', () => {\n\n            const rule = Joi.string().min(2).max(3).regex(/^a/).allow('');\n            Helper.validate(rule, [\n                ['x', false, {\n                    message: '\"value\" length must be at least 2 characters long',\n                    path: [],\n                    type: 'string.min',\n                    context: {\n                        limit: 2,\n                        value: 'x',\n                        encoding: undefined,\n                        label: 'value'\n                    }\n                }],\n                ['123', false, {\n                    message: '\"value\" with value \"123\" fails to match the required pattern: /^a/',\n                    path: [],\n                    type: 'string.pattern.base',\n                    context: {\n                        name: undefined,\n                        regex: /^a/,\n                        value: '123',\n                        label: 'value'\n                    }\n                }],\n                ['1234', false, {\n                    message: '\"value\" length must be less than or equal to 3 characters long',\n                    path: [],\n                    type: 'string.max',\n                    context: {\n                        limit: 3,\n                        value: '1234',\n                        encoding: undefined,\n                        label: 'value'\n                    }\n                }],\n                ['12', false, {\n                    message: '\"value\" with value \"12\" fails to match the required pattern: /^a/',\n                    path: [],\n                    type: 'string.pattern.base',\n                    context: {\n                        name: undefined,\n                        regex: /^a/,\n                        value: '12',\n                        label: 'value'\n                    }\n                }],\n                ['ab', true],\n                ['abc', true],\n                ['abcd', false, {\n                    message: '\"value\" length must be less than or equal to 3 characters long',\n                    path: [],\n                    type: 'string.max',\n                    context: {\n                        limit: 3,\n                        value: 'abcd',\n                        encoding: undefined,\n                        label: 'value'\n                    }\n                }],\n                ['', true],\n                [null, false, {\n                    message: '\"value\" must be a string',\n                    path: [],\n                    type: 'string.base',\n                    context: { value: null, label: 'value' }\n                }]\n            ]);\n        });\n\n        it('validates combination of min, max, regex, and required', () => {\n\n            const rule = Joi.string().min(2).max(3).regex(/^a/).required();\n            Helper.validate(rule, [\n                ['x', false, {\n                    message: '\"value\" length must be at least 2 characters long',\n                    path: [],\n                    type: 'string.min',\n                    context: {\n                        limit: 2,\n                        value: 'x',\n                        encoding: undefined,\n                        label: 'value'\n                    }\n                }],\n                ['123', false, {\n                    message: '\"value\" with value \"123\" fails to match the required pattern: /^a/',\n                    path: [],\n                    type: 'string.pattern.base',\n                    context: {\n                        name: undefined,\n                        regex: /^a/,\n                        value: '123',\n                        label: 'value'\n                    }\n                }],\n                ['1234', false, {\n                    message: '\"value\" length must be less than or equal to 3 characters long',\n                    path: [],\n                    type: 'string.max',\n                    context: {\n                        limit: 3,\n                        value: '1234',\n                        encoding: undefined,\n                        label: 'value'\n                    }\n                }],\n                ['12', false, {\n                    message: '\"value\" with value \"12\" fails to match the required pattern: /^a/',\n                    path: [],\n                    type: 'string.pattern.base',\n                    context: {\n                        name: undefined,\n                        regex: /^a/,\n                        value: '12',\n                        label: 'value'\n                    }\n                }],\n                ['ab', true],\n                ['abc', true],\n                ['abcd', false, {\n                    message: '\"value\" length must be less than or equal to 3 characters long',\n                    path: [],\n                    type: 'string.max',\n                    context: {\n                        limit: 3,\n                        value: 'abcd',\n                        encoding: undefined,\n                        label: 'value'\n                    }\n                }],\n                ['', false, {\n                    message: '\"value\" is not allowed to be empty',\n                    path: [],\n                    type: 'string.empty',\n                    context: { value: '', label: 'value' }\n                }],\n                [null, false, {\n                    message: '\"value\" must be a string',\n                    path: [],\n                    type: 'string.base',\n                    context: { value: null, label: 'value' }\n                }]\n            ]);\n        });\n    });\n\n    describe('normalize()', () => {\n\n        // The characters chosen for the \"original\" string below are such that\n        // it and its four normalization forms are all different from each other\n        // See: http://www.unicode.org/faq/normalization.html#6\n        // and: http://www.unicode.org/reports/tr15/#Singletons_Figure\n\n        const normalizations = {\n            original: '\\u03D3 \\u212B',   // 'ϓ Å'\n            NFC: '\\u03D3 \\u00C5',        // 'ϓ Å'\n            NFD: '\\u03D2\\u0301 A\\u030A', // 'ϓ Å'\n            NFKC: '\\u038E \\u00C5',       // 'Ύ Å'\n            NFKD: '\\u03A5\\u0301 A\\u030A' // 'Ύ Å'\n        };\n\n        it('throws when normalization form is invalid', () => {\n\n            expect(() => {\n\n                Joi.string().normalize('NFCD');\n            }).to.throw('normalization form must be one of NFC, NFD, NFKC, NFKD');\n        });\n\n        it('only allow strings that are in NFC form', () => {\n\n            const schema = Joi.string().normalize('NFC');\n\n            Helper.validate(schema, { convert: false }, [\n                [normalizations.original, false, {\n                    message: '\"value\" must be unicode normalized in the NFC form',\n                    path: [],\n                    type: 'string.normalize',\n                    context: {\n                        form: 'NFC',\n                        value: normalizations.original,\n                        label: 'value'\n                    }\n                }],\n                [normalizations.NFC, true]\n            ]);\n        });\n\n        it('only allow strings that are in NFD form', () => {\n\n            const schema = Joi.string().normalize('NFD');\n\n            Helper.validate(schema, { convert: false }, [\n                [normalizations.original, false, {\n                    message: '\"value\" must be unicode normalized in the NFD form',\n                    path: [],\n                    type: 'string.normalize',\n                    context: {\n                        form: 'NFD',\n                        value: normalizations.original,\n                        label: 'value'\n                    }\n                }],\n                [normalizations.NFD, true]\n            ]);\n        });\n\n        it('only allow strings that are in NFKC form', () => {\n\n            const schema = Joi.string().normalize('NFKC');\n\n            Helper.validate(schema, { convert: false }, [\n                [normalizations.original, false, {\n                    message: '\"value\" must be unicode normalized in the NFKC form',\n                    path: [],\n                    type: 'string.normalize',\n                    context: {\n                        form: 'NFKC',\n                        value: normalizations.original,\n                        label: 'value'\n                    }\n                }],\n                [normalizations.NFKC, true]\n            ]);\n        });\n\n        it('only allow strings that are in NFKD form', () => {\n\n            const schema = Joi.string().normalize('NFKD');\n\n            Helper.validate(schema, { convert: false }, [\n                [normalizations.original, false, {\n                    message: '\"value\" must be unicode normalized in the NFKD form',\n                    path: [],\n                    type: 'string.normalize',\n                    context: {\n                        form: 'NFKD',\n                        value: normalizations.original,\n                        label: 'value'\n                    }\n                }],\n                [normalizations.NFKD, true]\n            ]);\n        });\n\n        it('normalizes string using NFC before validation', () => {\n\n            Helper.validate(Joi.string().normalize('NFC'), [[normalizations.original, true, normalizations.NFC]]);\n        });\n\n        it('normalizes string using NFD before validation', () => {\n\n            Helper.validate(Joi.string().normalize('NFD'), [[normalizations.original, true, normalizations.NFD]]);\n        });\n\n        it('normalizes string using NFKC before validation', () => {\n\n            Helper.validate(Joi.string().normalize('NFKC'), [[normalizations.original, true, normalizations.NFKC]]);\n        });\n\n        it('normalizes string using NFKD before validation', () => {\n\n            Helper.validate(Joi.string().normalize('NFKD'), [[normalizations.original, true, normalizations.NFKD]]);\n        });\n\n        it('should default to NFC form', () => {\n\n            Helper.validate(Joi.string().normalize(), [[normalizations.original, true, normalizations.NFC]]);\n        });\n\n        // The below tests use the composed and decomposed form\n        // of the 'ñ' character\n\n        it('should work in combination with min', () => {\n\n            const baseSchema = Joi.string().min(2);\n            Helper.validate(baseSchema.normalize('NFD'), [['\\u00F1', true, 'n\\u0303']]);\n\n            Helper.validate(baseSchema.normalize('NFC'), [['n\\u0303', false, {\n                message: '\"value\" length must be at least 2 characters long',\n                path: [],\n                type: 'string.min',\n                context: {\n                    limit: 2,\n                    value: '\\u00F1',\n                    encoding: undefined,\n                    label: 'value'\n                }\n            }]]);\n        });\n\n        it('should work in combination with max', () => {\n\n            const baseSchema = Joi.string().max(1);\n            Helper.validate(baseSchema.normalize('NFC'), [['n\\u0303', true, '\\u00F1']]);\n\n            Helper.validate(baseSchema.normalize('NFD'), [['\\u00F1', false, {\n                message: '\"value\" length must be less than or equal to 1 characters long',\n                path: [],\n                type: 'string.max',\n                context: {\n                    limit: 1,\n                    value: 'n\\u0303',\n                    encoding: undefined,\n                    label: 'value'\n                }\n            }]]);\n        });\n\n        it('composition should work in combination with length', () => {\n\n            const schema = Joi.string().length(2).normalize('NFC');\n\n            Helper.validate(schema, [\n                ['\\u00F1', false, {\n                    message: '\"value\" length must be 2 characters long',\n                    path: [],\n                    type: 'string.length',\n                    context: {\n                        limit: 2,\n                        value: '\\u00F1',\n                        encoding: undefined,\n                        label: 'value'\n                    }\n                }],\n                ['n\\u0303', false, {\n                    message: '\"value\" length must be 2 characters long',\n                    path: [],\n                    type: 'string.length',\n                    context: {\n                        limit: 2,\n                        value: '\\u00F1',\n                        encoding: undefined,\n                        label: 'value'\n                    }\n                }],\n                ['\\u00F1\\u00F1', true, '\\u00F1\\u00F1'.normalize('NFC')],\n                ['\\u00F1n\\u0303', true, '\\u00F1n\\u0303'.normalize('NFC')],\n                ['n\\u0303n\\u0303', true, 'n\\u0303n\\u0303'.normalize('NFC')]\n            ]);\n        });\n\n        it('decomposition should work in combination with length', () => {\n\n            const schema = Joi.string().length(2).normalize('NFD');\n\n            Helper.validate(schema, [\n                ['\\u00F1\\u00F1', false, {\n                    message: '\"value\" length must be 2 characters long',\n                    path: [],\n                    type: 'string.length',\n                    context: {\n                        limit: 2,\n                        value: 'n\\u0303n\\u0303',\n                        encoding: undefined,\n                        label: 'value'\n                    }\n                }],\n                ['\\u00F1n\\u0303', false, {\n                    message: '\"value\" length must be 2 characters long',\n                    path: [],\n                    type: 'string.length',\n                    context: {\n                        limit: 2,\n                        value: 'n\\u0303n\\u0303',\n                        encoding: undefined,\n                        label: 'value'\n                    }\n                }],\n                ['n\\u0303n\\u0303', false, {\n                    message: '\"value\" length must be 2 characters long',\n                    path: [],\n                    type: 'string.length',\n                    context: {\n                        limit: 2,\n                        value: 'n\\u0303n\\u0303',\n                        encoding: undefined,\n                        label: 'value'\n                    }\n                }],\n                ['\\u00F1', true, '\\u00F1'.normalize('NFD')],\n                ['n\\u0303', true, 'n\\u0303'.normalize('NFD')]\n            ]);\n        });\n\n        it('should work in combination with lowercase', () => {\n\n            const baseSchema = Joi.string().lowercase();\n            Helper.validate(baseSchema.normalize('NFC'), [['N\\u0303', true, '\\u00F1']]);\n            Helper.validate(baseSchema.normalize('NFD'), [['\\u00D1', true, 'n\\u0303']]);\n        });\n\n        it('should work in combination with uppercase', () => {\n\n            const baseSchema = Joi.string().uppercase();\n            Helper.validate(baseSchema.normalize('NFC'), [['n\\u0303', true, '\\u00D1']]);\n            Helper.validate(baseSchema.normalize('NFD'), [['\\u00F1', true, 'N\\u0303']]);\n        });\n    });\n\n    describe('regex()', () => {\n\n        it('validates regex', () => {\n\n            const schema = Joi.string().regex(/^[0-9][-][a-z]+$/);\n            Helper.validate(schema, [\n                ['van', false, {\n                    message: '\"value\" with value \"van\" fails to match the required pattern: /^[0-9][-][a-z]+$/',\n                    path: [],\n                    type: 'string.pattern.base',\n                    context: {\n                        name: undefined,\n                        regex: /^[0-9][-][a-z]+$/,\n                        value: 'van',\n                        label: 'value'\n                    }\n                }],\n                ['0-www', true]\n            ]);\n        });\n\n        it('rejects regex with global or sticky flag', () => {\n\n            expect(() => Joi.string().regex(/a/g)).to.throw('regex should not use global or sticky mode');\n            expect(() => Joi.string().regex(/a/y)).to.throw('regex should not use global or sticky mode');\n        });\n\n        it('should not include a pattern name by default', () => {\n\n            const schema = Joi.string().regex(/[a-z]+/).regex(/[0-9]+/);\n            Helper.validate(schema, [['abcd', false, {\n                message: '\"value\" with value \"abcd\" fails to match the required pattern: /[0-9]+/',\n                path: [],\n                type: 'string.pattern.base',\n                context: {\n                    name: undefined,\n                    regex: /[0-9]+/,\n                    value: 'abcd',\n                    label: 'value'\n                }\n            }]]);\n        });\n\n        it('should include a pattern name if specified', () => {\n\n            const schema = Joi.string().regex(/[a-z]+/, 'letters').regex(/[0-9]+/, 'numbers');\n            Helper.validate(schema, [['abcd', false, {\n                message: '\"value\" with value \"abcd\" fails to match the numbers pattern',\n                path: [],\n                type: 'string.pattern.name',\n                context: {\n                    name: 'numbers',\n                    regex: /[0-9]+/,\n                    value: 'abcd',\n                    label: 'value'\n                }\n            }]]);\n        });\n\n        it('should include a pattern name in options object', () => {\n\n            const schema = Joi.string().regex(/[a-z]+/, { name: 'letters' }).regex(/[0-9]+/, { name: 'numbers' });\n            Helper.validate(schema, [['abcd', false, {\n                message: '\"value\" with value \"abcd\" fails to match the numbers pattern',\n                path: [],\n                type: 'string.pattern.name',\n                context: {\n                    name: 'numbers',\n                    regex: /[0-9]+/,\n                    value: 'abcd',\n                    label: 'value'\n                }\n            }]]);\n        });\n\n        it('should \"invert\" regex pattern if specified in options object', () => {\n\n            const schema = Joi.string().regex(/[a-z]/, { invert: true });\n            Helper.validate(schema, [\n                ['0123456789', true],\n                ['abcdefg', false, {\n                    message: '\"value\" with value \"abcdefg\" matches the inverted pattern: /[a-z]/',\n                    path: [],\n                    type: 'string.pattern.invert.base',\n                    context: {\n                        name: undefined,\n                        regex: /[a-z]/,\n                        value: 'abcdefg',\n                        label: 'value'\n                    }\n                }]\n            ]);\n        });\n\n        it('should include inverted pattern name if specified', () => {\n\n            const schema = Joi.string().regex(/[a-z]/, {\n                name: 'lowercase',\n                invert: true\n            });\n            Helper.validate(schema, [\n                ['0123456789', true],\n                ['abcdefg', false, {\n                    message: '\"value\" with value \"abcdefg\" matches the inverted lowercase pattern',\n                    path: [],\n                    type: 'string.pattern.invert.name',\n                    context: {\n                        name: 'lowercase',\n                        regex: /[a-z]/,\n                        value: 'abcdefg',\n                        label: 'value'\n                    }\n                }]\n            ]);\n        });\n    });\n\n    describe('replace()', () => {\n\n        it('successfully replaces the first occurrence of the expression', () => {\n\n            const schema = Joi.string().replace(/\\s+/, ''); // no \"g\" flag\n            Helper.validate(schema, { convert: true }, [\n                ['\\tsomething', true, 'something'],\n                ['something\\r', true, 'something'],\n                ['something  ', true, 'something'],\n                ['some  thing', true, 'something'],\n                ['so me thing', true, 'some thing'] // first occurrence!\n            ]);\n        });\n\n        it('successfully replaces all occurrences of the expression', () => {\n\n            const schema = Joi.string().replace(/\\s+/g, ''); // has \"g\" flag\n            Helper.validate(schema, { convert: true }, [\n                ['\\tsomething', true, 'something'],\n                ['something\\r', true, 'something'],\n                ['something  ', true, 'something'],\n                ['some  thing', true, 'something'],\n                ['so me thing', true, 'something']\n            ]);\n        });\n\n        it('successfully replaces all occurrences of a string pattern', () => {\n\n            const schema = Joi.string().replace('foo', 'X'); // has \"g\" flag\n            Helper.validate(schema, { convert: true }, [\n                ['foobarfoobazfoo', true, 'XbarXbazX']\n            ]);\n        });\n\n        it('successfully replaces multiple times', () => {\n\n            const schema = Joi.string().replace(/a/g, 'b').replace(/b/g, 'c');\n            Helper.validate(schema, [['a quick brown fox', true, 'c quick crown fox']]);\n        });\n\n        it('should work in combination with trim', () => {\n\n            // The string below is the name \"Yamada Tarou\" separated by a\n            // carriage return, a \"full width\" ideographic space and a newline\n\n            const schema = Joi.string().trim().replace(/\\s+/g, ' ');\n            Helper.validate(schema, [[' \\u5C71\\u7530\\r\\u3000\\n\\u592A\\u90CE ', true, '\\u5C71\\u7530 \\u592A\\u90CE']]);\n        });\n\n        it('should work in combination with min', () => {\n\n            const schema = Joi.string().min(4).replace(/\\s+/g, ' ');\n            Helper.validate(schema, [\n                ['   a   ', false, {\n                    message: '\"value\" length must be at least 4 characters long',\n                    path: [],\n                    type: 'string.min',\n                    context: {\n                        limit: 4,\n                        value: ' a ',\n                        encoding: undefined,\n                        label: 'value'\n                    }\n                }],\n                ['abc    ', true, 'abc '],\n                ['a\\t\\rbc', true, 'a bc']\n            ]);\n        });\n\n        it('should work in combination with max', () => {\n\n            const schema = Joi.string().max(5).replace(/ CHANGE ME /g, '-b-');\n            Helper.validate(schema, [\n                ['a CHANGE ME c', true, 'a-b-c'],\n                ['a-b-c', true, 'a-b-c'] // nothing changes here!\n            ]);\n        });\n\n        it('should work in combination with length', () => {\n\n            const schema = Joi.string().length(5).replace(/\\s+/g, ' ');\n            Helper.validate(schema, [\n                ['a    bc', false, {\n                    message: '\"value\" length must be 5 characters long',\n                    path: [],\n                    type: 'string.length',\n                    context: {\n                        limit: 5,\n                        value: 'a bc',\n                        encoding: undefined,\n                        label: 'value'\n                    }\n                }],\n                ['a\\tb\\nc', true, 'a b c']\n            ]);\n        });\n\n    });\n\n    describe('required()', () => {\n\n        it('denies undefined, deny empty string', () => {\n\n            Helper.validate(Joi.string().required(), [\n                [undefined, false, {\n                    message: '\"value\" is required',\n                    path: [],\n                    type: 'any.required',\n                    context: { label: 'value' }\n                }],\n                ['', false, {\n                    message: '\"value\" is not allowed to be empty',\n                    path: [],\n                    type: 'string.empty',\n                    context: { value: '', label: 'value' }\n                }]\n            ]);\n        });\n\n        it('prints a friend error message for an empty string', () => {\n\n            const schema = Joi.string().required();\n            Helper.validate(Joi.compile(schema), [['', false, {\n                message: '\"value\" is not allowed to be empty',\n                path: [],\n                type: 'string.empty',\n                context: { value: '', label: 'value' }\n            }]]);\n        });\n\n        it('prints a friendly error message for trimmed whitespace', () => {\n\n            const schema = Joi.string().trim().required();\n\n            Helper.validate(Joi.compile(schema), [['    ', false, {\n                message: '\"value\" is not allowed to be empty',\n                path: [],\n                type: 'string.empty',\n                context: { value: '', label: 'value' }\n            }]]);\n        });\n\n        it('validates non-empty strings', () => {\n\n            const schema = Joi.string().required();\n            Helper.validate(schema, [\n                ['test', true],\n                ['0', true],\n                [null, false, {\n                    message: '\"value\" must be a string',\n                    path: [],\n                    type: 'string.base',\n                    context: { value: null, label: 'value' }\n                }]\n            ]);\n        });\n    });\n\n    describe('token()', () => {\n\n        it('validates token', () => {\n\n            const schema = Joi.string().token();\n            Helper.validate(schema, [\n                ['w0rld_of_w4lm4rtl4bs', true],\n                ['w0rld of_w4lm4rtl4bs', false, {\n                    message: '\"value\" must only contain alpha-numeric and underscore characters',\n                    path: [],\n                    type: 'string.token',\n                    context: { value: 'w0rld of_w4lm4rtl4bs', label: 'value' }\n                }],\n                ['abcd#f?h1j orly?', false, {\n                    message: '\"value\" must only contain alpha-numeric and underscore characters',\n                    path: [],\n                    type: 'string.token',\n                    context: { value: 'abcd#f?h1j orly?', label: 'value' }\n                }]\n            ]);\n        });\n    });\n\n    describe('trim()', () => {\n\n        it('only allow strings that have no leading or trailing whitespace', () => {\n\n            const schema = Joi.string().trim();\n            Helper.validate(schema, { convert: false }, [\n                [' something', false, {\n                    message: '\"value\" must not have leading or trailing whitespace',\n                    path: [],\n                    type: 'string.trim',\n                    context: { value: ' something', label: 'value' }\n                }],\n                ['something ', false, {\n                    message: '\"value\" must not have leading or trailing whitespace',\n                    path: [],\n                    type: 'string.trim',\n                    context: { value: 'something ', label: 'value' }\n                }],\n                ['something\\n', false, {\n                    message: '\"value\" must not have leading or trailing whitespace',\n                    path: [],\n                    type: 'string.trim',\n                    context: { value: 'something\\n', label: 'value' }\n                }],\n                ['some thing', true],\n                ['something', true]\n            ]);\n        });\n\n        it('disable existing trim flag when passing enabled: false', () => {\n\n            const trimEnabledSchema = Joi.string().trim(true);\n            Helper.validate(trimEnabledSchema, { convert: false }, [\n                [' something', false, {\n                    message: '\"value\" must not have leading or trailing whitespace',\n                    path: [],\n                    type: 'string.trim',\n                    context: { value: ' something', label: 'value' }\n                }]\n            ]);\n\n            const trimDisabledSchema = trimEnabledSchema.trim(false);\n            Helper.validate(trimDisabledSchema, { convert: false }, [\n                [' something', true]\n            ]);\n\n            Helper.validate(trimDisabledSchema, [\n                [' something', true]\n            ]);\n        });\n\n        it('removes leading and trailing whitespace before validation', () => {\n\n            const schema = Joi.string().trim();\n            Helper.validate(schema, [[' trim this ', true, 'trim this']]);\n        });\n\n        it('removes leading and trailing whitespace before validation', () => {\n\n            const schema = Joi.string().trim().allow('');\n            Helper.validate(schema, [['     ', true, '']]);\n        });\n\n        it('should work in combination with min', () => {\n\n            const schema = Joi.string().min(4).trim();\n            Helper.validate(schema, [\n                [' a ', false, {\n                    message: '\"value\" length must be at least 4 characters long',\n                    path: [],\n                    type: 'string.min',\n                    context: {\n                        limit: 4,\n                        value: 'a',\n                        encoding: undefined,\n                        label: 'value'\n                    }\n                }],\n                ['abc ', false, {\n                    message: '\"value\" length must be at least 4 characters long',\n                    path: [],\n                    type: 'string.min',\n                    context: {\n                        limit: 4,\n                        value: 'abc',\n                        encoding: undefined,\n                        label: 'value'\n                    }\n                }],\n                ['abcd ', true, 'abcd']\n            ]);\n        });\n\n        it('should work in combination with max', () => {\n\n            const schema = Joi.string().max(4).trim();\n            Helper.validate(schema, [\n                [' abcde ', false, {\n                    message: '\"value\" length must be less than or equal to 4 characters long',\n                    path: [],\n                    type: 'string.max',\n                    context: {\n                        limit: 4,\n                        value: 'abcde',\n                        encoding: undefined,\n                        label: 'value'\n                    }\n                }],\n                ['abc ', true, 'abc'],\n                ['abcd ', true, 'abcd']\n            ]);\n        });\n\n        it('should work in combination with length', () => {\n\n            const schema = Joi.string().length(4).trim();\n            Helper.validate(schema, [\n                [' ab ', false, {\n                    message: '\"value\" length must be 4 characters long',\n                    path: [],\n                    type: 'string.length',\n                    context: {\n                        limit: 4,\n                        value: 'ab',\n                        encoding: undefined,\n                        label: 'value'\n                    }\n                }],\n                ['abc ', false, {\n                    message: '\"value\" length must be 4 characters long',\n                    path: [],\n                    type: 'string.length',\n                    context: {\n                        limit: 4,\n                        value: 'abc',\n                        encoding: undefined,\n                        label: 'value'\n                    }\n                }],\n                ['abcd ', true, 'abcd']\n            ]);\n        });\n\n        it('should work in combination with a case change', () => {\n\n            const schema = Joi.string().trim().lowercase();\n            Helper.validate(schema, [\n                [' abc', true, 'abc'],\n                [' ABC', true, 'abc'],\n                ['ABC', true, 'abc']\n            ]);\n        });\n\n        it('throws when option is not a boolean', () => {\n\n            expect(() => {\n\n                Joi.string().trim(42);\n            }).to.throw('enabled must be a boolean');\n        });\n    });\n\n    describe('truncate()', () => {\n\n        it('avoids unnecessary cloning when called twice', () => {\n\n            const schema = Joi.string().truncate();\n            expect(schema.truncate()).to.shallow.equal(schema);\n        });\n\n        it('switches the truncate flag', () => {\n\n            const schema = Joi.string().truncate();\n            const desc = schema.describe();\n            expect(desc).to.equal({\n                type: 'string',\n                flags: { truncate: true }\n            });\n        });\n\n        it('switches the truncate flag with explicit value', () => {\n\n            const schema = Joi.string().truncate(true);\n            const desc = schema.describe();\n            expect(desc).to.equal({\n                type: 'string',\n                flags: { truncate: true }\n            });\n        });\n\n        it('switches the truncate flag back', () => {\n\n            const schema = Joi.string().truncate().truncate(false);\n            const desc = schema.describe();\n            expect(desc).to.equal({\n                type: 'string'\n            });\n        });\n\n        it('does not change anything when used without max', () => {\n\n            const schema = Joi.string().min(2).truncate();\n            Helper.validate(schema, [['fooooooooooooooooooo', true, 'fooooooooooooooooooo']]);\n        });\n\n        it('truncates a string when used with max', () => {\n\n            const schema = Joi.string().max(5).truncate();\n\n            Helper.validate(schema, [\n                ['abc', true, 'abc'],\n                ['abcde', true, 'abcde'],\n                ['abcdef', true, 'abcde']\n            ]);\n        });\n\n        it('truncates a string after transformations', () => {\n\n            const schema = Joi.string().max(5).truncate().trim().replace(/a/g, 'aa');\n\n            Helper.validate(schema, [\n                ['abc', true, 'aabc'],\n                ['abcde', true, 'aabcd'],\n                ['abcdef', true, 'aabcd'],\n                ['  abcdef  ', true, 'aabcd']\n            ]);\n        });\n\n        it('truncates a string (ref)', () => {\n\n            const ref = Joi.ref('b');\n            const schema = Joi.object({\n                a: Joi.string().max(ref).truncate(),\n                b: Joi.number()\n            });\n\n            Helper.validate(schema, [\n                [{ a: 'abc', b: 4 }, true, { a: 'abc', b: 4 }],\n                [{ a: 'abcde', b: 2 }, true, { a: 'ab', b: 2 }],\n                [{ a: 'abcdef', b: 5 }, true, { a: 'abcde', b: 5 }],\n                [{ a: 'abc' }, false, {\n                    message: '\"a\" limit references \"ref:b\" which must be a positive integer',\n                    path: ['a'],\n                    type: 'any.ref',\n                    context: { key: 'a', label: 'a', ref, arg: 'limit', reason: 'must be a positive integer' }\n                }]\n            ]);\n        });\n    });\n\n    describe('uppercase()', () => {\n\n        it('only allow strings that are entirely uppercase', () => {\n\n            const schema = Joi.string().uppercase();\n            Helper.validate(schema, { convert: false }, [\n                ['THIS IS ALL UPPERCASE', true],\n                ['5', true],\n                ['UPPER\\nCASE', true],\n                ['lOWERCASE', false, {\n                    message: '\"value\" must only contain uppercase characters',\n                    path: [],\n                    type: 'string.uppercase',\n                    context: { value: 'lOWERCASE', label: 'value' }\n                }],\n                ['MixEd cAsE', false, {\n                    message: '\"value\" must only contain uppercase characters',\n                    path: [],\n                    type: 'string.uppercase',\n                    context: { value: 'MixEd cAsE', label: 'value' }\n                }],\n                [1, false, {\n                    message: '\"value\" must be a string',\n                    path: [],\n                    type: 'string.base',\n                    context: { value: 1, label: 'value' }\n                }]\n            ]);\n        });\n\n        it('coerce string to uppercase before validation', () => {\n\n            const schema = Joi.string().uppercase();\n            Helper.validate(schema, [['lower to upper', true, 'LOWER TO UPPER']]);\n        });\n\n        it('works in combination with a forced trim', () => {\n\n            const schema = Joi.string().uppercase().trim();\n            Helper.validate(schema, [\n                [' abc', true, 'ABC'],\n                [' ABC', true, 'ABC'],\n                ['ABC', true],\n                [1, false, {\n                    message: '\"value\" must be a string',\n                    path: [],\n                    type: 'string.base',\n                    context: { value: 1, label: 'value' }\n                }]\n            ]);\n        });\n\n        it('works in combination with a forced replacement', () => {\n\n            const schema = Joi.string().uppercase().replace(/\\s+/g, ' ');\n            Helper.validate(schema, [\n                ['a\\r b\\n c', true, 'A B C'],\n                ['A\\t B  C', true, 'A B C'],\n                ['ABC', true, 'ABC'],\n                [1, false, {\n                    message: '\"value\" must be a string',\n                    path: [],\n                    type: 'string.base',\n                    context: { value: 1, label: 'value' }\n                }]\n            ]);\n        });\n\n        it('validates combination of uppercase, min, max, alphanum and valid', () => {\n\n            const rule = Joi.string().uppercase().min(2).max(3).alphanum().valid('AB', 'BC');\n            Helper.validate(rule, [\n                ['x', false, {\n                    message: '\"value\" must be one of [AB, BC]',\n                    path: [],\n                    type: 'any.only',\n                    context: { value: 'X', valids: ['AB', 'BC'], label: 'value' }\n                }],\n                ['123', false, {\n                    message: '\"value\" must be one of [AB, BC]',\n                    path: [],\n                    type: 'any.only',\n                    context: { value: '123', valids: ['AB', 'BC'], label: 'value' }\n                }],\n                ['1234', false, {\n                    message: '\"value\" must be one of [AB, BC]',\n                    path: [],\n                    type: 'any.only',\n                    context: { value: '1234', valids: ['AB', 'BC'], label: 'value' }\n                }],\n                ['12', false, {\n                    message: '\"value\" must be one of [AB, BC]',\n                    path: [],\n                    type: 'any.only',\n                    context: { value: '12', valids: ['AB', 'BC'], label: 'value' }\n                }],\n                ['ab', true, 'AB'],\n                ['abc', false, {\n                    message: '\"value\" must be one of [AB, BC]',\n                    path: [],\n                    type: 'any.only',\n                    context: { value: 'ABC', valids: ['AB', 'BC'], label: 'value' }\n                }],\n                ['a2c', false, {\n                    message: '\"value\" must be one of [AB, BC]',\n                    path: [],\n                    type: 'any.only',\n                    context: { value: 'A2C', valids: ['AB', 'BC'], label: 'value' }\n                }],\n                ['abcd', false, {\n                    message: '\"value\" must be one of [AB, BC]',\n                    path: [],\n                    type: 'any.only',\n                    context: { value: 'ABCD', valids: ['AB', 'BC'], label: 'value' }\n                }],\n                ['*ab', false, {\n                    message: '\"value\" must be one of [AB, BC]',\n                    path: [],\n                    type: 'any.only',\n                    context: { value: '*AB', valids: ['AB', 'BC'], label: 'value' }\n                }],\n                ['', false, {\n                    message: '\"value\" must be one of [AB, BC]',\n                    path: [],\n                    type: 'any.only',\n                    context: { value: '', valids: ['AB', 'BC'], label: 'value' }\n                }],\n                ['bc', true, 'BC'],\n                ['BC', true],\n                ['de', false, {\n                    message: '\"value\" must be one of [AB, BC]',\n                    path: [],\n                    type: 'any.only',\n                    context: { value: 'DE', valids: ['AB', 'BC'], label: 'value' }\n                }],\n                ['ABc', false, {\n                    message: '\"value\" must be one of [AB, BC]',\n                    path: [],\n                    type: 'any.only',\n                    context: { value: 'ABC', valids: ['AB', 'BC'], label: 'value' }\n                }],\n                ['AB', true],\n                [null, false, {\n                    message: '\"value\" must be one of [AB, BC]',\n                    path: [],\n                    type: 'any.only',\n                    context: { value: null, valids: ['AB', 'BC'], label: 'value' }\n                }]\n            ]);\n        });\n    });\n\n    describe('uri()', () => {\n\n        it('validates uri', () => {\n\n            // Handful of tests taken from Node: https://github.com/joyent/node/blob/cfcb1de130867197cbc9c6012b7e84e08e53d032/test/simple/test-url.js\n            // Also includes examples from RFC 3986: http://tools.ietf.org/html/rfc3986#page-7\n\n            const schema = Joi.string().uri();\n\n            Helper.validate(schema, [\n                ['foo://example.com:8042/over/there?name=ferret#nose', true],\n                ['https://example.com?abc[]=123&abc[]=456', false, {\n                    message: '\"value\" must be a valid uri',\n                    path: [],\n                    type: 'string.uri',\n                    context: { value: 'https://example.com?abc[]=123&abc[]=456', label: 'value' }\n                }],\n                ['urn:example:animal:ferret:nose', true],\n                ['ftp://ftp.is.co.za/rfc/rfc1808.txt', true],\n                ['http://www.ietf.org/rfc/rfc2396.txt', true],\n                ['ldap://[2001:db8::7]/c=GB?objectClass?one', true],\n                ['ldap://2001:db8::7/c=GB?objectClass?one', false, {\n                    message: '\"value\" must be a valid uri',\n                    path: [],\n                    type: 'string.uri',\n                    context: { value: 'ldap://2001:db8::7/c=GB?objectClass?one', label: 'value' }\n                }],\n                ['mailto:John.Doe@example.com', true],\n                ['news:comp.infosystems.www.servers.unix', true],\n                ['tel:+1-816-555-1212', true],\n                ['telnet://192.0.2.16:80/', true],\n                ['urn:oasis:names:specification:docbook:dtd:xml:4.1.2', true],\n                ['file:///example.txt', true],\n                ['http://asdf:qw%20er@localhost:8000?asdf=12345&asda=fc%2F#bacon', true],\n                ['http://asdf@localhost:8000', true],\n                ['http://[v1.09azAZ-._~!$&\\'()*+,;=:]', true],\n                ['http://[a:b:c:d:e::1.2.3.4]', true],\n                ['coap://[FEDC:BA98:7654:3210:FEDC:BA98:7654:3210]', true],\n                ['http://[1080:0:0:0:8:800:200C:417A]', true],\n                ['http://v1.09azAZ-._~!$&\\'()*+,;=:', true], // This doesn't look valid, but it is. The `v1.09azAZ-._~!$&\\'()*+,;=` part is a valid registered name as it has no invalid characters\n                ['http://a:b:c:d:e::1.2.3.4', false, {\n                    message: '\"value\" must be a valid uri',\n                    path: [],\n                    type: 'string.uri',\n                    context: { value: 'http://a:b:c:d:e::1.2.3.4', label: 'value' }\n                }],\n                ['coap://FEDC:BA98:7654:3210:FEDC:BA98:7654:3210', false, {\n                    message: '\"value\" must be a valid uri',\n                    path: [],\n                    type: 'string.uri',\n                    context: { value: 'coap://FEDC:BA98:7654:3210:FEDC:BA98:7654:3210', label: 'value' }\n                }],\n                ['http://1080:0:0:0:8:800:200C:417A', false, {\n                    message: '\"value\" must be a valid uri',\n                    path: [],\n                    type: 'string.uri',\n                    context: { value: 'http://1080:0:0:0:8:800:200C:417A', label: 'value' }\n                }],\n                ['http://127.0.0.1:8000/foo?bar', true],\n                ['http://asdf:qwer@localhost:8000', true],\n                ['http://user:pass%3A@localhost:80', true],\n                ['http://localhost:123', true],\n                ['https://localhost:123', true],\n                ['file:///whatever', true],\n                ['mailto:asdf@asdf.com', true],\n                ['ftp://www.example.com', true],\n                ['javascript:alert(\\'hello\\');', true],\n                ['xmpp:isaacschlueter@jabber.org', true],\n                ['f://some.host/path', true],\n                ['http://localhost:18/asdf', true],\n                ['http://localhost:42/asdf?qwer=zxcv', true],\n                ['HTTP://www.example.com/', true],\n                ['HTTP://www.example.com', true],\n                ['http://www.ExAmPlE.com/', true],\n                ['http://user:pw@www.ExAmPlE.com/', true],\n                ['http://USER:PW@www.ExAmPlE.com/', true],\n                ['http://user@www.example.com/', true],\n                ['http://user%3Apw@www.example.com/', true],\n                ['http://x.com/path?that%27s#all,%20folks', true],\n                ['HTTP://X.COM/Y', true],\n                ['http://www.narwhaljs.org/blog/categories?id=news', true],\n                ['http://mt0.google.com/vt/lyrs=m@114&hl=en&src=api&x=2&y=2&z=3&s=', true],\n                ['http://mt0.google.com/vt/lyrs=m@114???&hl=en&src=api&x=2&y=2&z=3&s=', true],\n                ['http://user:pass@mt0.google.com/vt/lyrs=m@114???&hl=en&src=api&x=2&y=2&z=3&s=', true],\n                ['http://_jabber._tcp.google.com:80/test', true],\n                ['http://user:pass@_jabber._tcp.google.com:80/test', true],\n                ['http://[fe80::1]/a/b?a=b#abc', true],\n                ['http://fe80::1/a/b?a=b#abc', false, {\n                    message: '\"value\" must be a valid uri',\n                    path: [],\n                    type: 'string.uri',\n                    context: { value: 'http://fe80::1/a/b?a=b#abc', label: 'value' }\n                }],\n                ['http://user:password@[3ffe:2a00:100:7031::1]:8080', true],\n                ['coap://[1080:0:0:0:8:800:200C:417A]:61616/', true],\n                ['coap://1080:0:0:0:8:800:200C:417A:61616/', false, {\n                    message: '\"value\" must be a valid uri',\n                    path: [],\n                    type: 'string.uri',\n                    context: { value: 'coap://1080:0:0:0:8:800:200C:417A:61616/', label: 'value' }\n                }],\n                ['git+http://github.com/joyent/node.git', true],\n                ['http://bucket_name.s3.amazonaws.com/image.jpg', true],\n                ['dot.test://foo/bar', true],\n                ['svn+ssh://foo/bar', true],\n                ['dash-test://foo/bar', true],\n                ['xmpp:isaacschlueter@jabber.org', true],\n                ['http://atpass:foo%40bar@127.0.0.1:8080/path?search=foo#bar', true],\n                ['javascript:alert(\\'hello\\');', true],\n                ['file://localhost/etc/node/', true],\n                ['file:///etc/node/', true],\n                ['http://USER:PW@www.ExAmPlE.com/', true],\n                ['mailto:local1@domain1?query1', true],\n                ['http://example/a/b?c/../d', true],\n                ['http://example/x%2Fabc', true],\n                ['http://a/b/c/d;p=1/g;x=1/y', true],\n                ['http://a/b/c/g#s/../x', true],\n                ['http://a/b/c/.foo', true],\n                ['http://example.com/b//c//d;p?q#blarg', true],\n                ['g:h', true],\n                ['http://a/b/c/g', true],\n                ['http://a/b/c/g/', true],\n                ['http://a/g', true],\n                ['http://g', true],\n                ['http://a/b/c/d;p?y', true],\n                ['http://a/b/c/g?y', true],\n                ['http://a/b/c/d;p?q#s', true],\n                ['http://a/b/c/g#s', true],\n                ['http://a/b/c/g?y#s', true],\n                ['http://a/b/c/;x', true],\n                ['http://a/b/c/g;x', true],\n                ['http://a/b/c/g;x?y#s', true],\n                ['http://a/b/c/d;p?q', true],\n                ['http://a/b/c/', true],\n                ['http://a/b/', true],\n                ['http://a/b/g', true],\n                ['http://a/', true],\n                ['http://a/g', true],\n                ['http://a/g', true],\n                ['file:/asda', true],\n                ['qwerty', false, {\n                    message: '\"value\" must be a valid uri',\n                    path: [],\n                    type: 'string.uri',\n                    context: { value: 'qwerty', label: 'value' }\n                }],\n                ['invalid uri', false, {\n                    message: '\"value\" must be a valid uri',\n                    path: [],\n                    type: 'string.uri',\n                    context: { value: 'invalid uri', label: 'value' }\n                }],\n                ['1http://google.com', false, {\n                    message: '\"value\" must be a valid uri',\n                    path: [],\n                    type: 'string.uri',\n                    context: { value: '1http://google.com', label: 'value' }\n                }],\n                ['http://testdomain`,.<>/?\\'\";{}][++\\\\|~!@#$%^&*().org', false, {\n                    message: '\"value\" must be a valid uri',\n                    path: [],\n                    type: 'string.uri',\n                    context: { value: 'http://testdomain`,.<>/?\\'\";{}][++\\\\|~!@#$%^&*().org', label: 'value' }\n                }],\n                ['', false, {\n                    message: '\"value\" is not allowed to be empty',\n                    path: [],\n                    type: 'string.empty',\n                    context: { value: '', label: 'value' }\n                }],\n                ['(╯°□°)╯︵ ┻━┻', false, {\n                    message: '\"value\" must be a valid uri',\n                    path: [],\n                    type: 'string.uri',\n                    context: { value: '(╯°□°)╯︵ ┻━┻', label: 'value' }\n                }],\n                ['one/two/three?value=abc&value2=123#david-rules', false, {\n                    message: '\"value\" must be a valid uri',\n                    path: [],\n                    type: 'string.uri',\n                    context: { value: 'one/two/three?value=abc&value2=123#david-rules', label: 'value' }\n                }],\n                ['//username:password@test.example.com/one/two/three?value=abc&value2=123#david-rules', false, {\n                    message: '\"value\" must be a valid uri',\n                    path: [],\n                    type: 'string.uri',\n                    context: { value: '//username:password@test.example.com/one/two/three?value=abc&value2=123#david-rules', label: 'value' }\n                }],\n                ['http://a\\r\" \\t\\n<\\'b:b@c\\r\\nd/e?f', false, {\n                    message: '\"value\" must be a valid uri',\n                    path: [],\n                    type: 'string.uri',\n                    context: { value: 'http://a\\r\" \\t\\n<\\'b:b@c\\r\\nd/e?f', label: 'value' }\n                }],\n                ['/absolute', false, {\n                    message: '\"value\" must be a valid uri',\n                    path: [],\n                    type: 'string.uri',\n                    context: { value: '/absolute', label: 'value' }\n                }]\n            ]);\n        });\n\n        it('validates uri with a single scheme provided', () => {\n\n            const schema = Joi.string().uri({\n                scheme: 'http'\n            });\n\n            Helper.validate(schema, [\n                ['http://google.com', true],\n                ['https://google.com', false, {\n                    message: '\"value\" must be a valid uri with a scheme matching the http pattern',\n                    path: [],\n                    type: 'string.uriCustomScheme',\n                    context: {\n                        scheme: 'http',\n                        value: 'https://google.com',\n                        label: 'value'\n                    }\n                }],\n                ['ftp://google.com', false, {\n                    message: '\"value\" must be a valid uri with a scheme matching the http pattern',\n                    path: [],\n                    type: 'string.uriCustomScheme',\n                    context: {\n                        scheme: 'http',\n                        value: 'ftp://google.com',\n                        label: 'value'\n                    }\n                }],\n                ['file:/asdf', false, {\n                    message: '\"value\" must be a valid uri with a scheme matching the http pattern',\n                    path: [],\n                    type: 'string.uriCustomScheme',\n                    context: {\n                        scheme: 'http',\n                        value: 'file:/asdf',\n                        label: 'value'\n                    }\n                }],\n                ['/path?query=value#hash', false, {\n                    message: '\"value\" must be a valid uri with a scheme matching the http pattern',\n                    path: [],\n                    type: 'string.uriCustomScheme',\n                    context: {\n                        scheme: 'http',\n                        value: '/path?query=value#hash',\n                        label: 'value'\n                    }\n                }]\n            ]);\n        });\n\n        it('validates uri with a single regex scheme provided', () => {\n\n            const schema = Joi.string().uri({\n                scheme: /https?/\n            });\n\n            Helper.validate(schema, [\n                ['http://google.com', true],\n                ['https://google.com', true],\n                ['ftp://google.com', false, {\n                    message: '\"value\" must be a valid uri with a scheme matching the https? pattern',\n                    path: [],\n                    type: 'string.uriCustomScheme',\n                    context: {\n                        scheme: 'https?',\n                        value: 'ftp://google.com',\n                        label: 'value'\n                    }\n                }],\n                ['file:/asdf', false, {\n                    message: '\"value\" must be a valid uri with a scheme matching the https? pattern',\n                    path: [],\n                    type: 'string.uriCustomScheme',\n                    context: {\n                        scheme: 'https?',\n                        value: 'file:/asdf',\n                        label: 'value'\n                    }\n                }],\n                ['/path?query=value#hash', false, {\n                    message: '\"value\" must be a valid uri with a scheme matching the https? pattern',\n                    path: [],\n                    type: 'string.uriCustomScheme',\n                    context: {\n                        scheme: 'https?',\n                        value: '/path?query=value#hash',\n                        label: 'value'\n                    }\n                }]\n            ]);\n        });\n\n        it('validates uri with multiple schemes provided', () => {\n\n            const schema = Joi.string().uri({\n                scheme: [/https?/, 'ftp', 'file', 'git+http']\n            });\n\n            Helper.validate(schema, [\n                ['http://google.com', true],\n                ['https://google.com', true],\n                ['ftp://google.com', true],\n                ['file:/asdf', true],\n                ['git+http://github.com/hapijs/joi', true],\n                ['/path?query=value#hash', false, {\n                    message: '\"value\" must be a valid uri with a scheme matching the https?|ftp|file|git\\\\+http pattern',\n                    path: [],\n                    type: 'string.uriCustomScheme',\n                    context: {\n                        scheme: 'https?|ftp|file|git\\\\+http',\n                        value: '/path?query=value#hash',\n                        label: 'value'\n                    }\n                }]\n            ]);\n        });\n\n        it('validates uri', () => {\n\n            const schema = { item: Joi.string().uri() };\n\n            Helper.validate(Joi.compile(schema), [[{ item: 'something invalid' }, false, '\"item\" must be a valid uri']]);\n        });\n\n        it('validates uri with a custom scheme', () => {\n\n            const schema = {\n                item: Joi.string().uri({\n                    scheme: 'http'\n                })\n            };\n\n            Helper.validate(Joi.compile(schema), [[{ item: 'something invalid' }, false, '\"item\" must be a valid uri with a scheme matching the http pattern']]);\n        });\n\n        it('validates uri with a custom array of schemes', () => {\n\n            const schema = {\n                item: Joi.string().uri({\n                    scheme: ['http', /https?/]\n                })\n            };\n\n            Helper.validate(Joi.compile(schema), [[{ item: 'something invalid' }, false, '\"item\" must be a valid uri with a scheme matching the http|https? pattern']]);\n        });\n\n        it('validates uri treats scheme as optional', () => {\n\n            expect(() => {\n\n                Joi.string().uri({});\n            }).to.not.throw();\n        });\n\n        it('handles missing domain', () => {\n\n            const schema = Joi.string().uri({ domain: { tlds: { allow: true } } });\n            expect(() => schema.validate('http:example.com')).to.not.throw();\n        });\n\n        it('validates uri requires uriOptions as an object', () => {\n\n            expect(() => {\n\n                Joi.string().uri('http');\n            }).to.throw(Error, 'Options must be of type object');\n        });\n\n        it('validates uri requires scheme to be a RegExp, String, or Array', () => {\n\n            expect(() => {\n\n                Joi.string().uri({\n                    scheme: {}\n                });\n            }).to.throw(Error, 'scheme must be a RegExp, String, or Array');\n        });\n\n        it('validates uri requires scheme to not be an empty array', () => {\n\n            expect(() => {\n\n                Joi.string().uri({\n                    scheme: []\n                });\n            }).to.throw(Error, 'scheme must have at least 1 scheme specified');\n        });\n\n        it('validates uri requires scheme to be an Array of schemes to all be valid schemes', () => {\n\n            expect(() => {\n\n                Joi.string().uri({\n                    scheme: [\n                        'http',\n                        '~!@#$%^&*()_'\n                    ]\n                });\n            }).to.throw(Error, 'scheme at position 1 must be a valid scheme');\n        });\n\n        it('validates uri requires scheme to be an Array of schemes to be strings or RegExp', () => {\n\n            expect(() => {\n\n                Joi.string().uri({\n                    scheme: [\n                        'http',\n                        {}\n                    ]\n                });\n            }).to.throw(Error, 'scheme at position 1 must be a RegExp or String');\n        });\n\n        it('validates uri requires scheme to be a valid String scheme', () => {\n\n            expect(() => {\n\n                Joi.string().uri({\n                    scheme: '~!@#$%^&*()_'\n                });\n            }).to.throw(Error, 'scheme at position 0 must be a valid scheme');\n        });\n\n        it('validates uri and domain', () => {\n\n            const schema = Joi.string().uri({ domain: { minDomainSegments: 3, tlds: { allow: ['com'] } } });\n\n            Helper.validate(schema, [\n                ['http://test.google.com', true],\n                ['http://google.com', false, {\n                    message: '\"value\" must contain a valid domain name',\n                    path: [],\n                    type: 'string.domain',\n                    context: {\n                        value: 'google.com',\n                        label: 'value'\n                    }\n                }],\n                ['http://test.google.net', false, {\n                    message: '\"value\" must contain a valid domain name',\n                    path: [],\n                    type: 'string.domain',\n                    context: {\n                        value: 'test.google.net',\n                        label: 'value'\n                    }\n                }]\n            ]);\n        });\n\n        it('validates uri and domain (true)', () => {\n\n            const schema = Joi.string().uri({ domain: {} });\n\n            Helper.validate(schema, [\n                ['http://test.google.com', true],\n                ['http://google.com%F', false, '\"value\" must contain a valid domain name']\n            ]);\n        });\n\n        it('validates relative uri', () => {\n\n            const schema = Joi.string().uri({ allowRelative: true });\n            Helper.validate(schema, [\n                ['foo://example.com:8042/over/there?name=ferret#nose', true],\n                ['urn:example:animal:ferret:nose', true],\n                ['ftp://ftp.is.co.za/rfc/rfc1808.txt', true],\n                ['http://www.ietf.org/rfc/rfc2396.txt', true],\n                ['ldap://[2001:db8::7]/c=GB?objectClass?one', true],\n                ['mailto:John.Doe@example.com', true],\n                ['news:comp.infosystems.www.servers.unix', true],\n                ['tel:+1-816-555-1212', true],\n                ['telnet://192.0.2.16:80/', true],\n                ['urn:oasis:names:specification:docbook:dtd:xml:4.1.2', true],\n                ['file:///example.txt', true],\n                ['http://asdf:qw%20er@localhost:8000?asdf=12345&asda=fc%2F#bacon', true],\n                ['http://asdf@localhost:8000', true],\n                ['http://[v1.09azAZ-._~!$&\\'()*+,;=:]', true],\n                ['http://[a:b:c:d:e::1.2.3.4]', true],\n                ['coap://[FEDC:BA98:7654:3210:FEDC:BA98:7654:3210]', true],\n                ['http://[1080:0:0:0:8:800:200C:417A]', true],\n                ['http://127.0.0.1:8000/foo?bar', true],\n                ['http://asdf:qwer@localhost:8000', true],\n                ['http://user:pass%3A@localhost:80', true],\n                ['http://localhost:123', true],\n                ['https://localhost:123', true],\n                ['file:///whatever', true],\n                ['mailto:asdf@asdf.com', true],\n                ['ftp://www.example.com', true],\n                ['javascript:alert(\\'hello\\');', true],\n                ['xmpp:isaacschlueter@jabber.org', true],\n                ['f://some.host/path', true],\n                ['http://localhost:18/asdf', true],\n                ['http://localhost:42/asdf?qwer=zxcv', true],\n                ['HTTP://www.example.com/', true],\n                ['HTTP://www.example.com', true],\n                ['http://www.ExAmPlE.com/', true],\n                ['http://user:pw@www.ExAmPlE.com/', true],\n                ['http://USER:PW@www.ExAmPlE.com/', true],\n                ['http://user@www.example.com/', true],\n                ['http://user%3Apw@www.example.com/', true],\n                ['http://x.com/path?that%27s#all,%20folks', true],\n                ['HTTP://X.COM/Y', true],\n                ['http://www.narwhaljs.org/blog/categories?id=news', true],\n                ['http://mt0.google.com/vt/lyrs=m@114&hl=en&src=api&x=2&y=2&z=3&s=', true],\n                ['http://mt0.google.com/vt/lyrs=m@114???&hl=en&src=api&x=2&y=2&z=3&s=', true],\n                ['http://user:pass@mt0.google.com/vt/lyrs=m@114???&hl=en&src=api&x=2&y=2&z=3&s=', true],\n                ['http://_jabber._tcp.google.com:80/test', true],\n                ['http://user:pass@_jabber._tcp.google.com:80/test', true],\n                ['http://[fe80::1]/a/b?a=b#abc', true],\n                ['http://user:password@[3ffe:2a00:100:7031::1]:8080', true],\n                ['coap://[1080:0:0:0:8:800:200C:417A]:61616/', true],\n                ['git+http://github.com/joyent/node.git', true],\n                ['http://bucket_name.s3.amazonaws.com/image.jpg', true],\n                ['dot.test://foo/bar', true],\n                ['svn+ssh://foo/bar', true],\n                ['dash-test://foo/bar', true],\n                ['xmpp:isaacschlueter@jabber.org', true],\n                ['http://atpass:foo%40bar@127.0.0.1:8080/path?search=foo#bar', true],\n                ['javascript:alert(\\'hello\\');', true],\n                ['file://localhost/etc/node/', true],\n                ['file:///etc/node/', true],\n                ['http://USER:PW@www.ExAmPlE.com/', true],\n                ['mailto:local1@domain1?query1', true],\n                ['http://example/a/b?c/../d', true],\n                ['http://example/x%2Fabc', true],\n                ['http://a/b/c/d;p=1/g;x=1/y', true],\n                ['http://a/b/c/g#s/../x', true],\n                ['http://a/b/c/.foo', true],\n                ['http://example.com/b//c//d;p?q#blarg', true],\n                ['g:h', true],\n                ['http://a/b/c/g', true],\n                ['http://a/b/c/g/', true],\n                ['http://a/g', true],\n                ['http://g', true],\n                ['http://a/b/c/d;p?y', true],\n                ['http://a/b/c/g?y', true],\n                ['http://a/b/c/d;p?q#s', true],\n                ['http://a/b/c/g#s', true],\n                ['http://a/b/c/g?y#s', true],\n                ['http://a/b/c/;x', true],\n                ['http://a/b/c/g;x', true],\n                ['http://a/b/c/g;x?y#s', true],\n                ['http://a/b/c/d;p?q', true],\n                ['http://a/b/c/', true],\n                ['http://a/b/', true],\n                ['http://a/b/g', true],\n                ['http://a/', true],\n                ['http://a/g', true],\n                ['http://a/g', true],\n                ['file:/asda', true],\n                ['qwerty', true],\n                ['invalid uri', false, {\n                    message: '\"value\" must be a valid uri',\n                    path: [],\n                    type: 'string.uri',\n                    context: { value: 'invalid uri', label: 'value' }\n                }],\n                ['1http://google.com', false, {\n                    message: '\"value\" must be a valid uri',\n                    path: [],\n                    type: 'string.uri',\n                    context: { value: '1http://google.com', label: 'value' }\n                }],\n                ['http://testdomain`,.<>/?\\'\";{}][++\\\\|~!@#$%^&*().org', false, {\n                    message: '\"value\" must be a valid uri',\n                    path: [],\n                    type: 'string.uri',\n                    context: { value: 'http://testdomain`,.<>/?\\'\";{}][++\\\\|~!@#$%^&*().org', label: 'value' }\n                }],\n                ['', false, {\n                    message: '\"value\" is not allowed to be empty',\n                    path: [],\n                    type: 'string.empty',\n                    context: { value: '', label: 'value' }\n                }],\n                ['(╯°□°)╯︵ ┻━┻', false, {\n                    message: '\"value\" must be a valid uri',\n                    path: [],\n                    type: 'string.uri',\n                    context: { value: '(╯°□°)╯︵ ┻━┻', label: 'value' }\n                }],\n                ['one/two/three?value=abc&value2=123#david-rules', true],\n                ['//username:password@test.example.com/one/two/three?value=abc&value2=123#david-rules', true],\n                ['http://a\\r\" \\t\\n<\\'b:b@c\\r\\nd/e?f', false, {\n                    message: '\"value\" must be a valid uri',\n                    path: [],\n                    type: 'string.uri',\n                    context: { value: 'http://a\\r\" \\t\\n<\\'b:b@c\\r\\nd/e?f', label: 'value' }\n                }],\n                ['/absolute', true],\n                ['http://', false, {\n                    message: '\"value\" must be a valid uri',\n                    path: [],\n                    type: 'string.uri',\n                    context: { value: 'http://', label: 'value' }\n                }],\n                ['http:/', false, {\n                    message: '\"value\" must be a valid uri',\n                    path: [],\n                    type: 'string.uri',\n                    context: { value: 'http:/', label: 'value' }\n                }],\n                ['https:/', false, {\n                    message: '\"value\" must be a valid uri',\n                    path: [],\n                    type: 'string.uri',\n                    context: { value: 'https:/', label: 'value' }\n                }]\n            ]);\n        });\n\n        it('validates relative only uri', () => {\n\n            const schema = Joi.string().uri({ relativeOnly: true });\n            Helper.validate(schema, [\n                ['foo://example.com:8042/over/there?name=ferret#nose', false, {\n                    message: '\"value\" must be a valid relative uri',\n                    path: [],\n                    type: 'string.uriRelativeOnly',\n                    context: {\n                        value: 'foo://example.com:8042/over/there?name=ferret#nose',\n                        label: 'value'\n                    }\n                }],\n                ['urn:example:animal:ferret:nose', false, {\n                    message: '\"value\" must be a valid relative uri',\n                    path: [],\n                    type: 'string.uriRelativeOnly',\n                    context: {\n                        value: 'urn:example:animal:ferret:nose',\n                        label: 'value'\n                    }\n                }],\n                ['ftp://ftp.is.co.za/rfc/rfc1808.txt', false, {\n                    message: '\"value\" must be a valid relative uri',\n                    path: [],\n                    type: 'string.uriRelativeOnly',\n                    context: {\n                        value: 'ftp://ftp.is.co.za/rfc/rfc1808.txt',\n                        label: 'value'\n                    }\n                }],\n                ['http://www.ietf.org/rfc/rfc2396.txt', false, {\n                    message: '\"value\" must be a valid relative uri',\n                    path: [],\n                    type: 'string.uriRelativeOnly',\n                    context: {\n                        value: 'http://www.ietf.org/rfc/rfc2396.txt',\n                        label: 'value'\n                    }\n                }],\n                ['ldap://[2001:db8::7]/c=GB?objectClass?one', false, {\n                    message: '\"value\" must be a valid relative uri',\n                    path: [],\n                    type: 'string.uriRelativeOnly',\n                    context: {\n                        value: 'ldap://[2001:db8::7]/c=GB?objectClass?one',\n                        label: 'value'\n                    }\n                }],\n                ['mailto:John.Doe@example.com', false, {\n                    message: '\"value\" must be a valid relative uri',\n                    path: [],\n                    type: 'string.uriRelativeOnly',\n                    context: {\n                        value: 'mailto:John.Doe@example.com',\n                        label: 'value'\n                    }\n                }],\n                ['news:comp.infosystems.www.servers.unix', false, {\n                    message: '\"value\" must be a valid relative uri',\n                    path: [],\n                    type: 'string.uriRelativeOnly',\n                    context: {\n                        value: 'news:comp.infosystems.www.servers.unix',\n                        label: 'value'\n                    }\n                }],\n                ['tel:+1-816-555-1212', false, {\n                    message: '\"value\" must be a valid relative uri',\n                    path: [],\n                    type: 'string.uriRelativeOnly',\n                    context: {\n                        value: 'tel:+1-816-555-1212',\n                        label: 'value'\n                    }\n                }],\n                ['telnet://192.0.2.16:80/', false, {\n                    message: '\"value\" must be a valid relative uri',\n                    path: [],\n                    type: 'string.uriRelativeOnly',\n                    context: {\n                        value: 'telnet://192.0.2.16:80/',\n                        label: 'value'\n                    }\n                }],\n                ['urn:oasis:names:specification:docbook:dtd:xml:4.1.2', false, {\n                    message: '\"value\" must be a valid relative uri',\n                    path: [],\n                    type: 'string.uriRelativeOnly',\n                    context: {\n                        value: 'urn:oasis:names:specification:docbook:dtd:xml:4.1.2',\n                        label: 'value'\n                    }\n                }],\n                ['file:///example.txt', false, {\n                    message: '\"value\" must be a valid relative uri',\n                    path: [],\n                    type: 'string.uriRelativeOnly',\n                    context: {\n                        value: 'file:///example.txt',\n                        label: 'value'\n                    }\n                }],\n                ['http://asdf:qw%20er@localhost:8000?asdf=12345&asda=fc%2F#bacon', false, {\n                    message: '\"value\" must be a valid relative uri',\n                    path: [],\n                    type: 'string.uriRelativeOnly',\n                    context: {\n                        value: 'http://asdf:qw%20er@localhost:8000?asdf=12345&asda=fc%2F#bacon',\n                        label: 'value'\n                    }\n                }],\n                ['http://asdf@localhost:8000', false, {\n                    message: '\"value\" must be a valid relative uri',\n                    path: [],\n                    type: 'string.uriRelativeOnly',\n                    context: {\n                        value: 'http://asdf@localhost:8000',\n                        label: 'value'\n                    }\n                }],\n                ['http://[v1.09azAZ-._~!$&\\'()*+,;=:]', false, {\n                    message: '\"value\" must be a valid relative uri',\n                    path: [],\n                    type: 'string.uriRelativeOnly',\n                    context: {\n                        value: 'http://[v1.09azAZ-._~!$&\\'()*+,;=:]',\n                        label: 'value'\n                    }\n                }],\n                ['http://[a:b:c:d:e::1.2.3.4]', false, {\n                    message: '\"value\" must be a valid relative uri',\n                    path: [],\n                    type: 'string.uriRelativeOnly',\n                    context: {\n                        value: 'http://[a:b:c:d:e::1.2.3.4]',\n                        label: 'value'\n                    }\n                }],\n                ['coap://[FEDC:BA98:7654:3210:FEDC:BA98:7654:3210]', false, {\n                    message: '\"value\" must be a valid relative uri',\n                    path: [],\n                    type: 'string.uriRelativeOnly',\n                    context: {\n                        value: 'coap://[FEDC:BA98:7654:3210:FEDC:BA98:7654:3210]',\n                        label: 'value'\n                    }\n                }],\n                ['http://[1080:0:0:0:8:800:200C:417A]', false, {\n                    message: '\"value\" must be a valid relative uri',\n                    path: [],\n                    type: 'string.uriRelativeOnly',\n                    context: {\n                        value: 'http://[1080:0:0:0:8:800:200C:417A]',\n                        label: 'value'\n                    }\n                }],\n                ['http://127.0.0.1:8000/foo?bar', false, {\n                    message: '\"value\" must be a valid relative uri',\n                    path: [],\n                    type: 'string.uriRelativeOnly',\n                    context: {\n                        value: 'http://127.0.0.1:8000/foo?bar',\n                        label: 'value'\n                    }\n                }],\n                ['http://asdf:qwer@localhost:8000', false, {\n                    message: '\"value\" must be a valid relative uri',\n                    path: [],\n                    type: 'string.uriRelativeOnly',\n                    context: {\n                        value: 'http://asdf:qwer@localhost:8000',\n                        label: 'value'\n                    }\n                }],\n                ['http://user:pass%3A@localhost:80', false, {\n                    message: '\"value\" must be a valid relative uri',\n                    path: [],\n                    type: 'string.uriRelativeOnly',\n                    context: {\n                        value: 'http://user:pass%3A@localhost:80',\n                        label: 'value'\n                    }\n                }],\n                ['http://localhost:123', false, {\n                    message: '\"value\" must be a valid relative uri',\n                    path: [],\n                    type: 'string.uriRelativeOnly',\n                    context: {\n                        value: 'http://localhost:123',\n                        label: 'value'\n                    }\n                }],\n                ['https://localhost:123', false, {\n                    message: '\"value\" must be a valid relative uri',\n                    path: [],\n                    type: 'string.uriRelativeOnly',\n                    context: {\n                        value: 'https://localhost:123',\n                        label: 'value'\n                    }\n                }],\n                ['file:///whatever', false, {\n                    message: '\"value\" must be a valid relative uri',\n                    path: [],\n                    type: 'string.uriRelativeOnly',\n                    context: {\n                        value: 'file:///whatever',\n                        label: 'value'\n                    }\n                }],\n                ['mailto:asdf@asdf.com', false, {\n                    message: '\"value\" must be a valid relative uri',\n                    path: [],\n                    type: 'string.uriRelativeOnly',\n                    context: {\n                        value: 'mailto:asdf@asdf.com',\n                        label: 'value'\n                    }\n                }],\n                ['ftp://www.example.com', false, {\n                    message: '\"value\" must be a valid relative uri',\n                    path: [],\n                    type: 'string.uriRelativeOnly',\n                    context: {\n                        value: 'ftp://www.example.com',\n                        label: 'value'\n                    }\n                }],\n                ['javascript:alert(\\'hello\\');', false, {\n                    message: '\"value\" must be a valid relative uri',\n                    path: [],\n                    type: 'string.uriRelativeOnly',\n                    context: {\n                        value: 'javascript:alert(\\'hello\\');',\n                        label: 'value'\n                    }\n                }],\n                ['xmpp:isaacschlueter@jabber.org', false, {\n                    message: '\"value\" must be a valid relative uri',\n                    path: [],\n                    type: 'string.uriRelativeOnly',\n                    context: {\n                        value: 'xmpp:isaacschlueter@jabber.org',\n                        label: 'value'\n                    }\n                }],\n                ['f://some.host/path', false, {\n                    message: '\"value\" must be a valid relative uri',\n                    path: [],\n                    type: 'string.uriRelativeOnly',\n                    context: {\n                        value: 'f://some.host/path',\n                        label: 'value'\n                    }\n                }],\n                ['http://localhost:18/asdf', false, {\n                    message: '\"value\" must be a valid relative uri',\n                    path: [],\n                    type: 'string.uriRelativeOnly',\n                    context: {\n                        value: 'http://localhost:18/asdf',\n                        label: 'value'\n                    }\n                }],\n                ['http://localhost:42/asdf?qwer=zxcv', false, {\n                    message: '\"value\" must be a valid relative uri',\n                    path: [],\n                    type: 'string.uriRelativeOnly',\n                    context: {\n                        value: 'http://localhost:42/asdf?qwer=zxcv',\n                        label: 'value'\n                    }\n                }],\n                ['HTTP://www.example.com/', false, {\n                    message: '\"value\" must be a valid relative uri',\n                    path: [],\n                    type: 'string.uriRelativeOnly',\n                    context: {\n                        value: 'HTTP://www.example.com/',\n                        label: 'value'\n                    }\n                }],\n                ['HTTP://www.example.com', false, {\n                    message: '\"value\" must be a valid relative uri',\n                    path: [],\n                    type: 'string.uriRelativeOnly',\n                    context: {\n                        value: 'HTTP://www.example.com',\n                        label: 'value'\n                    }\n                }],\n                ['http://www.ExAmPlE.com/', false, {\n                    message: '\"value\" must be a valid relative uri',\n                    path: [],\n                    type: 'string.uriRelativeOnly',\n                    context: {\n                        value: 'http://www.ExAmPlE.com/',\n                        label: 'value'\n                    }\n                }],\n                ['http://user:pw@www.ExAmPlE.com/', false, {\n                    message: '\"value\" must be a valid relative uri',\n                    path: [],\n                    type: 'string.uriRelativeOnly',\n                    context: {\n                        value: 'http://user:pw@www.ExAmPlE.com/',\n                        label: 'value'\n                    }\n                }],\n                ['http://USER:PW@www.ExAmPlE.com/', false, {\n                    message: '\"value\" must be a valid relative uri',\n                    path: [],\n                    type: 'string.uriRelativeOnly',\n                    context: {\n                        value: 'http://USER:PW@www.ExAmPlE.com/',\n                        label: 'value'\n                    }\n                }],\n                ['http://user@www.example.com/', false, {\n                    message: '\"value\" must be a valid relative uri',\n                    path: [],\n                    type: 'string.uriRelativeOnly',\n                    context: {\n                        value: 'http://user@www.example.com/',\n                        label: 'value'\n                    }\n                }],\n                ['http://user%3Apw@www.example.com/', false, {\n                    message: '\"value\" must be a valid relative uri',\n                    path: [],\n                    type: 'string.uriRelativeOnly',\n                    context: {\n                        value: 'http://user%3Apw@www.example.com/',\n                        label: 'value'\n                    }\n                }],\n                ['http://x.com/path?that%27s#all,%20folks', false, {\n                    message: '\"value\" must be a valid relative uri',\n                    path: [],\n                    type: 'string.uriRelativeOnly',\n                    context: {\n                        value: 'http://x.com/path?that%27s#all,%20folks',\n                        label: 'value'\n                    }\n                }],\n                ['HTTP://X.COM/Y', false, {\n                    message: '\"value\" must be a valid relative uri',\n                    path: [],\n                    type: 'string.uriRelativeOnly',\n                    context: {\n                        value: 'HTTP://X.COM/Y',\n                        label: 'value'\n                    }\n                }],\n                ['http://www.narwhaljs.org/blog/categories?id=news', false, {\n                    message: '\"value\" must be a valid relative uri',\n                    path: [],\n                    type: 'string.uriRelativeOnly',\n                    context: {\n                        value: 'http://www.narwhaljs.org/blog/categories?id=news',\n                        label: 'value'\n                    }\n                }],\n                ['http://mt0.google.com/vt/lyrs=m@114&hl=en&src=api&x=2&y=2&z=3&s=', false, {\n                    message: '\"value\" must be a valid relative uri',\n                    path: [],\n                    type: 'string.uriRelativeOnly',\n                    context: {\n                        value: 'http://mt0.google.com/vt/lyrs=m@114&hl=en&src=api&x=2&y=2&z=3&s=',\n                        label: 'value'\n                    }\n                }],\n                ['http://mt0.google.com/vt/lyrs=m@114???&hl=en&src=api&x=2&y=2&z=3&s=', false, {\n                    message: '\"value\" must be a valid relative uri',\n                    path: [],\n                    type: 'string.uriRelativeOnly',\n                    context: {\n                        value: 'http://mt0.google.com/vt/lyrs=m@114???&hl=en&src=api&x=2&y=2&z=3&s=',\n                        label: 'value'\n                    }\n                }],\n                ['http://user:pass@mt0.google.com/vt/lyrs=m@114???&hl=en&src=api&x=2&y=2&z=3&s=', false, {\n                    message: '\"value\" must be a valid relative uri',\n                    path: [],\n                    type: 'string.uriRelativeOnly',\n                    context: {\n                        value: 'http://user:pass@mt0.google.com/vt/lyrs=m@114???&hl=en&src=api&x=2&y=2&z=3&s=',\n                        label: 'value'\n                    }\n                }],\n                ['http://_jabber._tcp.google.com:80/test', false, {\n                    message: '\"value\" must be a valid relative uri',\n                    path: [],\n                    type: 'string.uriRelativeOnly',\n                    context: {\n                        value: 'http://_jabber._tcp.google.com:80/test',\n                        label: 'value'\n                    }\n                }],\n                ['http://user:pass@_jabber._tcp.google.com:80/test', false, {\n                    message: '\"value\" must be a valid relative uri',\n                    path: [],\n                    type: 'string.uriRelativeOnly',\n                    context: {\n                        value: 'http://user:pass@_jabber._tcp.google.com:80/test',\n                        label: 'value'\n                    }\n                }],\n                ['http://[fe80::1]/a/b?a=b#abc', false, {\n                    message: '\"value\" must be a valid relative uri',\n                    path: [],\n                    type: 'string.uriRelativeOnly',\n                    context: {\n                        value: 'http://[fe80::1]/a/b?a=b#abc',\n                        label: 'value'\n                    }\n                }],\n                ['http://user:password@[3ffe:2a00:100:7031::1]:8080', false, {\n                    message: '\"value\" must be a valid relative uri',\n                    path: [],\n                    type: 'string.uriRelativeOnly',\n                    context: {\n                        value: 'http://user:password@[3ffe:2a00:100:7031::1]:8080',\n                        label: 'value'\n                    }\n                }],\n                ['coap://[1080:0:0:0:8:800:200C:417A]:61616/', false, {\n                    message: '\"value\" must be a valid relative uri',\n                    path: [],\n                    type: 'string.uriRelativeOnly',\n                    context: {\n                        value: 'coap://[1080:0:0:0:8:800:200C:417A]:61616/',\n                        label: 'value'\n                    }\n                }],\n                ['git+http://github.com/joyent/node.git', false, {\n                    message: '\"value\" must be a valid relative uri',\n                    path: [],\n                    type: 'string.uriRelativeOnly',\n                    context: {\n                        value: 'git+http://github.com/joyent/node.git',\n                        label: 'value'\n                    }\n                }],\n                ['http://bucket_name.s3.amazonaws.com/image.jpg', false, {\n                    message: '\"value\" must be a valid relative uri',\n                    path: [],\n                    type: 'string.uriRelativeOnly',\n                    context: {\n                        value: 'http://bucket_name.s3.amazonaws.com/image.jpg',\n                        label: 'value'\n                    }\n                }],\n                ['dot.test://foo/bar', false, {\n                    message: '\"value\" must be a valid relative uri',\n                    path: [],\n                    type: 'string.uriRelativeOnly',\n                    context: {\n                        value: 'dot.test://foo/bar',\n                        label: 'value'\n                    }\n                }],\n                ['svn+ssh://foo/bar', false, {\n                    message: '\"value\" must be a valid relative uri',\n                    path: [],\n                    type: 'string.uriRelativeOnly',\n                    context: {\n                        value: 'svn+ssh://foo/bar',\n                        label: 'value'\n                    }\n                }],\n                ['dash-test://foo/bar', false, {\n                    message: '\"value\" must be a valid relative uri',\n                    path: [],\n                    type: 'string.uriRelativeOnly',\n                    context: {\n                        value: 'dash-test://foo/bar',\n                        label: 'value'\n                    }\n                }],\n                ['xmpp:isaacschlueter@jabber.org', false, {\n                    message: '\"value\" must be a valid relative uri',\n                    path: [],\n                    type: 'string.uriRelativeOnly',\n                    context: {\n                        value: 'xmpp:isaacschlueter@jabber.org',\n                        label: 'value'\n                    }\n                }],\n                ['http://atpass:foo%40bar@127.0.0.1:8080/path?search=foo#bar', false, {\n                    message: '\"value\" must be a valid relative uri',\n                    path: [],\n                    type: 'string.uriRelativeOnly',\n                    context: {\n                        value: 'http://atpass:foo%40bar@127.0.0.1:8080/path?search=foo#bar',\n                        label: 'value'\n                    }\n                }],\n                ['javascript:alert(\\'hello\\');', false, {\n                    message: '\"value\" must be a valid relative uri',\n                    path: [],\n                    type: 'string.uriRelativeOnly',\n                    context: {\n                        value: 'javascript:alert(\\'hello\\');',\n                        label: 'value'\n                    }\n                }],\n                ['file://localhost/etc/node/', false, {\n                    message: '\"value\" must be a valid relative uri',\n                    path: [],\n                    type: 'string.uriRelativeOnly',\n                    context: {\n                        value: 'file://localhost/etc/node/',\n                        label: 'value'\n                    }\n                }],\n                ['file:///etc/node/', false, {\n                    message: '\"value\" must be a valid relative uri',\n                    path: [],\n                    type: 'string.uriRelativeOnly',\n                    context: {\n                        value: 'file:///etc/node/',\n                        label: 'value'\n                    }\n                }],\n                ['http://USER:PW@www.ExAmPlE.com/', false, {\n                    message: '\"value\" must be a valid relative uri',\n                    path: [],\n                    type: 'string.uriRelativeOnly',\n                    context: {\n                        value: 'http://USER:PW@www.ExAmPlE.com/',\n                        label: 'value'\n                    }\n                }],\n                ['mailto:local1@domain1?query1', false, {\n                    message: '\"value\" must be a valid relative uri',\n                    path: [],\n                    type: 'string.uriRelativeOnly',\n                    context: {\n                        value: 'mailto:local1@domain1?query1',\n                        label: 'value'\n                    }\n                }],\n                ['http://example/a/b?c/../d', false, {\n                    message: '\"value\" must be a valid relative uri',\n                    path: [],\n                    type: 'string.uriRelativeOnly',\n                    context: {\n                        value: 'http://example/a/b?c/../d',\n                        label: 'value'\n                    }\n                }],\n                ['http://example/x%2Fabc', false, {\n                    message: '\"value\" must be a valid relative uri',\n                    path: [],\n                    type: 'string.uriRelativeOnly',\n                    context: {\n                        value: 'http://example/x%2Fabc',\n                        label: 'value'\n                    }\n                }],\n                ['http://a/b/c/d;p=1/g;x=1/y', false, {\n                    message: '\"value\" must be a valid relative uri',\n                    path: [],\n                    type: 'string.uriRelativeOnly',\n                    context: {\n                        value: 'http://a/b/c/d;p=1/g;x=1/y',\n                        label: 'value'\n                    }\n                }],\n                ['http://a/b/c/g#s/../x', false, {\n                    message: '\"value\" must be a valid relative uri',\n                    path: [],\n                    type: 'string.uriRelativeOnly',\n                    context: {\n                        value: 'http://a/b/c/g#s/../x',\n                        label: 'value'\n                    }\n                }],\n                ['http://a/b/c/.foo', false, {\n                    message: '\"value\" must be a valid relative uri',\n                    path: [],\n                    type: 'string.uriRelativeOnly',\n                    context: {\n                        value: 'http://a/b/c/.foo',\n                        label: 'value'\n                    }\n                }],\n                ['http://example.com/b//c//d;p?q#blarg', false, {\n                    message: '\"value\" must be a valid relative uri',\n                    path: [],\n                    type: 'string.uriRelativeOnly',\n                    context: {\n                        value: 'http://example.com/b//c//d;p?q#blarg',\n                        label: 'value'\n                    }\n                }],\n                ['g:h', false, {\n                    message: '\"value\" must be a valid relative uri',\n                    path: [],\n                    type: 'string.uriRelativeOnly',\n                    context: {\n                        value: 'g:h',\n                        label: 'value'\n                    }\n                }],\n                ['http://a/b/c/g', false, {\n                    message: '\"value\" must be a valid relative uri',\n                    path: [],\n                    type: 'string.uriRelativeOnly',\n                    context: {\n                        value: 'http://a/b/c/g',\n                        label: 'value'\n                    }\n                }],\n                ['http://a/b/c/g/', false, {\n                    message: '\"value\" must be a valid relative uri',\n                    path: [],\n                    type: 'string.uriRelativeOnly',\n                    context: {\n                        value: 'http://a/b/c/g/',\n                        label: 'value'\n                    }\n                }],\n                ['http://a/g', false, {\n                    message: '\"value\" must be a valid relative uri',\n                    path: [],\n                    type: 'string.uriRelativeOnly',\n                    context: {\n                        value: 'http://a/g',\n                        label: 'value'\n                    }\n                }],\n                ['http://g', false, {\n                    message: '\"value\" must be a valid relative uri',\n                    path: [],\n                    type: 'string.uriRelativeOnly',\n                    context: {\n                        value: 'http://g',\n                        label: 'value'\n                    }\n                }],\n                ['http://a/b/c/d;p?y', false, {\n                    message: '\"value\" must be a valid relative uri',\n                    path: [],\n                    type: 'string.uriRelativeOnly',\n                    context: {\n                        value: 'http://a/b/c/d;p?y',\n                        label: 'value'\n                    }\n                }],\n                ['http://a/b/c/g?y', false, {\n                    message: '\"value\" must be a valid relative uri',\n                    path: [],\n                    type: 'string.uriRelativeOnly',\n                    context: {\n                        value: 'http://a/b/c/g?y',\n                        label: 'value'\n                    }\n                }],\n                ['http://a/b/c/d;p?q#s', false, {\n                    message: '\"value\" must be a valid relative uri',\n                    path: [],\n                    type: 'string.uriRelativeOnly',\n                    context: {\n                        value: 'http://a/b/c/d;p?q#s',\n                        label: 'value'\n                    }\n                }],\n                ['http://a/b/c/g#s', false, {\n                    message: '\"value\" must be a valid relative uri',\n                    path: [],\n                    type: 'string.uriRelativeOnly',\n                    context: {\n                        value: 'http://a/b/c/g#s',\n                        label: 'value'\n                    }\n                }],\n                ['http://a/b/c/g?y#s', false, {\n                    message: '\"value\" must be a valid relative uri',\n                    path: [],\n                    type: 'string.uriRelativeOnly',\n                    context: {\n                        value: 'http://a/b/c/g?y#s',\n                        label: 'value'\n                    }\n                }],\n                ['http://a/b/c/;x', false, {\n                    message: '\"value\" must be a valid relative uri',\n                    path: [],\n                    type: 'string.uriRelativeOnly',\n                    context: {\n                        value: 'http://a/b/c/;x',\n                        label: 'value'\n                    }\n                }],\n                ['http://a/b/c/g;x', false, {\n                    message: '\"value\" must be a valid relative uri',\n                    path: [],\n                    type: 'string.uriRelativeOnly',\n                    context: {\n                        value: 'http://a/b/c/g;x',\n                        label: 'value'\n                    }\n                }],\n                ['http://a/b/c/g;x?y#s', false, {\n                    message: '\"value\" must be a valid relative uri',\n                    path: [],\n                    type: 'string.uriRelativeOnly',\n                    context: {\n                        value: 'http://a/b/c/g;x?y#s',\n                        label: 'value'\n                    }\n                }],\n                ['http://a/b/c/d;p?q', false, {\n                    message: '\"value\" must be a valid relative uri',\n                    path: [],\n                    type: 'string.uriRelativeOnly',\n                    context: {\n                        value: 'http://a/b/c/d;p?q',\n                        label: 'value'\n                    }\n                }],\n                ['http://a/b/c/', false, {\n                    message: '\"value\" must be a valid relative uri',\n                    path: [],\n                    type: 'string.uriRelativeOnly',\n                    context: {\n                        value: 'http://a/b/c/',\n                        label: 'value'\n                    }\n                }],\n                ['http://a/b/', false, {\n                    message: '\"value\" must be a valid relative uri',\n                    path: [],\n                    type: 'string.uriRelativeOnly',\n                    context: {\n                        value: 'http://a/b/',\n                        label: 'value'\n                    }\n                }],\n                ['http://a/b/g', false, {\n                    message: '\"value\" must be a valid relative uri',\n                    path: [],\n                    type: 'string.uriRelativeOnly',\n                    context: {\n                        value: 'http://a/b/g',\n                        label: 'value'\n                    }\n                }],\n                ['http://a/', false, {\n                    message: '\"value\" must be a valid relative uri',\n                    path: [],\n                    type: 'string.uriRelativeOnly',\n                    context: {\n                        value: 'http://a/',\n                        label: 'value'\n                    }\n                }],\n                ['http://a/g', false, {\n                    message: '\"value\" must be a valid relative uri',\n                    path: [],\n                    type: 'string.uriRelativeOnly',\n                    context: {\n                        value: 'http://a/g',\n                        label: 'value'\n                    }\n                }],\n                ['http://a/g', false, {\n                    message: '\"value\" must be a valid relative uri',\n                    path: [],\n                    type: 'string.uriRelativeOnly',\n                    context: {\n                        value: 'http://a/g',\n                        label: 'value'\n                    }\n                }],\n                ['file:/asda', false, {\n                    message: '\"value\" must be a valid relative uri',\n                    path: [],\n                    type: 'string.uriRelativeOnly',\n                    context: {\n                        value: 'file:/asda',\n                        label: 'value'\n                    }\n                }],\n                ['qwerty', true],\n                ['invalid uri', false, {\n                    message: '\"value\" must be a valid relative uri',\n                    path: [],\n                    type: 'string.uriRelativeOnly',\n                    context: {\n                        value: 'invalid uri',\n                        label: 'value'\n                    }\n                }],\n                ['1http://google.com', false, {\n                    message: '\"value\" must be a valid relative uri',\n                    path: [],\n                    type: 'string.uriRelativeOnly',\n                    context: {\n                        value: '1http://google.com',\n                        label: 'value'\n                    }\n                }],\n                ['http://testdomain`,.<>/?\\'\";{}][++\\\\|~!@#$%^&*().org', false, {\n                    message: '\"value\" must be a valid relative uri',\n                    path: [],\n                    type: 'string.uriRelativeOnly',\n                    context: {\n                        value: 'http://testdomain`,.<>/?\\'\";{}][++\\\\|~!@#$%^&*().org',\n                        label: 'value'\n                    }\n                }],\n                ['', false, {\n                    message: '\"value\" is not allowed to be empty',\n                    path: [],\n                    type: 'string.empty',\n                    context: { value: '', label: 'value' }\n                }],\n                ['(╯°□°)╯︵ ┻━┻', false, {\n                    message: '\"value\" must be a valid relative uri',\n                    path: [],\n                    type: 'string.uriRelativeOnly',\n                    context: {\n                        value: '(╯°□°)╯︵ ┻━┻',\n                        label: 'value'\n                    }\n                }],\n                ['one/two/three?value=abc&value2=123#david-rules', true],\n                ['//username:password@test.example.com/one/two/three?value=abc&value2=123#david-rules', true],\n                ['http://a\\r\" \\t\\n<\\'b:b@c\\r\\nd/e?f', false, {\n                    message: '\"value\" must be a valid relative uri',\n                    path: [],\n                    type: 'string.uriRelativeOnly',\n                    context: {\n                        value: 'http://a\\r\" \\t\\n<\\'b:b@c\\r\\nd/e?f',\n                        label: 'value'\n                    }\n                }],\n                ['/absolute', true]\n            ]);\n        });\n\n        it('validates relative only uri with domain', () => {\n\n            const schema = Joi.string().uri({ relativeOnly: true, domain: { minDomainSegments: 3, tlds: { allow: ['com'] } } });\n            Helper.validate(schema, [\n                ['foo://example.com:8042/over/there?name=ferret#nose', false, '\"value\" must be a valid relative uri'],\n                ['//test.example.com:8042/over/there?name=ferret#nose', true],\n                ['//example.com:8042/over/there?name=ferret#nose', false, '\"value\" must contain a valid domain name']\n            ]);\n        });\n\n        it('validates allowed relative uri with domain', () => {\n\n            const schema = Joi.string().uri({ allowRelative: true, domain: { minDomainSegments: 3, tlds: { allow: ['com'] } } });\n            Helper.validate(schema, [\n                ['//test.example.com:8042/over/there?name=ferret#nose', true],\n                ['//example.com:8042/over/there?name=ferret#nose', false, '\"value\" must contain a valid domain name'],\n                ['relative', true]\n            ]);\n        });\n\n        it('validates uri with square brackets allowed', () => {\n\n            const schema = Joi.string().uri({ allowQuerySquareBrackets: true });\n\n            Helper.validate(schema, [\n                ['https://example.com?abc[]=123&abc[]=456', true]\n            ]);\n        });\n\n        it('validates uri with accented characters with encoding', () => {\n\n            const schema = Joi.string().uri({ encodeUri: true });\n\n            Helper.validate(schema, { convert: true }, [\n                ['https://linkedin.com/in/aïssa/', true, 'https://linkedin.com/in/a%C3%AFssa/'],\n                ['https://linkedin.com/in/a%C3%AFssa/', true, 'https://linkedin.com/in/a%C3%AFssa/'],\n                ['/#.domain.com/', false, {\n                    message: '\"value\" must be a valid uri',\n                    path: [],\n                    type: 'string.uri',\n                    context: { label: 'value', value: '/#.domain.com/' }\n                }]\n            ]);\n        });\n\n        it('validates relative uri with accented characters with encoding', () => {\n\n            const schema = Joi.string().uri({ encodeUri: true, allowRelative: true });\n\n            Helper.validate(schema, { convert: true }, [\n                ['/in/aïssa/', true, '/in/a%C3%AFssa/'],\n                ['/in/a%C3%AFssa/', true, '/in/a%C3%AFssa/']\n            ]);\n        });\n\n        it('validates uri with encodeUri and scheme', () => {\n\n            const schema = Joi.string().uri({ encodeUri: true, scheme: 'https' });\n\n            Helper.validate(schema, { convert: true }, [\n                ['https://linkedin.com/in/aïssa/', true, 'https://linkedin.com/in/a%C3%AFssa/'],\n                ['http://linkedin.com/in/aïssa/', false, {\n                    message: '\"value\" must be a valid uri with a scheme matching the https pattern',\n                    path: [],\n                    type: 'string.uriCustomScheme',\n                    context: {\n                        scheme: 'https',\n                        value: 'http://linkedin.com/in/aïssa/',\n                        label: 'value'\n                    }\n                }]\n            ]);\n        });\n\n        it('validates uri with accented characters without encoding', () => {\n\n            const schema = Joi.string().uri({ encodeUri: true });\n\n            Helper.validate(schema, { convert: false }, [\n                ['https://linkedin.com/in/aïssa/', false, {\n                    message: '\"value\" must be a valid uri',\n                    path: [],\n                    type: 'string.uri',\n                    context: { value: 'https://linkedin.com/in/aïssa/', label: 'value' }\n                }]\n            ]);\n        });\n\n        it('errors on unknown options', () => {\n\n            expect(() => Joi.string().uri({ foo: 'bar', baz: 'qux' })).to.throw('Options contain unknown keys: foo,baz');\n        });\n    });\n\n    describe('valid()', () => {\n\n        it('validates case sensitive values', () => {\n\n            Helper.validate(Joi.string().valid('a', 'b'), [\n                ['a', true],\n                ['b', true],\n                ['A', false, {\n                    message: '\"value\" must be one of [a, b]',\n                    path: [],\n                    type: 'any.only',\n                    context: { value: 'A', valids: ['a', 'b'], label: 'value' }\n                }],\n                ['B', false, {\n                    message: '\"value\" must be one of [a, b]',\n                    path: [],\n                    type: 'any.only',\n                    context: { value: 'B', valids: ['a', 'b'], label: 'value' }\n                }]\n            ]);\n        });\n\n        it('validates case insensitive values', () => {\n\n            Helper.validate(Joi.string().valid('a', 'b').insensitive(), [\n                ['a', true],\n                ['b', true],\n                ['A', true, 'a'],\n                ['B', true, 'b'],\n                [4, false, {\n                    message: '\"value\" must be one of [a, b]',\n                    path: [],\n                    type: 'any.only',\n                    context: { value: 4, valids: ['a', 'b'], label: 'value' }\n                }]\n            ]);\n        });\n\n        it('validates case insensitive values with non-strings', () => {\n\n            const valids = ['a', 'b', 5, Buffer.from('c')];\n            Helper.validate(Joi.string().valid(...valids).insensitive(), [\n                ['a', true],\n                ['b', true],\n                ['A', true, 'a'],\n                ['B', true, 'b'],\n                [4, false, {\n                    message: '\"value\" must be one of [a, b, 5, c]',\n                    path: [],\n                    type: 'any.only',\n                    context: { value: 4, valids, label: 'value' }\n                }],\n                [5, true],\n                [Buffer.from('c'), true]\n            ]);\n        });\n    });\n});\n"
  },
  {
    "path": "test/types/symbol.js",
    "content": "'use strict';\n\nconst Code = require('@hapi/code');\nconst Lab = require('@hapi/lab');\nconst Joi = require('../..');\n\nconst Helper = require('../helper');\n\n\nconst internals = {};\n\n\nconst { describe, it } = exports.lab = Lab.script();\nconst { expect } = Code;\n\n\ndescribe('symbol', () => {\n\n    it('cannot be called on its own', () => {\n\n        const symbol = Joi.symbol;\n        expect(() => symbol()).to.throw('Must be invoked on a Joi instance.');\n    });\n\n    it('should throw an exception if arguments were passed.', () => {\n\n        expect(() => Joi.symbol('invalid argument.')).to.throw('The symbol type does not allow arguments');\n    });\n\n    describe('clone()', () => {\n\n        it('clones a symbols type', () => {\n\n            const schema = Joi.symbol();\n            const clone = schema.clone();\n            Helper.equal(schema, clone);\n            expect(schema).to.not.shallow.equal(clone);\n        });\n    });\n\n    describe('validate()', () => {\n\n        it('handles plain symbols', () => {\n\n            const symbols = [Symbol(1), Symbol(2)];\n            const rule = Joi.symbol();\n            Helper.validate(rule, [\n                [symbols[0], true, symbols[0]],\n                [symbols[1], true, symbols[1]],\n                [1, false, {\n                    message: '\"value\" must be a symbol',\n                    path: [],\n                    type: 'symbol.base',\n                    context: { label: 'value', value: 1 }\n                }]\n            ]);\n        });\n\n        it('handles simple lookup', () => {\n\n            const symbols = [Symbol(1), Symbol(2)];\n            const otherSymbol = Symbol(1);\n            const rule = Joi.symbol().valid(...symbols);\n            Helper.validate(rule, [\n                [symbols[0], true, symbols[0]],\n                [symbols[1], true, symbols[1]],\n                [otherSymbol, false, {\n                    message: '\"value\" must be one of [Symbol(1), Symbol(2)]',\n                    path: [],\n                    type: 'any.only',\n                    context: { value: otherSymbol, label: 'value', valids: symbols }\n                }]\n            ]);\n        });\n\n        describe('map', () => {\n\n            it('converts keys to correct symbol', () => {\n\n                const symbols = [Symbol(1), Symbol(2)];\n                const otherSymbol = Symbol(1);\n                const map = new Map([[1, symbols[0]], ['two', symbols[1]]]);\n                const rule = Joi.symbol().map(map);\n                Helper.validate(rule, [\n                    [1, true, symbols[0]],\n                    [symbols[0], true, symbols[0]],\n                    ['1', false, {\n                        message: `\"value\" must be one of [1 -> Symbol(1), two -> Symbol(2)]`,\n                        path: [],\n                        type: 'symbol.map',\n                        context: { label: 'value', value: '1', map }\n                    }],\n                    ['two', true, symbols[1]],\n                    [otherSymbol, false, {\n                        message: '\"value\" must be one of [Symbol(1), Symbol(2)]',\n                        path: [],\n                        type: 'any.only',\n                        context: { value: otherSymbol, label: 'value', valids: symbols }\n                    }]\n                ]);\n            });\n\n            it('converts keys from object', () => {\n\n                const symbols = [Symbol('one'), Symbol('two')];\n                const otherSymbol = Symbol('one');\n                const rule = Joi.symbol().map({ one: symbols[0], two: symbols[1] });\n                Helper.validate(rule, [\n                    [symbols[0], true, symbols[0]],\n                    ['one', true, symbols[0]],\n                    ['two', true, symbols[1]],\n                    [otherSymbol, false, {\n                        message: '\"value\" must be one of [Symbol(one), Symbol(two)]',\n                        path: [],\n                        type: 'any.only',\n                        context: { value: otherSymbol, label: 'value', valids: symbols }\n                    }],\n                    ['toString', false, {\n                        message: `\"value\" must be one of [one -> Symbol(one), two -> Symbol(two)]`,\n                        path: [],\n                        type: 'symbol.map',\n                        context: { label: 'value', value: 'toString', map: new Map([['one', symbols[0]], ['two', symbols[1]]]) }\n                    }]\n                ]);\n            });\n\n            it('appends to existing map', () => {\n\n                const symbols = [Symbol(1), Symbol(2)];\n                const otherSymbol = Symbol(1);\n                const rule = Joi.symbol().map([[1, symbols[0]]]).map([[2, symbols[1]]]);\n                Helper.validate(rule, [\n                    [1, true, symbols[0]],\n                    [2, true, symbols[1]],\n                    [otherSymbol, false, {\n                        message: '\"value\" must be one of [Symbol(1), Symbol(2)]',\n                        path: [],\n                        type: 'any.only',\n                        context: { value: otherSymbol, label: 'value', valids: symbols }\n                    }]\n                ]);\n            });\n\n            it('throws on bad input', () => {\n\n                expect(\n                    () => Joi.symbol().map()\n                ).to.throw('Iterable must be an iterable or object');\n\n                expect(\n                    () => Joi.symbol().map(Symbol())\n                ).to.throw('Iterable must be an iterable or object');\n\n                expect(\n                    () => Joi.symbol().map([undefined])\n                ).to.throw('Entry must be an iterable');\n\n                expect(\n                    () => Joi.symbol().map([123])\n                ).to.throw('Entry must be an iterable');\n\n                expect(\n                    () => Joi.symbol().map([[123, 456]])\n                ).to.throw('Value must be a Symbol');\n\n                expect(\n                    () => Joi.symbol().map([[{}, Symbol()]])\n                ).to.throw('Key must not be of type object, function, or Symbol');\n\n                expect(\n                    () => Joi.symbol().map([[() => { }, Symbol()]])\n                ).to.throw('Key must not be of type object, function, or Symbol');\n\n                expect(\n                    () => Joi.symbol().map([[Symbol(), Symbol()]])\n                ).to.throw('Key must not be of type object, function, or Symbol');\n            });\n        });\n\n        it('handles plain symbols when convert is disabled', () => {\n\n            const symbols = [Symbol(1), Symbol(2)];\n            const schema = Joi.symbol().map([[1, symbols[0]], ['two', symbols[1]]]).prefs({ convert: false });\n            Helper.validate(schema, [[symbols[1], true, symbols[1]]]);\n        });\n\n        it('errors on mapped input and convert is disabled', () => {\n\n            const symbols = [Symbol(1), Symbol(2)];\n            const schema = Joi.symbol().map([[1, symbols[0]], ['two', symbols[1]]]).prefs({ convert: false });\n            Helper.validate(schema, [[1, false, {\n                message: '\"value\" must be one of [Symbol(1), Symbol(2)]',\n                path: [],\n                type: 'any.only',\n                context: { value: 1, valids: symbols, label: 'value' }\n            }]]);\n        });\n    });\n});\n"
  },
  {
    "path": "test/validator.js",
    "content": "'use strict';\n\nconst Code = require('@hapi/code');\nconst Hoek = require('@hapi/hoek');\nconst Joi = require('..');\nconst Lab = require('@hapi/lab');\n\nconst Helper = require('./helper');\n\nconst internals = {};\n\n\nconst { describe, it } = exports.lab = Lab.script();\nconst { expect } = Code;\n\n\ndescribe('Validator', () => {\n\n    describe('entryAsync()', () => {\n\n        it('should work with a successful promise', async () => {\n\n            expect(await Joi.string().validateAsync('foo')).to.equal('foo');\n        });\n\n        it('should work with a successful promise and a catch in between', () => {\n\n            const promise = Joi.string().validateAsync('foo');\n\n            return promise\n                .catch(() => {\n\n                    throw new Error('Should not go here');\n                })\n                .then((value) => {\n\n                    expect(value).to.equal('foo');\n                }, () => {\n\n                    throw new Error('Should not go here');\n                });\n        });\n\n        it('should work with a failing promise', () => {\n\n            const promise = Joi.string().validateAsync(0);\n\n            return promise.then((value) => {\n\n                throw new Error('Should not go here');\n            }, (err) => {\n\n                expect(err).to.be.an.error('\"value\" must be a string');\n                expect(err.details).to.equal([{\n                    message: '\"value\" must be a string',\n                    path: [],\n                    type: 'string.base',\n                    context: { value: 0, label: 'value' }\n                }]);\n            });\n        });\n\n        it('should work with a failing promise and a then in between', () => {\n\n            const promise = Joi.string().validateAsync(0);\n\n            return promise\n                .then((value) => {\n\n                    throw new Error('Should not go here');\n                })\n                .catch((err) => {\n\n                    expect(err).to.be.an.error('\"value\" must be a string');\n                    expect(err.details).to.equal([{\n                        message: '\"value\" must be a string',\n                        path: [],\n                        type: 'string.base',\n                        context: { value: 0, label: 'value' }\n                    }]);\n                });\n        });\n\n        it('should work with a failing promise (with catch)', () => {\n\n            const promise = Joi.string().validateAsync(0);\n\n            return promise.catch((err) => {\n\n                expect(err).to.be.an.error('\"value\" must be a string');\n                expect(err.details).to.equal([{\n                    message: '\"value\" must be a string',\n                    path: [],\n                    type: 'string.base',\n                    context: { value: 0, label: 'value' }\n                }]);\n            });\n        });\n\n        it('should catch errors in a successful promise callback', () => {\n\n            const promise = Joi.string().validateAsync('foo');\n\n            return promise.then((value) => {\n\n                throw new Error('oops');\n            }).then(() => {\n\n                throw new Error('Should not go here');\n            }, (err) => {\n\n                expect(err).to.be.an.error('oops');\n            });\n        });\n\n        it('should catch errors in a failing promise callback', () => {\n\n            const promise = Joi.string().validateAsync(0);\n\n            return promise.then((value) => {\n\n                throw new Error('Should not go here');\n            }, () => {\n\n                throw new Error('oops');\n            }).then(() => {\n\n                throw new Error('Should not go here');\n            }, (err) => {\n\n                expect(err).to.be.an.error('oops');\n            });\n        });\n\n        it('should catch errors in a failing promise callback (with catch)', () => {\n\n            const promise = Joi.string().validateAsync(0);\n\n            return promise.catch(() => {\n\n                throw new Error('oops');\n            }).then(() => {\n\n                throw new Error('Should not go here');\n            }, (err) => {\n\n                expect(err).to.be.an.error('oops');\n            });\n        });\n\n        it('validates schema', async () => {\n\n            const schema = Joi.number();\n            expect(await schema.validateAsync(5)).to.equal(5);\n        });\n\n        it('validates schema with warnings (empty)', async () => {\n\n            const schema = Joi.number();\n            expect(await schema.validateAsync(5, { warnings: true })).to.equal({ value: 5 });\n        });\n    });\n\n    describe('externals()', () => {\n\n        it('executes externals on object child', async () => {\n\n            const check = async (id) => {\n\n                await Hoek.wait(0);\n\n                if (id === 'valid') {\n                    return 'verified';\n                }\n\n                if (id === 'skip') {\n                    return;\n                }\n\n                throw new Error('Invalid id');\n            };\n\n            const append = async (id) => {\n\n                await Hoek.wait(0);\n                return id + '!';\n            };\n\n            const schema = Joi.object({\n                id: Joi.string().external(check).external(append)\n            });\n\n            expect(await schema.validateAsync({ id: 'valid' })).to.equal({ id: 'verified!' });\n            expect(await schema['~standard'].validate({ id: 'valid' })).to.equal({ value: { id: 'verified!' } });\n\n            expect(await schema.validateAsync({ id: 'skip' })).to.equal({ id: 'skip!' });\n            expect(await schema['~standard'].validate({ id: 'skip' })).to.equal({ value: { id: 'skip!' } });\n        });\n\n        it('executes externals on nested object child', async () => {\n\n            const check = async (id) => {\n\n                await Hoek.wait(0);\n\n                if (id === 'valid') {\n                    return 'verified';\n                }\n\n                if (id === 'skip') {\n                    return;\n                }\n\n                if (id === 'unchanged') {\n                    return id;\n                }\n\n                throw new Error('Invalid id');\n            };\n\n            const append = async (id) => {\n\n                await Hoek.wait(0);\n                return id + '!';\n            };\n\n            const schema = Joi.object({\n                user: {\n                    id: Joi.string().external(check).external(append)\n                }\n            });\n\n            expect(await schema.validateAsync({ user: { id: 'valid' } })).to.equal({ user: { id: 'verified!' } });\n            expect(await schema['~standard'].validate({ user: { id: 'valid' } })).to.equal({ value: { user: { id: 'verified!' } } });\n\n            expect(await schema.validateAsync({ user: { id: 'skip' } })).to.equal({ user: { id: 'skip!' } });\n            expect(await schema['~standard'].validate({ user: { id: 'skip' } })).to.equal({ value: { user: { id: 'skip!' } } });\n\n            expect(await schema.validateAsync({ user: { id: 'unchanged' } })).to.equal({ user: { id: 'unchanged!' } });\n            expect(await schema['~standard'].validate({ user: { id: 'unchanged' } })).to.equal({ value: { user: { id: 'unchanged!' } } });\n\n            await expect(schema.validateAsync({ user: { id: 'other' } })).to.reject('Invalid id (user.id)');\n            expect(await schema['~standard'].validate({ user: { id: 'other' } })).to.equal({ issues: [{ message: 'Invalid id (user.id)' }] });\n        });\n\n        it('executes externals on root', async () => {\n\n            const check = async (id) => {\n\n                await Hoek.wait(0);\n                if (id === 'valid') {\n                    return 'verified';\n                }\n\n                throw new Error('Invalid id');\n            };\n\n            const append = async (id) => {\n\n                await Hoek.wait(0);\n                return id + '!';\n            };\n\n            const schema = Joi.string().external(check).external(append);\n\n            const result = await schema.validateAsync('valid');\n            expect(result).to.equal('verified!');\n            expect(await schema['~standard'].validate('valid')).to.equal({ value: 'verified!' });\n        });\n\n        it('executes externals on array item', async () => {\n\n            const check = async (id) => {\n\n                await Hoek.wait(0);\n\n                if (id === 'valid') {\n                    return 'verified';\n                }\n\n                if (id === 'skip') {\n                    return;\n                }\n\n                throw new Error('Invalid id');\n            };\n\n            const append = async (id) => {\n\n                await Hoek.wait(0);\n                return id + '!';\n            };\n\n            const schema = Joi.array().items(Joi.string().external(check).external(append));\n\n            expect(await schema.validateAsync(['valid'])).to.equal(['verified!']);\n            expect(await schema['~standard'].validate(['valid'])).to.equal({ value: ['verified!'] });\n\n            expect(await schema.validateAsync(['skip'])).to.equal(['skip!']);\n            expect(await schema['~standard'].validate(['skip'])).to.equal({ value: ['skip!'] });\n        });\n\n        it('executes externals on array', async () => {\n\n            const schema = Joi.array().items(Joi.string()).external((value) => [...value, 'extra']);\n\n            expect(await schema.validateAsync(['valid'])).to.equal(['valid', 'extra']);\n            expect(await schema['~standard'].validate(['valid'])).to.equal({ value: ['valid', 'extra'] });\n        });\n\n        it('skips externals when prefs is false', async () => {\n\n            const check = (id) => {\n\n                throw new Error('Invalid id');\n            };\n\n            const schema = Joi.object({\n                id: Joi.string().external(check)\n            });\n\n            await expect(schema.validateAsync({ id: 'valid' })).to.reject('Invalid id (id)');\n            expect(await schema['~standard'].validate({ id: 'valid' })).to.equal({ issues: [{ message: 'Invalid id (id)' }] });\n\n            expect(() => schema.validate({ id: 'valid' }, { externals: false })).to.not.throw();\n            expect(() => schema.validate({ id: 'valid' })).to.throw('Schema with external rules must use validateAsync()');\n        });\n\n        it('skips externals when validation failed', async () => {\n\n            let called = false;\n            const check = (id) => {\n\n                called = true;\n            };\n\n            const schema = Joi.object({\n                id: Joi.string().min(10).external(check)\n            });\n\n            await expect(schema.validateAsync({ id: 'valid' })).to.reject('\"id\" length must be at least 10 characters long');\n            expect(await schema['~standard'].validate({ id: 'valid' })).to.equal({ issues: [{ message: '\"id\" length must be at least 10 characters long', path: ['id'] }] });\n            expect(called).to.be.false();\n        });\n\n        it('supports describe', () => {\n\n            const append = async (id) => {\n\n                await Hoek.wait(0);\n                return id + '!';\n            };\n\n            const schema = Joi.string().external(append);\n            const description = schema.describe();\n            expect(description).to.equal({ type: 'string', externals: [{ method: append }] });\n        });\n\n        it('skips when used to match', async () => {\n\n            let called = false;\n            const check = () => {\n\n                called = true;\n            };\n\n            const schema = Joi.array().has(Joi.string().external(check));\n\n            const result = await schema.validateAsync(['valid']);\n            expect(result).to.equal(['valid']);\n            expect(await schema['~standard'].validate(['valid'])).to.equal({ value: ['valid'] });\n\n            expect(called).to.be.false();\n        });\n\n        it('does not modify original value on generic object', async () => {\n\n            const tag = (obj) => {\n\n                obj.x = true;\n            };\n\n            const schema = Joi.object().external(tag);\n            const input = { x: false };\n\n            const result = await schema.validateAsync(input);\n            expect(result).to.equal({ x: true });\n            expect(await schema['~standard'].validate(input)).to.equal({ value: { x: true } });\n\n            expect(input).to.equal({ x: false });\n        });\n\n        it('does not modify original value on nested object', async () => {\n\n            const tag = (obj) => {\n\n                obj.x = true;\n            };\n\n            const schema = Joi.object({ a: Joi.object().external(tag) });\n            const input = { a: { x: false } };\n\n            const result = await schema.validateAsync(input);\n            expect(result).to.equal({ a: { x: true } });\n            expect(await schema['~standard'].validate(input)).to.equal({ value: { a: { x: true } } });\n\n            expect(input).to.equal({ a: { x: false } });\n        });\n\n        it('does not modify original value on generic array', async () => {\n\n            const tag = (array) => {\n\n                array.push('x');\n            };\n\n            const schema = Joi.array().external(tag);\n            const input = [1];\n\n            const result = await schema.validateAsync(input);\n            expect(result).to.equal([1, 'x']);\n            expect(await schema['~standard'].validate(input)).to.equal({ value: [1, 'x'] });\n\n            expect(input).to.equal([1]);\n        });\n\n        it('does not modify original value on nested array', async () => {\n\n            const tag = (array) => {\n\n                array.push('x');\n            };\n\n            const schema = Joi.array().items(Joi.array().external(tag));\n            const input = [[1]];\n\n            const result = await schema.validateAsync(input);\n            expect(result).to.equal([[1, 'x']]);\n            expect(await schema['~standard'].validate(input)).to.equal({ value: [[1, 'x']] });\n            expect(input).to.equal([[1]]);\n        });\n\n        it('has access to helpers', async () => {\n\n            const context = { foo: 'bar' };\n\n            const tag = (value, helpers) => {\n\n                expect(value).to.equal('my stringmy string');\n                expect(helpers).to.be.an.object();\n                expect(Joi.isSchema(helpers.schema)).to.be.true();\n                expect(helpers.schema.type).to.equal('string');\n                expect(helpers.state).to.be.an.object();\n                expect(helpers.state.ancestors).to.equal([]);\n                expect(helpers.state.path).to.equal([]);\n                expect(helpers.prefs.context).to.equal({ foo: 'bar' });\n                expect(helpers.original).to.equal('my string');\n                expect(helpers.warn).to.be.a.function();\n                expect(helpers.message).to.be.a.function();\n                return helpers.prefs.context.foo;\n            };\n\n            const schema = Joi.string().custom((v) => v + v).external(tag);\n            const input = 'my string';\n\n            const result = await schema.validateAsync(input, { context });\n            expect(result).to.equal('bar');\n        });\n\n        it('has access to helpers on nested objects', async () => {\n\n            const context = { foo: 'bar' };\n\n            const ext = (value, helpers) => {\n\n                expect(value).to.equal('my stringmy string');\n                expect(helpers).to.be.an.object();\n                expect(Joi.isSchema(helpers.schema)).to.be.true();\n                expect(helpers.schema.type).to.equal('string');\n                expect(helpers.state).to.be.an.object();\n                expect(helpers.state.ancestors).to.equal([\n                    { parent: 'my stringmy string' },\n                    {\n                        grandparent: { parent: 'my stringmy string' }\n                    }\n                ]);\n                expect(helpers.state.path).to.equal(['grandparent', 'parent']);\n                expect(helpers.prefs.context).to.equal({ foo: 'bar' });\n                expect(helpers.original).to.equal('my string');\n                expect(helpers.warn).to.be.a.function();\n                expect(helpers.message).to.be.a.function();\n                return helpers.prefs.context.foo;\n            };\n\n            const schema = Joi.object({\n                grandparent: Joi.object({\n                    parent: Joi.string().custom((v) => v + v).external(ext)\n                })\n            });\n            const input = { grandparent: { parent: 'my string' } };\n\n            const result = await schema.validateAsync(input, { context });\n            expect(result).to.equal({ grandparent: { parent: 'bar' } });\n        });\n\n        it('changes the message depending on label\\'s value', async () => {\n\n            const context = { foo: 'bar' };\n\n            const tag = (value, { prefs }) => {\n\n                throw new Error('Oops');\n            };\n\n            const schema = Joi.string().external(tag);\n            const input = 'my string';\n\n            await expect(schema.validateAsync(input, { context })).to.reject('Oops (value)');\n            await expect(schema.validateAsync(input, { context, errors: { label: false } })).to.reject('Oops');\n        });\n\n        it('should add warnings when warn helper is used', async () => {\n\n            const ext = (value, { warn }) => {\n\n                const newValue = value + value;\n\n                warn('string.max', { limit: 10, value: newValue });\n                warn('string.min', { limit: 1, value: newValue });\n\n                return newValue;\n            };\n\n            const schema = Joi.string().external(ext);\n            const input = 'my string';\n\n            const result = await schema.validateAsync(input, { warnings: true });\n            expect(result.value).to.equal('my stringmy string');\n            expect(result.warning).to.equal({\n                message: '\"value\" length must be less than or equal to 10 characters long. \"value\" length must be at least 1 characters long',\n                details: [\n                    {\n                        message: '\"value\" length must be less than or equal to 10 characters long',\n                        path: [],\n                        type: 'string.max',\n                        context: {\n                            label: 'value',\n                            limit: 10,\n                            value: 'my stringmy string'\n                        }\n                    },\n                    {\n                        message: '\"value\" length must be at least 1 characters long',\n                        path: [],\n                        type: 'string.min',\n                        context: {\n                            label: 'value',\n                            limit: 1,\n                            value: 'my stringmy string'\n                        }\n                    }\n                ]\n            });\n        });\n\n        it('should add errors when error helper is used', async () => {\n\n            const ext = (value, { error }) => {\n\n                const newValue = value + value;\n\n                return error('string.max', { limit: 10, value: newValue });\n            };\n\n            const schema = Joi.string().external(ext);\n            const input = 'my string';\n\n            const error = await expect(schema.validateAsync(input)).to.reject('\"value\" length must be less than or equal to 10 characters long');\n            expect(error.details).to.equal([{\n                message: '\"value\" length must be less than or equal to 10 characters long',\n                path: [],\n                type: 'string.max',\n                context: {\n                    label: 'value',\n                    limit: 10,\n                    value: 'my stringmy string'\n                }\n            }]);\n            expect(await schema['~standard'].validate(input)).to.equal({ issues: [{ message: '\"value\" length must be less than or equal to 10 characters long', path: [] }] });\n        });\n\n        it('should add multiple errors when errorsArray helper is used', async () => {\n\n            const ext = (value, { error, errorsArray }) => {\n\n                const newValue = value + value;\n\n                const errors = errorsArray();\n                errors.push(\n                    error('string.max', { limit: 10, value: newValue }),\n                    error('string.min', { limit: 1, value: newValue })\n                );\n                return errors;\n            };\n\n            const schema = Joi.string().external(ext);\n            const input = 'my string';\n\n            const error = await expect(schema.validateAsync(input)).to.reject('\"value\" length must be less than or equal to 10 characters long. \"value\" length must be at least 1 characters long');\n            expect(error.details).to.equal([{\n                message: '\"value\" length must be less than or equal to 10 characters long',\n                path: [],\n                type: 'string.max',\n                context: {\n                    label: 'value',\n                    limit: 10,\n                    value: 'my stringmy string'\n                }\n            }, {\n                message: '\"value\" length must be at least 1 characters long',\n                path: [],\n                type: 'string.min',\n                context: {\n                    label: 'value',\n                    limit: 1,\n                    value: 'my stringmy string'\n                }\n            }]);\n            expect(await schema['~standard'].validate(input)).to.equal({ issues: [{ message: '\"value\" length must be less than or equal to 10 characters long', path: [] }, { message: '\"value\" length must be at least 1 characters long', path: [] }] });\n        });\n\n        it('should add a custom error when message helper is used', async () => {\n\n            const ext = (value, { message }) => {\n\n                return message('{{#label}} has an invalid value {{#value}} ({{#custom}})', { custom: 'denied' });\n            };\n\n            const schema = Joi.string().external(ext);\n            const input = 'my string';\n\n            const error = await expect(schema.validateAsync(input)).to.reject('\"value\" has an invalid value my string (denied)');\n            expect(error.details).to.equal([{\n                message: '\"value\" has an invalid value my string (denied)',\n                path: [],\n                type: 'external',\n                context: {\n                    label: 'value',\n                    value: 'my string',\n                    custom: 'denied'\n                }\n            }]);\n            expect(await schema['~standard'].validate(input)).to.equal({ issues: [{ message: '\"value\" has an invalid value my string (denied)', path: [] }] });\n        });\n\n        it('should add warnings when warn helper is used on a link', async () => {\n\n            const schema = Joi.object({\n                a: Joi.number(),\n                b: Joi.link('a').external((value, helpers) => {\n\n                    expect(helpers.schema.type).to.equal('link');\n                    expect(helpers.linked.type).to.equal('number');\n                    expect(helpers.state.schemas).to.have.length(2);\n                    expect(helpers.state.schemas[0].schema.type).to.equal('link');\n                    expect(helpers.state.schemas[1].schema.type).to.equal('object');\n                    helpers.warn('number.max', { limit: 1, value });\n                    return value * 2;\n                })\n            });\n\n            const result = await schema.validateAsync({ a: 1, b: 4 }, { warnings: true });\n            expect(result.value).to.equal({ a: 1, b: 8 });\n            expect(result.warning).to.equal({\n                message: '\"b\" must be less than or equal to 1',\n                details: [{\n                    context: {\n                        key: 'b',\n                        label: 'b',\n                        limit: 1,\n                        value: 4\n                    },\n                    message: '\"b\" must be less than or equal to 1',\n                    path: ['b'],\n                    type: 'number.max'\n                }]\n            });\n        });\n\n        it('should add errors when error helper is used on a link', async () => {\n\n            const schema = Joi.object({\n                a: Joi.number(),\n                b: Joi.link('a').external((value, helpers) => {\n\n                    expect(helpers.schema.type).to.equal('link');\n                    expect(helpers.linked.type).to.equal('number');\n                    expect(helpers.state.schemas).to.have.length(2);\n                    expect(helpers.state.schemas[0].schema.type).to.equal('link');\n                    expect(helpers.state.schemas[1].schema.type).to.equal('object');\n                    return helpers.error('number.max', { limit: 1, value });\n                })\n            });\n\n            const error = await expect(schema.validateAsync({ a: 1, b: 4 }, { warnings: true })).to.reject('\"b\" must be less than or equal to 1');\n            expect(error.details).to.equal([{\n                context: {\n                    key: 'b',\n                    label: 'b',\n                    limit: 1,\n                    value: 4\n                },\n                message: '\"b\" must be less than or equal to 1',\n                path: ['b'],\n                type: 'number.max'\n            }]);\n        });\n\n        it('should add a custom error when message helper is used on a link', async () => {\n\n            const schema = Joi.object({\n                a: Joi.number(),\n                b: Joi.link('a').external((value, helpers) => {\n\n                    return helpers.message('{{#label}} should be {{#sign}} {{#value}}', { sign: '>' });\n                })\n            });\n\n            const error = await expect(schema.validateAsync({ a: 1, b: 4 })).to.reject('\"b\" should be > 4');\n            expect(error.details).to.equal([{\n                message: '\"b\" should be > 4',\n                path: ['b'],\n                type: 'external',\n                context: {\n                    key: 'b',\n                    label: 'b',\n                    value: 4,\n                    sign: '>'\n                }\n            }]);\n            expect(await schema['~standard'].validate({ a: 1, b: 4 })).to.equal({ issues: [{ message: '\"b\" should be > 4', path: ['b'] }] });\n        });\n\n        it('should call multiple externals when abortEarly is false and error helper is used', async () => {\n\n            const ext1 = (value, { error }) => {\n\n                const newValue = value + value;\n\n                return error('string.max', { limit: 10, value: newValue });\n            };\n\n            const ext2 = (value, { error, errorsArray }) => {\n\n                const errors = errorsArray();\n                errors.push(\n                    error('string.min', { limit: 8, value }),\n                    error('string.max', { limit: 15, value })\n                );\n                return errors;\n            };\n\n            const schema = Joi.string().external(ext1).external(ext2);\n            const input = 'my string';\n\n            const error = await expect(schema.validateAsync(input, { abortEarly: false })).to.reject('\"value\" length must be less than or equal to 10 characters long. \"value\" length must be at least 8 characters long. \"value\" length must be less than or equal to 15 characters long');\n            expect(error.details).to.equal([\n                {\n                    message: '\"value\" length must be less than or equal to 10 characters long',\n                    path: [],\n                    type: 'string.max',\n                    context: {\n                        label: 'value',\n                        limit: 10,\n                        value: 'my stringmy string'\n                    }\n                },\n                {\n                    message: '\"value\" length must be at least 8 characters long',\n                    path: [],\n                    type: 'string.min',\n                    context: {\n                        label: 'value',\n                        limit: 8,\n                        value: 'my string'\n                    }\n                },\n                {\n                    message: '\"value\" length must be less than or equal to 15 characters long',\n                    path: [],\n                    type: 'string.max',\n                    context: {\n                        label: 'value',\n                        limit: 15,\n                        value: 'my string'\n                    }\n                }\n            ]);\n        });\n\n        it('should add debug information', async () => {\n\n            const ext1 = (value, { error }) => {\n\n                const newValue = value + value;\n\n                return newValue;\n            };\n\n            const ext2 = (value, { error, errorsArray }) => {\n\n                const errors = errorsArray();\n                errors.push(\n                    error('string.min', { limit: 8, value }),\n                    error('string.max', { limit: 15, value })\n                );\n                return errors;\n            };\n\n            const schema = Joi.string().external(ext1).external(ext2);\n            const input = 'my string';\n\n            const error = await expect(schema.validateAsync(input, { abortEarly: false, debug: true })).to.reject();\n            expect(error.debug).to.equal( [\n                { type: 'entry', path: [] },\n                { type: 'value', by: 'rule', path: [], from: 'my string', name: 'external', to: 'my stringmy string' },\n                { type: 'rule', name: 'external', result: 'error', path: [] },\n                { type: 'resolve', ref: 'ref:local:label', to: 'value', path: [] },\n                { type: 'resolve', ref: 'ref:local:limit', to: 8, path: [] },\n                { type: 'resolve', ref: 'ref:local:label', to: 'value', path: [] },\n                { type: 'resolve', ref: 'ref:local:limit', to: 15, path: [] }\n            ]);\n        });\n    });\n\n    describe('finalize()', () => {\n\n        it('applies raw after validation', async () => {\n\n            const schema = Joi.object({\n                a: Joi.number().raw(),\n                b: Joi.ref('a')\n            });\n\n            expect(await schema.validateAsync({ a: '5', b: 5 })).to.equal({ a: '5', b: 5 });\n        });\n    });\n\n    describe('warnings', () => {\n\n        it('reports warnings (sync)', () => {\n\n            const schema = Joi.any().warning('custom.x', { w: 'world' }).message({ 'custom.x': 'hello {#w}!' });\n            const { value, error, warning } = schema.validate('something');\n            expect(value).to.equal('something');\n            expect(error).to.not.exist();\n            expect(warning).to.equal({\n                message: 'hello world!',\n                details: [\n                    {\n                        message: 'hello world!',\n                        path: [],\n                        type: 'custom.x',\n                        context: {\n                            w: 'world',\n                            label: 'value',\n                            value: 'something'\n                        }\n                    }\n                ]\n            });\n        });\n\n        it('reports warnings (async)', async () => {\n\n            const schema = Joi.any().warning('custom.x', { w: 'world' }).message({ 'custom.x': 'hello {#w}!' });\n            const { value, warning } = await schema.validateAsync('something', { warnings: true });\n            expect(value).to.equal('something');\n            expect(warning).to.equal({\n                message: 'hello world!',\n                details: [\n                    {\n                        message: 'hello world!',\n                        path: [],\n                        type: 'custom.x',\n                        context: {\n                            w: 'world',\n                            label: 'value',\n                            value: 'something'\n                        }\n                    }\n                ]\n            });\n        });\n\n        it('reports warnings with externals', async () => {\n\n            const append = (value) => value + '!';\n            const schema = Joi.string()\n                .warning('custom.x').message('test')\n                .external(append);\n\n            expect(await schema.validateAsync('x', { externals: true, warnings: true })).to.equal({\n                value: 'x!',\n                warning: {\n                    message: 'test',\n                    details: [\n                        {\n                            message: 'test',\n                            path: [],\n                            type: 'custom.x',\n                            context: {\n                                label: 'value',\n                                value: 'x'\n                            }\n                        }\n                    ]\n                }\n            });\n        });\n\n        it('does not report warnings on mismatched array items', async () => {\n\n            const schema = Joi.array().items(\n                Joi.string().min(5).warning('custom.x'),\n                Joi.string()\n            );\n\n            expect(await schema.validateAsync(['x', 'y'], { warnings: true, abortEarly: false })).to.equal({ value: ['x', 'y'] });\n        });\n\n        it('does not report warnings on mismatched alternatives', async () => {\n\n            const schema = Joi.alternatives().try(\n                Joi.string().min(5).warning('custom.x'),\n                Joi.string()\n            );\n\n            expect(await schema.validateAsync('x', { warnings: true, abortEarly: false })).to.equal({ value: 'x' });\n        });\n\n        it('does not report warnings on mismatched alternatives with match mode \"one\"', async () => {\n\n            const schema = Joi.alternatives().try(\n                Joi.string().min(5).warning('custom.x'),\n                Joi.string()\n            ).match('one');\n\n            expect(await schema.validateAsync('x', { warnings: true, abortEarly: false })).to.equal({ value: 'x' });\n        });\n\n        it('does not report custom warnings on mismatched array items', async () => {\n\n            const schema = Joi.array().items(\n                Joi.string().min(5).custom((value, { warn }) => {\n\n                    warn('custom.x');\n                    return value;\n                }),\n                Joi.string()\n            );\n\n            expect(await schema.validateAsync(['x', 'y'], { warnings: true, abortEarly: false })).to.equal({ value: ['x', 'y'] });\n        });\n\n        it('does not report custom warnings on mismatched alternatives', async () => {\n\n            const schema = Joi.alternatives().try(\n                Joi.string().min(5).custom((value, { warn }) => {\n\n                    warn('custom.x');\n                    return value;\n                }),\n                Joi.string()\n            );\n\n            expect(await schema.validateAsync('x', { warnings: true, abortEarly: false })).to.equal({ value: 'x' });\n        });\n\n        it('does not report custom warnings on mismatched alternatives with match mode \"one\"', async () => {\n\n            const schema = Joi.alternatives().try(\n                Joi.string().min(5).custom((value, { warn }) => {\n\n                    warn('custom.x');\n                    return value;\n                }),\n                Joi.string()\n            ).match('one');\n\n            expect(await schema.validateAsync('x', { warnings: true, abortEarly: false })).to.equal({ value: 'x' });\n        });\n    });\n\n    describe('Shadow', () => {\n\n        it('ignores result flags on root values', () => {\n\n            const schema = Joi.string().strip();\n            Helper.validate(schema, [['xyz', true, undefined]]);\n        });\n\n        it('reaches deep into shadow', async () => {\n\n            const schema = Joi.object({\n                a: {\n                    b: {\n                        c: {\n                            d: {\n                                e: Joi.number().raw()\n                            },\n                            g: Joi.boolean().raw()\n                        }\n                    }\n                },\n                f: Joi.ref('a.b.c.d.e'),\n                h: Joi.ref('a.b.c.g')\n            });\n\n            const value = { a: { b: { c: { d: { e: '100' }, g: 'TRUE' } } }, f: 100, h: true };\n\n            expect(await schema.validateAsync(value)).to.equal(value);\n        });\n    });\n});\n"
  },
  {
    "path": "test/values.js",
    "content": "'use strict';\n\nconst Code = require('@hapi/code');\nconst Joi = require('..');\nconst Lab = require('@hapi/lab');\n\nconst Helper = require('./helper');\nconst Values = require('../lib/values');\n\n\nconst internals = {};\n\n\nconst { describe, it } = exports.lab = Lab.script();\nconst { expect } = Code;\n\n\ndescribe('Values', () => {\n\n    describe('add()', () => {\n\n        it('allows valid values to be set', () => {\n\n            expect(() => {\n\n                const set = new Values();\n                set.add(true);\n                set.add(1);\n                set.add('hello');\n                set.add(new Date());\n                set.add(Symbol('foo'));\n            }).not.to.throw();\n        });\n\n        it('ignores refs added multiple times', () => {\n\n            const set = new Values();\n            const ref = Joi.ref('x');\n            set.add(ref);\n            set.add(ref);\n            expect(set).to.have.length(1);\n        });\n    });\n\n    describe('clone()', () => {\n\n        it('returns a new Values', () => {\n\n            const set = new Values();\n            set.add(null);\n            const otherValids = set.clone();\n            otherValids.add('null');\n            expect(set.has(null)).to.equal(true);\n            expect(otherValids.has(null)).to.equal(true);\n            expect(set.has('null')).to.equal(false);\n            expect(otherValids.has('null')).to.equal(true);\n        });\n    });\n\n    describe('concat()', () => {\n\n        it('merges into a new Values', () => {\n\n            const set = new Values();\n            const otherValids = set.clone();\n            set.add(null);\n            otherValids.add('null');\n            const thirdSet = otherValids.concat(set);\n            expect(set.has(null)).to.equal(true);\n            expect(otherValids.has(null)).to.equal(false);\n            expect(set.has('null')).to.equal(false);\n            expect(otherValids.has('null')).to.equal(true);\n            expect(thirdSet.has(null)).to.equal(true);\n            expect(thirdSet.has('null')).to.equal(true);\n        });\n\n        it('merges keeps refs flag set', () => {\n\n            const set = new Values();\n            set.add(Joi.ref('x'));\n            set.concat(new Values());\n            expect(set._refs.size).to.equal(1);\n        });\n    });\n\n    describe('get()', () => {\n\n        it('compares empty string to refs when insensitive', () => {\n\n            const schema = Joi.object({\n                a: Joi.string().allow(3).default(''),\n                b: Joi.string().insensitive().valid(Joi.ref('a'))\n            });\n\n            Helper.validate(schema, [\n                [{ b: '' }, true, { b: '', a: '' }],\n                [{ b: 'x' }, false, '\"b\" must be [ref:a]'],\n                [{ b: 2 }, false, '\"b\" must be [ref:a]'],\n                [{ a: 3, b: 3 }, true]\n            ]);\n        });\n    });\n\n    describe('has()', () => {\n\n        it('compares date to null', () => {\n\n            const set = new Values();\n            set.add(null);\n            expect(set.has(new Date())).to.be.false();\n        });\n\n        it('compares buffer to null', () => {\n\n            const set = new Values();\n            set.add(null);\n            expect(set.has(Buffer.from(''))).to.be.false();\n        });\n\n        it('compares different types of values', () => {\n\n            let set = new Values();\n            set.add(1);\n            expect(set.has(1)).to.be.true();\n            expect(set.has(2)).to.be.false();\n\n            const d = new Date();\n            set = new Values();\n            set.add(d);\n            expect(set.has(new Date(d.getTime()))).to.be.true();\n            expect(set.has(new Date(d.getTime() + 1))).to.be.false();\n\n            const str = 'foo';\n            set = new Values();\n            set.add(str);\n            expect(set.has(str)).to.be.true();\n            expect(set.has('foobar')).to.be.false();\n\n            const s = Symbol('foo');\n            set = new Values();\n            set.add(s);\n            expect(set.has(s)).to.be.true();\n            expect(set.has(Symbol('foo'))).to.be.false();\n\n            const o = {};\n            set = new Values();\n            set.add(o);\n            expect(set.has(o)).to.be.true();\n            expect(set.has({})).to.be.true();\n\n            const f = () => { };\n            set = new Values();\n            set.add(f);\n            expect(set.has(f)).to.be.true();\n            expect(set.has(() => { })).to.be.false();\n\n            const b = Buffer.from('foo');\n            set = new Values();\n            set.add(b);\n            expect(set.has(b)).to.be.true();\n            expect(set.has(Buffer.from('foobar'))).to.be.false();\n        });\n    });\n\n    describe('values()', () => {\n\n        it('returns array', () => {\n\n            const set = new Values();\n            set.add('x');\n            set.add('y');\n            expect(set.values()).to.equal(['x', 'y']);\n        });\n\n        it('strips undefined', () => {\n\n            const set = new Values();\n            set.add(undefined);\n            set.add('x');\n            expect(set.values({ display: true })).to.not.include(undefined).and.to.equal(['x']);\n        });\n\n        it('ignores absent display option', () => {\n\n            const set = new Values();\n            set.add(undefined);\n            set.add('x');\n            expect(set.values({})).to.equal([undefined, 'x']);\n        });\n    });\n});\n"
  }
]