Search code examples
scalahttp4s

Add exception handling in http4s with rho


I'm using http4s & rho (mainly for Swagger integration)

My services are using this DAO object, that methods that can throw Exceptions (fail the Task)

case class BasicMatchDao() {
 def readAll(): Task[List[BasicMatch]] = Task.fail(ActionNotImplemented("readAll")) 
 def read(id: String): Task[Option[BasicMatch]] = readQuery(id).option.transact(xa)
}

In my RhoService I can handle these like

private def exceptionToJson(t: Throwable):Json = Json.obj("error" -> t.getMessage.asJson)

val rhoService = new RhoService {
   GET / path |>> { (request: Request) =>
     Ok(dao.readAll.map(_.asJson)).handleWith {
        case t:ActionNotImplemented => NotImplemented(exceptionToJson(t))
        case t:Throwable => InternalServerError(exceptionToJson(t))
   }
} 

This way I make sure that whatever I return, it's always a Json

Since I don't want to pollute every RhoRoute with a similar errorhandling I want to do something which is possible with the default http4s.dsl, but I can't seem to get working with rho:

1. Create default error handler

e.g. add

 ...
 Ok(dao.readAll.map(_.asJson)).handleWith(errorHandler)
 ...

private def errorHandler(): PartialFunction[Throwable, Task[Response]] = {
      case t:ActionNotImplemented => NotImplemented(exceptionToJson(t))
      case t:Throwable => InternalServerError(exceptionToJson(t))
}

This will fail because NotImplemented is not a Response (I can call .pure on these to make type checking work) But then the code will compile, but I get this exception:

Cannot convert from fs2.Task[Product with Serializable] to an Entity, because no EntityEncoder[fs2.Task[Product with Serializable]] instance could be found. Ok(dao.readAll.map(_.asJson)).handleWith(errorHandler)

2. Add errorhandler to each RhoRoute

After defining the rhoRoute I'd like to map over it and add the errorhandler to each route, so do something at the r that let's me add the 'handleWith' somewhere (below will not work)

 new RhoService(rhoService.getRoutes.map(_.handleWith(errorHandler))

If I can't get this to work, I'll probably move back to the default dsl, but I really liked rho


Solution

  • So Part 1 is fixed for now. Defining the Task as Task[BaseResult] instead of Task[Response] will work

    import org.http4s.rho.Result.BaseResult
    
    val errorHandler: PartialFunction[Throwable, Task[BaseResult]] = {
        case t:ActionNotImplemented => NotImplemented(exceptionToJson(t))
        case t:Throwable => InternalServerError(exceptionToJson(t))
      }
    

    I'm looking into part 2 as well. All help is welcome :-)