dataplug-io/dataplug

View on GitHub
src/configMapping.ts

Summary

Maintainability
A
1 hr
Test Coverage
// Copyright (C) 2017-2019 Brainbean Apps OU (https://brainbeanapps.com).
// License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).

import { forOwn, clone, isRegExp } from 'lodash'
import check from 'check-types'

export default class ConfigMapping {
  /**
   * @constructor
   * @param that Other mapping to clone
   */
  constructor(that?: Object) {
    if (that) {
      forOwn(that, (declaration, selector) => {
        this[selector] = clone(declaration)
      })
    }
  }

  /**
   * Returns copy of this mapping, extended using specified modifier
   *
   * @param modifier An modifier functor
   * @return Copy of this mapping
   */
  extended(modifier?: Function) {
    const clone = new ConfigMapping(this)
    return modifier ? modifier(clone) : clone
  }

  /**
   * Remaps a parameter matched to specified selector using specified mapper
   *
   * @param selector Parameter selector, supports regexp
   * @param mapper Mapper function
   * @return This instance (for chaining purposes)
   */
  remap(selector: string | RegExp, mapper: Function) {
    check.assert(
      check.any([check.instance(selector, RegExp), check.not.null(selector)]),
    )

    // Convert regexp to a string, if needed
    if (isRegExp(selector)) {
      // @ts-ignore
      selector = selector.toString().match(/^\/(.*?)\/[gimuy]?$/)[1]
      if (!selector) {
        throw new Error(`'${selector}' selector is not valid Regexp`)
      }
    }
    if (this[selector] && this[selector].mapper) {
      throw new Error(`'${selector}' selector already mapped`)
    }

    this[selector] = Object.assign({}, this[selector], {
      mapper,
    })

    return this
  }

  /**
   * Renames a parameter
   *
   * @param selector Parameter selector, supports regexp
   * @param newName New name for matching parameter
   * @return This instance (for chaining purposes)
   */
  rename(selector: string, newName: string) {
    check.assert.nonEmptyString(newName)

    return this.remap(selector, (value: any) => {
      if (!value) {
        return
      }
      let mapped = {}
      mapped[newName] = value
      return mapped
    })
  }

  /**
   * Uses parameter as-is
   *
   * @param selector Parameter selector, supports regexp
   * @return This instance (for chaining purposes)
   */
  asIs(selector: string) {
    return this.remap(selector, (value: any, currentName: string) => {
      if (!value) {
        return
      }
      let mapped = {}
      mapped[currentName] = value
      return mapped
    })
  }

  /**
   * Sets default value factory for the selector
   *
   * @param selector Parameter selector, supports regexp
   * @param defaultValueFactory Default value factory
   * @return This instance (for chaining purposes)
   */
  default(selector: string, defaultValueFactory: Function) {
    // TODO: check selector type

    if (this[selector] && this[selector].defaultValueFactory) {
      throw new Error(
        `'${selector}' selector already has default value factory`,
      )
    }

    this[selector] = Object.assign({}, this[selector], {
      defaultValueFactory,
    })

    return this
  }

  /**
   * Applies mappings to given values
   *
   * @param values Values to map
   * @return Mapped values
   */
  apply(values: Object) {
    let mappedValues = {}
    forOwn(this, (declaration: any, selector) => {
      let wasMatched = false
      if (declaration.mapper) {
        let matched = 0
        forOwn(values, (parameterValue, parameter) => {
          if (!parameter.match(new RegExp(selector))) {
            return
          }

          const mappedValue = declaration.mapper(
            parameterValue,
            parameter,
            values,
          )
          if (mappedValue === undefined) {
            return
          }

          mappedValues = Object.assign({}, mappedValues, mappedValue)
          matched++
        })
        wasMatched = matched > 0
      }

      if (!wasMatched && declaration.defaultValueFactory) {
        const defaultValue = declaration.defaultValueFactory()
        if (defaultValue) {
          let defaultValueObject = {}
          defaultValueObject[selector] = defaultValue
          mappedValues = Object.assign({}, mappedValues, defaultValueObject)
        }
      }
    })

    return mappedValues
  }
}