OpenVeo server API for plugins

API Docs for: 3.0.0
Show:

File: lib/util.js

'use strict';

var util = require('util');

/**
 * Provides functions for common JavaScript operations.
 *
 * @example
 *
 *     // Get util
 *     var util = require('@openveo/api').util;
 *
 * @module util
 * @class util
 * @main util
 */

/**
 * Merges, recursively, all properties of object2 in object1.
 *
 * This will not create copies of objects.
 *
 * @method merge
 * @param {Object} object1 The JavaScript final object
 * @param {Object} object2 A second JavaScript object to merge into
 * the first one
 * @return {Object} object1
 */
module.exports.merge = function(object1, object2) {
  if (!object2)
    return object1;

  if (!object1)
    return object2;

  for (var property in object2) {

    try {

      // Object property is an object
      // Recusively merge its properties
      if (typeof object2[property] === 'object' && !util.isArray(object2[property])) {
        object1[property] = object1[property] || {};
        object1[property] = this.merge(object1[property], object2[property]);
      } else
        object1[property] = object2[property];

    } catch (e) {

      // Property does not exist in object1, create it
      object1[property] = object2[property];

    }

  }

  return object1;
};

/**
 * Makes union of two arrays.
 *
 * @method joinArray
 * @param {Array} [array1] An array
 * @param {Array} [array2] An array
 * @return {Array} The union of the two arrays
 */
module.exports.joinArray = function(array1, array2) {
  return array1.concat(array2.filter(function(item) {
    return array1.indexOf(item) < 0;
  }));
};

/**
 * Makes intersection of two arrays.
 *
 * @method intersectArray
 * @param {Array} [array1] An array
 * @param {Array} [array2] An array
 * @return {Array} The intersection of the two arrays
 */
module.exports.intersectArray = function(array1, array2) {
  return array2.filter(function(item) {
    return array1.indexOf(item) >= 0;
  });
};

/**
 * Checks if an email address is valid or not.
 *
 * @method isEmailValid
 * @param {String} email The email address
 * @return {Boolean} true if the email is valid, false otherwise
 */
module.exports.isEmailValid = function(email) {
  var reg = new RegExp('[a-z0-9!#$%&\'*+\/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&\'*+\/=?^_`{|}~-]+)*@(?:[a-z0-9]' +
                       '(?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?');
  return reg.test(email);
};

/**
 * Checks if a value is isContained into another comparing primitive types.
 *
 * All values in expectedValue must be found in value to pass the test.
 *
 * @method isContained
 * @param {Object|Number|String|Array} expectedValue The value expecting to be found in "value"
 * @return {Boolean} true if the expected value has been found in value
 */
module.exports.isContained = function(expectedValue, value) {
  if (Object.prototype.toString.call(expectedValue) === '[object Array]') {
    if (Object.prototype.toString.call(value) !== '[object Array]')
      return false;

    for (var i = 0; i < expectedValue.length; i++) {
      if (!this.isContained(expectedValue[i], value[i]))
        return false;
    }
  } else if (Object.prototype.toString.call(expectedValue) === '[object Object]') {
    if (Object.prototype.toString.call(value) !== '[object Object]')
      return false;

    for (var property in expectedValue) {
      if (!this.isContained(expectedValue[property], value[property]))
        return false;
    }
  } else if (expectedValue !== value)
    return false;

  return true;
};

/**
 * Validates first level object properties using the given validation description object.
 *
 * It helps validating that an object, coming from a request query string parameters correspond to the expected
 * type, if it has to be required, if it must be contained into a list of values etc.
 *
 * Available features by types :
 *  - **string**
 *    - **default** Specify a default value
 *    - **required** Boolean to indicate if the value is required (if default is specified, value will always be set)
 *    - **in** Specify an array of strings to validate that the value is inside this array
 *  - **number**
 *    - **default** Specify a default value
 *    - **required** Boolean to indicate if the value is required (if default is specified, value will always be set)
 *    - **in** Specify an array of numbers to validate that the value is inside this array
 *    - **gt** Specify a number to validate that the value is greater than this number
 *    - **lt** Specify a number to validate that the value is lesser than this number
 *    - **gte** Specify a number to validate that the value is greater or equal to this number
 *    - **lte** Specify a number to validate that the value is lesser or equal to this number
 *  - **array<string>**
 *    - **required** Boolean to indicate if the value is required (an empty array is not an error)
 *  - **array<number>**
 *    - **required** Boolean to indicate if the value is required (an empty array is not an error)
 *  - **date**
 *    - **required** Boolean to indicate if the value is required
 *    - **gt** Specify a date to validate that the value is greater than this date
 *    - **lt** Specify a date to validate that the value is lesser than this date
 *    - **gte** Specify a date to validate that the value is greater or equal to this date
 *    - **lte** Specify a date to validate that the value is lesser or equal to this date
 *  - **object**
 *    - **default** Specify a default value
 *    - **required** Boolean to indicate if the value is required (if default is specified, value will always be set)
 *
 * @example
 *
 *     // Get util
 *     var util = require('@openveo/api').util;
 *
 *     // Validate parameters
 *     var params = util.shallowValidateObject({
 *       myStringProperty: 'my value',
 *       myNumberProperty: 25,
 *       myArrayStringProperty: ['value1', 'value2']
 *       myArrayNumberProperty: [10, 5]
 *       myDateProperty: '02/25/2016'
 *       myObjectProperty: {firstKey: 'firstValue'}
 *     }, {
 *       myStringProperty: {type: 'string', required: true, default: 'default', in: ['my value', 'value']},
 *       myNumberProperty: {type: 'number', required: true, default: 0, in: [0, 5, 10], gte: 0, lte: 5},
 *       myArrayStringProperty: {type: 'array<string>', required: true},
 *       myArrayNumberProperty: {type: 'array<number>', required: true},
 *       myDateProperty: {type: 'date', required: true, gte: '02/20/2016', lte: '03/30/2016'}
 *       myObjectProperty: {type: 'object', required: true}
 *     });
 *
 *     console.log(params);
 *
 * @method shallowValidateObject
 * @param {Object} objectToAnalyze The object to analyze
 * @param {Object} validationDescription The validation description object
 * @return {Object} A new object with the list of properties as expected
 */
module.exports.shallowValidateObject = function(objectToAnalyze, validationDescription) {
  var dateFormat = /(\d{2})[-\/](\d{2})[-\/](\d{4})/;
  var properties = {};

  // Iterate through the list of expected properties
  for (var name in validationDescription) {
    var expectedProperty = validationDescription[name];
    var value = objectToAnalyze[name];

    if (expectedProperty) {

      // This property was expected

      // Options
      var defaultValue = expectedProperty.default || null;
      var required = expectedProperty.required || false;
      var inside = expectedProperty.in || null;
      var gt = expectedProperty.gt || null;
      var lt = expectedProperty.lt || null;
      var gte = expectedProperty.gte || null;
      var lte = expectedProperty.lte || null;

      switch (expectedProperty.type) {
        case 'string':
          value = value !== undefined ? String(value) : defaultValue;
          if (inside && inside.indexOf(value) < 0)
            throw new Error('Property ' + name + ' must be one of ' + inside.join(', '));
          break;
        case 'number':
          value = value !== undefined ? parseInt(value) : defaultValue;
          value = value || defaultValue || null;

          if (gt && value <= gt)
            throw new Error('Property ' + name + ' must be greater than ' + gt);

          if (lt && value >= lt)
            throw new Error('Property ' + name + ' must be lesser than ' + lt);

          if (gte && value < gte)
            throw new Error('Property ' + name + ' must be greater or equal to ' + gte);

          if (lte && value > lte)
            throw new Error('Property ' + name + ' must be lesser or equal to ' + lte);

          if (inside && inside.indexOf(value) < 0)
            throw new Error('Property ' + name + ' must be one of ' + inside.join(', '));

          break;
        case 'array<string>':
        case 'array<number>':
          var arrayType = /array<([^>]*)>/.exec(expectedProperty.type)[1];

          if (typeof value === 'string' || typeof value === 'number') {
            value = arrayType === 'string' ? String(value) : parseInt(value);
            value = value ? [value] : null;
          } else if (Object.prototype.toString.call(value) === '[object Array]') {
            var arrayValues = [];
            for (var i = 0; i < value.length; i++) {
              var convertedValue = arrayType === 'string' ? String(value[i]) : parseInt(value[i]);
              if (convertedValue)
                arrayValues.push(convertedValue);
            }

            value = arrayValues.length ? arrayValues : null;
          } else
            value = null;

          break;

        case 'date':
          var date;

          if (!value)
            value = null;
          else {
            if (typeof value === 'string') {

              // Convert literal date into Date object
              var dateChunks = dateFormat.exec(value);
              if (dateChunks && dateChunks.length === 4)
                date = new Date(value).getTime();
              else
                date = null;

            } else if (Object.prototype.toString.call(value) === '[object Date]') {

              // Already a Date object
              date = value.getTime();

            }

            if (date)
              value = date;

            if (gt) {
              var gtDate = typeof gt === 'object' ? gt : new Date(gt);
              if (value <= gtDate.getTime())
                throw new Error('Property ' + name + ' must be greater than ' + gtDate.toString());
            }

            if (lt) {
              var ltDate = typeof lt === 'object' ? lt : new Date(lt);
              if (value >= ltDate.getTime())
                throw new Error('Property ' + name + ' must be lesser than ' + ltDate.toString());
            }

            if (gte) {
              var gteDate = typeof gte === 'object' ? gte : new Date(gte);
              if (value < gteDate.getTime())
                throw new Error('Property ' + name + ' must be greater or equal to ' + gteDate.toString());
            }

            if (lte) {
              var lteDate = typeof lte === 'object' ? lte : new Date(lte);
              if (value > lteDate.getTime())
                throw new Error('Property ' + name + ' must be lesser or equal to ' + lteDate.toString());
            }
          }
          break;
        case 'object':
          var valueType = Object.prototype.toString.call(value);
          value = value !== undefined && valueType ? value : defaultValue;
          break;
        default:
          value = null;
      }

      if (required && (value === null || typeof value === 'undefined'))
        throw new Error('Property ' + name + ' required');
      else if (value !== null && typeof value !== 'undefined')
        properties[name] = value;

    }

  }

  return properties;
};