'use strict' const _ = require('lodash'), expect = require('chai').expect, errors = require('./errors') // const eyes = require('eyes'), p = eyes.inspect.bind(eyes) function oneFunctionArgWithLength(length, func) { expect(func).to.be.a('function') const fname = func.name expect(func).to.have.length.of.at.least(length, `Wrapped function ${fname} must accept at least ${length} arguments`) return fname } function argumentsWithCallback(arraylike, length, fname) { let args = _.toArray(arraylike) expect(args).to.have.length.of.at.least(length, `Function ${fname} called with ${args.length} arguments requires at least ${length}`) expect(args[args.length - 1]).to.be.a('function', `Function ${fname} cannot be called without a callback as its final argument`) return args } /** * Standard `function(error, result) { ... }` callbacks that get automatically wrapped so that either `error` or `result` must be `null`. * - **Write your callbacks like you normally would they will be scrubbed automatically.** Code in scrubbed callbacks does not have to worry about cases in which both arguments passed to the callback will be non-`null` at least one will always be `null` (callbacks that expect `null` as a result will still work). * @callback ScrubbedCallback * @memberof module:classeur-api-client * @param {?(module:classeur-api-client.ServerError|module:classeur-api-client.ClientError)} 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. * - `error` will be a [ServerError]{@link module:classeur-api-client.ServerError} or [ClientError]{@link module:classeur-api-client.ClientError}, or `null` on success. * - All functions in this module will supply a [ServerError]{@link module:classeur-api-client.ServerError} or [ClientError]{@link module:classeur-api-client.ClientError} (both of which are instances of [Error](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error)) to their callback there is no need to check for stringy errors. * - `error` will always be `null` (not `undefined` or another falsy value) if no error occurred. * @param {?*} result - Result of the function to which this callback was supplied, or `null` if the function was unsuccessful. * - For type information of `result`, see the documentation for the function to which the callback was passed. * - `result` will be `null` even if the function was _partially_ successful, and was supposed to return an Array partial result arrays are not supported. */ // The function that does the callback scrubbing. module.exports.scrubArrayCallback = function(cb, wantarray, length) { return function(err, res) { if ( ! err ) { expect(res).to.be.instanceof(Array, 'Result is not an Array') expect(res.length).to.equal(length, 'Result array size mismatch') if ( ! wantarray ) { res = res[0] } else { res = _.flatten(res) } } cb(err || null, err ? null : res) } } // Simulates function(a, b, c, ...args, cb), or function(a, b, c, [args], cb). module.exports.restOrArrayAndCallback = function(wrapped, thisArg) { 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 && _.isArrayLike(args[0]) ) ) { args = [args] } _.bind(wrapped, thisArg)(...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, thisArg) { const fname = oneFunctionArgWithLength(2, wrapped) return function() { let args = argumentsWithCallback(arguments, 2, fname) const idx = args.length - 2 expect(args[idx]).not.to.be.instanceof(Array, `Function ${fname} cannot be called with an array as the 2nd-to-last argument`) args[idx] = [args[idx]] _.bind(wrapped, thisArg)(...args) } }