/**
* Utility functions.
* @exports utils
*/
var utils = {};
/*
* Copyright 2015 Splunk, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"): you may
* not use this file except in compliance with the License. You may obtain
* a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
/* Utility Functions */
/**
* Formats the time for Splunk Enterprise or Splunk Cloud as a the epoch time in seconds.
*
* @param {(string|number|date)} time - A date string, timestamp, or <code>Date</code> object.
* @returns {number|null} Epoch time in seconds, or <code>null</code> if <code>time</code> is malformed.
* @static
*/
utils.formatTime = function(time) {
var cleanTime;
// If time is a Date object, return its value.
if (time instanceof Date) {
time = time.valueOf();
}
if (!time || time === null) {
return null;
}
// Values with decimals
if (time.toString().indexOf(".") !== -1) {
cleanTime = parseFloat(time).toFixed(3); // Clean up the extra decimals right away.
// A perfect time in milliseconds, with the decimal in the right spot.
if (cleanTime.toString().indexOf(".") >= 10) {
cleanTime = parseFloat(cleanTime.toString().substring(0, 14)).toFixed(3);
}
}
// Values without decimals
else {
// A time in milliseconds, no decimal (ex: Date.now()).
if (time.toString().length === 13) {
cleanTime = (parseFloat(time) / 1000).toFixed(3);
}
// A time with fewer than expected digits.
else if (time.toString().length <= 12) {
cleanTime = parseFloat(time).toFixed(3);
}
// Any other value has more digits than the expected time format, get the first 14.
else {
cleanTime = parseFloat(time.toString().substring(0, 13)/1000).toFixed(3);
}
}
return cleanTime;
};
/**
* Converts an iterable into to an array.
*
* @param {(Array|Object)} iterable - Thing to convert to an <code>Array</code>.
* @returns {Array}
* @static
*/
utils.toArray = function(iterable) {
return Array.prototype.slice.call(iterable);
};
// TODO: this isn't used anymore, remove it
/**
* Run async function in a chain, like {@link https://github.com/caolan/async#waterfall|Async.waterfall}.
*
* @param {(function[]|function)} tasks - <code>Array</code> of callback functions.
* @param {function} [callback] - Final callback.
* @static
*/
utils.chain = function(tasks, callback) {
// Allow for just a list of functions
if (arguments.length > 1 && typeof arguments[0] === "function") {
var args = utils.toArray(arguments);
tasks = args.slice(0, args.length - 1);
callback = args[args.length - 1];
}
tasks = tasks || [];
callback = callback || function() {};
if (tasks.length === 0) {
callback();
}
else {
var nextTask = function(task, remainingTasks, result) {
var taskCallback = function(err) {
if (err) {
callback(err);
}
else {
var args = utils.toArray(arguments);
args.shift();
nextTask(remainingTasks[0], remainingTasks.slice(1), args);
}
};
var args = result;
if (remainingTasks.length === 0) {
args.push(callback);
}
else {
args.push(taskCallback);
}
task.apply(null, args);
};
nextTask(tasks[0], tasks.slice(1), []);
}
};
/**
* Asynchronous while loop.
*
* @param {function} [condition] - A function returning a boolean, the loop condition.
* @param {function} [body] - A function, the loop body.
* @param {function} [callback] - Final callback.
* @static
*/
utils.whilst = function (condition, body, callback) {
condition = condition || function() { return false; };
body = body || function(done){ done(); };
callback = callback || function() {};
var wrappedCallback = function(err) {
if (err) {
callback(err);
}
else {
utils.whilst(condition, body, callback);
}
};
if (condition()) {
body(wrappedCallback);
}
else {
callback(null);
}
};
/**
* Waits using exponential backoff.
*
* @param {object} [opts] - Settings for this function. Expected keys: attempt, rand.
* @param {function} [callback] - A callback function: <code>function(err, timeout)</code>.
*/
utils.expBackoff = function(opts, callback) {
callback = callback || function(){};
if (!opts || typeof opts !== "object") {
callback(new Error("Must send opts as an object."));
}
else if (opts && !opts.hasOwnProperty("attempt")) {
callback(new Error("Must set opts.attempt."));
}
else {
var min = 10;
var max = 1000 * 60 * 2; // 2 minutes is a reasonable max delay
var rand = Math.random();
if (opts.hasOwnProperty("rand")) {
rand = opts.rand;
}
rand++;
var timeout = Math.round(rand * min * Math.pow(2, opts.attempt));
timeout = Math.min(timeout, max);
setTimeout(
function() {
callback(null, timeout);
},
timeout
);
}
};
/**
* Binds a function to an instance of an object.
*
* @param {object} [self] - An object to bind the <code>fn</code> function parameter to.
* @param {object} [fn] - A function to bind to the <code>self</code> argument.
* @returns {function}
* @static
*/
utils.bind = function(self, fn) {
return function () {
return fn.apply(self, arguments);
};
};
/**
* Copies all properties into a new object which is returned.
*
* @param {object} [obj] - Object to copy properties from.
*/
utils.copyObject = function(obj) {
var ret = {};
for (var key in obj) {
if (obj.hasOwnProperty(key)) {
ret[key] = obj[key];
}
}
return ret;
};
/**
* Copies all elements into a new array, which is returned.
*
* @param {array} [arr] - Array to copy elements from.
* @returns {array}
* @static
*/
utils.copyArray = function(arr) {
var ret = [];
for (var i = 0; arr && i < arr.length; i++) {
ret[i] = arr[i];
}
return ret;
};
/**
* Takes a property name, then any number of objects as arguments
* and performs logical OR operations on them one at a time
* Returns true as soon as a truthy
* value is found, else returning false.
*
* @param {string} [prop] - property name for other arguments.
* @returns {boolean}
* @static
*/
utils.orByProp = function(prop) {
var ret = false;
for (var i = 1; !ret && i < arguments.length; i++) {
if (arguments[i]) {
ret = ret || arguments[i][prop];
}
}
return ret;
};
/**
* Like <code>utils.orByProp()</code> but for a falsey property.
* The first argument after <code>prop</code> with that property
* defined will be returned.
* Useful for Booleans and numbers.
*
* @param {string} [prop] - property name for other arguments.
* @returns {boolean}
* @static
*/
utils.orByFalseyProp = function(prop) {
var ret = null;
// Logic is reversed here, first value wins
for (var i = arguments.length - 1; i > 0; i--) {
if (arguments[i] && arguments[i].hasOwnProperty(prop)) {
ret = arguments[i][prop];
}
}
return ret;
};
/**
* Tries to validate the <code>value</code> parameter as a non-negative
* integer.
*
* @param {number} [value] - Some value, expected to be a positive integer.
* @param {number} [label] - Human readable name for <code>value</code>
* for error messages.
* @returns {number}
* @throws Will throw an error if the <code>value</code> parameter cannot by parsed as an integer.
* @static
*/
utils.validateNonNegativeInt = function(value, label) {
value = parseInt(value, 10);
if (isNaN(value)) {
throw new Error(label + " must be a number, found: " + value);
}
else if (value < 0) {
throw new Error(label + " must be a positive number, found: " + value);
}
return value;
};
module.exports = utils;