Source: query/query-payload.js

const QueryContentType = require('../http/query-content-type');

/**
 * Base class from which all query payload classes derives. Successors must
 * implement {@link #validateParams} and {@link #getSupportedContentTypes}.
 *
 * @abstract
 * @class
 * @author Mihail Radkov
 * @author Svilen Velikov
 */
class QueryPayload {
  /**
   * Does basic initialization.
   */
  constructor() {
    this.payload = {};
    this.contentType = null;
  }

  /**
   * @param {boolean} [inference] Specifies whether inferred statements should
   *                  be included in the query evaluation.
   * @return {QueryPayload}
   */
  setInference(inference) {
    if (typeof inference !== 'boolean') {
      throw new Error('Inference must be a boolean!');
    }

    this.payload.infer = inference;
    return this;
  }

  /**
   * @param {number} [timeout] Specifies a maximum query execution time, in
   *                 whole seconds.
   * @return {QueryPayload}
   */
  setTimeout(timeout) {
    if (typeof timeout !== 'number') {
      throw new Error('Timeout must be a number!');
    }

    this.payload.timeout = timeout;
    return this;
  }

  /**
   * An optional parameter which is used for defining the request Content-Type.
   *
   * @param {string} [contentType] One of the supported content types for given
   * operation.
   * @return {QueryPayload}
   */
  setContentType(contentType) {
    const supportedTypes = this.getSupportedContentTypes();
    if (typeof contentType !== 'string' ||
      supportedTypes.indexOf(contentType) === -1) {
      throw new Error(`Content type must be one of ${supportedTypes}!`);
    }

    this.contentType = contentType;
    return this;
  }

  /**
   * @return {string} content type which was populated in this payload.
   */
  getContentType() {
    return this.contentType;
  }

  /**
   * Serializes all query parameters populated in the payload. Only parameters
   * which are present will be returned.
   *
   * Mandatory and dependent parameters are validated and errors are thrown if
   * necessary.
   *
   * @return {string} a serialized payload which holds all available query
   * parameters in this payload object.
   */
  getParams() {
    // when contentType is 'x-www-form-urlencoded', then all parameters should
    // be considered
    if (this.getContentType() === QueryContentType.X_WWW_FORM_URLENCODED) {
      const isValid = this.validateParams();
      return isValid && this.serialize(this.payload);
    }

    const query = this.getQuery();
    if (!query) {
      throw new Error('Parameter query is mandatory!');
    }
    return query;
  }

  /**
   * Utility method which serializes a single level json object to properly
   * encoded string that can be used in a request.
   *
   * @protected
   * @param {Object} data object which holds request parameter key:value pairs.
   * @return {string} provided object serialized and encoded to string.
   */
  serialize(data) {
    return Object.entries(data)
      .filter((x) => x[1] !== undefined)
      .map((x) => `${encodeURIComponent(x[0])}=${encodeURIComponent(x[1])}`)
      .join('&');
  }

  /**
   * Must be implemented in successors.
   *
   * Validates payload for mandatory and invalid parameters.
   *
   * @abstract
   * @protected
   *
   * @return {boolean} <code>true</code> if parameters are valid and
   * <code>false</code> otherwise.
   */
  validateParams() {
    return false;
  }

  /**
   * Must be implemented in successors and should return a list with supported
   * content types.
   * @abstract
   * @protected
   *
   * @return {Array<string>}
   */
  getSupportedContentTypes() {
    return [];
  }
}

module.exports = QueryPayload;