We have a Scala/Play application in which we have several implicit classes that create Try objects from the Request, e.g.
implicit class RequestUtils[+T](req: Request[T]) {
def user: Try[User] = // pull the User from the Session, or throw an UnauthorizedException
def paging: Try[Paging] = // create a Paging object, or throw an IllegalArgumentException
}
We then access the wrapped objects via flatMaps
def route(pathParam: String) = BasicAction {
request =>
request.user.flatMap(user =>
request.paging.flatMap(paging =>
Try{ ... }
))}
And finally, an ActionBuilder generates a SimpleResult from the Try
case class BasicRequest[A](request: Request[A]) extends WrappedRequest(request)
class BasicActionBuilder extends ActionBuilder[BasicRequest] {
def invokeBlock[A](request: Request[A], block: (BasicRequest[A]) => Future[SimpleResult]) = {
block(BasicRequest(request))
}
}
def BasicAction[T](block: BasicRequest[AnyContent] => Try[T]) = {
val f: BasicRequest[AnyContent] => SimpleResult = (req: BasicRequest[AnyContent]) =>
block(req) match {
case Success(s) => Ok(convertToJson(s))
case Failure(e: UnauthorizedException) => Unauthorized(e.getMessage)
case Failure(e: Exception) => BadRequest(e.getMessage)
case Failure(t: Throwable) => InternalServerError(e.getMessage)
}
val ab = new BasicActionBuilder
ab.apply(f)
}
We're trying to find a way to essentially compose multiple Try objects together (or something along those lines - we're not wedded to using Trys) - the flatMaps are working fine for one or two Trys, but nesting them more than that hampers program readability. We can manually compose the objects together, e.g.
case class UserAndPaging(user: User, paging: Paging)
implicit class UserAndPagingUtils[+T](req: Request[T]) {
def userAndPaging: Try[UserAndPaging] = req.user.flatMap(user => req.paging.flatMap(paging => UserAndPaging(user, paging))
}
but that's going to result in an explosion of case class + implicit class def combinations. Ideally I'd like to be able to compose multiple Try objects together in an ad hoc fashion, e.g.
def route(pathParam: String) = BasicAction {
request => compose(request.user, request.paging).flatMap(userWithPaging => ...)
}
and have a Try[User with Paging] magically composed for me, but I have no idea how I'd go about doing this - I've been wrestling with the type system to try to assign a meaningful type to "compose" without any success.
How can I compose multiple Try objects together, or something equivalent using another language construct?