All files / FeatureServer/lib route.js

93.02% Statements 80/86
84.61% Branches 44/52
100% Functions 13/13
96.38% Lines 80/83

Press n or j to go to the next uncovered block, b, p or k for the previous block.

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 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 15915x 15x 15x 15x 15x 15x 15x 15x 15x 15x 15x 15x   15x         15x       15x               37x   37x   37x 1x     37x 37x 36x   36x 36x     36x 14x 22x 8x 14x 5x 9x 3x 6x   6x 5x   1x 1x 1x     29x   8x 8x         14x 8x 6x 1x 5x 3x 2x 1x   1x 1x 1x             37x 37x       1x 1x 1x         37x 37x 1x 1x   36x       36x   36x         36x 36x 36x 74x 74x 74x   74x   74x         74x 74x   30x         22x       14x       9x       6x       6x    
const joi = require('joi')
const geojsonhint = require('geojson-validation')
const chalk = require('chalk')
const layerInfo = require('./layer-metadata')
const query = require('./query')
const queryRelatedRecords = require('./queryRelatedRecords.js')
const generateRenderer = require('./generate-renderer')
const restInfo = require('./rest-info-route-handler')
const serverInfo = require('./server-info-route-handler')
const layersInfo = require('./layers-metadata')
const relationshipsInfo = require('./relationships-info-route-handler')
const responseHandler = require('./response-handler')
 
const queryParamSchema = joi.object({
  limit: joi.number().optional(),
  resultRecordCount: joi.number().optional()
}).unknown()
 
const geojsonMetadataSchema = joi.object({
  maxRecordCount: joi.number().prefs({ convert: false }).optional().default(2000)
}).unknown()
 
module.exports = function route (req, res, geojson = {}, options = {}) {
  const {
    params: {
      method
    },
    url,
    originalUrl,
    path
  } = req
 
  const [route] = (url || originalUrl).split('?')
 
  if (shouldValidateGeojson()) {
    validateGeojson(geojson, path)
  }
 
  try {
    const metadata = validateGeojsonMetadata(geojson.metadata)
    const queryParams = validateAndCoerceQueryParams(req.query, metadata)
 
    geojson = { ...geojson, metadata }
    req = { ...req, query: queryParams }
    let result
 
    if (method) {
      result = handleMethodRequest({ method, geojson, req })
    } else if (isRestInfoRequest(route)) {
      result = restInfo(geojson, req)
    } else if (isServerMetadataRequest(route)) {
      result = serverInfo(geojson, req)
    } else if (isLayersMetadataRequest(route)) {
      result = layersInfo(geojson, queryParams)
    } else Iif (isRelationshipsMetadataRequest(route)) {
      result = relationshipsInfo(geojson, queryParams)
    } else if (isLayerMetadataRequest(route)) {
      result = layerInfo(geojson, req)
    } else {
      const error = new Error('Not Found')
      error.code = 404
      throw error
    }
 
    return responseHandler(req, res, 200, result)
  } catch (error) {
    Iif (process.env.KOOP_LOG_LEVEL === 'debug') console.trace(error)
    return responseHandler(req, res, error.code || 500, { error: error.message })
  }
}
 
function handleMethodRequest ({ method, geojson, req }) {
  if (method === 'query') {
    return query(geojson, req.query)
  } else if (method === 'queryRelatedRecords') {
    return queryRelatedRecords(geojson, req.query)
  } else if (method === 'generateRenderer') {
    return generateRenderer(geojson, req.query)
  } else if (method === 'info') {
    return layerInfo(geojson, req)
  }
  const error = new Error('Method not supported')
  error.code = 400
  throw error
}
 
function shouldValidateGeojson () {
  const {
    KOOP_LOG_LEVEL,
    KOOP_DISABLE_GEOJSON_VALIDATION
  } = process.env
  return KOOP_LOG_LEVEL === 'debug' && KOOP_DISABLE_GEOJSON_VALIDATION !== 'true'
}
 
function validateGeojson (geojson, path) {
  const geojsonErrors = geojsonhint.valid(geojson, true)
  Eif (geojsonErrors.length > 0) {
    console.log(chalk.yellow(`WARNING: source data for ${path} contains invalid GeoJSON; ${geojsonErrors[0]}`))
  }
}
 
function validateGeojsonMetadata (metadata = {}) {
  const { error, value } = geojsonMetadataSchema.validate(metadata)
  if (error) {
    error.code = 500
    throw error
  }
  return value
}
 
function validateAndCoerceQueryParams (queryParams, { maxRecordCount }) {
  const { error, value: query } = queryParamSchema.validate(queryParams)
 
  Iif (error) {
    error.code = 400
    throw error
  }
 
  const { limit, resultRecordCount } = query
  query.limit = limit || resultRecordCount || maxRecordCount
  return Object.keys(query).reduce((acc, key) => {
    const value = query[key]
    Iif (value === 'false') acc[key] = false
    else Iif (value === 'true') acc[key] = true
    else {
      acc[key] = tryParse(value)
    }
    return acc
  }, {})
}
 
function tryParse (value) {
  try {
    return JSON.parse(value)
  } catch (e) {
    return value
  }
}
 
function isRestInfoRequest (url) {
  return /\/rest\/info$/i.test(url)
}
 
function isServerMetadataRequest (url) {
  return /\/FeatureServer$/i.test(url) || /\/FeatureServer\/info$/i.test(url) || /\/FeatureServer\/($|\?)/.test(url)
}
 
function isLayersMetadataRequest (url) {
  return /\/FeatureServer\/layers$/i.test(url)
}
 
function isRelationshipsMetadataRequest (url) {
  return /\/FeatureServer\/relationships$/i.test(url)
}
 
function isLayerMetadataRequest (url) {
  return /\/FeatureServer\/\d+$/i.test(url) || /\/FeatureServer\/\d+\/info$/i.test(url) || /\/FeatureServer\/\d+\/$/.test(url)
}