Comparing version 1.2.3 to 2.0.0
@@ -0,0 +0,0 @@ # How to contribute |
// Load modules | ||
var Messages = require('./messages'); | ||
var Utils = require('./utils'); | ||
@@ -12,38 +11,28 @@ | ||
module.exports = internals.Errors = function (object, config) { | ||
exports.process = function (errors, object, options) { | ||
this._object = object; | ||
this._values = []; | ||
config = config || {}; | ||
this._messages = new Messages({ languagePath: config.languagePath } ); | ||
}; | ||
if (!errors || !errors.length) { | ||
return null; | ||
} | ||
var resources = require(options.languagePath); | ||
internals.Errors.prototype.add = function (message, path) { | ||
var values = []; | ||
for (var i = 0, il = errors.length; i < il; ++i) { | ||
var item = errors[i]; | ||
var message = Utils.reach(resources, item.type); | ||
if (message) { | ||
message = message.replace(/\{\{\s*([^\s}]+?)\s*\}\}/ig, function (match, name) { return Utils.reach(item.context, name); }); | ||
} | ||
else { | ||
message = item.context.key; | ||
} | ||
this._values.push({ | ||
message: message, | ||
path: path | ||
}); | ||
}; | ||
values.push({ message: message, path: item.path }); | ||
} | ||
internals.Errors.prototype.addLocalized = function (type, key, replacements, path) { | ||
var message = this._messages.print(type, key, replacements); | ||
Utils.assert(message, 'No message resource found in languages'); | ||
this._values.push({ | ||
message: message, | ||
path: path | ||
}); | ||
return new internals.ValidationError(values, object); | ||
}; | ||
internals.Errors.prototype.toError = function () { | ||
return this._values.length > 0 ? new internals.ValidationError(this._values, this._object) : null; | ||
}; | ||
internals.ValidationError = function (errors, object) { | ||
@@ -66,7 +55,3 @@ | ||
if (message) { | ||
message += ', '; | ||
} | ||
message += error.message; | ||
message += (message ? '. ' : '') + error.message; | ||
}); | ||
@@ -82,32 +67,50 @@ | ||
var errorObject = Utils.clone(this._object || {}); | ||
var hasTemplate = false; | ||
var obj = Utils.clone(this._object || {}); | ||
this._errors.forEach(function (error) { | ||
var el = this._errors.length; | ||
for (var e = el - 1; e >= 0; --e) { // Reverse order to process deepest child first | ||
var error = this._errors[e]; | ||
var path = error.path.split('.'); | ||
var ref = obj; | ||
for (var i = 0, il = path.length; i < il && ref; ++i) { | ||
var seg = path[i]; | ||
if (i + 1 === il) { | ||
var value = ref[seg]; | ||
if (value !== undefined) { | ||
delete ref[seg]; | ||
ref[seg + '_$key$_' + (e + 1) + '_$end$_'] = value; | ||
} | ||
else { | ||
ref['_$miss$_' + seg + '|' + (e + 1) + '_$end$_'] = '__missing__'; | ||
} | ||
var paths = error.path.split('.'); | ||
var result = errorObject; | ||
for (var i = 0, il = paths.length; i < il; ++i) { | ||
if (i + 1 === il) { | ||
hasTemplate = true; | ||
var pathMessage = typeof result[paths[i]] === 'object' ? JSON.stringify(result[paths[i]]) : result[paths[i]]; | ||
result[paths[i]] = '{{red}}' + pathMessage + '{{/}}{{br}}' + error.message + '{{/}}'; | ||
break; | ||
} | ||
result = result[paths[i]]; | ||
ref = ref[seg]; | ||
} | ||
} | ||
var annotated = JSON.stringify(obj, null, 2); | ||
annotated = annotated.replace(/_\$key\$_(\d+)_\$end\$_\"/g, function ($0, $1) { | ||
return '" \u001b[31m[' + $1 + ']\u001b[0m'; | ||
}); | ||
var annotated = JSON.stringify(errorObject, null, 1); | ||
this.message = annotated.replace(/\"_\$miss\$_([^\|]+)\|(\d+)_\$end\$_\"\: \"__missing__\"/g, function ($0, $1, $2) { | ||
if (hasTemplate) { | ||
annotated = annotated.replace(/\{\{\/\}\}/gi, '\u001b[0m'); | ||
annotated = annotated.replace(/\{\{red\}\}/gi, '\u001b[41m'); | ||
annotated = annotated.replace(/\{\{br\}\}/gi, '"\n\t"\u001b[31m'); | ||
return '\u001b[41m"' + $1 + '"\u001b[0m\u001b[31m [' + $2 + ']: -- missing --\u001b[0m'; | ||
}); | ||
this.message += '\n\u001b[31m'; | ||
for (e = 0; e < el; ++e) { | ||
this.message += '\n[' + (e + 1) + '] ' + this._errors[e].message; | ||
} | ||
this.message = annotated; | ||
this.message += '\u001b[0m'; | ||
return this; | ||
}; | ||
}; | ||
155
lib/index.js
@@ -5,3 +5,3 @@ // Load modules | ||
var Utils = require('./utils'); | ||
var Types = require('./types'); | ||
var Any = require('./any'); | ||
@@ -14,131 +14,66 @@ | ||
exports.Types = exports.types = Types; | ||
exports.Utils = exports.utils = Utils; | ||
internals.create = function (Type) { | ||
return function (arg) { | ||
exports.settings = { | ||
skipFunctions: false, | ||
saveConversions: false, | ||
skipConversions: false, | ||
stripExtraKeys: false, | ||
allowExtraKeys: false | ||
return new Type(arg); | ||
}; | ||
}; | ||
internals.joiConfig = { | ||
skipFunctions: 'boolean', | ||
saveConversions: 'boolean', | ||
skipConversions: 'boolean', | ||
stripExtraKeys: 'boolean', | ||
allowExtraKeys: 'boolean', | ||
languagePath: 'string' | ||
}; | ||
exports.any = internals.create(Any); | ||
exports.array = internals.create(require('./array')); | ||
exports.boolean = internals.create(require('./boolean')); | ||
exports.date = internals.create(require('./date')); | ||
exports.func = internals.create(require('./function')); | ||
exports.number = internals.create(require('./number')); | ||
exports.object = internals.create(require('./object')); | ||
exports.string = internals.create(require('./string')); | ||
exports.validate = function (object, config) { | ||
exports.types = exports.Types = { | ||
any: exports.any, | ||
array: exports.array, | ||
boolean: exports.boolean, | ||
date: exports.date, | ||
func: exports.func, | ||
number: exports.number, | ||
object: exports.object, | ||
string: exports.string | ||
}; | ||
var settings = Utils.clone(exports.settings); | ||
var localSettings = {}; | ||
if (!(config instanceof Types.Base)) { | ||
var names = Object.keys(config); | ||
for (var i = 0, il = names.length; i < il; ++ i) { | ||
// Backwards compatibility | ||
var name = names[i]; | ||
exports.types.Any = exports.types.any; | ||
exports.types.Array = exports.types.array; | ||
exports.types.Boolean = exports.types.boolean; | ||
exports.types.Date = exports.types.date; | ||
exports.types.Function = exports.types.func; | ||
exports.types.Number = exports.types.number; | ||
exports.types.Object = exports.types.object; | ||
exports.types.String = exports.types.string; | ||
if (typeof config[name] === internals.joiConfig[name]) { | ||
localSettings[name] = settings[name] = config[name]; | ||
delete config[name]; | ||
} | ||
} | ||
} | ||
var skipFunctions = settings.skipFunctions; | ||
var stripExtraKeys = settings.stripExtraKeys; | ||
var allowExtraKeys = settings.allowExtraKeys; | ||
var errors = new Errors(object, settings); | ||
exports.validate = function (object, schema, options) { | ||
var processConfig = function () { | ||
Utils.assert(typeof schema === 'object', 'Schema must be an object'); | ||
if (config instanceof Types.Base) { | ||
return validateKeyConfig(config, null, object); | ||
} | ||
if (schema instanceof Any) { | ||
return schema.validate(object, options); | ||
} | ||
var unprocessedObject = Utils.clone(object || {}); | ||
var keys = Object.keys(config || {}); | ||
for (var i = 0, il = keys.length; i < il; ++ i) { | ||
return exports.object(schema).validate(object, options); | ||
}; | ||
var key = keys[i]; | ||
var keyConfig = config[key]; | ||
var value = object ? object[key] : object; | ||
delete unprocessedObject[key]; | ||
validateKeyConfig(keyConfig, key, value); | ||
} | ||
exports.describe = function (schema) { | ||
Object.keys(unprocessedObject).forEach(function (unprocessedKey) { | ||
Utils.assert(typeof schema === 'object', 'Schema must be an object'); | ||
var unprocessedValueType = typeof unprocessedObject[unprocessedKey]; | ||
if ((!skipFunctions || unprocessedValueType !== 'function') && unprocessedValueType !== 'undefined') { | ||
if (stripExtraKeys && allowExtraKeys) { | ||
delete object[unprocessedKey] | ||
} | ||
else if (!allowExtraKeys) { | ||
errors.addLocalized('base.unknown', unprocessedKey, null, unprocessedKey); | ||
} | ||
} | ||
}); | ||
}; | ||
var validateKeyConfig = function (keyConfig, key, value) { | ||
var converted; | ||
if (Array.isArray(keyConfig)) { | ||
var localErrors = new Errors(object, settings); | ||
for (var i = 0, il = keyConfig.length; i < il; ++i) { | ||
converted = convertType(keyConfig[i], key, value); | ||
if (converted && keyConfig[i].validate(converted.value, object, key, localErrors, key)) { | ||
return true; | ||
} | ||
} | ||
localErrors._values.map(function (error) { | ||
errors.add(error.message, error.path); | ||
}); | ||
return false; | ||
} | ||
converted = convertType(keyConfig, key, value); | ||
return converted && keyConfig.validate(converted.value, object, key, errors, key); | ||
}; | ||
var convertType = function (keyConfig, key, value) { | ||
var T = Types[keyConfig.type]; | ||
var converter = T && T().convert || null; | ||
if (!settings.skipConversions && typeof converter === 'function') { | ||
value = converter(value); | ||
if (settings.saveConversions && key !== null) { | ||
object[key] = value; | ||
} | ||
return { value: value }; | ||
} | ||
return { value: value }; // If no convert function then just return the value | ||
}; | ||
processConfig(); | ||
var names = Object.keys(localSettings); | ||
for (var i = 0, il = names.length; i < il; ++ i) { | ||
var name = names[i]; | ||
config[name] = localSettings[name]; | ||
if (schema instanceof Any) { | ||
return schema._describe(); | ||
} | ||
return errors.toError(); | ||
return exports.object(schema)._describe(); | ||
}; | ||
@@ -26,15 +26,2 @@ // Load modules | ||
exports.mixin = function (self, parent) { | ||
for (var i in parent.prototype) { | ||
if (parent.prototype.hasOwnProperty(i)) { | ||
if (!self.hasOwnProperty(i)) { | ||
self[i] = parent.prototype[i]; | ||
} | ||
} | ||
} | ||
return self; | ||
}; | ||
exports.inherits = Util.inherits; |
{ | ||
"name": "joi", | ||
"description": "Object schema validation", | ||
"version": "1.2.3", | ||
"version": "2.0.0", | ||
"repository": "git://github.com/spumko/joi", | ||
@@ -6,0 +6,0 @@ "main": "index", |
818
README.md
@@ -1,39 +0,63 @@ | ||
<a href="https://github.com/spumko"><img src="https://raw.github.com/spumko/spumko/master/images/from.png" align="right" /></a> | ||
<a href="https://github.com/spumko"><img src="https://raw.github.com/spumko/spumko/master/images/from.png" align="right" /></a> | ||
![joi Logo](https://raw.github.com/spumko/joi/master/images/joi.png) | ||
Object schema validation | ||
Object schema description language and validator for JavaScript objects. | ||
Current version: **2.x.x** | ||
[![Build Status](https://secure.travis-ci.org/spumko/joi.png)](http://travis-ci.org/spumko/joi) | ||
# Table of Contents | ||
## Table of Contents | ||
<img src="https://raw.github.com/spumko/joi/master/images/validation.png" align="right" /> | ||
* [Introduction](#introduction "Introduction") | ||
* [Type Registry](#type-registry "Type Registry") | ||
* [Constraints](#constraints "Constraints") | ||
* [BaseType](#basetype "BaseType") | ||
* [String](#string "String") | ||
* [Number](#number "Number") | ||
* [Boolean](#boolean "Boolean") | ||
* [Array](#array "Array") | ||
* [Object](#object "Object") | ||
* [Function](#function "Function") | ||
* [Any](#any "Any") | ||
* [Usage](#usage "Usage") | ||
* [Config Syntax](#config-syntax "Config Syntax") | ||
* [Evaluation Order](#evaluation-order "Evaluation Order") | ||
* [Special Options](#special-options "Special Options") | ||
* [Security Considerations](#security-considerations "Security Considerations") | ||
* [Examples](#examples "Examples") | ||
* [References](#references "References") | ||
* [Reference A: Other Types](#reference-a-other-types "Reference A: Other Types") | ||
- [Example](#example) | ||
- [Usage](#usage) | ||
- [`validate(value, schema, options)`](#validatevalue-schema-options) | ||
- [`any()`](#any) | ||
- [`any.allow(value)`](#anyallowvalue) | ||
- [`any.valid(value)`](#anyvalidvalue) | ||
- [`any.invalid(value)`](#anyinvalidvalue) | ||
- [`any.required()`](#anyrequired) | ||
- [`any.optional()`](#anyoptional) | ||
- [`any.nullOk()`](#anynullok) | ||
- [`any.with(peer)`](#anywithpeer) | ||
- [`any.without(peer)`](#anywithoutpeer) | ||
- [`any.xor(peer)`](#anyxorpeer) | ||
- [`description(desc)`](#descriptiondesc) | ||
- [`any.notes(notes)`](#anynotesnotes) | ||
- [`any.tags(tags)`](#anytagstags) | ||
- [`any.options(options)`](#anyoptionsoptions) | ||
- [`any.strict()`](#anystrict) | ||
- [`any.rename(to, [options])`](#anyrenameto-options) | ||
- [`array()`](#array) | ||
- [`array.emptyOk()`](#arrayemptyok) | ||
- [`array.includes(type)`](#arrayincludestype) | ||
- [`array.excludes(type)`](#arrayexcludestype) | ||
- [`array.min(limit)`](#arrayminlimit) | ||
- [`array.max(limit)`](#arraymaxlimit) | ||
- [`array.length(limit)`](#arraylengthlimit) | ||
- [`boolean()`](#boolean) | ||
- [`date()`](#date) | ||
- [`date.min(date)`](#datemindate) | ||
- [`date.max(date)`](#datemaxdate) | ||
- [`func()`](#func) | ||
- [`number()`](#number) | ||
- [`number.min(limit)`](#numberminlimit) | ||
- [`number.max(limit)`](#numbermaxlimit) | ||
- [`number.integer()`](#numberinteger) | ||
- [`object(schema)`](#objectschema) | ||
- [`string()`](#string) | ||
- [`string.emptyOk()`](#stringemptyok) | ||
- [`string.insensitive()`](#stringinsensitive) | ||
- [`string.min(limit)`](#stringminlimit) | ||
- [`string.max(limit)`](#stringmaxlimit) | ||
- [`string.length(limit)`](#stringlengthlimit) | ||
- [`string.regex(pattern)`](#stringregexpattern) | ||
- [`string.alphanum()`](#stringalphanum) | ||
- [`string.token()`](#stringtoken) | ||
- [`string.email()`](#stringemail) | ||
# Example | ||
# Introduction | ||
The **joi** validation system is used to validate JavaScript objects based on a rich descriptive schema. | ||
Schema validation is the process of ensuring that objects match pre-defined expectations. | ||
For example, the following schema: | ||
```javascript | ||
@@ -43,549 +67,591 @@ var Joi = require('joi'); | ||
var schema = { | ||
username: Joi.types.String().alphanum().min(3).max(30).with('birthyear').required(), | ||
password: Joi.types.String().regex(/[a-zA-Z0-9]{3,30}/).without('access_token'), | ||
access_token: Joi.types.String(), | ||
birthyear: Joi.types.Number().min(1850).max(2012), | ||
email: Joi.types.String().email() | ||
username: Joi.string().alphanum().min(3).max(30).with('birthyear').required(), | ||
password: Joi.string().regex(/[a-zA-Z0-9]{3,30}/).without('access_token'), | ||
access_token: [Joi.string(), Joi.number()], | ||
birthyear: Joi.number().integer().min(1900).max(2013), | ||
email: Joi.string().email() | ||
}; | ||
var err = Joi.validate({ username: 'abc', birthyear: 1994 }, schema); // err === null -> valid | ||
``` | ||
defines these constraints: | ||
* 'username' | ||
The above schema defines the following constraints: | ||
* `username` | ||
* a required string | ||
* must contain only alphanumeric characters | ||
* at least 3 chars long but no more than 30 | ||
* must be accompanied by 'birthyear' (logical AND) | ||
* 'password' | ||
* at least 3 characters long but no more than 30 | ||
* must be accompanied by `birthyear` | ||
* `password` | ||
* an optional string | ||
* must satisfy the custom regex | ||
* cannot appear together with 'access_token' | ||
* 'access_token' | ||
* an optional, unconstrained string | ||
* 'birthyear' | ||
* an integer between 1850 and 2012 | ||
* 'email' | ||
* cannot appear together with `access_token` | ||
* `access_token` | ||
* an optional, unconstrained string or number | ||
* `birthyear` | ||
* an integer between 1900 and 2013 | ||
* `email` | ||
* a valid email address string | ||
The above constraints point out some non-obvious features: | ||
* Keys are optional by default | ||
* Strings are by default utf-8 encoded | ||
* relationships are defined in an additive fashion | ||
* "X.join(Y), Y.join(Z)" is the same as requiring all three to be present: "X AND Y AND Z" | ||
* Likewise "X.xor(Y), Y.xor(Z)" => requires that only one of three be present: "X XOR Y XOR Z" | ||
* .regex may or may not override other string-related constraints (.alphanum, .min, .max) | ||
** constraints are evaluated in order | ||
* order of chained functions matter | ||
** ".min(0).max(100).min(1)" sets the min to 1, overwriting the result of the first min call | ||
** if ".regex(/[a-z]{0,3}/)" and ".max(50)" both supplied, only the overlap is valid (length 3 or less = valid) | ||
# Usage | ||
Below is an example of how to validate an object against the above schema: | ||
Usage is a two steps process. First, a schema is constructed using the provided types and constraints: | ||
```javascript | ||
var err = Joi.validate(obj, schema); | ||
// err will be set if the object failed to validate against the schema | ||
var schema = { | ||
a: Joi.string() | ||
}; | ||
``` | ||
# Type Registry | ||
Then the value is validated against the schema: | ||
The Types object is pre-populated with a mutable list of JavaScript's valid data types. However, for convenience, the registry also includes subset helpers with common constraints already applied. For a list of helpers see the following sections... | ||
```javascript | ||
var err = Joi.validate({ a: 'a string' }, schema); | ||
``` | ||
* [String](#string "String") | ||
* [Number](#number "Number") | ||
* [Boolean](#boolean "Boolean") | ||
* [Array](#array "Array") | ||
* [Object](#object "Object") | ||
* [Function](#function "Function") | ||
* [Any](#any "Any") | ||
If the value is valid, `null` is returned, otherwise an `Error` object. | ||
Any custom, user-defined data type is derived from one of the base types (although it may also combine additional types for sub-elements). Thus, there are two valid ways of creating your own types. | ||
The schema can be a plain JavaScript object where every key is assigned a **joi** type, or it can be a **joi** type directly: | ||
The first method is to add the type directly to the Type Registry. This makes the new type explicitly available as a base Type. | ||
```javascript | ||
var IntDef = _.extends({}, Number, function () { | ||
// Constructor | ||
return this.integer(); | ||
}); | ||
Types.set("Int", IntDef); | ||
var Int = Types.Int; | ||
var schema = Joi.string().min(10); | ||
``` | ||
The second, simpler, and more acceptable method is to alias the new Type within the config file. | ||
If the schema is a **joi** type, the `schema.validate(value)` can be called directly on the type. When passing a non-type schema object, | ||
the module converts it internally to an object() type equivalent to: | ||
```javascript | ||
var PositiveInt = Number().integer().min(0) | ||
PositiveInt.max(999999); | ||
var schema = Joi.object({ | ||
a: Joi.string() | ||
}); | ||
``` | ||
Thus, subsequent calls to the new "type" will behave as fully registered types in much less code. | ||
When validating a schema: | ||
* Keys are optional by default. | ||
* Strings are utf-8 encoded by default. | ||
* Rules are defined in an additive fashion and evaluated in order after whitelist and blacklist checks. | ||
*Note: The first method may eventually be deprecated. Then, the Type Registry becomes non-mutable which simplies the logic significantly.* | ||
### `validate(value, schema, options)` | ||
*Note: See "Reference A" before suggesting a pre-included Type for the Type Registry.* | ||
Validates a value using the given schema and options where: | ||
- `value` - the value being validated. | ||
- `schema` - the validation schema. Can be a **joi** type object or a plain object where every key is assigned a **joi** type object. | ||
- `options` - an optional object with the following optional keys: | ||
- `abortEarly` - when `true`, stops validation on the first error, otherwise returns all the errors found. Defaults to `true`. | ||
- `convert` - when `true`, attepmts to cast values to the required types (e.g. a string to a number). Defaults to `true`. | ||
- `modify` - when `true`, converted values are written back to the provided value (only when value is an object). Defaults to `false`. | ||
- `allowUnknown` - when `true`, allows object to contain unknown keys which are ignored. Defaults to `false`. | ||
- `skipFunctions` - when `true`, ignores unknown keys with a function value. Defaults to `false`. | ||
- `stripUnknown` - when `true`, unknown keys are deleted (only when value is an object). Defaults to `false`. | ||
- `languagePath` - the location of the language file used to localize error messages. Defaults to `'languages/en-us.json'`. | ||
## Constraints | ||
Constraints are functions that restrict the input value in some way. | ||
By default, all without explicit constraints, Types are optional. | ||
### Implementation | ||
```javascript | ||
var schema = { | ||
username: Joi.types.String().min(6).max(30).allow('admin').deny('Administrator'), | ||
a: Joi.number() | ||
}; | ||
``` | ||
The above example demonstrates that even though the username has a minimum length of 6, extra constraints can be appended that allow 'admin' to be used as a username. Likewise, even though 'Administrator' would be allowed by the other constraints, it is explicitly denied by the _'deny'_ constraint. | ||
var value = { | ||
a: '123' | ||
}; | ||
### By Type | ||
var err = Joi.validate(value, schema, { modify: true }); | ||
#### BaseType | ||
// err -> null | ||
// value.a -> 123 (number, not string) | ||
``` | ||
All types inherit the following builtin constraints: | ||
### `any()` | ||
##### BaseType.required() | ||
Generates a schema object that matches any data type. | ||
Specifies that the input may not be undefined (unspecified). | ||
```javascript | ||
var any = Joi.any(); | ||
any.valid('a'); | ||
##### BaseType.allow(value) | ||
var err = any.validate('a'); | ||
``` | ||
Specifies that the input may equal this value. This is type specific, so you cannot allow a number on a string type and vice-versa. | ||
#### `any.allow(value)` | ||
This function is idempotent. | ||
Whitelists a value where: | ||
- `value` - the allowed value which can be of any type and will be matched against the validated value before applying any other rules. | ||
`value` can be an array of values, or multiple values can be passed as individual arguments. | ||
*Note: This function does not verify that value is the correct type.* | ||
```javascript | ||
var schema = { | ||
a: Joi.any().allow('a'), | ||
b: Joi.any().allow('b', 'B'), | ||
c: Joi.any().allow(['c', 'C']) | ||
}; | ||
``` | ||
##### BaseType.deny(value) | ||
#### `any.valid(value)` | ||
Specifies that the input may NOT equal this value. | ||
Adds the provided values into the allowed whitelist and marks them as the only valid values allowed where: | ||
- `value` - the allowed value which can be of any type and will be matched against the validated value before applying any other rules. | ||
`value` can be an array of values, or multiple values can be passed as individual arguments. | ||
This function is idempotent. | ||
```javascript | ||
var schema = { | ||
a: Joi.any().valid('a'), | ||
b: Joi.any().valid('b', 'B'), | ||
c: Joi.any().valid(['c', 'C']) | ||
}; | ||
``` | ||
*Note: This function does not verify that value is the correct type.* | ||
#### `any.invalid(value)` | ||
##### Basetype.valid(a1[, a2, ...]) | ||
Blacklists a value where: | ||
- `value` - the forbidden value which can be of any type and will be matched against the validated value before applying any other rules. | ||
`value` can be an array of values, or multiple values can be passed as individual arguments. | ||
Specifies an arbitrary number of valid values for this input. | ||
```javascript | ||
var schema = { | ||
a: Joi.any().invalid('a'), | ||
b: Joi.any().invalid('b', 'B'), | ||
c: Joi.any().invalid(['c', 'C']) | ||
}; | ||
``` | ||
If no inputs are supplied, it returns an Error. | ||
#### `any.required()` | ||
If one or more of inputs given do not match the basic type, an Error is raised. | ||
Marks a key as required which will not allow `undefined` as value. All keys are optional by default. | ||
##### Basetype.invalid(a1[, a2, ...]) | ||
```javascript | ||
var schema = { | ||
a: Joi.any().required() | ||
}; | ||
``` | ||
Specifies an arbitrary number of invalid values for this input. | ||
#### `any.optional()` | ||
If no inputs are supplied, it returns an Error. | ||
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. | ||
If one or more of inputs given do not match the basic type, an Error is raised. | ||
```javascript | ||
var schema = { | ||
a: Joi.any().optional() | ||
}; | ||
``` | ||
##### BaseType.with(a1[, a2, ...]) | ||
#### `any.nullOk()` | ||
Specifies an arbitrary number of inputs that must also be supplied (a1..an) with this input. | ||
Allows `null` as valid value. Note that while `undefined` is a valid value for an optional key, `null` is not and must be explicitly allowed. | ||
*Note: This may or may not have aliases in the final version (.join, .with, .and... etc)* | ||
```javascript | ||
var schema = { | ||
a: Joi.any().nullOk() | ||
}; | ||
``` | ||
##### BaseType.without(a1[, a2, ...]) | ||
#### `any.with(peer)` | ||
Specifies an arbitrary number of inputs that cannot exist alongside this input (logical XOR). | ||
Requires the presence of another key whenever this value is present where: | ||
- `peer` - the required key name that must appear together with the current value. `peer` can be an array of values, or multiple values can be | ||
passed as individual arguments. | ||
*Note: This may or may not have aliases in the final version (.disjoin, .without, .xor... etc)* | ||
```javascript | ||
var schema = { | ||
a: Joi.any().with('b'), | ||
b: Joi.any() | ||
}; | ||
``` | ||
##### BaseType.nullOk() | ||
#### `any.without(peer)` | ||
Specifies that the value is allowed to be null. | ||
Forbids the presence of another key whenever this value is present where: | ||
- `peer` - the forbidden key name that must not appear together with the current value. `peer` can be an array of values, or multiple values can be | ||
passed as individual arguments. | ||
##### BaseType.rename(to[, options]) | ||
```javascript | ||
var schema = { | ||
a: Joi.any().without('b'), | ||
b: Joi.any() | ||
}; | ||
``` | ||
Specifies a key to rename the current parameter to. | ||
#### `any.xor(peer)` | ||
Options take the form of an object with the follow default values: | ||
Defines an exclusive relationship with another key where this or one of the peers is required but not at the same time where: | ||
- `peer` - the exclusive key name that must not appear together with the current value but where one of them is required. `peer` can be an array | ||
of values, or multiple values can be passed as individual arguments. | ||
```javascript | ||
{ | ||
deleteOrig: false, | ||
allowMult: false, | ||
allowOverwrite: false | ||
} | ||
var schema = { | ||
a: Joi.any().xor('b'), | ||
b: Joi.any() | ||
}; | ||
``` | ||
The option "deleteOrig" specifies whether or not to delete the original key of the param (effectively a permanent "move"). | ||
#### `description(desc)` | ||
The option "allowMult" specifies whether or not multiple parameters can be renamed to a single key. | ||
Annotates the key where: | ||
- `desc` - the description string. | ||
The option "allowOverwrite" specifies whether or not the rename function can overwrite existing keys. | ||
```javascript | ||
var schema = Joi.any().description('this key will match anything you give it'); | ||
``` | ||
#### `any.notes(notes)` | ||
#### String | ||
Annotates the key where: | ||
- `notes` - the notes string or array of strings. | ||
Strings, by default, match JavaScript Strings. They are typically unbounded in length unless limited by interpreter. They are encoded in UTF-8 (this is true in Node.js at least). They may contain any allowable characters in the specified encoding. | ||
```javascript | ||
var schema = Joi.any().notes(['this is special', 'this is important']); | ||
``` | ||
The Type Registry's implementation of String also includes some builtin constraints: | ||
#### `any.tags(tags)` | ||
##### String.emptyOk() | ||
Annotates the key where: | ||
- `tags` - the tag string or array of strings. | ||
Specifies that the input may be equal to '' (the empty string). | ||
```javascript | ||
var schema = Joi.any().tags(['api', 'user']); | ||
``` | ||
##### String.min(n) | ||
#### `any.options(options)` | ||
Specifies a minimum length for this input string, inclusive. | ||
Overrides the global `validate()` options for the current key and any sub-key where: | ||
- `options` - an object with the same optional keys as [`Joi.validate(value, schema, options)`](#joivalidatevalue-schema-options). | ||
If n is not specified, it returns an Error. | ||
```javascript | ||
var schema = { | ||
a: Joi.any().options({ modify: true }) | ||
}; | ||
``` | ||
If n is not a non-negative integer, it returns an Error. | ||
#### `any.strict()` | ||
##### String.max(n) | ||
Sets the `options.convert` options to `false` which prevent type casting for the current key and any child keys. | ||
Specifies a maximum length for this input string, inclusive. | ||
```javascript | ||
var schema = { | ||
a: Joi.any().strinct() | ||
}; | ||
``` | ||
If n is not specified, it returns an Error. | ||
#### `any.rename(to, [options])` | ||
If n is not a positive integer, it returns an Error. | ||
Renames a key to another name where: | ||
- `to` - the new key name. | ||
- `options` - an optional object with the following optional keys: | ||
- `move` - if `true`, deletes the old key name, otherwise both old and new keys are kept. Defaults to `false`. | ||
- `multiple` - if `true`, allows renaming multiple keys to the same destination where the last rename wins. Defaults to `false`. | ||
- `override` - if `true`, allows renaming a key over an existing key. Defaults to `false`. | ||
##### String.alphanum() | ||
### `array()` | ||
Specifies that this input may only consist of alphanumeric characters. | ||
Generates a schema object that matches an array data type. | ||
##### String.regex(pattern) | ||
Supports the following subset of the [`any()`](#any) type: | ||
- `required()` | ||
- `optional()` | ||
- `nullOk()` | ||
- `with(peer)` | ||
- `without(peer)` | ||
- `xor(peer)` | ||
- `description(desc)` | ||
- `notes(notes)` | ||
- `tags(tags)` | ||
- `options(options)` | ||
- `rename(to, [options])` | ||
Specifies that this input matches the given RegExp pattern. | ||
```javascript | ||
var array = Joi.array(); | ||
array.includes(Joi.string().valid('a', 'b')); | ||
If pattern is not specified, it returns an Error. | ||
var err = array.validate(['a', 'b', 'a']); | ||
``` | ||
If pattern is not a valid RegExp object, it returns an error. | ||
#### `array.emptyOk()` | ||
##### String.email() | ||
Allows the value to be assigned an empty array. | ||
Specifies that this input is a valid email string. | ||
```javascript | ||
var schema = { | ||
a: Joi.array().emptyOk() | ||
}; | ||
``` | ||
##### String.date() | ||
#### `array.includes(type)` | ||
Specifies that this input is a valid Date string (locale string but also accepts unix timestamp in milliseconds). | ||
List the types allowed for the array values where: | ||
- `type` - a **joi** schema object to validate each array item against. `type` can be an array of values, or multiple values can be passed as individual arguments. | ||
##### String.encoding(enc) | ||
```javascript | ||
var schema = { | ||
a: Joi.array().includes(Joi.string(), Joi.number()) | ||
}; | ||
``` | ||
Specifies an explicit encoding for this input string. | ||
#### `array.excludes(type)` | ||
*Warning: This may or may not be included in the final version. A better solution may be to forcibly convert from the encoding specified by enc to utf-8. However, this is not always possible (i.e. UTF-16 converting to UTF-8 would truncate a lot of characters).* | ||
List the types forbidden for the array values where: | ||
- `type` - a **joi** schema object to validate each array item against. `type` can be an array of values, or multiple values can be passed as individual arguments. | ||
```javascript | ||
var schema = { | ||
a: Joi.array().excludes(Joi.object()) | ||
}; | ||
``` | ||
#### Number | ||
#### `array.min(limit)` | ||
##### Number.integer() | ||
Specifies the minimum number of items in the array where: | ||
- `limit` - the lowest number of array items allowed. | ||
Specifies that this input be a valid integer. | ||
```javascript | ||
var schema = { | ||
a: Joi.array().min(2) | ||
}; | ||
``` | ||
##### Number.float() | ||
#### `array.max(limit)` | ||
Specifies that this input be a valid float or double. | ||
Specifies the maximum number of items in the array where: | ||
- `limit` - the highest number of array items allowed. | ||
##### Number.min(n) | ||
```javascript | ||
var schema = { | ||
a: Joi.array().max(10) | ||
}; | ||
``` | ||
Specifies a minimum value for this input, inclusive. | ||
#### `array.length(limit)` | ||
If n is not specified, it returns an Error. | ||
Specifies the exact number of items in the array where: | ||
- `limit` - the number of array items allowed. | ||
If n is not an integer, it returns an Error. | ||
```javascript | ||
var schema = { | ||
a: Joi.array().length(5) | ||
}; | ||
``` | ||
##### Number.max(n) | ||
### `boolean()` | ||
Specifies a maximum value for this input, inclusive. | ||
Generates a schema object that matches a boolean data type (as well as the strings 'true', 'false', 'yes', and 'no'). | ||
If n is not specified, it returns an Error. | ||
Supports the same methods of the [`any()`](#any) type. | ||
If n is not an integer, it returns an Error. | ||
```javascript | ||
var boolean = Joi.boolean(); | ||
boolean.nullOk(); | ||
var err = any.validate(true); | ||
``` | ||
#### Boolean | ||
### `date()` | ||
Boolean values accept a case-insensitive string parameter. If the value is "true", true is returned. Otherwise, false is returned. | ||
Generates a schema object that matches a date type (as well as a JavaScript date string or number of milliseconds). | ||
*Note: Boolean has no special methods other than those inherited from BaseType* | ||
Supports the same methods of the [`any()`](#any) type. | ||
```javascript | ||
var date = Joi.date(); | ||
date.min('12-20-2012'); | ||
#### Array | ||
**Note** | ||
Array values take the querystring form of | ||
var err = date.validate('12-21-2012'); | ||
``` | ||
?cars=1&cars=2 | ||
``` | ||
and get converted to | ||
``` | ||
{ cars: [ '1', '2' ] } | ||
``` | ||
by the server. | ||
*Note: Array has no special methods other than those inherited from BaseType* | ||
#### `date.min(date)` | ||
Specifies the oldest date allowed where: | ||
- `date` - the oldest date allowed. | ||
##### Array.includes(n1, n2, ...) | ||
```javascript | ||
var schema = { | ||
a: Joi.date().min('1-1-1974') | ||
}; | ||
``` | ||
Specifies allowed types for the array value to include. The values of n1, n2, ... are Type Registry constraints (usually of other types). | ||
#### `date.max(date)` | ||
##### Array.excludes(n1, n2, ...) | ||
Specifies the latest date allowed where: | ||
- `date` - the latest date allowed. | ||
Specifies allowed types for the array value to exclude. The values of n1, n2, ... are Type Registry constraints (usually of other types). | ||
```javascript | ||
var schema = { | ||
a: Joi.date().max('12-31-2020') | ||
}; | ||
``` | ||
### `func()` | ||
#### Object | ||
Generates a schema object that matches a function type. | ||
##### Object.allowOtherKeys() | ||
Supports the same methods of the [`any()`](#any) type. | ||
Will cause any unknown keys in the object being validated to not cause the object to be invalid. | ||
```javascript | ||
var func = Joi.func(); | ||
func.nullOk(); | ||
var err = func.validate(function () {}); | ||
``` | ||
#### Function | ||
### `number()` | ||
Function types accept any value that is a function. | ||
Generates a schema object that matches a number data type (as well as strings that can be converted to numbers). | ||
*Note: Function has no special methods other than those inherited from BaseType* | ||
Supports the same methods of the [`any()`](#any) type. | ||
```javascript | ||
var number = Joi.number(); | ||
number.min(1).max(10).integer(); | ||
#### Any | ||
var err = number.validate(5); | ||
``` | ||
Accept any type of value where the value is not null. By default the value must not be null but is allowed to be undefined. To change this behavior use either the _'required'_ or _'nullOk'_ methods. | ||
#### `number.min(limit)` | ||
*Note: Any has no special methods other than those inherited from BaseType* | ||
Specifies the minimum value where: | ||
- `limit` - the minimum value allowed. | ||
## Usage | ||
```javascript | ||
var schema = { | ||
a: Joi.number().min(2) | ||
}; | ||
``` | ||
### Config Syntax | ||
#### `number.max(limit)` | ||
In Hapi's routes configuration array, the routes are listed as JavaScript objects. Route objects may include an optional "query" key, the value of which should be an object. This object should associate querystring input names to validation constraints. | ||
Specifies the maximum value where: | ||
- `limit` - the maximum value allowed. | ||
```javascript | ||
var queryObj = { | ||
input_name: constraints | ||
var schema = { | ||
a: Joi.number().max(10) | ||
}; | ||
``` | ||
In the above code example, "input_name" must conform to typical JavaScript object key constraints (no spaces, no quotes unless escaped and surrounded by quotes, etc). | ||
#### `number.integer()` | ||
In place of "constraints", there should be a combination of constraints. The combination of constraints must be formed starting from a valid base type. The base type may be followed by zero or more pre-defined constraint functions chained consecutively. These combinations can be pre-combined into "alias" variables that may also be followed by zero or more pre-defined constraint functions chained consecutively. An example is shown below: | ||
Requires the number to be an integer (no floating point). | ||
```javascript | ||
Base().constraint_one().constraint_two()... | ||
BaseAlias = Base().constraint() | ||
BaseAlias.constraint_one().constraint_two()... | ||
var schema = { | ||
a: Joi.number().integer() | ||
}; | ||
``` | ||
Constraint functions may accept optional and arbitrary parameters. | ||
### `object(schema)` | ||
Every call must have its own `Base()` prefix. This creates a new validation object. Otherwise, it will retain settings from any prior constraints. | ||
Generates a schema object that matches an object data type (as well as JSON strings that parsed into objects) where: | ||
- `schema` - optional object where each key is assinged a **joi** type object. If the schema is `{}` no keys allowed. | ||
Defaults to 'undefined' which allows any child key. | ||
### Evaluation Order | ||
Supports the same methods of the [`any()`](#any) type. | ||
#### Overrides | ||
```javascript | ||
var object = Joi.object({ | ||
a: number.min(1).max(10).integer() | ||
}); | ||
Each constraint is evaluated independantly and in order of chaining. In some cases, a subsequent constraint may override a prior constraint: | ||
```javascript | ||
String.required().optional() # This input will be considered optional | ||
String.required(false).required() # This input will be considered required | ||
var err = object.validate({ a: 5 }); | ||
``` | ||
Constraints that can override modify the query validation state upon the function's evocation. The actual evaluation is performed at the end of the chain (or once the entire querystring validation is finished). | ||
### `string()` | ||
These constraint functions are special cases: | ||
* required/optional | ||
* with/without | ||
* rename | ||
Generates a schema object that matches a string data type. | ||
Rename is always performed at the end of the chain. | ||
Supports the same methods of the [`any()`](#any) type. | ||
```javascript | ||
var string = Joi.string(); | ||
string.min(1).max(10); | ||
###### With/Without | ||
var err = string.validate('12345'); | ||
``` | ||
Below is an example of a schema that is likely to be used for defining a username/password constraint. Notice that _'with'_ is used on the _'username'_ to indicate that _'password'_ is required to appear whenever _'username'_ exists. Similarly, _'password'_ has a constraint indicating that the key _'access_token'_ must not exist when _'password'_ exists. | ||
#### `string.emptyOk()` | ||
Allows the value to be assigned an empty string. | ||
```javascript | ||
username: Joi.types.String().with('password'), | ||
password: Joi.types.String().without('access_token') | ||
var schema = { | ||
a: Joi.string().emptyOk() | ||
}; | ||
``` | ||
#### Overrules | ||
#### `string.insensitive()` | ||
Yet, in another case, a prior constraint may overrule a subsequent constraint: | ||
Allows the value to match any whitelist of blacklist item in a case insensitive comparison. | ||
```javascript | ||
Types.String().max(5).max(10) # This input cannot be larger than 5 characters | ||
Types.String().max(3).regex(/.{0,5}/) # This input cannot be larger than 3 characters | ||
var schema = { | ||
a: Joi.string().valid('a').insensitive() | ||
}; | ||
``` | ||
This should apply to all other constraints that do not override. | ||
#### `string.min(limit)` | ||
Specifies the minimum number string characters where: | ||
- `limit` - the minimum number of string characters required. | ||
```javascript | ||
var schema = { | ||
a: Joi.string().min(2) | ||
}; | ||
``` | ||
## Special Options | ||
#### `string.max(limit)` | ||
Joi has special settings that will modify certain behaviors. | ||
Specifies the maximum number of string characters where: | ||
- `limit` - the maximum number of string characters allowed. | ||
### Global | ||
```javascript | ||
var schema = { | ||
a: Joi.string().max(10) | ||
}; | ||
``` | ||
#### Skip Functions | ||
#### `string.length(limit)` | ||
On occasion, an object must be validated which contains functions as properties. To force Joi to ignore validation on such functions, use the `skipFunctions` option: | ||
Specifies the exact string length required where: | ||
- `limit` - the required string length. | ||
Joi.settings.skipFunctions = true; | ||
```javascript | ||
var schema = { | ||
a: Joi.string().length(5) | ||
}; | ||
``` | ||
#### `string.regex(pattern)` | ||
#### Save Conversions | ||
Defines a regular expression rule where: | ||
- `pattern` - a regular expression object the string value must match against. | ||
Through the process of validation, some inputs will be converted to accommodate the various constraint functions. For example, if an input is of type Joi.Types.Number() but is defined as a string, the validator will convert to Number during validation. This does not persist and does not affect the original input. | ||
```javascript | ||
var schema = { | ||
a: Joi.string().regex(\^[abc]+$\) | ||
}; | ||
``` | ||
To force Joi to save the conversion, use the `saveConversions` option: | ||
#### `string.alphanum()` | ||
Joi.settings.saveConversions = true; | ||
Requires the string value to only contain a-z, A-Z, and 0-9. | ||
#### Skip Conversions | ||
By default Joi tries to parse and convert object's values into correct type. You might want to disable this behaviour e.g. when you are validating program's internal objects instead of user input. | ||
To force Joi to not convert object values, use the `skipConversions` option: | ||
Joi.settings.skipConversions = true; | ||
#### Allow Extra Keys & Strip Extra Keys | ||
By default Joi will throw an error on keys that are not specified in the configuration object. | ||
To force Joi to not throw errors when it encounters an unknown key, use the `allowExtraKeys` option: | ||
Joi.settings.allowExtraKeys = true; | ||
If you'd like Joi to remove the unknown keys from the object, enable both the `stripExtraKeys` option and the `allowExtraKeys` option: | ||
Joi.settings.allowExtraKeys = true; | ||
Joi.settings.stripExtraKeys = true; | ||
### Local | ||
All global options may be overridden by specifying the option directly on the schema object. | ||
#### Custom Messages | ||
Joi error messages can be updated and replaced with localized versions. Use the `languagePath` option to specify a file path to a JSON file that contains error messages. Each message supports a mustache style template with the following keys: | ||
- `{{key}}` - the schema property that fails validation | ||
- `{{value}}` - the invalid value assigned to the key | ||
- `{{validTypes}}` - an optional list of acceptable inputs | ||
```javascript | ||
var S = Joi.Types.String(); | ||
var config = { | ||
username: S().alphanum().min(3).max(30), | ||
password: S().regex(/[a-zA-Z0-9]{3,30}/), | ||
languagePath: Path.join(__dirname, 'languages', 'en-US.json') | ||
var schema = { | ||
a: Joi.string().alphanum() | ||
}; | ||
Joi.validate({ | ||
username: 'ab', | ||
password: '1' | ||
}, config); | ||
``` | ||
#### `string.token()` | ||
### Type-Specific | ||
Requires the string value to only contain a-z, A-Z, 0-9, and underscore _. | ||
#### Short Circuit Breakout | ||
When validating an input for a specific type with lots of constraints, Joi will, by default, return error immediately upon the first error condition. In some rare cases, iterating through all of the constraint functions is actually ideal (to identify all the reasons why an input is invalid at once). To force Joi to evaluate all constraints, use the `shortCircuit` option: | ||
var S = Joi.Types.String(); | ||
S.options.shortCircuit = false; | ||
var schema = { | ||
nickname: S().valid('Silly').min(2) | ||
} | ||
schema.nickname.validate('o', null, null, errors) // => populates errors with all failing constraints | ||
// alternative way | ||
var input = { amount: 2.5 }; | ||
var schema = { amount: T.Number().integer().min(3).max(5).noShortCircuit() }; | ||
Joi.validate(input, schema); | ||
#### Non-Exclusive Valid | ||
The `.valid` constraint is currently exclusive - if the input is NOT one of the values passed to `.valid`, the validator returns false. In the event this is too strict, use the hidden `__allowOnly` option. | ||
var S = Joi.Types.String(); | ||
S.__allowOnly = false; | ||
var schema = { | ||
username: S().valid('walmart') | ||
} | ||
schema.username.validate('test') // => this returns true | ||
## Security Considerations | ||
Encodings could potentially play a role in security - some strings in one encoding, when exec()'d in another encoding could execute malicious code. If this type of validation is enabled, it will likely provide little to no explicit protection for developers. Developers could unintentionally (and even worse, unknowingly) expose a significant security risk. | ||
## Examples | ||
### Validating username and password | ||
```javascript | ||
var Joi = require('joi'); | ||
var schema = { | ||
username: Joi.types.String().alphanum().min(3).max(30).required(), | ||
password: Joi.types.String().regex(/[a-zA-Z0-9]{3,30}/).required(), | ||
a: Joi.string().token() | ||
}; | ||
var invalidObj = { username: 'roger' }; | ||
var validObj = { username: 'roger', password: 'pa55word' }; | ||
var err = Joi.validate(invalidObj, schema); | ||
if (err) throw err; | ||
var err = Joi.validate(validObj, schema); | ||
if (err) throw err; | ||
``` | ||
Executing the above code outputs the following: | ||
``` | ||
Error: [ValidationError]: the value of `password` is not allowed to be undefined | ||
``` | ||
#### `string.email()` | ||
### Validating a number | ||
Requires the string value to be a valid email address. | ||
```javascript | ||
var Joi = require('joi'); | ||
var schema = { | ||
num: Joi.types.Number().required() | ||
a: Joi.string().email() | ||
}; | ||
var obj = { num: '1' }; | ||
var err = Joi.validate(obj, schema); | ||
if (err) throw err; | ||
else console.log('Success!'); | ||
``` | ||
Executing the above code outputs the following: | ||
``` | ||
Success! | ||
``` | ||
## References | ||
### Reference A: Other Types | ||
#### "null" | ||
The "null" variable is considered to be of type "object". An alias could easily be added for this type if necessary. However, for the purposes of querystring validation, this appears to be unnecessary. | ||
#### "undefined" | ||
Unlike null, undefined is its own type with its own special properties. For the purposes of querystring validation, any blank or indefinite inputs will appear as blank strings (""). As far as I know, there is no way to force the undefined object into the querystring. Thus, unless otherwise proven, "undefined" will not be included in the Type Registry. |
@@ -20,41 +20,15 @@ // Load modules | ||
var it = Lab.test; | ||
var T = Joi.types; | ||
var Validate = require('./helper'); | ||
describe('#validate', function () { | ||
describe('Joi', function () { | ||
var config1 = { | ||
a: Joi.types.Number().min(0).max(3), | ||
b: Joi.types.String().valid('a', 'b', 'c'), | ||
c: Joi.types.String().email().optional() | ||
}; | ||
it('validates object', function (done) { | ||
var config2 = { | ||
d: [Joi.types.String(), Joi.types.Boolean()], | ||
e: [Joi.types.Number(), Joi.types.Object()] | ||
}; | ||
var schema = { | ||
a: Joi.number().min(0).max(3).without('none'), | ||
b: Joi.string().valid('a', 'b', 'c'), | ||
c: Joi.string().email().optional() | ||
}; | ||
var config3 = { | ||
f: [Joi.types.Number(), Joi.types.Boolean()], | ||
g: [Joi.types.String(), Joi.types.Object()] | ||
}; | ||
var config4 = { | ||
h: Joi.types.Number(), | ||
i: Joi.types.String(), | ||
j: Joi.types.Object() | ||
}; | ||
var config5 = { | ||
txt: Joi.types.String().xor('upc'), | ||
upc: Joi.types.String().xor('txt') | ||
}; | ||
var config6 = { | ||
txt: Joi.types.String().required().without('upc'), | ||
upc: Joi.types.String().required().without('txt') | ||
}; | ||
it('should validate object successfully', function (done) { | ||
var obj = { | ||
@@ -65,4 +39,4 @@ a: 1, | ||
}; | ||
var err = Joi.validate(obj, config1); | ||
var err = Joi.validate(obj, schema); | ||
expect(err).to.not.exist; | ||
@@ -72,6 +46,5 @@ done(); | ||
it('should validate null', function (done) { | ||
it('validates null', function (done) { | ||
var err = Joi.validate(null, config1); | ||
var err = Joi.validate(null, Joi.string()); | ||
expect(err).to.exist; | ||
@@ -81,25 +54,29 @@ done(); | ||
it('should validate xor statements', function (done) { | ||
it('validates xor', function (done) { | ||
expect(Joi.validate({ upc: null }, config5)).to.not.be.null; | ||
expect(Joi.validate({ upc: 'test' }, config5)).to.be.null; | ||
expect(Joi.validate({ txt: null }, config5)).to.not.be.null; | ||
expect(Joi.validate({ txt: 'test' }, config5)).to.be.null; | ||
var schema = Joi.object({ | ||
txt: Joi.string().xor('upc'), | ||
upc: Joi.string().xor('txt') | ||
}); | ||
var err = Joi.validate({ upc: null, txt: null }, config5); | ||
expect(err).to.not.be.null; | ||
expect(err.message).to.contain('the value of txt must exist without upc'); | ||
expect(err.message).to.contain('the value of upc must exist without txt'); | ||
var err = Joi.validate({ upc: null, txt: null }, schema, { abortEarly: false }); | ||
expect(err.message).to.equal('txt conflict with exclusive peer upc. upc conflict with exclusive peer txt'); | ||
expect(Joi.validate({ txt: 'test', upc: 'test' }, config5)).to.not.be.null; | ||
expect(Joi.validate({ txt: 'test', upc: null }, config5)).to.be.null; | ||
expect(Joi.validate({ txt: 'test', upc: '' }, config5)).to.be.null; | ||
expect(Joi.validate({ txt: '', upc: 'test' }, config5)).to.be.null; | ||
expect(Joi.validate({ txt: null, upc: 'test' }, config5)).to.be.null; | ||
expect(Joi.validate({ txt: undefined, upc: 'test' }, config5)).to.be.null; | ||
expect(Joi.validate({ txt: 'test', upc: undefined }, config5)).to.be.null; | ||
expect(Joi.validate({ txt: 'test', upc: '' }, config5)).to.be.null; | ||
expect(Joi.validate({ txt: 'test', upc: null }, config5)).to.be.null; | ||
expect(Joi.validate({ txt: '', upc: undefined }, config5)).to.not.be.null; | ||
expect(Joi.validate({ txt: '', upc: '' }, config5)).to.not.be.null; | ||
Validate(schema, [ | ||
[{ upc: null }, false], | ||
[{ upc: 'test' }, true], | ||
[{ txt: null }, false], | ||
[{ txt: 'test' }, true], | ||
[{ txt: 'test', upc: null }, true], | ||
[{ txt: 'test', upc: '' }, true], | ||
[{ txt: '', upc: 'test' }, true], | ||
[{ txt: null, upc: 'test' }, true], | ||
[{ txt: undefined, upc: 'test' }, true], | ||
[{ txt: 'test', upc: undefined }, true], | ||
[{ txt: 'test', upc: '' }, true], | ||
[{ txt: 'test', upc: null }, true], | ||
[{ txt: '', upc: undefined }, false], | ||
[{ txt: '', upc: '' }, false], | ||
[{ txt: 'test', upc: 'test' }, false] | ||
]); | ||
@@ -109,25 +86,26 @@ done(); | ||
it('should validate required without statements like xor', function (done) { | ||
it('validates an array of valid types', function (done) { | ||
expect(Joi.validate({ upc: null }, config6)).to.not.be.null; | ||
expect(Joi.validate({ upc: 'test' }, config6)).to.be.null; | ||
expect(Joi.validate({ txt: null }, config6)).to.not.be.null; | ||
expect(Joi.validate({ txt: 'test' }, config6)).to.be.null; | ||
expect(Joi.validate({ upc: null, txt: null }, config6)).to.not.be.null; | ||
var schema = { | ||
auth: [ | ||
Joi.object({ | ||
mode: Joi.string().valid('required', 'optional', 'try').nullOk() | ||
}).nullOk(), | ||
Joi.string(), | ||
Joi.boolean() | ||
] | ||
}; | ||
var err = Joi.validate({ txt: 'test', upc: 'test' }, config6); | ||
expect(err).to.not.be.null; | ||
expect(err.message).to.contain('the value of txt must exist without upc'); | ||
expect(err.message).to.contain('the value of upc must exist without txt'); | ||
var err = Joi.validate({ auth: { mode: 'none' } }, schema); | ||
expect(err).to.exist; | ||
expect(err.message).to.equal('the value of mode must be one of required, optional, try, null. the value of auth must be a string. the value of auth must be a boolean'); | ||
expect(Joi.validate({ txt: 'test', upc: null }, config6)).to.be.null; | ||
expect(Joi.validate({ txt: 'test', upc: '' }, config6)).to.be.null; | ||
expect(Joi.validate({ txt: '', upc: 'test' }, config6)).to.be.null; | ||
expect(Joi.validate({ txt: null, upc: 'test' }, config6)).to.be.null; | ||
expect(Joi.validate({ txt: undefined, upc: 'test' }, config6)).to.be.null; | ||
expect(Joi.validate({ txt: 'test', upc: undefined }, config6)).to.be.null; | ||
expect(Joi.validate({ txt: 'test', upc: '' }, config6)).to.be.null; | ||
expect(Joi.validate({ txt: 'test', upc: null }, config6)).to.be.null; | ||
expect(Joi.validate({ txt: '', upc: undefined }, config6)).to.not.be.null; | ||
expect(Joi.validate({ txt: '', upc: '' }, config6)).to.not.be.null; | ||
Validate(schema, [ | ||
[{ auth: { mode: 'try' } }, true], | ||
[{ something: undefined }, false], | ||
[{ auth: { something: undefined } }, false], | ||
[{ auth: null }, true], | ||
[{ auth: true }, true], | ||
[{ auth: 123 }, false] | ||
]); | ||
@@ -137,68 +115,92 @@ done(); | ||
it('should validate an array of valid types', function (done) { | ||
it('validates an array of string with valid', function (done) { | ||
var config = { | ||
auth: [ | ||
Joi.types.Object({ | ||
mode: T.String().valid('required', 'optional', 'try').nullOk() | ||
}).nullOk(), | ||
T.String().nullOk(), | ||
T.Boolean().nullOk() | ||
] | ||
var schema = { | ||
brand: Joi.array().includes(Joi.string().valid('amex', 'visa')) | ||
}; | ||
var err = Joi.validate({ auth: { mode: 'none' } }, config); | ||
expect(err).to.not.be.null; | ||
expect(Joi.validate({ brand: ['amex'] }, schema)).to.not.exist; | ||
expect(Joi.validate({ brand: ['visa', 'mc'] }, schema)).to.exist; | ||
done(); | ||
}); | ||
expect(err.message).to.contain('the value of mode must be one of undefined, try, optional, required, null'); | ||
expect(err.message).to.contain('the value of auth must be a string'); | ||
expect(err.message).to.contain('the value of auth must be a boolean'); | ||
it('validates pre and post convert value', function (done) { | ||
expect(Joi.validate({ auth: { mode: 'try' } }, config)).to.be.null; | ||
expect(Joi.validate({ something: undefined }, config)).to.be.null; | ||
expect(Joi.validate({ auth: { something: undefined } }, config)).to.be.null; | ||
var schema = Joi.number().valid(5); | ||
Validate(schema, [ | ||
[5, true], | ||
['5', true] | ||
]); | ||
done(); | ||
}); | ||
it('should validate config where the root item is a joi type', function (done) { | ||
it('does not change object when validation fails', function (done) { | ||
expect(Joi.validate(true, T.Boolean().nullOk())).to.be.null; | ||
expect(Joi.validate({ auth: { mode: 'try' } }, T.Object())).to.be.null; | ||
var schema = { | ||
a: Joi.number().valid(2) | ||
}; | ||
var err = Joi.validate(true, T.Object()); | ||
expect(err).to.not.be.null; | ||
expect(err.message).to.contain('the value of <root> must be an object'); | ||
var obj = { | ||
a: '5' | ||
}; | ||
err = Joi.validate(true, T.String()); | ||
expect(err).to.not.be.null; | ||
expect(err.message).to.contain('the value of <root> must be a string'); | ||
expect(Joi.validate(obj, schema, { modify: true })).to.exist; | ||
expect(obj.a).to.equal('5'); | ||
done(); | ||
}); | ||
expect(Joi.validate('test@test.com', T.String().email())).to.be.null; | ||
expect(Joi.validate({ param: 'item'}, T.Object({ param: T.String().required() }, true))).to.be.null; | ||
it('invalidates pre and post convert value', function (done) { | ||
var schema = Joi.number().invalid(5); | ||
Validate(schema, [ | ||
[5, false], | ||
['5', false] | ||
]); | ||
done(); | ||
}); | ||
it('should validate config where the root item is a joi Object and saveConversions setting is enabled', function (done) { | ||
Joi.settings.saveConversions = true; | ||
it('invalidates missing peers', function (done) { | ||
var config = T.Object({ | ||
a: T.String() | ||
}); | ||
var schema = { | ||
username: Joi.string().with('password'), | ||
password: Joi.string().without('access_token') | ||
}; | ||
expect(Joi.validate({ a: 'okay' }, config)).to.be.null; | ||
var err = Joi.validate({ username: 'bob' }, schema); | ||
expect(err).to.exist; | ||
done(); | ||
}); | ||
Joi.settings.saveConversions = false; | ||
it('validates config where the root item is a joi type', function (done) { | ||
expect(Joi.validate(true, Joi.boolean().nullOk())).to.be.null; | ||
expect(Joi.validate({ auth: { mode: 'try' } }, Joi.object())).to.be.null; | ||
var err = Joi.validate(true, Joi.object()); | ||
expect(err.message).to.contain('the value of <root> must be an object'); | ||
err = Joi.validate(true, Joi.string()); | ||
expect(err.message).to.contain('the value of <root> must be a string'); | ||
expect(Joi.validate('test@test.com', Joi.string().email())).to.be.null; | ||
expect(Joi.validate({ param: 'item' }, Joi.object({ param: Joi.string().required() }))).to.be.null; | ||
done(); | ||
}); | ||
it('should support setting the saveConversions setting locally', function (done) { | ||
it('validates config where the root item is a joi Object and modify setting is enabled', function (done) { | ||
expect(Joi.settings.saveConversions).to.equal(false); | ||
var config = Joi.object({ | ||
a: Joi.string() | ||
}); | ||
expect(Joi.validate({ a: 'okay' }, config, { modify: true })).to.be.null; | ||
done(); | ||
}); | ||
it('converts string to number in a schema', function (done) { | ||
var config = { | ||
a: T.Number(), | ||
saveConversions: true | ||
a: Joi.number() | ||
}; | ||
@@ -209,48 +211,55 @@ | ||
expect(Joi.validate(original, config)).to.be.null; | ||
expect(Joi.validate(original, config, { modify: true })).to.be.null; | ||
expect(validated).to.deep.equal(original); | ||
done(); | ||
}); | ||
expect(Joi.settings.saveConversions).to.equal(false); | ||
it('moves a key', function (done) { | ||
var schema = { | ||
a: Joi.number().rename('b', { move: true }) | ||
}; | ||
var obj = { a: 10 }; | ||
var err = Joi.validate(obj, schema); | ||
expect(err).to.not.exist; | ||
expect(obj.a).to.not.exist; | ||
expect(obj.b).to.equal(10); | ||
done(); | ||
}); | ||
it('should not alter valid top level objects when saveConversions setting is enabled', function (done) { | ||
Joi.settings.saveConversions = true; | ||
it('does not alter valid top level objects when modify setting is enabled', function (done) { | ||
var config = T.Object({ | ||
a: T.String() | ||
var config = Joi.object({ | ||
a: Joi.string() | ||
}); | ||
var original = { a: 'okay' }; | ||
var original = { a: 'okay' }; | ||
var validated = { a: 'okay' }; | ||
expect(Joi.validate(validated, config)).to.be.null; | ||
expect(Joi.validate(validated, config, { modify: true })).to.be.null; | ||
expect(validated).to.deep.equal(original); | ||
Joi.settings.saveConversions = false; | ||
done(); | ||
}); | ||
it('should allow unknown keys in objects if no schema was given', function (done) { | ||
it('allows unknown keys in objects if no schema was given', function (done) { | ||
expect(Joi.validate({ foo: 'bar' }, T.Object())).to.not.exist; | ||
expect(Joi.validate({ foo: 'bar' }, Joi.object())).to.not.exist; | ||
done(); | ||
}); | ||
it('should fail on unkown keys in objects if a schema was given', function (done) { | ||
it('fails on unknown keys in objects if a schema was given', function (done) { | ||
var err = Joi.validate({ foo: 'bar' }, T.Object({})); | ||
var err = Joi.validate({ foo: 'bar' }, Joi.object({})); | ||
expect(err).to.exist; | ||
expect(err.message).to.contain('the key (foo) is not allowed'); | ||
expect(err.message).to.equal('the keys foo are not allowed'); | ||
err = Joi.validate({ foo: 'bar' }, {}); | ||
expect(err).to.exist; | ||
expect(err.message).to.contain('the key (foo) is not allowed'); | ||
expect(err.message).to.equal('the keys foo are not allowed'); | ||
err = Joi.validate({ foo: 'bar' }, { other: T.Number() }); | ||
err = Joi.validate({ foo: 'bar' }, { other: Joi.number() }); | ||
expect(err).to.exist; | ||
expect(err.message).to.contain('the key (foo) is not allowed'); | ||
expect(err.message).to.equal('the keys foo are not allowed'); | ||
@@ -260,7 +269,7 @@ done(); | ||
it('should validate an unknown option', function (done) { | ||
it('validates an unknown option', function (done) { | ||
var config = { | ||
auth: Joi.types.Object({ | ||
mode: T.String().valid('required', 'optional', 'try').nullOk() | ||
auth: Joi.object({ | ||
mode: Joi.string().valid('required', 'optional', 'try').nullOk() | ||
}).nullOk() | ||
@@ -271,7 +280,7 @@ }; | ||
expect(err).to.not.be.null; | ||
expect(err.message).to.contain('the key (unknown) is not allowed'); | ||
expect(err.message).to.contain('the keys unknown are not allowed'); | ||
err = Joi.validate({ something: false }, config); | ||
expect(err).to.not.be.null; | ||
expect(err.message).to.contain('the key (something) is not allowed'); | ||
expect(err.message).to.contain('the keys something are not allowed'); | ||
@@ -281,42 +290,2 @@ done(); | ||
it('should work with complex configs', function (done) { | ||
var config = { | ||
handler: [T.Object(), T.Function(), T.String().valid('notFound')], | ||
payload: T.String().valid('stream', 'raw', 'parse').nullOk(), | ||
response: T.Object({ | ||
schema: T.Object().nullOk(), | ||
sample: T.Number(), | ||
failAction: T.String().valid('error', 'log', 'ignore') | ||
}).nullOk().allow(true).allow(false), | ||
auth: [ | ||
T.Object({ | ||
mode: T.String().valid(['required', 'optional', 'try']).nullOk(), | ||
scope: T.String().nullOk(), | ||
tos: T.Number().nullOk(), | ||
entity: T.String().nullOk(), | ||
strategy: T.String().nullOk(), | ||
strategies: T.Array().nullOk(), | ||
payload: T.String().nullOk() | ||
}).nullOk(), | ||
T.Boolean().allow(false).nullOk(), | ||
T.String().nullOk() | ||
], | ||
cache: T.Object({ | ||
mode: T.String().valid(['server+client', 'client+server', 'client', 'server']), | ||
segment: T.String(), | ||
privacy: T.String().valid('default', 'public', 'private'), | ||
expiresIn: T.Number().xor('expiresAt'), | ||
expiresAt: T.String(), | ||
staleIn: T.Number().with('staleTimeout'), | ||
staleTimeout: T.Number().with('staleIn') | ||
}).nullOk() | ||
}; | ||
expect(Joi.validate({ payload: 'raw' }, config)).to.be.null; | ||
expect(Joi.validate({ auth: { mode: 'required', payload: 'required' }, payload: 'raw' }, config)).to.be.null; | ||
expect(Joi.validate({ handler: internals.item, cache: { expiresIn: 20000, staleIn: 10000, staleTimeout: 500 } }, config)).to.be.null; | ||
done(); | ||
}); | ||
it('validates required key with multiple options', function (done) { | ||
@@ -326,7 +295,7 @@ | ||
module: [ | ||
T.Object({ | ||
compile: T.Function().required(), | ||
execute: T.Function() | ||
Joi.object({ | ||
compile: Joi.func().required(), | ||
execute: Joi.func() | ||
}).required(), | ||
T.String().required() | ||
Joi.string().required() | ||
] | ||
@@ -351,7 +320,7 @@ }; | ||
it('should not require optional numbers', function (done) { | ||
it('does not require optional numbers', function (done) { | ||
var config = { | ||
position: T.Number(), | ||
suggestion: T.String() | ||
position: Joi.number(), | ||
suggestion: Joi.string() | ||
}; | ||
@@ -365,7 +334,7 @@ | ||
it('should not require optional objects', function (done) { | ||
it('does not require optional objects', function (done) { | ||
var config = { | ||
position: T.Number(), | ||
suggestion: T.Object() | ||
position: Joi.number(), | ||
suggestion: Joi.object() | ||
}; | ||
@@ -379,4 +348,9 @@ | ||
it('should validate object successfully when config has an array of types', function (done) { | ||
it('validates object successfully when config has an array of types', function (done) { | ||
var schema = { | ||
f: [Joi.number(), Joi.boolean()], | ||
g: [Joi.string(), Joi.object()] | ||
}; | ||
var obj = { | ||
@@ -386,3 +360,3 @@ f: true, | ||
}; | ||
var err = Joi.validate(obj, config3); | ||
var err = Joi.validate(obj, schema); | ||
@@ -393,4 +367,10 @@ expect(err).to.not.exist; | ||
it('should validate object successfully when config allows for optional key and key is missing', function (done) { | ||
it('validates object successfully when config allows for optional key and key is missing', function (done) { | ||
var schema = { | ||
h: Joi.number(), | ||
i: Joi.string(), | ||
j: Joi.object() | ||
}; | ||
var obj = { | ||
@@ -400,3 +380,3 @@ h: 12, | ||
}; | ||
var err = Joi.validate(obj, config4); | ||
var err = Joi.validate(obj, schema); | ||
@@ -407,4 +387,10 @@ expect(err).to.not.exist; | ||
it('should fail validation', function (done) { | ||
it('fails validation', function (done) { | ||
var schema = { | ||
a: Joi.number().min(0).max(3), | ||
b: Joi.string().valid('a', 'b', 'c'), | ||
c: Joi.string().email().optional() | ||
}; | ||
var obj = { | ||
@@ -415,3 +401,3 @@ a: 10, | ||
}; | ||
var err = Joi.validate(obj, config1); | ||
var err = Joi.validate(obj, schema); | ||
@@ -422,4 +408,10 @@ expect(err).to.exist; | ||
it('should fail validation when the wrong types are supplied', function (done) { | ||
it('fails validation when the wrong types are supplied', function (done) { | ||
var schema = { | ||
a: Joi.number().min(0).max(3), | ||
b: Joi.string().valid('a', 'b', 'c'), | ||
c: Joi.string().email().optional() | ||
}; | ||
var obj = { | ||
@@ -430,3 +422,3 @@ a: 'a', | ||
}; | ||
var err = Joi.validate(obj, config1); | ||
var err = Joi.validate(obj, schema); | ||
@@ -437,3 +429,3 @@ expect(err).to.exist; | ||
it('should fail validation when missing a required parameter', function (done) { | ||
it('fails validation when missing a required parameter', function (done) { | ||
@@ -443,3 +435,3 @@ var obj = { | ||
}; | ||
var err = Joi.validate(obj, { a: Joi.types.String().required() }); | ||
var err = Joi.validate(obj, { a: Joi.string().required() }); | ||
@@ -450,3 +442,3 @@ expect(err).to.exist; | ||
it('should fail validation when missing a required parameter within an object config', function (done) { | ||
it('fails validation when missing a required parameter within an object config', function (done) { | ||
@@ -456,3 +448,3 @@ var obj = { | ||
}; | ||
var err = Joi.validate(obj, { a: Joi.types.Object({ b: Joi.types.String().required() }) }); | ||
var err = Joi.validate(obj, { a: Joi.object({ b: Joi.string().required() }) }); | ||
@@ -463,8 +455,8 @@ expect(err).to.exist; | ||
it('should fail validation when parameter is required to be an object but is given as string', function (done) { | ||
it('fails validation when parameter is required to be an object but is given as string', function (done) { | ||
var obj = { | ||
a: "a string" | ||
a: 'a string' | ||
}; | ||
var err = Joi.validate(obj, { a: Joi.types.Object({ b: Joi.types.String().required() }) }); | ||
var err = Joi.validate(obj, { a: Joi.object({ b: Joi.string().required() }) }); | ||
expect(err).to.exist; | ||
@@ -474,3 +466,3 @@ done(); | ||
it('should pass validation when parameter is required to be an object and is given correctly as a json string', function (done) { | ||
it('validates when parameter is required to be an object and is given correctly as a json string', function (done) { | ||
@@ -480,3 +472,3 @@ var obj = { | ||
}; | ||
var err = Joi.validate(obj, { a: Joi.types.Object({ b: Joi.types.String().required() }) }); | ||
var err = Joi.validate(obj, { a: Joi.object({ b: Joi.string().required() }) }); | ||
expect(err).to.be.null; | ||
@@ -486,3 +478,3 @@ done(); | ||
it('should fail validation when parameter is required to be an object but is given as a json string that is incorrect (number instead of string)', function (done) { | ||
it('fails validation when parameter is required to be an object but is given as a json string that is incorrect (number instead of string)', function (done) { | ||
@@ -492,3 +484,3 @@ var obj = { | ||
}; | ||
var err = Joi.validate(obj, { a: Joi.types.Object({ b: Joi.types.String().required() }) }); | ||
var err = Joi.validate(obj, { a: Joi.object({ b: Joi.string().required() }) }); | ||
expect(err).to.exist; | ||
@@ -498,3 +490,3 @@ done(); | ||
it('should fail validation when parameter is required to be an Array but is given as string', function (done) { | ||
it('fails validation when parameter is required to be an Array but is given as string', function (done) { | ||
@@ -504,3 +496,3 @@ var obj = { | ||
}; | ||
var err = Joi.validate(obj, { a: Joi.types.Array() }); | ||
var err = Joi.validate(obj, { a: Joi.array() }); | ||
expect(err).to.exist; | ||
@@ -510,3 +502,3 @@ done(); | ||
it('should pass validation when parameter is required to be an Array and is given correctly as a json string', function (done) { | ||
it('validates when parameter is required to be an Array and is given correctly as a json string', function (done) { | ||
@@ -516,3 +508,3 @@ var obj = { | ||
}; | ||
var err = Joi.validate(obj, { a: Joi.types.Array() }); | ||
var err = Joi.validate(obj, { a: Joi.array() }); | ||
expect(err).to.be.null; | ||
@@ -522,3 +514,3 @@ done(); | ||
it('should fail validation when parameter is required to be an Array but is given as a json that is incorrect (object instead of array)', function (done) { | ||
it('fails validation when parameter is required to be an Array but is given as a json that is incorrect (object instead of array)', function (done) { | ||
@@ -528,3 +520,3 @@ var obj = { | ||
}; | ||
var err = Joi.validate(obj, { a: Joi.types.Object({ b: Joi.types.String().required() }) }); | ||
var err = Joi.validate(obj, { a: Joi.object({ b: Joi.string().required() }) }); | ||
expect(err).to.exist; | ||
@@ -534,4 +526,9 @@ done(); | ||
it('should fail validation when config is an array and fails', function (done) { | ||
it('fails validation when config is an array and fails', function (done) { | ||
var schema = { | ||
d: [Joi.string(), Joi.boolean()], | ||
e: [Joi.number(), Joi.object()] | ||
}; | ||
var obj = { | ||
@@ -541,3 +538,3 @@ d: 10, | ||
}; | ||
var err = Joi.validate(obj, config2); | ||
var err = Joi.validate(obj, schema); | ||
@@ -548,4 +545,9 @@ expect(err).to.exist; | ||
it('should fail validation when config is an array and fails with extra keys', function (done) { | ||
it('fails validation when config is an array and fails with extra keys', function (done) { | ||
var schema = { | ||
d: [Joi.string(), Joi.boolean()], | ||
e: [Joi.number(), Joi.object()] | ||
}; | ||
var obj = { | ||
@@ -555,3 +557,3 @@ a: 10, | ||
}; | ||
var err = Joi.validate(obj, config2); | ||
var err = Joi.validate(obj, schema); | ||
@@ -562,10 +564,13 @@ expect(err).to.exist; | ||
it('should fail validation with extra keys', function (done) { | ||
it('fails validation with extra keys', function (done) { | ||
var schema = { | ||
a: Joi.number(), | ||
}; | ||
var obj = { | ||
a: 1, | ||
b: 'a', | ||
d: 'c' | ||
}; | ||
var err = Joi.validate(obj, config1); | ||
var err = Joi.validate(obj, schema); | ||
@@ -576,9 +581,9 @@ expect(err).to.exist; | ||
it('should pass string validation of missing optional key with regex string condition', function (done) { | ||
it('validates missing optional key with string condition', function (done) { | ||
var rules = { | ||
key: Joi.types.String().alphanum(false).min(8) | ||
var schema = { | ||
key: Joi.string().alphanum(false).min(8) | ||
}; | ||
var err = Joi.validate({}, rules); | ||
var err = Joi.validate({}, schema); | ||
expect(err).to.not.exist; | ||
@@ -588,6 +593,9 @@ done(); | ||
it('should pass validation with extra keys and remove them when stripExtraKeys is set', function (done) { | ||
it('validates with extra keys and remove them when stripUnknown is set', function (done) { | ||
Joi.settings.stripExtraKeys = true; | ||
Joi.settings.allowExtraKeys = true; | ||
var schema = { | ||
a: Joi.number().min(0).max(3), | ||
b: Joi.string().valid('a', 'b', 'c'), | ||
c: Joi.string().email().optional() | ||
}; | ||
@@ -599,16 +607,16 @@ var obj = { | ||
}; | ||
var err = Joi.validate(obj, config1); | ||
var err = Joi.validate(obj, schema, { stripUnknown: true, allowUnknown: true }); | ||
expect(err).to.be.null; | ||
expect(obj).to.deep.equal({a: 1, b: 'a'}); | ||
Joi.settings.stripExtraKeys = false; | ||
Joi.settings.allowExtraKeys = false; | ||
expect(obj).to.deep.equal({ a: 1, b: 'a' }); | ||
done(); | ||
}); | ||
it('should pass validation with extra keys when allowExtraKeys is set', function (done) { | ||
it('should pass validation with extra keys when allowUnknown is set', function (done) { | ||
Joi.settings.allowExtraKeys = true; | ||
var schema = { | ||
a: Joi.number().min(0).max(3), | ||
b: Joi.string().valid('a', 'b', 'c'), | ||
c: Joi.string().email().optional() | ||
}; | ||
@@ -620,20 +628,14 @@ var obj = { | ||
}; | ||
var err = Joi.validate(obj, config1); | ||
var err = Joi.validate(obj, schema, { allowUnknown: true }); | ||
expect(err).to.be.null; | ||
expect(obj).to.deep.equal({a: 1, b: 'a', d: 'c'}); | ||
Joi.settings.allowExtraKeys = false; | ||
expect(obj).to.deep.equal({ a: 1, b: 'a', d: 'c' }); | ||
done(); | ||
}); | ||
it('should pass validation with extra keys set locally', function (done) { | ||
it('should pass validation with extra keys set', function (done) { | ||
expect(Joi.settings.stripExtraKeys).to.equal(false); | ||
var localConfig = { | ||
a: Joi.types.Number().min(0).max(3), | ||
b: Joi.types.String().valid('a', 'b', 'c'), | ||
allowExtraKeys: true | ||
a: Joi.number().min(0).max(3), | ||
b: Joi.string().valid('a', 'b', 'c'), | ||
}; | ||
@@ -646,14 +648,11 @@ | ||
}; | ||
var err = Joi.validate(obj, localConfig); | ||
var err = Joi.validate(obj, localConfig, { allowUnknown: true }); | ||
expect(err).to.be.null; | ||
expect(obj).to.deep.equal({a: 1, b: 'a', d: 'c'}); | ||
expect(Joi.settings.stripExtraKeys).to.equal(false); | ||
expect(obj).to.deep.equal({ a: 1, b: 'a', d: 'c' }); | ||
err = Joi.validate(obj, localConfig); | ||
err = Joi.validate(obj, localConfig, { allowUnknown: true }); | ||
expect(err).to.be.null; | ||
expect(obj).to.deep.equal({a: 1, b: 'a', d: 'c'}); | ||
expect(Joi.settings.stripExtraKeys).to.equal(false); | ||
expect(obj).to.deep.equal({ a: 1, b: 'a', d: 'c' }); | ||
done(); | ||
@@ -665,9 +664,5 @@ }); | ||
expect(Joi.settings.stripExtraKeys).to.equal(false); | ||
var localConfig = { | ||
a: Joi.types.Number().min(0).max(3), | ||
b: Joi.types.String().valid('a', 'b', 'c'), | ||
stripExtraKeys: true, | ||
allowExtraKeys: true | ||
a: Joi.number().min(0).max(3), | ||
b: Joi.string().valid('a', 'b', 'c') | ||
}; | ||
@@ -680,13 +675,11 @@ | ||
}; | ||
var err = Joi.validate(obj, localConfig); | ||
var err = Joi.validate(obj, localConfig, { stripUnknown: true, allowUnknown: true }); | ||
expect(err).to.be.null; | ||
expect(obj).to.deep.equal({a: 1, b: 'a'}); | ||
expect(Joi.settings.stripExtraKeys).to.equal(false); | ||
expect(obj).to.deep.equal({ a: 1, b: 'a' }); | ||
err = Joi.validate(obj, localConfig); | ||
err = Joi.validate(obj, localConfig, { stripUnknown: true, allowUnknown: true }); | ||
expect(err).to.be.null; | ||
expect(obj).to.deep.equal({a: 1, b: 'a'}); | ||
expect(Joi.settings.stripExtraKeys).to.equal(false); | ||
expect(obj).to.deep.equal({ a: 1, b: 'a' }); | ||
@@ -698,4 +691,3 @@ done(); | ||
Joi.settings.skipFunctions = true; | ||
var schema = { username: Joi.types.String() }; | ||
var schema = Joi.object({ username: Joi.string() }).options({ skipFunctions: true }); | ||
var input = { username: 'test', func: function () { } }; | ||
@@ -705,3 +697,2 @@ var err = Joi.validate(input, schema); | ||
expect(err).to.not.exist; | ||
Joi.settings.skipFunctions = false; | ||
done(); | ||
@@ -712,31 +703,27 @@ }); | ||
Joi.settings.skipFunctions = false; | ||
var schema = { username: Joi.types.String() }; | ||
var schema = { username: Joi.string() }; | ||
var input = { username: 'test', func: function () { } }; | ||
var err = Joi.validate(input, schema); | ||
var err = Joi.validate(input, schema, { skipFunctions: false }); | ||
expect(err).to.exist; | ||
expect(err.message).to.contain('the key (func) is not allowed'); | ||
expect(err.message).to.contain('the keys func are not allowed'); | ||
done(); | ||
}); | ||
it('should work when the saveConversions setting is enabled', function (done) { | ||
it('should work when the modify setting is enabled', function (done) { | ||
Joi.settings.saveConversions = true; | ||
var schema = { item: Joi.types.Number() }; | ||
var schema = { item: Joi.number() }; | ||
var input = { item: '1' }; | ||
var err = Joi.validate(input, schema); | ||
var err = Joi.validate(input, schema, { modify: true }); | ||
expect(err).to.not.exist; | ||
expect(input.item).to.equal(1); | ||
Joi.settings.saveConversions = false; | ||
done(); | ||
}); | ||
it('should work when the saveConversions setting is disabled', function (done) { | ||
it('should work when the modify setting is disabled', function (done) { | ||
Joi.settings.saveConversions = false; | ||
var schema = { item: Joi.types.Number() }; | ||
var schema = { item: Joi.number() }; | ||
var input = { item: '1' }; | ||
var err = Joi.validate(input, schema); | ||
var err = Joi.validate(input, schema, { modify: false }); | ||
@@ -748,23 +735,10 @@ expect(err).to.not.exist; | ||
it('should display correct processed pluralization messsage when skipFunctions is enabled', function (done) { | ||
it('should not convert values when convert is false', function (done) { | ||
Joi.settings.skipFunctions = true; | ||
var schema = { username: Joi.types.String() }; | ||
var input = { username: 'test', item1: 'test', 'item2': 'test' }; | ||
var err = Joi.validate(input, schema); | ||
expect(err).to.exist; | ||
Joi.settings.skipFunctions = false; | ||
done(); | ||
}); | ||
it('should not convert values when skipConversions is set', function (done) { | ||
Joi.settings.skipConversions = true; | ||
var schema = { | ||
arr: Joi.types.Array().includes(Joi.types.String()) | ||
arr: Joi.array().includes(Joi.string()) | ||
}; | ||
var input = { arr: 'foo' }; | ||
var err = Joi.validate(input, schema); | ||
var err = Joi.validate(input, schema, { convert: false }); | ||
@@ -775,37 +749,34 @@ expect(err).to.exist; | ||
it('should fail validation when a child object has an invalid string value but object traversal isn\'t complete', function (done) { | ||
it('full errors when abortEarly is false', function (done) { | ||
var input = { method: 'GET', path: '/', config: { payload: 'something' } }; | ||
var err = Joi.validate(input, internals.routeSchema); | ||
var schema = { | ||
a: Joi.string(), | ||
b: Joi.string() | ||
}; | ||
expect(err).to.exist; | ||
done(); | ||
}); | ||
var input = { a: 1, b: 2 }; | ||
it('validation errors should provide an annotated message when making the error annotated', function (done) { | ||
var errOne = Joi.validate(input, schema); | ||
var errFull = Joi.validate(input, schema, { abortEarly: false }); | ||
var input = { method: 'GET', path: '/', config: { payload: 'something' } }; | ||
var err = Joi.validate(input, internals.routeSchema); | ||
err.annotated(); | ||
expect(err.message).to.contain('\u001b[0m'); | ||
expect(errOne).to.exist | ||
expect(errFull).to.exist | ||
expect(errFull._errors.length).to.be.greaterThan(errOne._errors.length); | ||
done(); | ||
}); | ||
it('there should be more validation errors when short circuit is disabled', function (done) { | ||
it('supports custom errors when validating types', function (done) { | ||
var input = { a: 1, b: 2 }; | ||
var schema = { | ||
email: Joi.string().email(), | ||
date: Joi.date(), | ||
alphanum: Joi.string().alphanum(), | ||
min: Joi.string().min(3), | ||
max: Joi.string().max(3), | ||
required: Joi.string().required().without('xor'), | ||
xor: Joi.string().without('required'), | ||
renamed: Joi.string().rename('required').valid('456'), | ||
notEmpty: Joi.string().required() | ||
}; | ||
var resultWithShortCircuit = Joi.validate(input, T.Object({})); | ||
var resultWithoutShortCircuit = Joi.validate(input, T.Object({}).noShortCircuit()); | ||
expect(resultWithShortCircuit).to.exist | ||
expect(resultWithoutShortCircuit).to.exist | ||
expect(resultWithoutShortCircuit._errors.length).to.be.greaterThan(resultWithShortCircuit._errors.length); | ||
done(); | ||
}); | ||
it('should support custom errors when validating types', function (done) { | ||
var input = { | ||
@@ -823,44 +794,221 @@ email: 'invalid-email', | ||
var err = Joi.validate(input, schema, { abortEarly: false, languagePath: Path.join(__dirname, 'languages', 'en-us.json') }); | ||
expect(err).to.exist; | ||
expect(err.message).to.equal('19. 18. 16. 14. 15. 7. 7. 11. 3'); | ||
done(); | ||
}); | ||
it('returns key when language file missing item', function (done) { | ||
var input = { | ||
notEmpty: '' | ||
}; | ||
var schema = { | ||
email: T.String().email(), | ||
date: T.String().date(), | ||
alphanum: T.String().alphanum(), | ||
min: T.String().min(3), | ||
max: T.String().max(3), | ||
required: T.String().required().without('xor'), | ||
xor: T.String().without('required'), | ||
renamed: T.String().rename('required'), | ||
notEmpty: T.String().required(), | ||
languagePath: Path.join(__dirname, 'languages', 'en-US.json') | ||
notEmpty: Joi.string().required() | ||
}; | ||
var err = Joi.validate(input, schema); | ||
var err = Joi.validate(input, schema, { languagePath: Path.join(__dirname, 'languages', 'empty.json') }); | ||
expect(err).to.exist; | ||
expect(err.message).to.contain('The `email` field must be a valid e-mail address.'); | ||
expect(err.message).to.contain('The `date` field must be a valid date.'); | ||
expect(err.message).to.contain('The `alphanum` field failed one or more validation constraints.'); | ||
expect(err.message).to.contain('The `min` field must be at least 3 characters long.'); | ||
expect(err.message).to.contain('The `max` field may not exceed 3 characters.'); | ||
expect(err.message).to.contain('The `required` field must be omitted if `xor` is specified.'); | ||
expect(err.message).to.contain('`required` is already assigned to the `renamed` field.'); | ||
expect(err.message).to.contain('Invalid value for `notEmpty`: `empty`.'); | ||
expect(err.message).to.equal('notEmpty'); | ||
done(); | ||
}); | ||
it('annotates error', function (done) { | ||
var object = { | ||
a: 'm', | ||
y: { | ||
b: { | ||
c: 10 | ||
} | ||
} | ||
}; | ||
var schema = { | ||
a: Joi.string().valid('a', 'b', 'c', 'd'), | ||
y: Joi.object({ | ||
u: Joi.string().valid(['e', 'f', 'g', 'h']).required(), | ||
b: Joi.string().valid('i', 'j').allow(false), | ||
d: Joi.object({ | ||
x: Joi.string().valid('k', 'l').required(), | ||
c: Joi.number() | ||
}) | ||
}) | ||
}; | ||
var err = Joi.validate(object, schema, { abortEarly: false }); | ||
expect(err).to.exist; | ||
err.annotated(); | ||
expect(err.message).to.equal('{\n \"y\": {\n \"b\" \u001b[31m[5]\u001b[0m: {\n \"c\": 10\n },\n \u001b[41m\"b\"\u001b[0m\u001b[31m [4]: -- missing --\u001b[0m,\n \u001b[41m\"u\"\u001b[0m\u001b[31m [3]: -- missing --\u001b[0m,\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] the value of a must be one of a, b, c, d\n[2] the value of u is not allowed to be undefined\n[3] the value of u must be one of e, f, g, h\n[4] the value of b must be one of i, j, false\n[5] the value of b must be a string\u001b[0m'); | ||
done(); | ||
}); | ||
internals.routeSchema = { | ||
method: T.String().invalid('head').required(), | ||
path: T.String().required(), | ||
handler: [T.Object(), T.Function(), T.String().valid('notFound')], | ||
config: T.Object({ | ||
handler: [T.Object(), T.Function(), T.String().valid('notFound')], | ||
payload: T.String().valid(['stream', 'raw', 'parse']), | ||
response: T.Object({ | ||
schema: T.Object().nullOk(), | ||
sample: T.Number(), | ||
failAction: T.String().valid(['error', 'log', 'ignore']) | ||
}) | ||
}) | ||
}; | ||
describe('#describe', function () { | ||
var schema = { | ||
sub: { | ||
email: Joi.string().email(), | ||
date: Joi.date(), | ||
child: Joi.object({ | ||
alphanum: Joi.string().alphanum() | ||
}), | ||
}, | ||
min: [Joi.number(), Joi.string().min(3)], | ||
max: Joi.string().max(3), | ||
required: Joi.string().required().without('xor'), | ||
xor: Joi.string().without('required'), | ||
renamed: Joi.string().rename('required').valid('456'), | ||
notEmpty: Joi.string().required().description('a').notes('b').tags('c') | ||
}; | ||
var result = { | ||
type: 'object', | ||
flags: { | ||
insensitive: false, | ||
allowOnly: false | ||
}, | ||
valids: [undefined], | ||
invalids: [null], | ||
children: { | ||
sub: { | ||
type: 'object', | ||
flags: { | ||
insensitive: false, | ||
allowOnly: false | ||
}, | ||
valids: [undefined], | ||
invalids: [null], | ||
children: { | ||
email: { | ||
type: 'string', | ||
flags: { | ||
insensitive: false, | ||
allowOnly: false | ||
}, | ||
valids: [undefined], | ||
invalids: [null, ''], | ||
rules: [{ name: 'email' }] | ||
}, | ||
date: { | ||
type: 'date', | ||
flags: { | ||
insensitive: false, | ||
allowOnly: false | ||
}, | ||
valids: [undefined], | ||
invalids: [null] | ||
}, | ||
child: { | ||
type: 'object', | ||
flags: { | ||
insensitive: false, | ||
allowOnly: false | ||
}, | ||
valids: [undefined], | ||
invalids: [null], | ||
children: { | ||
alphanum: { | ||
type: 'string', | ||
flags: { | ||
insensitive: false, | ||
allowOnly: false | ||
}, | ||
valids: [undefined], | ||
invalids: [null, ''], | ||
rules: [{ name: 'alphanum' }] | ||
} | ||
} | ||
} | ||
} | ||
}, | ||
min: [ | ||
{ | ||
type: 'number', | ||
flags: { | ||
insensitive: false, | ||
allowOnly: false | ||
}, | ||
valids: [undefined], | ||
invalids: [null] | ||
}, | ||
{ | ||
type: 'string', | ||
flags: { | ||
insensitive: false, | ||
allowOnly: false | ||
}, | ||
valids: [undefined], | ||
invalids: [null, ''], | ||
rules: [{ name: 'min', arg: 3 }] | ||
} | ||
], | ||
max: { | ||
type: 'string', | ||
flags: { | ||
insensitive: false, | ||
allowOnly: false | ||
}, | ||
valids: [undefined], | ||
invalids: [null, ''], | ||
rules: [{ name: 'max', arg: 3 }] | ||
}, | ||
required: { | ||
type: 'string', | ||
flags: { | ||
insensitive: false, | ||
allowOnly: false | ||
}, | ||
invalids: [null, '', undefined], | ||
rules: [{ name: 'without', arg: ['xor'] }] | ||
}, | ||
xor: { | ||
type: 'string', | ||
flags: { | ||
insensitive: false, | ||
allowOnly: false | ||
}, | ||
valids: [undefined], | ||
invalids: [null, ''], | ||
rules: [{ name: 'without', arg: ['required'] }] | ||
}, | ||
renamed: { | ||
type: 'string', | ||
flags: { | ||
insensitive: false, | ||
allowOnly: true | ||
}, | ||
valids: [undefined, '456'], | ||
invalids: [null, ''] | ||
}, | ||
notEmpty: { | ||
type: 'string', | ||
flags: { | ||
insensitive: false, | ||
allowOnly: false | ||
}, | ||
invalids: [null, '', undefined], | ||
description: 'a', | ||
notes: ['b'], | ||
tags: ['c'] | ||
} | ||
} | ||
}; | ||
it('describes schema', function (done) { | ||
var description = Joi.describe(schema); | ||
expect(description).to.deep.equal(result); | ||
done(); | ||
}); | ||
it('describes schema with object', function (done) { | ||
var description = Joi.describe(Joi.object(schema)); | ||
expect(description).to.deep.equal(result); | ||
done(); | ||
}); | ||
}); | ||
}); |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
Dynamic require
Supply chain riskDynamic require can indicate the package is performing dangerous or unsafe dynamic code execution.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Dynamic require
Supply chain riskDynamic require can indicate the package is performing dangerous or unsafe dynamic code execution.
Found 1 instance in 1 package
657
293183
35
3319
1