Source: object.js

'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 object data type.
 *
 * @constructor
 * @extends Type
 */
function ObjectType() {
  ObjectType.super_.call(this);
}

util.inherits(ObjectType, Type);

/**
 * Specifies the minimum number of keys in the object.
 * @param  {Number} limit The lowest number of keys allowed.
 * @return {ObjectType} The class reference so multiple calls can be chained.
 */
ObjectType.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 keys in the object.
 * @param  {Number} limit The highest number of keys allowed.
 * @return {ObjectType} The class reference so multiple calls can be chained.
 */
ObjectType.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 keys in the object.
 * @param  {Number} limit The number of object keys allowed.
 * @return {ObjectType} The class reference so multiple calls can be chained.
 */
ObjectType.prototype.length = function (limit) {
  assert(ut.isInteger(limit) && limit >= 0, 'limit must be a positive integer');

  this._rules.length = limit;
  return this;
};

/**
 * Sets the allowed object keys.
 * @param  {Object} keys Object where each key is assigned a joi type object.
 * @return {ObjectType} The class reference so multiple calls can be chained.
 */
ObjectType.prototype.keys = function (keys) {
  assert(ut.isPlainObject(keys), 'keys must be a plain object');

  this._rules.keys = keys;
  return this;
};

/**
 * Requires the object to be an instance of a given constructor
 * @param  {Function} constructor The constructor function that the object must be an instance of.
 * @return {ObjectType} The class reference so multiple calls can be chained.
 */
ObjectType.prototype.type = function (constructor) {
  assert(ut.isFunction(constructor), 'type must be a constructor function');

  this._rules.type = constructor;
  return this;
};

ObjectType.prototype._validate = function (value) {
  var rules = this._rules;

  if (typeof value !== 'object' || value === null) {
    return !rules.required && value === undefined;
  }

  if (!this._validateLength(value)) {
    return false;
  }

  if (rules.type !== undefined && value instanceof rules.type === false) {
    return false;
  }

  if (rules.keys !== undefined && !this._validateKeys(value, rules.keys)) {
    return false;
  }

  return rules.forbidden ? false : true;
};

ObjectType.prototype._validateLength = function (value) {
  var rules = this._rules;
  var length = -1;

  if (rules.min !== undefined) {
    length = ut.objectLength(value);

    if (length < rules.min) {
      return false;
    }
  }

  if (rules.max !== undefined) {
    if (length === -1) {
      length = ut.objectLength(value);
    }

    if (length > rules.max) {
      return false;
    }
  }

  if (rules.ruleLength !== undefined) {
    if (length === -1) {
      length = ut.objectLength(value);
    }

    if (length !== rules.ruleLength) {
      return false;
    }
  }

  return true;
};

ObjectType.prototype._validateKeys = function (value, keys) {
  var checked = {};
  var i;

  for (i in value) {
    if (keys[i] !== undefined && !keys[i].validate(value[i])) {
      return false;
    }

    checked[i] = 1;
  }

  for (i in keys) {
    if (keys[i]._rules.required && checked[i] === undefined) {
      return false;
    }
  }

  return true;
};

/**
 * Creates and returns a ObjectType object.
 * @return {ObjectType} The ObjectType object.
 * @global
 * @alias object
 */
module.exports = function object() {
  return new ObjectType();
};