Source: builder.js

/*
 * Copyright 2018-2022 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
'use strict'

/**
 * Builder:
 * Utility module to export builder functions useful for creating
 * values (grouped or not) to use in Cloudevents.
 * Should not be used outside of the plugin.
 * @module utility
 */

/**
 * Get a reference to CloudEvent class definition and related utilities.
 * See {@link CloudEvent}.
 * @private
 */
const { CloudEvent, CloudEventTransformer } = require('cloudevent')

/**
 * Utility function for creating grouped values to use in Cloudevents.
 * Should not be used outside of the plugin.
 *
 * @param {!object} options configuration options
 * @return {object} an object with builder functions (configured) to use
 * @private
 */
function builder (options = {}) {
  const {
    pluginName,
    pluginVersion,
    serverUrl,
    serverUrlMode,
    baseNamespace,
    idGenerator,
    includeHeaders,
    includeRedundantAttributes,
    cloudEventOptions,
    cloudEventExtensions
  } = options

  return {
    /**
     * Build the value for the source field of the CloudEvent,
     * depending on the plugin configuration of options
     * `serverUrlMode`, `serverUrl`,
     * and the uri part of the current request.
     * Note that this is mainly for usage inside the plugin,
     * but in some cases could be useful even outside.
     *
     * @param {string} [url='']  the uri part of the current request
     * @return {string} the source value to use, as a string
     * @private
     */
    buildSourceUrl (url = '') {
      let sourceUrl
      switch (serverUrlMode) {
        case null:
        case 'pluginAndRequestSimplified':
          sourceUrl = serverUrl + CloudEventTransformer.uriStripArguments(url)
          break
        case 'pluginAndRequestUrl':
          sourceUrl = serverUrl + url
          break
        case 'pluginServerUrl':
          sourceUrl = serverUrl
          break
        case 'requestUrl':
          sourceUrl = url
          break
        default:
          throw new Error(`Illegal value for serverUrlMode: '${serverUrlMode}'`)
      }
      return sourceUrl
    },

    /**
     * Extract and build the value for the client IP address,
     * useful to add into the CloudEvent in a custom attribute inside data,
     * otherwise nothing.
     *
     * @param {!object} request the request
     * @return {string} the IP address, as a string or undefined
     * @private
     */
    buildClientIP (request) {
      if (request === undefined || request === null) {
        throw new Error('Illegal value for request: undefined or null')
      }
      return request.ip || undefined
    },

    /**
     * Extract and build the value for the HTTP headers,
     * useful to add into the CloudEvent in a custom attribute inside data.
     * If plugin flag 'includeHeaders' is enabled, headers will be returned,
     * otherwise nothing.
     * Note that some config options for builders are used here.
     *
     * @param {!object} request the request
     * @return {string} HTTP request headers, as a string, or undefined
     * @private
     */
    buildHeaders (request) {
      if (request === undefined || request === null) {
        throw new Error('Illegal value for request: undefined or null')
      }
      const headers = (includeHeaders === null || includeHeaders === false) ? undefined : request.headers
      return headers
    },

    /**
     * Extract and build values from the given arguments,
     * and returns them inside a wrapper object.
     *
     * @param {!object} request the request
     * @return {object} an object containing headers, source URL, the IP address
     * @private
     */
    buildValues (request) {
      const clientIp = this.buildClientIP(request)
      const headers = this.buildHeaders(request)
      const sourceUrl = this.buildSourceUrl(request.raw.url)
      return { clientIp, headers, sourceUrl }
    },

    /**
     * Extract some values from the given arguments,
     * and returns them inside a wrapper object
     * to be used in a CloudEvent data (sub-)property.
     *
     * @param {!object} request the request
     * @return {object} an object containing extracted attributes
     * @private
     */
    buildRequestDataForCE (request) {
      return {
        id: request.id,
        headers: this.buildHeaders(request),
        clientIp: this.buildClientIP(request),
        params: request.params,
        query: request.query,
        body: request.body,
        method: request.raw.method,
        url: request.raw.url
      }
    },

    /**
     * Extract some values from the given arguments,
     * and returns them inside a wrapper object
     * to be used in a CloudEvent data (sub-)property.
     *
     * @param {!object} reply the reply
     * @return {object} an object containing extracted attributes
     * @private
     */
    buildReplyDataForCE (reply) {
      return {
        statusCode: reply.raw.statusCode,
        statusMessage: reply.raw.statusMessage,
        finished: reply.raw.finished
      }
    },

    /**
     * Extract some values from the given arguments,
     * and returns them inside a wrapper object
     * to be used in a CloudEvent data (sub-)property.
     * Note that some config options for builders are used here.
     *
     * @param {object} [description='']  the description (maybe related to the event)
     * @return {object} an object containing extracted attributes
     * @private
     */
    buildPluginDataForCE (description = '') {
      return {
        timestamp: CloudEventTransformer.timestampToNumber(),
        description,
        name: pluginName,
        version: pluginVersion
      }
    },

    /**
     * Build a CloudEvent instance from the given arguments,
     * useful for simplify plugin hooks.
     * If plugin flag 'includeRedundantAttributes' is enabled,
     * some redundant attributes will be added to data.
     * Note that some config options for builders are used here.
     *
     * @param {!string} hookName the name of the hook
     * @param {!object} request the request
     * @param {object} reply the reply
     * @param {object} payload the payload
     * @return {object} the CloudEvent instance
     * @private
     */
    buildCloudEventForHook (hookName, request, reply, payload) {
      if (!hookName || !request) {
        throw new Error('Illegal arguments: mandatory argument undefined or null')
      }
      const ceData = {
        request: this.buildRequestDataForCE(request),
        reply: (reply === null) ? undefined : this.buildReplyDataForCE(reply),
        payload
      }
      if (includeRedundantAttributes !== null && includeRedundantAttributes === true) {
        ceData.id = request.id
        ceData.timestamp = CloudEventTransformer.timestampToNumber()
      }
      const ce = new CloudEvent(idGenerator.next().value,
        `${baseNamespace}.${hookName}`,
        this.buildSourceUrl(request.raw.url),
        ceData,
        cloudEventOptions,
        cloudEventExtensions
      )
      return ce
    }
  }
}

module.exports = builder