Source: function-utils.js

'use strict';

const _ = require('lodash'),
    is = require('check-types').assert;



function oneFunctionArgWithLength(length, func) {
    is.function(func, `Expected a function, got ${typeof func} instead: ${func}`);
    const fname = func.name;
    is.greaterOrEqual(func.length, length, `Wrapped function ${fname} must accept at least ${length} arguments`);
    return fname;
}

function argumentsWithCallback(arraylike, length, fname) {
    let args = _.toArray(arraylike);
    is.greaterOrEqual(args.length, length, `Function ${fname} called with ${args.length} arguments; requires at least ${length}`);
    is.function(args[args.length - 1], `Function ${fname} cannot be called without a callback as its final argument`);
    return args;
}

/**
 * Sanitizes `function(error, result) { ... }` callbacks. Enforces that either `error` or `result` must be `null`.
 * @callback ClasseurClient~scrubbedCallback
 * @param {?(Error|String)} error - An Error or error String, if an error occurred executing the function to which this callback was supplied, or `null` if the function was successful.
 * @param {?*} result - Results of the function to which this callback was supplied, or `null` if the function was unsuccessful.
 * - `result` will be `null` even if the function was _partially_ successful, and was supposed to return an Array; partial result arrays are removed.
 */

module.exports.scrubArrayCallback = function(cb, wantarray) {
    return function(err, res) {
        if ( ! err ) {
            is.array(res, `Expected an array, got ${typeof res} instead: ${res}`);
            res = _.compact(res);
            if ( res.length === 0 ) {
                err = err || 'No results (should be 404, but API did not return an error)';
            } else if ( ! wantarray ) {
                res = res[0];
            }
        }
        cb(err, err ? null : res);
    };
}

// Simulates function(a, b, c, ...args, cb), or function(a, b, c, [args], cb).
module.exports.restOrArrayAndCallback = function(wrapped) {
    const fname = oneFunctionArgWithLength(2, wrapped);
    return function() {
        let args = argumentsWithCallback(arguments, 2, fname);
        // Assumes the last two are an array and a callback.
        const statics = args.splice(0, wrapped.length - 2),
            cb = args.pop();

        if ( ! ( args.length === 1 && _.isArray(args[0]) ) ) {
            args = [args];
        }

        _.spread(_.bind(wrapped, this))(statics.concat(args, cb));
    };
}

// Asserts that a function only takes a single, non-array argument and a
// callback in addition to its other arguments.
module.exports.singleElementAndCallback = function(wrapped) {
    const fname = oneFunctionArgWithLength(2, wrapped);
    return function() {
        let args = argumentsWithCallback(arguments, 2, fname);
        const idx = args.length - 2;
        is.not.array(args[idx], `Function ${fname} cannot be called with an array as the 2nd-to-last argument`);

        args[idx] = [args[idx]];
        _.spread(_.bind(wrapped, this))(args);
    };
}