Playback 2.0 RESTful Post Processing Request

Regarding this question I am wondering how you can handle REST after a la (raw) request:

def postProcessor[T](content: T) = { request match { case Accepts.Json() => asJson(content) case Accepts.Xml() => asXml(content) case _ => content } } 

redefinition of onRouteRequest in the Global configuration does not seem to provide access to the body of the response, so it seems that the Composition action is a way to intercept the response and complete tasks (tasks) after processing.

Question: is this a good idea, or is it better to list the content type directly in the controller method (or another class), where the type for translation is known?

I am currently doing things like this everywhere:

 toJson( i18n("account not found") ) toJson( Map('orderNum-> orderNum) ) 

while I would like the toJson / toXml conversion to be based on the post-request header of accepts.

+4
source share
3 answers

You want to be able to compute a Result containing a representation of an object of type A according to the Accept request for the header value. You can code this function with the following type of feature:

 trait Repr[-A] { def render(a: A, request: RequestHeader): Result } 

Then you can display any resource from your controller using the following auxiliary attribute:

 trait ReprSupport { def repr[A](a: A)(implicit request: RequestHeader, repr: Repr[A]) = repr.render(a, request) } object MyApp extends Controller with ReprSupport { def index = Action { implicit request => repr(Foo("bar")) } } 

Where Foo is a class of simple cases defined as follows:

 case class Foo(bar: String) 

To compile the above code, you need to have a value of type Repr[Foo] in your implicit scope. The first implementation can be written as follows:

 object Foo extends AcceptExtractors { implicit val fooRepr = new Repr[Foo] { def render(foo: Foo, request: RequestHeader): Result = request match { case Accepts.Html() => Ok(views.html.foo(foo)) // Assumes there is a foo.scala.html template taking just one parameter of type Foo case Accepts.Json() => Ok(Json.obj("bar" -> foo.bar)) case _ => NotAcceptable } } } 

But for each data type for which you want to write an instance of Repr , the render method will follow the same pattern:

 implicit val somethingRepr = new Repr[Something] { def render(value: Something, request: RequestHeader): Result = request match { // <Some interesting code> (eg case Accepts.Html() => Ok(views.html.something(value))) case _ => NotAcceptable } } 

You probably want to reduce the pattern and prevent users from forgetting the last case statement, abstracting from that pattern. For example, you can write the following helper method to build Repr[Something] :

 object Repr { def apply[A](f: PartialFunction[RequestHeader, A => Result]): Repr[A] = new Repr[A] { def render(a: A, request: RequestHeader): Result = if (f.isDefinedAt(request)) f(request)(a) else NotAcceptable } } 

So you just need to write the following to get Repr[Foo] :

 implicit val fooRepr = Repr[Foo] { case Accepts.Html() => foo => Ok(views.html.foo(foo)) case Accepts.Json() => foo => Ok(Json.obj("bar" -> foo.bar)) } 
+4
source

One option may be to create a kind of shell for this.

It should be something like the following:

  //THE WRAPPER that takes the action computation and the formatter def acceptEnabledAction[A]: (RequestHeader => A, (RequestHeader, A) => Result) => Action[AnyContent] = (a, f) => Action { request => f(request, a(request)) } //a sample formatter val encoder = (r, r) => r.accept /*or smthg*/ match { case x if x == "text/json" || x == "application/json" => Ok(toJson(r)) /*dummy to json*/ case x if x == "text/xml" || x == "application/xml" => Ok(toXml(r)) /*dummy to xml*/ case _ => BadRequest("not accepted") } //an action using it def index = acceptEnabledAction[Map[String, Boolean]]( rh => /*the real action content is here*/Map("a" -> true), encoder ) 
+1
source

Another option is to use the mimerender module (disclosure: I wrote it). You define the mapping once:

 val m = mapping( "text/html" -> { s: String => views.html.index(s) }, "application/xml" -> { s: String => <message>{s}</message> }, "application/json" -> { s: String => toJson(Map("message" -> toJson(s))) }, "text/plain" -> identity[String]_ ) 

and just reuse it on all your controllers:

 object Application extends Controller { def index = Action { implicit request => m.status(200)("Hello, world!") } } 

Note: this is a very early release and was tested only in Play 2.0.4

0
source

All Articles