Viewing File: /usr/local/cpanel/share/libraries/cjt2/src/io/request.js

/*
# cjt/io/request.js                                  Copyright 2022 cPanel, L.L.C.
#                                                           All rights reserved.
# copyright@cpanel.net                                         http://cpanel.net
# This code is subject to the cPanel license. Unauthorized copying is prohibited
*/

/* --------------------------*/
/* DEFINE GLOBALS FOR LINT
/*--------------------------*/
/* eslint-env amd */
/* --------------------------*/

// -----------------------------------------------------------------------
// DEVELOPER NOTES:
// -----------------------------------------------------------------------
// TODO: Change api2 to use this base class.
// -----------------------------------------------------------------------

/**
 * This is a base class factory for request objects so that the common part of their implementation
 * can be shared between concrete classes.
 *
 * @module cjt/io/request
 * @see module:cjt/io/request:Request
 * @example
 *
 *   require(["cjt/io/request"], function(generateRequestClass) {
 *       var BaseClass = generateRequestClass(1, function { ... });
 *       var CustomRequest = function() {
 *           Base.call(this);
 *       };
 *       CustomRequest.prototype = Object.create(Base.prototype, {
 *           method: {
 *               value: function() {
 *                   ...
 *               }
 *           },
 *           booleanProperty: {
 *               value: false
 *           },
 *           stringProperty: {
 *               value: "custom"
 *           },
 *           ...
 *       });
 *       CustomRequest.prototype.constructor = CustomRequest;
 *
 *       var request = new CustomRequest();
 *       ...
 *   });
 */
define([
    "lodash",
    "cjt/util/analytics",
], function(_, ANALYTICS) {

    "use strict";

    /**
     * Factory method that generates a Request base class based on the parameters passed.
     *
     * @param  {Number} version
     * @param  {Function} getEmptyMeta Used to generate the meta data for a specific API version.
     * @return {Function} Constructor function for the specific Request class.
     */
    return function generateRequestClass(version, getEmptyMeta) {

        /**
         * Base class for all requests
         *
         * @class
         * @exports module:cjt/io/request:Request
         */
        var Request = function() {

            /**
             * API version number to use for UAPI.
             *
             * @property {Number} version
             * @instance
             */
            this.version = version;

            /**
             * API module name for UAPI call.  Represents the module in <Module>::<Func>() for
             * UAPI. Optional for WHM APIv1. If provided in WHM APIv1, it will append to the func
             * name as: <Module>_<Func>.
             *
             * @property {String} module
             * @instance
             */
            this.module = "";

            /**
             * API function name to call.  Represents the call in <Module>::<Func>() for
             * UAPI or the method to lookup in WHM API 1 from the dispatch tables.
             *
             * @property {String} func
             * @instance
             */
            this.func = "";

            /**
             * Collection of arguments for the API call.
             *
             * @property {Object} args
             * @instance
             */
            this.args = {};

            /**
             * Whether to send the request as JSON.
             *
             * For backward compatibility with code already using uapi-request or whm-v1-request, this
             * defaults to false. To enable JSON requests, set this to true or use the opts parameter
             * in the initialize() method to enable json.
             *
             * @property {Boolean} json
             * @instance
             */
            this.json = false;

            /**
             * API call meta data for the UAPI or WHM API v1 call.  Should be an object with names for
             * each meta data parameter passed to the UAPI call. This data is used to
             * control properties such as sorting, filters and paging.
             *
             * @property {Object} meta
             * @instance
             */
            this.meta = getEmptyMeta();

            /**
             * An object of auto-increment counters that will keep track of the numeric
             * suffixes appended to arguments when the 'auto' option is set to true.
             *
             * @property {Number} autoCounter
             * @instance
             */
            this.autoCounter = {
                __startVal: 1,
            };
        };

        Request.prototype = {

            /**
             * @global
             * @typedef RequestOptions
             * @type {Object}
             * @property {Boolean} [json] Use JSON request formatting. With this, the request arguments are sent to the
             * server as JSON in the body of the request and the Content-Type header is set to 'application/json'.
             * @property {Boolean} [realNamespaces] When true, treats the module as a real namespace, defaults to false. Only applicable to whm-v1.
             */

            /**
             * Initialize the request
             *
             * @method initialize
             * @instance
             * @param  {String}         module Name of the module where the API function exists.
             * @param  {String}         func   Name of the function to call.
             * @param  {Object}         data   Data for the function call.
             * @param  {Object}         meta   Meta-data such as sorting, filtering, paging and similar data for the API call.
             * @param  {RequestOptions} opts   Use to set additional options for the request.
             * @return {Request} The current instance of the request so calls can be chained.
             * @throws When data is not an object or undefined or null.
             */
            initialize: function(module, func, data, meta, opts) {
                this.module = module;
                this.func = func;
                this.setArguments(data || {});
                this.meta = meta || {};
                this.json = opts && opts.json ? true : false;

                return this;    // for chaining
            },

            /**
             * Sets the args property of the request. This can be used in lieu of addArgument when
             * using JSON API Requests so you can set the arguments to any arbitrary object.
             *
             * @method setArguments
             * @instance
             * @param {Object} args The data must be serializable as JSON
             * @throws When the args parameter is not an object.
             * @return {Request} The current instance of the request so calls can be chained.
             * @throws When args is not an object
             */
            setArguments: function(args) {
                if (typeof args !== "object") {
                    throw new TypeError("args parameter for 'setArgumetnObject' method must be an Object");
                }
                this.args = args;
                return this;
            },

            /**
             * Adds an argument
             *
             * @method addArgument
             * @instance
             * @param  {String}  name       Name of the argument to add.
             * @param  {Object}  value      Value of the argument.
             * @param  {Boolean} [isAuto]   Optional. If true, an automatic numeric suffix will be
             *                              appended to the argument name.
             * @return {Request} The current instance of the request so calls can be chained.
             * @throws When this.args is not an object.
             */
            addArgument: function(name, value, isAuto) {
                this.validateArgs("addArgument");
                if (!isAuto) {
                    this.args[name] = value;
                } else {
                    this.args[name + this.getAutoSuffix(name)] = value;
                    this.incrementAuto(name);
                }
                return this;
            },

            /**
             * Removes an argument
             *
             * @method removeArgument
             * @instance
             * @param  {String}  name       Name of the argument to remove.
             * @param  {Boolean} [isAuto]   Optional. If true, the last auto-incremented argument
             *                              with the same base name will be removed.
             * @return {Request} The current instance of the request so calls can be chained.
             * @throws When this.args is not an object.
             */
            removeArgument: function(name, isAuto) {
                this.validateArgs("removeArgument");
                name = isAuto ? (name + this.decrementAuto(name)) : name;
                this.args[name] = null;
                delete this.args[name];
                return this;
            },

            /**
             * Clears the arguments
             *
             * @method  clearArguments
             * @instance
             * @param {String} name   Name of the argument
             * @param {Object} value  Value of the argument
             * @return {Request} The current instance of the request so calls can be chained.
             */
            clearArguments: function() {
                this.args = {};
                this.autoCounter = {
                    __startVal: this.autoCounter.__startVal,
                };
                return this;
            },

            /**
             * Get the current auto increment value for the given property.
             *
             * @method getAutoSuffix
             * @instance
             * @param  {String} name The name of the property that will be incremented.
             * @return {Number} New value of the auto increment for the property.
             */
            getAutoSuffix: function(name) {
                if (_.isUndefined( this.autoCounter[name] ) ) {
                    this.autoCounter[name] = this.autoCounter.__startVal;
                }
                return this.autoCounter[name];
            },

            /**
             * Increment the suffix counter for a given argument name.
             *
             * @method incrementAuto
             * @instance
             * @param  {String} name The name of the property that will be incremented.
             * @return {Number} New value of the auto increment for the property.
             */
            incrementAuto: function(name) {
                return (this.autoCounter[name] = this.getAutoSuffix(name) + 1);
            },

            /**
             * Decrement the argument suffix counter for a given argument name. The
             * counter will never go below the __startVal.
             *
             * @method decrementAuto
             * @instance
             * @param  {String} name The name of the property that will be decremented.
             * @return {Number} New value of the auto increment for the property.
             */
            decrementAuto: function(name) {
                var currVal = this.getAutoSuffix(name);
                if (currVal > this.autoCounter.__startVal) {
                    this.autoCounter[name] = currVal - 1;
                }

                return this.autoCounter[name];
            },

            /**
             * Add analytics metadata to the API request. If the AnalyticsState object has already
             * been instantiated, any options passed will update the instance with those key/value
             * pairs while leaving omitted properties intact. If you wish to remove properties you
             * must reset the analytics metadata using clearAnaltyics() first.
             *
             * @method addAnalytics
             * @instance
             * @param  {Object} [options]   Optional hash of options to pass to the AnalyticsState
             *                              constructor. See cjt/util/analytics for details.
             * @return {Request} The current instance of the request so calls can be chained.
             */
            addAnalytics: function(options) {
                if (!this.analytics) {
                    this.analytics = ANALYTICS.create(options);
                } else {
                    this.analytics.update(options);
                }
                return this;
            },

            /**
             * Clears the analytics metadata from this API request.
             *
             * @method clearAnalytics
             * @instance
             * @return {Request} The current instance of the request so calls can be chained.
             */
            clearAnalytics: function() {
                delete this.analytics;
                return this;
            },

            /**
             * @global
             * @typedef RunArguments
             * @type {Object}
             * @property {Object}  [analytics] Request analytics for this API call.
             * @property {Object}  args        Argument to the API call.
             * @property {String}  func        Name of the API call.
             * @property {Boolean} json        When true, send request as application/json, otherwise, send request as application/x-www-form-urlencoded
             * @property {Object}  meta        Filter, sort and paging rules for API call.
             * @property {String}  [module]    Name of the module where the API call exists. Required for UAPI. Optional for WHM API v1.
             * @property {Number}  [version]   Version of the API.
             */

            /**
             * Build the UAPI call data structure.
             *
             * @method getRunArguments
             * @instance
             * @return {RunArguments} Packages up an API call based on collected information.
             */
            getRunArguments: function() {
                return {
                    version: this.version,
                    module: this.module,
                    func: this.func,
                    meta: this.meta,
                    args: this.args,
                    analytics: this.analytics,
                    json: this.json
                };
            },

            /**
             * Validate that the args property is an object. Used by other methods that depend on this precondition.
             *
             * @protected
             * @param  {String} method Name of the method being attempted.
             * @throws When this.args is not an object.
             */
            validateArgs: function(method) {
                if (typeof this.args !== "object") {
                    throw new Error("You can not call '" + method + "'' if you the args property to something other than an Object");
                }
            },

            /**
             * Validate that the meta property is an object. Used by other methods that depend on this precondition.
             *
             * @protected
             * @param  {String} method Name of the method being attempted.
             * @throws When this.meta is not an object.
             */
            validateMeta: function(method) {
                if (typeof this.meta !== "object") {
                    throw new Error("You can not call '" + method + "'' if you the meta property to something other than an Object");
                }
            }
        };

        return Request;
    };
});
Back to Directory File Manager