1

I'm using http4s, and I have a Try that generates some json data for a response:

case GET -> Root / "something" =>
   getSomethingTry() match {
    case Success(something) => Ok(something)
    case Failure(CustomNotFoundException(reason)) => NotFound(reason)
    case Failure(CustomConflictException()) => Conflict()
   }

This function correctly returns a Task[Response]

However, I want to replace the Try with a Future. Matching no longer works, because the future may not have been resolved at the time of the match. So, I can map the future:

case GET -> Root / "something" =>
   getSomethingFuture().map {
    something => Ok(something)
   }.recover {
    case CustomNotFoundException(reason) => NotFound(reason)
    case CustomConflictException() => Conflict()
   }

But this returns a Future[Task[Response]] which is not what http4s wants. It doesn't seem appropriate to use Await.result to unbox the Future - I think this could cause thread pool issues - but it does make the code work.

http4s accepts futures as the argument to the task creator:

case GET -> Root / "something" =>
   Ok(getSomethingFuture())

But this doesn't let me set different status codes in the event of different errors. A solution could be to do a .recover on a task, but I can't see an obvious way to do that.

How can I call different http4s task wrappers in the event of different Future failure cases? Do I need to use middleware?

Timothy Jones
  • 21,495
  • 6
  • 60
  • 90

2 Answers2

2

Assuming you're using http4s 0.17 and higher, your Task is fs2.Task.

It's easy to convert the Future to Task and then deal with the latter:

case GET -> Root / "something" =>
   Task.fromFuture(getSomethingFuture())
     .flatMap {
       something => Ok(something)
     }
     .handleWith {
       case CustomNotFoundException(reason) => NotFound(reason)
       case CustomConflictException() => Conflict()
     }

I'd recommend, however, to use Task throughout your program instead of Try or Future

Oleg Pyzhcov
  • 7,323
  • 1
  • 18
  • 30
  • This is excellent, thank you. Trying this code says I need an implicit `Strategy` - is there any reason I shouldn't do: `implicit val strategy = Strategy.fromCachedDaemonPool()`? – Timothy Jones Oct 05 '17 at 05:05
  • @TimothyJones you can do that. I prefer using the default of standard `Future` - `Strategy.fromExecutionContext(scala.concurrent.ExecutionContext.global)`, but you can always fine-tune it later (just don't create a new strategy for every service :) – Oleg Pyzhcov Oct 05 '17 at 06:13
  • Thanks! The version of `fs2` that we have doesn't have a default, so I needed to provide one. – Timothy Jones Oct 05 '17 at 06:14
-2

You dont really need to unwrap the future. Play framework provides action.async for returning Future.

You can use it in the following way

Action.async {
  getSomethingFuture().map {
    something => Ok(something)
   }.recover {
    case CustomNotFoundException(reason) => NotFound(reason)
    case CustomConflictException() => Conflict()
   }
}

https://www.playframework.com/documentation/2.6.x/ScalaAsync#returning-futures

Shahbaz Shueb
  • 410
  • 4
  • 9