function ecsLoggingValidate()

in utils/lib/ecs-logging-validate.js [105:231]


function ecsLoggingValidate (rec, opts) {
  opts = opts || {}
  const ignoreIndex = !!opts.ignoreIndex

  let recObj
  let recStr = null
  if (typeof (rec) === 'string') {
    recStr = rec
    try {
      recObj = JSON.parse(recStr)
    } catch (parseErr) {
      return parseErr
    }
  } else {
    recObj = rec
  }

  const details = []
  const addDetail = detail => {
    if (ignoreIndex && detail.specKey === 'index') {
      return
    }
    details.push(detail)
  }
  const spec = loadSpec()
  const indexedNames = [] // to handle `field.index`

  for (const [name, field] of Object.entries(spec.fields)) {
    const specKeysToHandle = Object.assign({}, field)
    delete specKeysToHandle.comment
    delete specKeysToHandle.url
    delete specKeysToHandle.default

    const recVal = dottedLookup(recObj, name)

    // field.required
    if (recVal === undefined) {
      if (field.required) {
        addDetail({
          message: `required field '${name}' is missing`,
          specKey: 'required',
          name: name,
          spec: field
        })
      }
      continue
    }
    delete specKeysToHandle.required

    // field.top_level_field
    if (field.top_level_field && !hasOwnProperty.call(recObj, name)) {
      addDetail({
        message: `field '${name}' is not a top-level field`,
        specKey: 'top_level_field',
        name: name,
        spec: field
      })
    }
    delete specKeysToHandle.top_level_field

    // field.index
    if (field.index !== undefined) {
      indexedNames[field.index] = name
    }
    delete specKeysToHandle.index

    // field.type
    switch (field.type) {
      case 'datetime':
        // We'll use the approximation that if JavaScript's `new Date()` can
        // handle it, that it roughly satisfies:
        // https://www.elastic.co/guide/en/elasticsearch/reference/current/date.html
        if (new Date(recVal).toString() === 'Invalid Date') {
          addDetail({
            message: `field '${name}' is not a valid '${field.type}'`,
            specKey: 'type',
            name: name,
            spec: field
          })
        }
        break
      case 'string':
        if (typeof (recVal) !== 'string') {
          addDetail({
            message: `field '${name}' is not a valid '${field.type}'`,
            specKey: 'type',
            name: name,
            spec: field
          })
        }
        break
      default:
        throw new Error(`unknown field type: ${field.type}`)
    }
    delete specKeysToHandle.type

    if (Object.keys(specKeysToHandle).length !== 0) {
      throw new Error('do not know how to handle these ecs-logging spec ' +
        `fields from field '${name}': ${Object.keys(specKeysToHandle).join(', ')}`)
    }
  }

  // field.index
  if (indexedNames.length > 0 && recStr) {
    let expected = ['{']
    indexedNames.forEach(n => {
      expected.push('"')
      expected.push(n)
      expected.push('":')
      expected.push(JSON.stringify(dottedLookup(recObj, n)))
      expected.push(',')
    })
    expected = expected.join('')
    if (!recStr.startsWith(expected)) {
      addDetail({
        message: `the order of fields is not the expected: ${indexedNames.join(', ')}`,
        specKey: 'index'
      })
    }
  }

  if (details.length === 0) {
    return null
  } else {
    return new EcsLoggingValidationError(details)
  }
}