All files / lib filter.js

13.79% Statements 12/87
0% Branches 0/54
0% Functions 0/11
17.65% Lines 12/68
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138  1x   1x 1x 1x   1x                   1x                     1x               1x               1x                                           1x                                           1x                               1x                                                                  
'use strict'
const filter = module.exports = { }
 
const FILTER_OPERATORS = ['<', '>', '~', ':']
const STRING_ONLY_OPERATORS = ['~', ':']
const FILTER_SEPERATOR = ','
 
filter._resourceDoesNotHaveProperty = (resourceConfig, key) => {
  if (resourceConfig.attributes[key]) return null
  return {
    status: '403',
    code: 'EFORBIDDEN',
    title: 'Invalid filter',
    detail: `${resourceConfig.resource} do not have attribute or relationship '${key}'`
  }
}
 
filter._relationshipIsForeign = (resourceConfig, key) => {
  const relationSettings = resourceConfig.attributes[key]._settings
  if (!relationSettings || !relationSettings.__as) return null
  return {
    status: '403',
    code: 'EFORBIDDEN',
    title: 'Invalid filter',
    detail: `Filter relationship '${key}' is a foreign reference and does not exist on ${resourceConfig.resource}`
  }
}
 
filter._splitElement = element => {
  if (!element) return null
  if (FILTER_OPERATORS.indexOf(element[0]) !== -1) {
    return { operator: element[0], value: element.substring(1) }
  }
  return { operator: null, value: element }
}
 
filter._stringOnlyOperator = (operator, attributeConfig) => {
  if (!operator || !attributeConfig) return null
  if (STRING_ONLY_OPERATORS.indexOf(operator) !== -1 && attributeConfig._type !== 'string') {
    return `operator ${operator} can only be applied to string attributes`
  }
  return null
}
 
filter._parseScalarFilterElement = (attributeConfig, scalarElement) => {
  if (!scalarElement) return { error: 'invalid or empty filter element' }
 
  const splitElement = filter._splitElement(scalarElement)
  if (!splitElement) return { error: 'empty filter' }
 
  const error = filter._stringOnlyOperator(splitElement.operator, attributeConfig)
  if (error) return { error }
 
  if (attributeConfig._settings) { // relationship attribute: no further validation
    return { result: splitElement }
  }
 
  const validateResult = attributeConfig.validate(splitElement.value)
  if (validateResult.error) {
    return { error: validateResult.error.message }
  }
 
  const validatedElement = { operator: splitElement.operator, value: validateResult.value }
  return { result: validatedElement }
}
 
filter._parseFilterElementHelper = (attributeConfig, filterElement) => {
  if (!filterElement) return { error: 'invalid or empty filter element' }
 
  const parsedElements = [].concat(filterElement).map(scalarElement => filter._parseScalarFilterElement(attributeConfig, scalarElement))
 
  if (parsedElements.length === 1) return parsedElements[0]
 
  const errors = parsedElements.reduce((combined, element) => {
    if (!combined) {
      if (!element.error) return combined
      return [ element.error ]
    }
    return combined.concat(element.error)
  }, null)
 
  if (errors) return { error: errors }
 
  const results = parsedElements.map(element => element.result)
 
  return { result: results }
}
 
filter._parseFilterElement = (attributeName, attributeConfig, filterElement) => {
  const helperResult = filter._parseFilterElementHelper(attributeConfig, filterElement)
 
  if (helperResult.error) {
    return {
      error: {
        status: '403',
        code: 'EFORBIDDEN',
        title: 'Invalid filter',
        detail: `Filter value for key '${attributeName}' is invalid: ${helperResult.error}`
      }
    }
  }
  return { result: helperResult.result }
}
 
filter.parseAndValidate = request => {
  if (!request.params.filter) return null
 
  const resourceConfig = request.resourceConfig
 
  const processedFilter = { }
  let error
  let filterElement
  let parsedFilterElement
 
  for (const key in request.params.filter) {
    filterElement = request.params.filter[key]
 
    if (typeof filterElement === 'string') request.params.filter[key] = filterElement = filterElement.split(FILTER_SEPERATOR)
 
    if (!Array.isArray(filterElement) && filterElement instanceof Object) continue // skip deep filters
 
    error = filter._resourceDoesNotHaveProperty(resourceConfig, key)
    if (error) return error
 
    error = filter._relationshipIsForeign(resourceConfig, key)
    if (error) return error
 
    parsedFilterElement = filter._parseFilterElement(key, resourceConfig.attributes[key], filterElement)
    if (parsedFilterElement.error) return parsedFilterElement.error
 
    processedFilter[key] = [].concat(parsedFilterElement.result)
  }
 
  request.processedFilter = processedFilter
 
  return null
}