aboutsummaryrefslogtreecommitdiff
path: root/src/main/scala/xyz/driver/core/rest/DriverRoute.scala
blob: f3260d0296b8f02916be6960cc3447480c12259d (plain) (blame)
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
package xyz.driver.core.rest

import java.sql.SQLException

import akka.http.scaladsl.model._
import akka.http.scaladsl.model.StatusCodes.{BadRequest, Conflict, InternalServerError}
import akka.http.scaladsl.model.headers._
import akka.http.scaladsl.server.Directives._
import akka.http.scaladsl.server.{ExceptionHandler, RequestContext, Route}
import com.typesafe.scalalogging.Logger
import org.slf4j.MDC
import xyz.driver.core.rest
import xyz.driver.core.rest.errors.ServiceException

import scala.compat.Platform.ConcurrentModificationException

trait DriverRoute {
  val log: Logger

  def route: Route

  def routeWithDefaults: Route = handleExceptions(ExceptionHandler(exceptionHandler)) {
    route
  }

  /**
    * Override me for custom exception handling
    *
    * @return Exception handling route for exception type
    */
  protected def exceptionHandler: PartialFunction[Throwable, Route] = {
    case api: ServiceException if api.isPatientSensitive =>
      ctx =>
        log.info("PHI Sensitive error")
        errorResponse(ctx, InternalServerError, "Server error", api)(ctx)

    case api: ServiceException =>
      ctx =>
        log.info("API Error")
        errorResponse(ctx, api.statusCode, api.message, api)(ctx)

    case is: IllegalStateException =>
      ctx =>
        log.warn(s"Request is not allowed to ${ctx.request.method} ${ctx.request.uri}", is)
        errorResponse(ctx, BadRequest, message = is.getMessage, is)(ctx)

    case cm: ConcurrentModificationException =>
      ctx =>
        log.warn(s"Concurrent modification of the resource ${ctx.request.method} ${ctx.request.uri}", cm)
        errorResponse(ctx, Conflict, "Resource was changed concurrently, try requesting a newer version", cm)(ctx)

    case se: SQLException =>
      ctx =>
        log.warn(s"Database exception for the resource ${ctx.request.method} ${ctx.request.uri}", se)
        errorResponse(ctx, InternalServerError, "Data access error", se)(ctx)

    case t: Throwable =>
      ctx =>
        log.warn(s"Request to ${ctx.request.method} ${ctx.request.uri} could not be handled normally", t)
        errorResponse(ctx, InternalServerError, t.getMessage, t)(ctx)
  }

  protected def errorResponse[T <: Throwable](ctx: RequestContext,
                                              statusCode: StatusCode,
                                              message: String,
                                              exception: T): Route = {

    val trackingId    = rest.extractTrackingId(ctx.request)
    val tracingHeader = RawHeader(ContextHeaders.TrackingIdHeader, rest.extractTrackingId(ctx.request))

    MDC.put("trackingId", trackingId)

    optionalHeaderValueByType[Origin](()) { originHeader =>
      val responseHeaders = List[HttpHeader](tracingHeader,
                                             allowOrigin(originHeader),
                                             `Access-Control-Allow-Headers`(AllowedHeaders: _*),
                                             `Access-Control-Expose-Headers`(AllowedHeaders: _*))

      respondWithHeaders(responseHeaders) {
        complete(HttpResponse(statusCode, entity = message))
      }
    }
  }
}