Source: service/extractDtoFromEntity.js

'use strict'

const formatDate = require('../formatters/date-formatter')
const formatEnumeration = require('../formatters/enumeration-formatter')
const DateFormat = require('../enums/DateFormat')
const Enumeration = require('@northscaler/enum-support')

/**
 * Extracts a [data transfer object (DTO)](https://martinfowler.com/eaaCatalog/dataTransferObject.html) from the given entity.
 * The entity normally is an instance of a class with data properties whose names begin with the underscore character (`_`).
 * By default, this will descend recursively through the given object and drop the leading underscore prefix from its keys and convert any convertible values, mainly `Date`s to ISO-8601 strings and [`Enumeration` instances](https://www.npmjs.com/package/@northscaler/enum-support) to just their `name`s.
 * All methods found are excluded from the returned DTO.
 * Each formatter is customizable.
 *
 * @param {*} entity The entity from which to extract a data transfer object (DTO).
 * @param {object} arg1 The argument to be deconstructed.
 * @param {string|RegExp} [arg1.keyReplacementRegEx=/^_/] The key replacement regular expression given to the `replace` method of `String`; if falsey, no key replacement is performed.
 * @param {string|function} [arg1.keyReplacement=''] The key replacement given to the `replace` method of `String`.
 * @param {function} [arg1.dateFormatter] The formatter of `Date`s; defaults to `toISOString()` on `Date`.
 * @param {function} [arg1.enumerationFormatter] The formatter of [`Enumeration` instances](https://www.npmjs.com/package/@northscaler/enum-support); defaults to returning the `name`.
 */
function extractDtoFromEntity (entity, {
  keyReplacementRegEx = /^_/,
  keyReplacement = '',
  dateFormatter = date => formatDate({ date, format: DateFormat.ISO_8601 }),
  enumerationFormatter = enumeration => formatEnumeration({ enumeration, useName: true })
} = {}) {
  if (keyReplacementRegEx) keyReplacementRegEx = new RegExp(keyReplacementRegEx)

  function recurse (entity) { // makes the code below more readable
    return extractDtoFromEntity(entity, {
      keyReplacementRegEx,
      keyReplacement,
      dateFormatter,
      enumerationFormatter
    })
  }

  if (!entity) return entity
  if (Array.isArray(entity)) return entity.map(it => recurse(it))
  if (Enumeration.isEnumerationInstance(entity)) return enumerationFormatter(entity)
  if (typeof entity !== 'object') return entity

  return Object.keys(entity).reduce((dto, key) => {
    const newKey = keyReplacementRegEx ? key.replace(keyReplacementRegEx, keyReplacement) : key

    const type = typeof entity[key]
    if (!entity[key]) dto[newKey] = entity[key]
    else if (Array.isArray(entity[key])) dto[newKey] = entity[key].map(it => recurse(it))
    else if (entity[key] instanceof Date) dto[newKey] = dateFormatter(entity[key])
    else if (Enumeration.isEnumerationInstance(entity[key])) dto[newKey] = enumerationFormatter(entity[key])
    else if (type === 'object') dto[newKey] = recurse(entity[key])
    else if (type !== 'function') dto[newKey] = entity[key] // skip functions

    return dto
  }, {})
}

module.exports = extractDtoFromEntity