'use strict';
var assert = require('assert');
var util = require('util');
var ut = require('utjs');
var Type = require('./Type');
/**
* Generates a schema object that matches an array data type.
* Note that undefined values inside arrays are not allowed
* by default but can be by using sparse().
*
* @constructor
* @extends Type
*/
function ArrayType() {
ArrayType.super_.call(this);
}
util.inherits(ArrayType, Type);
/**
* Specifies the minimum number of items in the array.
* @param {Number} limit The lowest number of array items allowed.
* @return {ArrayType} The class reference so multiple calls can be chained.
*/
ArrayType.prototype.min = function (limit) {
assert(ut.isInteger(limit) && limit >= 0, 'limit must be a positive integer');
this._rules.min = limit;
return this;
};
/**
* Specifies the maximum number of items in the array.
* @param {Number} limit The highest number of array items allowed.
* @return {ArrayType} The class reference so multiple calls can be chained.
*/
ArrayType.prototype.max = function (limit) {
assert(ut.isInteger(limit) && limit >= 0, 'limit must be a positive integer');
this._rules.max = limit;
return this;
};
/**
* Specifies the exact number of items in the array.
* @param {Number} limit The number of array items allowed.
* @return {ArrayType} The class reference so multiple calls can be chained.
*/
ArrayType.prototype.length = function (limit) {
assert(ut.isInteger(limit) && limit >= 0, 'limit must be a positive integer');
this._rules.length = limit;
return this;
};
/**
* Allow this array to be sparse.
* @param {Boolean} [enabled] Can be used with a falsy value to go back
* to the default behavior.
* @return {ArrayType} The class reference so multiple calls can be chained.
*/
ArrayType.prototype.sparse = function (enabled) {
this._rules.sparse = enabled === undefined ? true : !!enabled;
return this;
};
/**
* List the types allowed for the array values.
* @param {...Type|Array.<Type>} type A schema object to validate each array item against.
* Type can be an array of values, or multiple values
* can be passed as individual arguments.
* @return {ArrayType} The class reference so multiple calls can be chained.
*/
ArrayType.prototype.items = function (type) {
this._rules.items = Array.isArray(type) ? type :
ut.argumentsToArray(arguments);
return this;
};
ArrayType.prototype._validate = function (value) {
var rules = this._rules;
if (!Array.isArray(value)) {
return !rules.required && value === undefined;
}
var min = rules.min;
var max = rules.max;
var length = rules.length;
var items = rules.items;
var valueLength = value.length;
if (min !== undefined && valueLength < min ||
max !== undefined && valueLength > max) {
return false;
}
if (length !== undefined && valueLength !== length) {
return false;
}
if (items !== undefined && !this._validateItems(value, items)) {
return false;
}
return rules.forbidden ? false : true;
};
ArrayType.prototype._validateItems = function (value, items) {
var checked = new Array(items.length);
var found;
for (var i = 0; i < value.length; i++) {
found = false;
for (var j = 0; j < items.length && !found; j++) {
if (this._rules.sparse && value[i] === undefined) {
found = true;
} else if (value[i] !== undefined && items[j].validate(value[i])) {
found = true;
checked[j] = true;
}
}
if (!found) {
return false;
}
}
for (i = 0; i < items.length; i++) {
if (items[i]._rules.required && !checked[i]) {
return false;
}
}
return true;
};
/**
* Creates and returns an ArrayType object.
* @return {ArrayType} The ArrayType object.
* @global
* @alias array
*/
module.exports = function array() {
return new ArrayType();
};