daniel m
daniel m

Reputation: 415

Global Exception Handling in Play Application

currently I'm implementing a REST API with Scala and the Play! Framework. When a user submits data da is not correct, or the user is not allowed to view a resource the API has to response with BadRequest or Forbidden.

I don't want to have a huge bulk of nested if-else-statements to check every permission. So I just want to raise RuntimeExceptions like BadRequestException or ForbiddenException.

class MapableRestException(msg: String) extends RuntimeException(msg)
class NotFoundException(msg: String) extends MapableRestException(msg)
class ForbiddenException(msg: String) extends MapableRestException(msg)
class BadRequestException(msg: String) extends MapableRestException(msg)

In the Global Class I override onError

override def onError(request: RequestHeader, ex: Throwable): Future[Result] = ex.getCause match {
  case notfound: NotFoundException => {
    Future.successful(NotFound(JsonHelper.toJsonAll(ErrorDTO(notfound.getMessage()))).as(JSON))
  }

  case forbidden: ForbiddenException => {
    Future.successful(Forbidden(JsonHelper.toJsonAll(ErrorDTO(forbidden.getMessage()))).as(JSON))
  }

  case badrequest: BadRequestException => {
    Future.successful(BadRequest(JsonHelper.toJsonAll(ErrorDTO(badrequest.getMessage))).as(JSON))
  }

  case _ => super.onError(request, ex)

}

But when an one of the above Exceptions is thrown the stacktrace is still printed on the console?

Greetings, Daniel

Upvotes: 3

Views: 1893

Answers (1)

Mikesname
Mikesname

Reputation: 8901

I too would be interested in a more direct answer to this, but for the meantime, here's a workaround.

Create an ActionBuilder that runs your block and uses recover (or recoverWith, if you need to do async stuff) to do your global error handling, then use it in place of Action:

object ActionWithErrorHandling extends ActionBuilder[Request] {
  override def invokeBlock[A](request: Request[A], block: (Request[A]) => Future[Result]): Future[Result] = {
    block(request) recover {
      case e: PermissionDenied => Forbidden(e.getMessage)
      case e: ItemNotFound => NotFound(e.getMessage)
    }
  }
}

And in controllers:

def myAction = ActionWithErrorHandling { implicit request =>
  // Non-exception code path...
}

This is arguably a more sanitary approach since it preserves the Global onError for genuine oh-dear situations.

Upvotes: 4

Related Questions