Source: lib/method.js

'use strict';

const isArray = require('lodash/isArray');
const isPlainObject = require('lodash/isPlainObject');
const isObject = require('lodash/isObject');
const extend = require('lodash/extend');
const keys = require('lodash/keys');
const reduce = require('lodash/reduce');
const pick = require('lodash/pick');
const toArray = require('lodash/toArray');
const toPlainObject = require('lodash/toPlainObject');
const utils = require('./utils');

/**
 * @summary Constructor for a Jayson Method
 * @class Method
 * @param {Function} [handler] Function to set as handler
 * @param {Object} [options] 
 * @param {Function} [options.handler] Same as separate handler
 * @param {Boolean} [options.useContext=false] When true, the handler expects a context object
 * @param {Array|Object} [options.params] Defines params that the handler accepts
 */
const Method = function(handler, options) {

  if(!(this instanceof Method)) {
    return new Method(handler, options);
  }

  // only got passed options
  if(isPlainObject(handler)) {
    options = handler;
    handler = null;
  }

  const defaults = {
    useContext: false,
  };

  options = options || {};

  this.options = utils.merge(defaults, options);
  this.handler = handler || options.handler;
};

module.exports = Method;

/**
 * @summary Returns the handler function associated with this method
 * @return {Function}
 */
Method.prototype.getHandler = function() {
  return this.handler;
};

/**
 * @summary Sets the handler function associated with this method
 * @param {Function} handler
 */
Method.prototype.setHandler = function(handler) {
  this.handler = handler;
};

/**
 * @summary Prepare parameters for the method handler
 * @private
 */
Method.prototype._getHandlerParams = function(params) {
  const options = this.options;

  const isObjectParams = !isArray(params) && isObject(params) && params;
  const isArrayParams = isArray(params);

  switch(true) {

      // handler always gets an array
    case options.params === Array:
      return isArrayParams ? params : toArray(params);

      // handler always gets an object
    case options.params === Object:
      return isObjectParams ? params : toPlainObject(params);

      // handler gets a list of defined properties that should always be set
    case isArray(options.params): {
      const undefinedParams = reduce(options.params, function(undefinedParams, key) {
        undefinedParams[key] = undefined;
        return undefinedParams;
      }, {});
      return extend(undefinedParams, pick(params, keys(params)));
    }

      // handler gets a map of defined properties and their default values
    case isPlainObject(options.params):
      return extend({}, options.params, pick(params, keys(params)));

      // give params as is
    default:
      return params;

  }

};

/**
 * @summary Executes this method in the context of a server
 * @param {Server} server
 * @param {Array|Object} requestParams
 * @param {Object} [context]
 * @param {Function} callback
 */
Method.prototype.execute = function(server, requestParams, context, callback) {
  if(typeof(context) === 'function') {
    callback = context;
    context = {};
  }

  if(!context) {
    context = {};
  }

  // when useContext is true, the handler gets a context object every time
  const useContext = Boolean(this.options.useContext);
  const handler = this.getHandler();
  const params = this._getHandlerParams(requestParams);

  const args = useContext ? [params, context, callback] : [params, callback];
  return handler.call(server, ...args);
};