Play: How to implement action composition

Given the followoing ActionBuilder implementations:

class SignedRequest[A](request: Request[A]) extends WrappedRequest[A](request) {}

object SignedAction extends ActionBuilder[SignedRequest] {

  def invokeBlock[A](request: Request[A], block: SignedRequest[A] => Future[SimpleResult]) = {
    block(new SignedRequest(request))
  }   
}     

class SecuredRequest[A](request: Request[A]) extends WrappedRequest[A](request) {}

object SecuredRequest extends ActionBuilder[SecuredRequest] {

  def invokeBlock[A](request: Request[A], block: SecuredRequest[A] => Future[SimpleResult]) = {
    block(new SecuredRequest(request))
  }   
}

How do I combine them? I've tried the following...

object MyController extends Controller {

  def doSomething = SignedAction.async(parse.json) {
    SecuredAction.async(parse.json) { implicit request =>
      Future.successful(Ok)
  }}
}

... but I always get the following error message:

/home/j3d/test/controllers/MyController.scala:37: type mismatch;
[error]  found   : play.api.mvc.Action[play.api.libs.json.JsValue]
[error]  required: scala.concurrent.Future[play.api.mvc.SimpleResult]
[error]       SecuredAction.async(parse.json) {
                                              ^

Am I missing something? Tx.


Solution 1:

Function async is expecting a Future[SimpleResult], but the nested SecuredAction.async is returning an Action to the top SignedAction.async (notes that in your sample code you omit to declare requests as class and SignedAction is declared twice).

You can compose result of nested SecuredAction within SignedAction by applying it to the signed request.

package controllers

import scala.concurrent.Future

import play.api._
import play.api.mvc._

case class SignedRequest[A](request: Request[A]) 
    extends WrappedRequest[A](request) {}

object SignedAction extends ActionBuilder[SignedRequest] {

  def invokeBlock[A](request: Request[A], 
    block: SignedRequest[A] => Future[Result]) = 
    block(new SignedRequest(request))

}     

case class SecuredRequest[A](request: Request[A]) 
    extends WrappedRequest[A](request) {}

object SecuredAction extends ActionBuilder[SecuredRequest] {

  def invokeBlock[A](request: Request[A], 
    block: SecuredRequest[A] => Future[Result]) = 
    block(new SecuredRequest(request))

}

object MyController extends Controller {
  def doSomething = SignedAction.async(parse.json) { signedReq =>
    SecuredAction.async(parse.json) { implicit securedReq =>
      Future.successful(Ok)
    } apply signedReq
  }
}

Such action composition can also be done without ActionBuilder (which can lead to some extra complexity).

package controllers

import scala.concurrent.Future

import play.api._
import play.api.mvc._

case class SignedRequest[A](request: Request[A])
case class SecuredRequest[A](request: Request[A]) 

object MyController extends Controller {
  def Signed[A](bodyParser: BodyParser[A])(signedBlock: SignedRequest[A] => Future[Result]): Action[A] = Action.async(bodyParser) { req => 
    signedBlock(SignedRequest(req)) 
  }

  def Secured[A](bodyParser: BodyParser[A])(securedBlock: SecuredRequest[A] => Future[Result]): Action[A] = Action.async(bodyParser) { req => 
    securedBlock(SecuredRequest(req)) 
  }

  def doSomething = Signed(parse.json) { signedReq =>
    Secured(parse.json) { implicit securedReq =>
      Future.successful(Ok)
    } apply signedReq.request
  }
}

Composition can be also done around Future[Result]:

package controllers

import scala.concurrent.Future

import play.api._
import play.api.mvc._
import play.api.libs.json.JsValue

case class SignedRequest[A](request: Request[A])
case class SecuredRequest[A](request: Request[A]) 

object MyController extends Controller {
  def Signed[A](signedBlock: SignedRequest[A] => Future[Result])(implicit req: Request[A]): Future[Result] = signedBlock(SignedRequest(req))

  def Secured[A](signedBlock: SecuredRequest[A] => Future[Result])(implicit req: Request[A]): Future[Result] = signedBlock(SecuredRequest(req))

  def doSomething = Action.async(parse.json) { implicit req =>
    Signed[JsValue] { signedReq =>
      Secured[JsValue] { securedReq => Future.successful(Ok) } 
    }
  }
}

Solution 2:

Using action-zipper you can compose ActionBuilders

import jp.t2v.lab.play2.actzip._

object MyController extends Controller {

  val MyAction = SignedAction zip SecuredAction 

  def doSomething = MyAction.async(parse.json) { case (signedReq, secureReqeq) =>
    Future.successful(Ok)
  }
}

Json parsing will be executed only once :)