holidayextras/jsonapi-server

View on GitHub
lib/responseHelper.js

Summary

Maintainability
B
6 hrs
Test Coverage
'use strict'
const responseHelper = module.exports = { }

const _ = {
  assign: require('lodash.assign'),
  pick: require('lodash.pick')
}
const async = require('async')
const pagination = require('./pagination.js')
const Joi = require('joi')
const debug = require('./debugging.js')

responseHelper.setBaseUrl = baseUrl => {
  responseHelper._baseUrl = baseUrl
}
responseHelper.setMetadata = meta => {
  responseHelper._metadata = meta
}

responseHelper._enforceSchemaOnArray = (items, schema, callback) => {
  if (!(Array.isArray(items))) {
    items = [ items ]
  }
  async.map(items, (item, done) => responseHelper._enforceSchemaOnObject(item, schema, done), (err, results) => {
    if (err) return callback(err)

    results = results.filter(result => !!result)
    return callback(null, results)
  })
}

responseHelper._enforceSchemaOnObject = (item, schema, callback) => {
  debug.validationOutput(JSON.stringify(item))
  Joi.validate(item, schema, (err, sanitisedItem) => {
    if (err) {
      debug.validationError(err.message, JSON.stringify(item))
      const res = {
        status: '500',
        code: 'EINVALIDITEM',
        title: 'Item in response does not validate',
        detail: {
          item: item,
          error: err.message
        }
      }
      return callback(res)
    }

    const dataItem = responseHelper._generateDataItem(sanitisedItem, schema)
    return callback(null, dataItem)
  })
}

responseHelper._generateDataItem = (item, schema) => {
  const isSpecialProperty = value => {
    if (!(value instanceof Object)) return false
    if (value._settings) return true
    return false
  }
  const linkProperties = Object.keys(schema).filter(someProperty => isSpecialProperty(schema[someProperty]))
  const attributeProperties = Object.keys(schema).filter(someProperty => {
    if (someProperty === 'id') return false
    if (someProperty === 'type') return false
    if (someProperty === 'meta') return false
    return !isSpecialProperty(schema[someProperty])
  })

  const result = {
    type: item.type,
    id: item.id,
    attributes: _.pick(item, attributeProperties),
    links: responseHelper._generateLinks(item, schema, linkProperties),
    relationships: responseHelper._generateRelationships(item, schema, linkProperties),
    meta: item.meta
  }

  return result
}

responseHelper._generateLinks = item => ({
  self: `${responseHelper._baseUrl + item.type}/${item.id}`
})

responseHelper._generateRelationships = (item, schema, linkProperties) => {
  if (linkProperties.length === 0) return undefined

  const links = { }

  linkProperties.forEach(linkProperty => {
    links[linkProperty] = responseHelper._generateLink(item, schema[linkProperty], linkProperty)
  })

  return links
}

responseHelper._generateLink = (item, schemaProperty, linkProperty) => {
  const link = {
    meta: {
      relation: 'primary',
      // type: schemaProperty._settings.__many || schemaProperty._settings.__one,
      readOnly: false
    },
    links: {
      self: `${responseHelper._baseUrl + item.type}/${item.id}/relationships/${linkProperty}`,
      related: `${responseHelper._baseUrl + item.type}/${item.id}/${linkProperty}`
    },
    data: null
  }

  if (schemaProperty._settings.__many) {
    // $FlowFixMe: the data property can be either undefined (not present), null or [ ]
    link.data = [ ]
    let linkItems = item[linkProperty]
    if (linkItems) {
      if (!(Array.isArray(linkItems))) linkItems = [ linkItems ]
      linkItems.forEach(linkItem => {
        link.data.push({
          type: linkItem.type,
          id: linkItem.id,
          meta: linkItem.meta
        })
      })
    }
  }

  if (schemaProperty._settings.__one) {
    const linkItem = item[linkProperty]
    if (linkItem) {
      // $FlowFixMe: the data property can be either undefined (not present), null or [ ]
      link.data = {
        type: linkItem.type,
        id: linkItem.id,
        meta: linkItem.meta
      }
    }
  }

  if (schemaProperty._settings.__as) {
    const relatedResource = (schemaProperty._settings.__one || schemaProperty._settings.__many)[0]
    // get information about the linkage - list of ids and types
    // /rest/bookings/relationships/?customer=26aa8a92-2845-4e40-999f-1fa006ec8c63
    link.links.self = `${responseHelper._baseUrl + relatedResource}/relationships/?${schemaProperty._settings.__as}=${item.id}`
    // get full details of all linked resources
    // /rest/bookings/?filter[customer]=26aa8a92-2845-4e40-999f-1fa006ec8c63
    link.links.related = `${responseHelper._baseUrl + relatedResource}/?filter[${schemaProperty._settings.__as}]=${item.id}`
    if (!item[linkProperty]) {
      // $FlowFixMe: the data property can be either undefined (not present), null or [ ]
      link.data = undefined
    }
    link.meta = {
      relation: 'foreign',
      belongsTo: relatedResource,
      as: schemaProperty._settings.__as,
      many: !!schemaProperty._settings.__many,
      readOnly: true
    }
  }

  return link
}

responseHelper.generateError = (request, err) => {
  debug.errors(request.route.verb, request.route.combined, JSON.stringify(err))
  if (!(Array.isArray(err))) err = [ err ]

  const errorResponse = {
    jsonapi: {
      version: '1.0'
    },
    meta: responseHelper._generateMeta(request),
    links: {
      self: responseHelper._baseUrl + request.route.path
    },
    errors: err.map(error => ({
      status: error.status,
      code: error.code,
      title: error.title,
      detail: error.detail
    }))
  }

  return errorResponse
}

responseHelper._generateResponse = (request, resourceConfig, sanitisedData, handlerTotal) => ({
  jsonapi: {
    version: '1.0'
  },

  meta: responseHelper._generateMeta(request, handlerTotal),

  links: _.assign({
    self: responseHelper._baseUrl + request.route.path + (request.route.query ? ('?' + request.route.query) : '')
  }, pagination.generatePageLinks(request, handlerTotal)),

  data: sanitisedData
})

responseHelper._generateMeta = (request, handlerTotal) => {
  let meta
  if (typeof responseHelper._metadata === 'function') {
    meta = responseHelper._metadata(request)
  } else {
    meta = _.assign({ }, responseHelper._metadata)
  }

  if (handlerTotal) {
    meta.page = pagination.generateMetaSummary(request, handlerTotal)
  }

  return meta
}