Composing multiple ActionTransformers - can I add fields in multiple transformers and access them all in the invokeBlock?

Using Play action composition, I’m wondering if there’s a way to add fields to a request in multiple ActionTransformers, 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.

Hi @mcintyre1994, yeah thats possible.

You can do that:

object AddNameAction {
  def apply(name: String) =  new ActionTransformer[Request, RequestWithName] {   
     override def executionContext: ExecutionContext = ec
     override def transform[A](request: Request[A]): Future[RequestWithName[A]] = Future {
       RequestWithName(request, name)
    }
 } 
}

And do the same for the other one.

Then you can call like

AddNameAction(name).andThen AddUserId

Other important thing that you may notest, the next action must know the return of the previous one if you want to share the information =)

So you can do that

  1. Name -> id : ActionTransformer[Request, RequestWithName] -> new ActionTransformer[RequestWithName, RequestWithNameAndId] -> Another ActionFunction

  2. id -> name: ActionTransformer[Request, RequestWithId] -> new ActionTransformer[RequestWithId, RequestWithNameAndId] -> Another ActionFunction

If you want to use them separatly, consider use the play request attributos to storage this data.

I may this can help =D

1 Like