Using Play action composition, I'm wondering if there's a way to add fields to a request in multiple ActionTransformer
s, such that I can access both fields in the request.
Simple example that doesn't work:
import scala.concurrent.{ExecutionContext, Future}
import play.api.mvc.{Action, ActionTransformer, Request, Results, WrappedRequest}
class RequestWithName[A](request: Request[A], val name: String) extends WrappedRequest[A](request)
def addName(implicit ec: ExecutionContext) = new ActionTransformer[Request, RequestWithName] {
override def executionContext: ExecutionContext = ec
override def transform[A](request: Request[A]): Future[RequestWithName[A]] = ???
}
class RequestWithUserId[A](request: Request[A], val userId: String) extends WrappedRequest[A](request)
def addUserId(implicit ec: ExecutionContext) = new ActionTransformer[Request, RequestWithUserId] {
override def executionContext: ExecutionContext = ec
override def transform[A](request: Request[A]): Future[RequestWithUserId[A]] = ???
}
Action.andThen(addName).andThen(addUserId) { req =>
Results.Ok(req.name + req.userId) // compile error: name not available
}
Action.andThen(addUserId).andThen(addName) { req =>
Results.Ok(req.name + req.userId) // compile error: userId not available
}
It makes sense why these compile errors happen - the last andThen
returns an ActionTransformer
that has only one of the two fields. But is there a way to accomplish the same thing, without making them aware of each other? Eg. I could add a RequestWithUserIdAndName
- but then I can't compose that with other transforms that add even more fields.
How about writing something like EnrichedRequest
which has a collection of enrichments (implemented as some sealed hierarchy), so that you would lift it first, and then add enrichments as you see fit?
sealed trait Enrichment
case class UserName(name: String) extends Enrichment
case class UserId(name: String) extends Enrichment
class EnrichedRequest[A](
request: Request[A],
val enrichments: List[Enrichment]
) extends WrappedRequest[A](request)
def asEnriched(implicit ec: ExecutionContext) = new ActionTransformer[Request, EnrichedRequest] {
...
}
def addName(implicit ec: ExecutionContext) = new ActionTransformer[EnrichedRequest, EnrichedRequest] {
...
}
def addUserId(implicit ec: ExecutionContext) = new ActionTransformer[EnrichedRequest, EnrichedRequest] {
...
}
Action.andThen(asEnriched).andThen(addName).andThen(addUserId) { ... }
Action.andThen(asEnriched).andThen(addUserId).andThen(addName) { ... }
That would be easy to implement and freely extensible. The only downside is that to extract data you would have to do something like:
enrichments.collect {
case UserName(name) => name
}.head
to extract data back.