Here is my routes file:
GET /:lang controller.Application.index(lang: String)
GET /:lang/news controller.Application.news(lang: String)
Note that all of them start with /:lang.
Currently, I write Application.scala as
def index(lang: String) = Action {
implicit val messages: Messages = play.api.i18n.Messages.Implicits.applicationMessages(
Lang(lang), play.api.Play.current)
Ok(views.html.index("title"))
}
In this way, I have to write as many implicit Messages as Action. Is there any better solution for this?
Passing just Lang is simpler option:
def lang(lang: String) = Action {
Ok(views.html.index("play")(Lang(lang)))
}
//template
#(text: String)(implicit lang: play.api.i18n.Lang)
#Messages("hello")
You can reuse some code by using action composition, define wrapped request and action:
case class LocalizedRequest(val lang: Lang, request: Request[AnyContent]) extends WrappedRequest(request)
def LocalizedAction(lang: String)(f: LocalizedRequest => Result) = {
Action{ request =>
f(LocalizedRequest(Lang(lang), request))
}
}
Now you are able to reuse LocalizedAction like this:
//template
#(text: String)(implicit request: controllers.LocalizedRequest)
#Messages("hello")
//controller
def lang(lang: String) = LocalizedAction(lang){implicit request =>
Ok(views.html.index("play"))
}
Finally, I solved this problem in the following way.
As #Infinity suggests, I defined wrapped request and action as:
case class LocalizedRequest(messages: Messages,
request: Request[AnyContent])
extends WrappedRequest(request)
object Actions {
def LocalizedAction(lang: String)(f: LocalizedRequest => Result) = {
Action { request =>
f(LocalizedRequest(applicationMessages(Lang(lang), current), request))
}
}
object Implicits {
implicit def localizedRequest2Messages(implicit request: LocalizedRequest): Messages = request.messages
}
}
Now I'm able to use LocalizedAction like this:
def lang(lang: String) = LocalizedAction(lang) { implicit request =>
Ok(views.html.index("play"))
}
However, in order to omit the implicit parameter of Messages, which should be a play.api.i18n.Messages, I added a line to my template as:
#import controllers.Actions.Implicits._
Related
The following sample code uses Play Framework to parse JSON to an object:
def createEvent(): Action[JsValue] = Action.async(parse.tolerantJson) {
request => {
request.body.validate[SomeEvent] match {
case o:JsSuccess[SomeEvent] => {
//do something
Future(Created)
}
}
}
}
Is it possible to generalise it so it can handle different event types? e.g.
def createEvent(): Action[JsValue] = Action.async(parse.tolerantJson) {
request => {
val eventType = request.contentType match {
case Some("SomeEventType") => SomeEvent
case Some("OtherEventType") => OtherEvent
}
request.body.validate[eventType] match {
case o:JsSuccess[eventType] => {
//do something
Future(Created)
}
}
}
}
Currently, the above code will fail in the line request.body.validate[eventType]
You can extract body.validate[T] into a function and call it from your patten matching construct with a proper type, i.e:
def extract[T: JsonFormat](implicit req: Request[AnyContent]) = req.body.valudate[T]
request.contentType match {
case Some("SomeEventType") => extract[SomeEvent]
case Some("OtherEventType") => extract[OtherEvent]
}
You can read and create class from contentType dynamically. But you can have problem, if will be no implicit Format for extracted type in scope:
error: No Json formatter found for type A. Try to implement an implicit Format for this type.
or
java.lang.ClassNotFoundException: models.Bazz
The available data:
model
package models
case class Bar(name: String)
request
request.contentType: Option[String] = Some("models.Bar")
incoming body
request.body: JsValue = """{"name": "bar name"}"""
format[Bar]
implicit val BarFormat = Json.format[models.Bar]
extractor:
def extract[A <: Any](ct: String, json: JsValue)(implicit format: Format[A]) = {
val manifest:Manifest[A] = Manifest.classType[A](Class.forName(ct))
json.validate[A]
}
request.contentType.map(extract(_, request.body))
res1: Option[play.api.libs.json.JsResult[models.Bar]] = Some(JsSuccess(Bar(bar name),/name))
you can see https://github.com/strongtyped/fun-cqrs/blob/demos/shop-sample/samples/shop/src/main/scala/funcqrs/json/TypedJson.scala for other way of deserializing self-contained json.
I use Silhouette and Play 2.4 and I'd like to restrict actions if a SecuredRequest body contains something wrong.
I know, I should use trait Authorization as described by official docs.
I'm trying to do the following:
case class WithCheck(checkCriteria: String) extends Authorization[User, CookieAuthenticator] {
def isAuthorized[B](user: User, authenticator: CookieAuthenticator)(implicit request: Request[B], messages: Messages) = {
Future.successful(user.criteria == checkCriteria)
}
}
and than
def myAction = SecuredAction(WithCheck("bar")) { implicit request =>
val foo = ...// deserialize object from request.body
val checkCriteria = foo.criteria
// do something else here
}
How can I use the checkCriteria value in the class WithCheck?
I found a solution.
Somehow, I was blind to see that isAuthorized has the same request as an implicit parameter. So, the check could be done entirely into the isAuthorized. For example,
case class WithCheck() extends Authorization[User, CookieAuthenticator] {
def isAuthorized[B](user: User, authenticator: CookieAuthenticator)(implicit request: Request[B], messages: Messages) = {
val foo = upickle.read[Foo](request.body.toString())
Future.successful(user.criteria == foo.criteria)
}
}
I would like to create a custom action which takes away the boilerplate of writing actions like this:
Action[MyClass](BodyParsers.parse.json[MyClass]) { req => ...
However, I keep running into class definition errors. Here has been my most successful attempt:
class JsonAction[A: Reads] extends ActionBuilder[Request] {
def hardcodedJson[A: Reads](action: Action[A]) =
Action.async(BodyParsers.parse.json[A]) { request => action(request) }
def invokeBlock[A: Reads](request: Request[A], block: (Request[A]) => Future[Result]) = {
block(request)
}
override def composeAction[A: Reads](action: Action[A]) = hardcodedJson(action)
}
but I get the following error: method composeAction overrides nothing.
If I change composeAction[A: Reads] to composeAction[A] it tells me there isn't a Json Serializer for type A.
Is there some other way to define this custom action?
Yep, I tried to get this to work with the official ActionBuilder way-of-doing-it and just could not get the types to line up.
Here's something that worked for me though:
object JsonActionHelper {
def withReads[A](act: Request[A] => Future[Result])(implicit reads:Reads[A]) =
Action.async(BodyParsers.parse.json(reads))(act)
}
Usage in your controller (FooJson is an object containing an implicit Reads[Foo]):
import models.FooJson._
import JsonActionHelper._
def handleIncomingFoo(fooId:String) = withReads[Foo] { req =>
val foo:Foo = req.body
...
Ok(...)
}
ActionBuilder is not generic enough for your use-case; there's nowhere for you to pass in your Reads[T].
There's nothing special about ActionBuilder though. It's a collection of apply and async factory methods. You can define your own Action type with whatever factory methods you need:
object JsonAction {
def apply[A : Reads](request: Request[A] => Result) = Action(BodyParsers.parse.json[A])(request)
}
// these are equivalent:
Action[MyClass](BodyParsers.parse.json[MyClass]) { req => ??? }
JsonAction[MyClass] { req => ??? }
I've made ActionRefiner to read language of current request from parameter in url:
class LangRequest[A](val lang: Lang, request: Request[A]) extends WrappedRequest[A](request)
def LangAction(lang: String) = new ActionRefiner[Request, LangRequest] {
def refine[A](input: Request[A]) = Future.successful {
val availLangs: List[String] = Play.current.configuration.getStringList("play.i18n.langs").get.toList
if (!availLangs.contains(lang))
Left {
input.acceptLanguages.head match {
case Lang(value, _) if availLangs.contains(value) => Redirect(controllers.routes.Application.index(value))
case _ => Redirect(controllers.routes.Application.index(availLangs.head))
}
}
else Right {
new LangRequest(Lang(lang), input)
}
}
}
and try to use it in action like this:
def login(lng: String) =
LangAction(lng) {
implicit request =>
Ok("Ok")
}
And here I get this "missing parameter type" error for implicit request
What I'm doing wrong?
Thanks in advance
Note: I am new to Play/Scala. There may be a simpler solution.
Likely you had same scenario as me.
Initially tried to implement code similar to examples in https://www.playframework.com/documentation/2.6.x/ScalaActionsComposition#Putting-it-all-together
The difficulty is in implementing shorter action chains than their 'putting it all together'. For example I just needed to refine action based on an ID, but I didn't need any auth.
So rather than having userAction andThen ItemAction(itemId) andThen PermissionCheckAction, I just needed ItemAction(itemId)
I ran into same error as you trying to naively remove the other 2 action funcs.
I believe the issue is that userAction in guide specifies/define what body-parser the request will use.
By removing this part, your request doesn't know what type of body-parser, so it doesn't know the [A] of request[A], hence complaining about type
My Fix: Use an action class (rather than just function), that can have a body-parser passed into constructor
class LeagueRequest[A](val league: League, request: Request[A]) extends WrappedRequest[A](request)
class LeagueAction(val parser: BodyParser[AnyContent], leagueId: String)(implicit val ec: ExecutionContext) extends ActionBuilder[LeagueRequest, AnyContent] with ActionRefiner[Request, LeagueRequest]{
def executionContext = ec
override def refine[A](input: Request[A]) = Future.successful {
inTransaction(
(for {
leagueIdLong <- IdParser.parseLongId(leagueId, "league")
league <- AppDB.leagueTable.lookup(leagueIdLong).toRight(NotFound(f"League id $leagueId does not exist"))
out <- Right(new LeagueRequest(league, input))
} yield out)
)
}
}
with my controller having
def get(leagueId: String) = (new LeagueAction(parse.default, leagueId)).async { implicit request =>
Future(Ok(Json.toJson(request.league)))
}
I could not manage to avoid OP's error using action composition functions, rather than class that extended ActionBuilder.
I am using action composition of play framework 2.3 and I would like to send parameters to the custom action.
For example, if you have a custom action that adds a cache how the custom action can receive the cache key and the desired cache time. Example code inside a play controller:
def myAction(p1: String) = CachedAction(key="myAction1"+p1, time = 300.seconds) {
implicit request =>
... do an expensive calculation …
Ok(views.html.example.template())
}
I have tested an ActionBuilder combined with a custom request but I haven't found a solution.
I am aware that play offers a cache for actions, unfortunately that cache does not meet all the requirements.
I am not sure about solution with ActionBuilder or ActionRefiner but that may work for you:
def CachedAction(key: String, time: Duration)(f: Request[AnyContent] => Result) = {
Action{ request =>
if(cache.contains(key)){
...
} else{
...
}
f(request)
}
}
and then:
def myAction(p1: String) = CachedAction("hello" + p1, 100 millis){ request =>
Ok("cached action")
}
Edit:
Since you need Action.async you can write something like that:
case class Caching[A](key: String, time: Duration)(action: Action[A]) extends Action[A] {
def apply(request: Request[A]): Future[Result] = {
if(cache.contains(key)){
Logger.info("Found key: " + key)
}
action(request)
}
lazy val parser = action.parser
}
def cached(p1: String) = Caching(key = p1, time = 100 millis){
Action.async { Future(Ok("hello")) }
//or
Action { Ok("hello") }
}
Case class with two parameter lists looks weird but it works.
Docs:https://www.playframework.com/documentation/2.3.x/ScalaActionsComposition#Composing-actions