feathersjs/feathers

View on GitHub
packages/express/src/handlers.ts

Summary

Maintainability
A
0 mins
Test Coverage
A
94%
import path from 'path'
import { NotFound, GeneralError } from '@feathersjs/errors'
import { Request, Response, NextFunction, ErrorRequestHandler, RequestHandler } from 'express'

const defaults = {
  public: path.resolve(__dirname, '..', 'public'),
  logger: console
}
const defaultHtmlError = path.resolve(defaults.public, 'default.html')

export function notFound({ verbose = false } = {}): RequestHandler {
  return function (req: Request, _res: Response, next: NextFunction) {
    const url = `${req.url}`
    const message = `Page not found${verbose ? ': ' + url : ''}`

    next(new NotFound(message, { url }))
  }
}

export type ErrorHandlerOptions = {
  public?: string
  logger?: boolean | { error?: (msg: any) => void; info?: (msg: any) => void }
  html?: any
  json?: any
}

export function errorHandler(_options: ErrorHandlerOptions = {}): ErrorRequestHandler {
  const options = Object.assign({}, defaults, _options)

  if (typeof options.html === 'undefined') {
    options.html = {
      401: path.resolve(options.public, '401.html'),
      404: path.resolve(options.public, '404.html'),
      default: defaultHtmlError
    }
  }

  if (typeof options.json === 'undefined') {
    options.json = {}
  }

  return function (error: any, req: Request, res: Response, next: NextFunction) {
    // Set the error code for HTTP processing semantics
    error.code = !isNaN(parseInt(error.code, 10)) ? parseInt(error.code, 10) : 500

    // Log the error if it didn't come from a service method call
    if (options.logger && typeof options.logger.error === 'function' && !res.hook) {
      if (error.code >= 500) {
        options.logger.error(error)
      } else {
        options.logger.info(error)
      }
    }

    if (error.type !== 'FeathersError') {
      const oldError = error

      error = oldError.errors
        ? new GeneralError(oldError.message, {
            errors: oldError.errors
          })
        : new GeneralError(oldError.message)

      if (oldError.stack) {
        error.stack = oldError.stack
      }
    }

    const formatter: { [key: string]: any } = {}

    // If the developer passed a custom function for ALL html errors
    if (typeof options.html === 'function') {
      formatter['text/html'] = options.html
    } else {
      let file = options.html[error.code]
      if (!file) {
        file = options.html.default || defaultHtmlError
      }
      // If the developer passed a custom function for individual html errors
      if (typeof file === 'function') {
        formatter['text/html'] = file
      } else {
        formatter['text/html'] = function () {
          res.set('Content-Type', 'text/html')
          res.sendFile(file)
        }
      }
    }

    // If the developer passed a custom function for ALL json errors
    if (typeof options.json === 'function') {
      formatter['application/json'] = options.json
    } else {
      const handler = options.json[error.code] || options.json.default
      // If the developer passed a custom function for individual json errors
      if (typeof handler === 'function') {
        formatter['application/json'] = handler
      } else {
        // Don't show stack trace if it is a 404 error
        if (error.code === 404) {
          error.stack = null
        }

        formatter['application/json'] = function () {
          const output = Object.assign({}, error.toJSON())

          if (process.env.NODE_ENV === 'production') {
            delete output.stack
          }

          res.set('Content-Type', 'application/json')
          res.json(output)
        }
      }
    }

    res.status(error.code)

    const contentType = req.headers['content-type'] || ''
    const accepts = req.headers.accept || ''

    // by default just send back json
    if (contentType.indexOf('json') !== -1 || accepts.indexOf('json') !== -1) {
      formatter['application/json'](error, req, res, next)
    } else if (options.html && (contentType.indexOf('html') !== -1 || accepts.indexOf('html') !== -1)) {
      formatter['text/html'](error, req, res, next)
    } else {
      // TODO (EK): Maybe just return plain text
      formatter['application/json'](error, req, res, next)
    }
  }
}