ActionBuilder
ActionBuilders are not made for ad-hoc composition, but rather to build a hierarchy of actions so you end up using only a couple of actions throughout your controllers.
So in your example you should build IsAuthenticated
on top of HasToken
as I illustrated here.
This is a viable solution and can actually simplify your code. How often do you really need to compose on the spot?
EssentialAction
Ad-hoc composition could be achieved with EssentialActions (simply because they haven't changed from 2.1), but they have a few downsides, as Johan pointed out. Their API is not really intended for ad-hoc use either, and Iteratees are too low-level and too cumbersome for controller actions.
Actions
So finally your last option would be to write Actions directly. Actions do not support passing a WrappedRequest by default (that's why ActionBuilder exists). However you can still pass a WrappedRequest and have the next Action deal with it.
The following is the best I have come up with so far and is rather fragile I guess.
case class HasToken[A](action: Action[A]) extends Action[A] {
def apply(request: Request[A]): Future[SimpleResult] = {
request.headers.get("X-TOKEN") map { token =>
action(TokenRequest(token, request))
} getOrElse {
Future.successful(Results.Unauthorized("401 No Security Token\n"))
}
}
lazy val parser = action.parser
}
case class IsAuthenticated[A](action: Action[A]) extends Action[A] {
def apply(request: Request[A]): Future[SimpleResult] = {
request.session.get("user").map { user =>
action(new AuthRequest(user, request))
} getOrElse {
Future.successful(Results.Unauthorized("401 No user\n"))
}
}
lazy val parser = action.parser
}
object ActionComposition extends Controller {
def myAction = HasToken {
Action.async(parse.empty) { case TokenRequest(token, request) =>
Future {
Ok(token)
}
}
}
def myOtherAction = IsAuthenticated {
Action(parse.json) { case AuthRequest(user, request) =>
Ok
}
}
def both = HasToken {
IsAuthenticated {
Action(parse.empty) { case AuthRequest(user, request: TokenRequest[_]) =>
Ok(request.token)
}
}
}
}
Results
You can also compose at the Result level and only use the built-in actions. This is especially useful when trying to factor out error handling and other repetitive stuff. I have an example here.
Conclusion
We are still missing the capabilities that Play 2.1's action composition offered. So far to me it seems that ActionBuilder + Result composition is the winner as its successor.