code-small.jpg

Cake Team Blogs

Errors, failures: I'm a teapot

Posted by Jan Machacek

Find me on:

10/12/12 09:19

Imagine you want to use the HTTP status codes to indicate failures in your Akka & Spray application. You receive a (JSON) request that maps to known route and can be unmarshalled into an instance of your object. But alas! validation fails. The response should indicate that the request was not OK (200), but you want to send some body that includes the details of the error.

Luckily, nothing is easier. We already have a good mechanism of passing bad and good values. The bad guys appear on the left projection of Either, the good guys appear on the right projection. Translating our baby-speak to Scala, we have Either[A, B], where the value of type A indicates error; and the value of type B indicates success. (Think that the value on the right is---erm---right.)

So, the task is to have be able to wire in some error resolution to the Spray routes.

class UserService(implicit val actorSystem: ActorSystem)
  extends Directives ... {

  def userActor = actorSystem.actorFor("/user/application/user")

  import ExecutionContext.Implicits.global

  val route = {
    path("users" / "register") {
      post {
        handleWith { registerUser: RegisterUser =>
          (userActor ? registerUser).mapTo[
            Either[NotRegisteredUser, RegisteredUser]
        }
      }
    }
}

We would like to send back HTTP status 200 when the userActor replies to our message RegisterUser with value on the right, and whatever the ErrorSelector for the bad value A returns when we get the value on the left. So, we need a marshaller that understands the errors.

trait Marshalling extends DefaultJsonProtocol with SprayJsonSupport
  with MetaMarshallers {
  type ErrorSelector[A] = A => StatusCode

  implicit def errorSelectingEitherMarshaller[A, B]
    (implicit ma: Marshaller[A], mb: Marshaller[B], esa: ErrorSelector[A]) =
    Marshaller[Either[A, B]] { (value, ctx) =>
      value match {
        case Left(a) =>
          val mc = new CollectingMarshallingContext()
          ma(a, mc)
          ctx.handleError(ErrorResponseException(esa(a), mc.entity))
        case Right(b) =>
          mb(b, ctx)
      }
    }

}

case class ErrorResponseException(responseStatus: StatusCode,
                                  response: Option[HttpEntity])
                                  extends RuntimeException

Now that we have the errorSelectingEitherMarshaller available, we need to sellotape the rest of the code together. We need to be able to handle our ErrorResponseException globally, for all routes. Enter RoutedHttpService.

class RoutedHttpService(route: Route) extends Actor with HttpService {

  implicit def actorRefFactory = context

  implicit val handler = ExceptionHandler.fromPF {
    case NonFatal(ErrorResponseException(statusCode, entity))
      => log => ctx =>
        ctx.complete(statusCode, entity)

    case NonFatal(e) => log => ctx =>
      log.error(e, "Error during processing of request {}", ctx.request)
      ctx.complete(InternalServerError)
    }

  def receive = {
    runRoute(route)(handler, RejectionHandler.Default, context,
                    RoutingSettings.Default)
  }

}

Now that all the mechanics is wired together, we're free to implement the instances of the ErrorSelector[A] for different As or take advantage of the fact that (by convention) all our errors extend ApplicationFailure and thus naively implement the ApplicaitonFailureErrorSelector:

implicit object ApplicationFailurErrorSelector
  extends ErrorSelector[ApplicationFailure] {
  def apply(v1: ApplicationFailure) = StatusCodes.UnprocessableEntity
}

Summary

With the appropriate instance of ErrorSelector for A, the errorSelectingEitherMarshaller[A, B], Marshaller for both A and B, we can correctly deal with errors: return appropriate HTTP status codes as well as the responses for those errors. For the complete code, head over to https://github.com/janm399/akka-patterns.

Topics: Spray

Posts by Topic

see all

Subscribe to Email Updates