Source: util/helpers.js

'use strict';

/** @module */

/**
 * Returns true if obj is a defined value
 * @param {Object} obj - the value to test
 * @returns {boolean}
 */
function defined (obj) {
    return typeof obj !== 'undefined';
}

/**
 * Returns true if obj is a non-null object
 * Checking for typeof 'object' without checking for nulls
 * is a very common source of bugs
 * @param {Object} obj - the value to test
 * @returns {boolean}
 */
function isObject (obj) {
    return typeof obj === 'object' && obj !== null;
}

/**
 * Returns the text used for logging purposes related to this socket
 * @param {Object} socket - the socket
 * @returns {string}
 */
function socketName (socket) {
    let result = socket.remoteAddress;
    if (socket.remotePort) {
        result += `:${socket.remotePort}`;
    }
    return result;
}

/**
 * Returns a deep clone of obj
 * @param {Object} obj - the object to clone
 * @returns {Object}
 */
function clone (obj) {
    return JSON.parse(JSON.stringify(obj));
}

/**
 * Returns a new object combining the two parameters
 * @param {Object} defaults - The base object
 * @param {Object} overrides - The object to merge from.  Where the same property exists in both defaults
 * and overrides, the values for overrides will be used
 * @returns {Object}
 */
function merge (defaults, overrides) {
    const result = clone(defaults);
    Object.keys(overrides).forEach(key => {
        if (typeof overrides[key] === 'object' && overrides[key] !== null) {
            result[key] = merge(result[key] || {}, overrides[key]);
        }
        else {
            result[key] = overrides[key];
        }
    });
    return result;
}

/**
 * Sets a value of nested key string descriptor inside a Object.
 * It changes the passed object.
 * Ex:
 *    let obj = {a: {b:{c:'initial'}}}
 *    setNestedKey(obj, ['a', 'b', 'c'], 'changed-value')
 *    assert(obj === {a: {b:{c:'changed-value'}}})
 *
 * @param {Object} obj   Object to set the nested key
 * @param {Array} path  An array to describe the path(Ex: ['a', 'b', 'c'])
 * @param {Object} value Any value
 * @returns {undefined}
 * from https://stackoverflow.com/a/49754647
 */
function setDeep (obj, path, value) {
    if (path.length === 1) {
        obj[path] = value;
        return;
    }
    setDeep(obj[path[0]], path.slice(1), value);
}

function simulateFault (socket, faultConfig, logger) {
    if (typeof faultConfig === 'undefined') {
        return false;
    }
    if (faultConfig === 'CONNECTION_RESET_BY_PEER') {
        logger.debug('Closing the connection');
        socket.destroy();
        return true;
    }
    else if (faultConfig === 'RANDOM_DATA_THEN_CLOSE') {
        logger.debug('Sending garbage data then closing the connection');
        socket.write(Buffer.from('Htijy%@tWXJ/hQ#[Q:7G@dH4"gu[QaX&', 'utf-8'));
        socket.destroy();
        return true;
    }
    else {
        logger.error('Unexpected fault type [' + faultConfig + '], expected either CONNECTION_RESET_BY_PEER or RANDOM_DATA_THEN_CLOSE');
        return false;
    }
}

/**
 * Remove specific key and value from object
 * @param {Object} obj Object to filter
 * @param {Array|Object|String} filter keys to remove
 * @returns {Object}
 */

function objFilter (obj, filter) {

    if (typeof filter === 'string') {
        delete obj[filter];
    }
    else if (Array.isArray(filter)) {
        filter.filter(keyFilter => obj[keyFilter]).forEach(keyFilter => objFilter(obj, keyFilter));
    }
    else {
        Object.keys(filter).filter(keyFilter => obj[keyFilter]).forEach(keyFilter => objFilter(obj[keyFilter], filter[keyFilter]));
    }
    return obj;
}

module.exports = { defined, isObject, socketName, clone, merge, setDeep, simulateFault, objFilter };