lib_factory_model_sanitize-body.js

/**
 * Sanitize the payload body based on the model's properties and rules.
 * @method
 * @memberof DoboModel
 * @async
 * @param {Object} [options={}] - Options object
 * @param {Object} [options.body={}] - Body object to sanitize
 * @param {boolean} [options.partial=false] - If `true`, only the fields present in the body will be sanitized
 * @param {boolean} [options.strict=false] - If `true`, only the fields defined in the model's properties will be included in the sanitized body
 * @param {Array} [options.extFields=[]] - Additional fields to include in the sanitized body
 * @param {boolean} [options.truncateString=false] - If `true`, string fields will be truncated to their maximum length defined in the model's properties
 * @param {string} [options.action] - The action being performed (e.g., 'createRecord', 'updateRecord', etc.)
 * @param {boolean} [options.allProps=false] - If `true`, all properties including virtual ones will be considered for sanitization
 * @returns {Object} Returns sanitized body object
 */
async function sanitizeBody (options = {}) {
  const { body = {}, partial, strict, extFields = [], truncateString, action, allProps } = options
  const { isSet } = this.app.lib.aneka
  const { sanitizeByType } = this.app.dobo
  const { omit, has } = this.app.lib._
  const result = {}

  const sanitize = (name, type) => {
    const opts = { strict, action, model: this.name, outputFormat: 'native' }
    result[name] = sanitizeByType(result[name], type, opts)
    if (['updateRecord', 'upsertRecord'].includes(action) && type === 'string' && result[name] === '') result[name] = null
  }

  const omitted = []
  const details = []
  const props = allProps ? this.properties : this.getNonVirtualProperties()
  const properties = [...props, ...extFields]
  for (const prop of properties) {
    try {
      if (partial && !has(body, prop.name)) {
        // if (prop.type === 'array') result[prop.name] = null
        continue
      }
      result[prop.name] = body[prop.name]
      if (prop.type === 'array' && isSet(result[prop.name]) && !Array.isArray(result[prop.name])) result[prop.name] = [result[prop.name]]
      if (isSet(result[prop.name])) sanitize(prop.name, prop.type)
      if (result[prop.name] === null) continue
      if (truncateString && isSet(result[prop.name]) && ['string', 'text'].includes(prop.type)) result[prop.name] = result[prop.name].slice(0, prop.maxLength)
      if (prop.name.endsWith('Id') && isSet(result[prop.name]) && prop.type === 'string' && ['smallint', 'integer'].includes(this.adapter.idField.type)) result[prop.name] = result[prop.name] + ''
      if (result[prop.name] === undefined) omitted.push(prop.name)
    } catch (err) {
      details.push({ field: prop.name, error: err.message, value: body[prop.name], ext: { type: prop.type } })
    }
  }
  if (details.length > 0) {
    const payload = { details, statusCode: 422, code: 'BODY_SANITIZATION', model: this.name }
    throw this.plugin.error('sanitizeBodyError', payload)
  }
  return omit(result, omitted)
}

export default sanitizeBody