making an implicit available within a play action - scala

My app hits many different dbs, which db depends on a query string parameter. I have a DatabaseConfigLocator that takes the string and returns the config and it works great. My problem is I want to make the config for each request available implicitly in my controller. I have tried two approaches.
class MyController extends Controller{
implicit def dbConfig(implicit request: RequestHeader): DatabaseConfig[JdbcProfile] = DatabaseConfigLocator.get[JdbcProfile](request.getQueryString("dbName")
}
This dosn't compile unless I change it to an implicit val with the same type but I need to recheck the query string every request not just once so I don't think an implicit val will work
The other approach was to create an action
object IODBAction extends ActionBuilder[Request]{
def invokeBlock[A](request: Request[A], block: (Request[A]) => Future[Result]) = {
implicit def dbConfig(implicit request: RequestHeader): DatabaseConfig[JdbcProfile] = DatabaseConfigLocator.get[JdbcProfile]("blah")
block(request)
}
}
but that implicit is not available in the scope of the block and I cant find any way to pass it in as an implicit.
My goal is to be able to do something like this
class MyController extends Controller {
def create = {
Action.async {
request =>
ApiResponse {
for {
id <- aProvider.save(validRegistrationRequest.toVisitor)
} yield id
}
}
}
}
class aProvider {
def save(v: Visitor)(implicit dbConfig: DatabaseConfig[JdbcProfile]): ApiResponse[VisitorId]
}
or if the provider can get the implicit at instantiation time all the better
class aProvider(implicit dbConfig: DatabaseConfig[JdbcPRofile]) {
def save(v: Visitor): ApiResponse[VisitorId]
}
Any advice on how to approach this or if its possible with play framework?

Unfortunately, I fear you're stuck with an action receiving a single parameter, so you need to stick to the standard play "Action Composition" pattern. This is documented pretty extensively in the play docs.
I'd define yourself a a "Context" like this:
case class Context(dbConfig: DatabaseConfig[JDBCProfile], request: Request[A])
extends WrappedRequest(request)
Then create a custom action builder like this:
object DBIOAction extends ActionBuilder[Context]{
def invokeBlock[A](request: Request[A], block: (Context[A]) => Future[Result]) = {
val dbConfig = DatabaseConfigLocator.get[JdbcProfile]("blah")
val context = Context(dbConfig, request)
block(context)
}
}
You then should be able to use it like this:
def index = DBIOAction { implicit context =>
// do some stuff. return a result
}
In order to make things simple, I'd pass the implicit context into your service methods, perhaps extracting the dbConfig from the context and passing it the rest of the way down to your DAO's.
class FunService {
def getSomeData(param1: String)(implicit context: Context) = {
// do some work, perhaps using context.dbConfig
}

Related

Play Action Composition - hardcoding parameterized parser

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 => ??? }

Play Framework 2.4 Scala missing parameter type for implicit request in Action after ActionRefiner

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.

Play framework + Scala: Inject dependency using Action Composition

I'm setting up a Play! app for our API. This API encapsulates different services. I want to inject these services inside an action but only the ones required for that particular endpoint. Something like:
object Application extends Controller {
def index = (UsersAction andThen OrdersAction) {
// boom UsersService and OrdersService is available here
for {
users <- usersService.list
orders <- ordersService.list
} yield "whatever"
}
}
I've been playing with this idea and using ActionTransformers I'm able to transform the incoming Request to a request that has a given service, but I don't see how I can make that generic enough so I can compose these actions in an arbitrary order without create ActionTransformers for all the possible combinations of WrapperRequests.
Maybe action composition is not the best way to achieve this. I'm all ears.
Thank you
UPDATE:
To clarify, the code above is pseudocode, the ideal scenario, in which usersService and ordersService are made available to that scope (implicits? I don't know). If that's not possible, then whatever adds the less amount of noise of top of that sample that would work. Thanks
The closest I could get to your question is this:
def index =
new UsersAction with OrdersAction {
def body =
for {
users <- userService.list
orders <- orderService.list
} yield Ok("whatever")
}
The implementation is quite straight forward
trait CustomAction extends Action[AnyContent] {
def body: Future[Result]
def apply(request: Request[AnyContent]): Future[Result] = body
val parser = BodyParsers.parse.anyContent
}
trait UsersAction extends CustomAction {
val userService: UserService = ???
}
trait OrdersAction extends CustomAction {
val orderService: OrderService = ???
}
These are the other parts I used to get it to compile:
trait User
trait Order
trait UserService {
def list: Future[Seq[User]]
}
trait OrderService {
def list: Future[Seq[Order]]
}
You can inject by guice, spring or what you want.
Example for guice.
Just change the object to class:
class Application #Inject(userAction:UsersAction,ordersAction:OrdersAction) extends Controller {
def index = (UsersAction andThen OrdersAction) {
// boom UsersService and OrdersService is available here
for {
users <- usersService.list
orders <- ordersService.list
} yield "whatever"
}
}
You have to override in Global:
object Global extends GlobalSettings{
private lazy val injector = Guice.createInjector(new CommonModule)
override def getControllerInstance[A](clazz: Class[A]) = {
injector.getInstance(clazz)
}
}
class CommonModule extends AbstractModule{
protected def configure() {
bind(classOf[UsersAction]).to(classOf[UsersActionImpl])
bind(classOf[OrdersAction]).to(classOf[OrdersActionImpl])
}
}
In route file add # to controllers:
GET /service #controllers.Application.index

How can I chain my custom actions when using ActionBuilder

Trying to better understand creating custom actions using action builder.
I have a custom action like:
class LogginInRequest[A](val currentUser: User, request: Request[A]) extends WrappedRequest[A](request)
object ActionWithContext extends ActionBuilder[LogginInRequest] {
def invokeBlock[A](request: Request[A], block: (LogginInRequest[A]) => Future[SimpleResult]) = {
val u = /// load User
block(new LogginInRequest(user, request)
}
}
Could I someone create another custom action like the above that inherits from the above one and builds upon it i.e. chain in the action and access the user object
e.g. Say I now create a new section on my website for admins, so I check the user object in this new action for:
user.isAdmin
If isAdmin is false, then I redirect using the Future.successful(Forbidden) call.
So I would create LoggedInAdmin based on LogginInRequest but simply checking for the property isAdmin (this is just an example but I want to know if I can do this).
Is it possible to pass an argument to my custom action that I created using action builder so I can do this?
def someAction(...):= MyCustomActionBuilder(userService) {
// ...
}
i.e. I am passing in a argument to it.
With Play 2.3, action composition using ActionBuilder got a whole lot better. The example below shows how you can define a standard ActionBuilder that creates a wrapped request, and then chain that builder together with an ActionTransformer to create a new wrapped request.
import scala.concurrent.Future
import org.scalatest.{MustMatchers, WordSpec}
import play.api.mvc._
import play.api.test.FakeRequest
import play.api.test.Helpers._
class ChainingTest extends WordSpec with MustMatchers {
class WrappedReq1[A](val str: String, request: Request[A]) extends WrappedRequest[A](request)
object ActionWithReq1 extends ActionBuilder[WrappedReq1] {
def invokeBlock[A](request: Request[A], block: (WrappedReq1[A]) => Future[Result]) = {
block(new WrappedReq1(request.headers("1"), request))
}
}
class WrappedReq2[A](val str1: String, val str2: String, request: Request[A]) extends WrappedRequest[A](request)
val ActionWithReq2 = ActionWithReq1 andThen new ActionTransformer[WrappedReq1, WrappedReq2] {
override protected def transform[A](request: WrappedReq1[A]): Future[WrappedReq2[A]] = {
Future.successful(new WrappedReq2[A](request.str, request.headers("2"), request))
}
}
"chained actions" should {
"work" in {
val request = FakeRequest().withHeaders("1" -> "one", "2" -> "two")
val response = ActionWithReq2 { r => Results.Ok(r.str1 + "-" + r.str2) }(request)
contentAsString(response) must equal("one-two")
}
}
}
There is an ActionTransformer trait for modifying your request wrapper, and an ActionFilter for intercepting certain requests and immediately returning a result.
ScalaActionsComposition describes the traits in more detail.

Play Framework 2.2 action composition returning a custom object

I am trying to create a custom play.api.mvc.Action which can be used to populate a CustomerAccount based on the request and pass the CustomerAccount into the controller.
Following the documentation for Play 2.2.x I've created an Action and ActionBuilder but I cannot seem to return the CustomerAccount from within the action.
My current code is:
case class AccountWrappedRequest[A](account: CustomerAccount, request: Request[A]) extends WrappedRequest[A](request)
case class Account[A](action: Action[A]) extends Action[A] {
lazy val parser = action.parser
def apply(request: Request[A]): Future[SimpleResult] = {
AccountService.getBySubdomain(request.host).map { account =>
// Do something to return the account like return a new AccountWrappedRequest?
action(AccountWrappedRequest(account, request))
} getOrElse {
Future.successful(NotFound(views.html.account_not_found()))
}
}
}
object AccountAction extends ActionBuilder[AccountWrappedRequest] {
def invokeBlock[A](request: Request[A], block: (AccountWrappedRequest[A]) => Future[SimpleResult]) = {
// Or here to pass it to the next request?
block(request) // block(AccountWrappedRequest(account??, request))
}
override def composeAction[A](action: Action[A]) = Account(action)
}
Note: This will not compile because the block(request) function is expecting a type of AccountWrappedRequest which I cannot populate. It will compile when using a straight Request
Additionally...
Ultimately I want to be able to combine this Account action with an Authentication action so that the CustomerAccount can be passed into the Authentication action and user authentication can be provided based on that customer's account. I would then want to pass the customer account and user into the controller.
For example:
Account(Authenticated(Action))) { request => request.account; request.user ... } or better yet as individual objects not requiring a custom request object.
I'm not sure if this is the best way to do it but I have managed to come up with a solution that seems to work pretty well.
The key was to match on the request converting it into an AccountWrappedRequest inside invokeBlock before passing it on to the next request. If another Action in the chain is expecting a value from an earlier action in the chain you can then similarly match the request converting it into the type you need to access the request parameters.
Updating the example from the original question:
case class AccountWrappedRequest[A](account: CustomerAccount, request: Request[A]) extends WrappedRequest[A](request)
case class Account[A](action: Action[A]) extends Action[A] {
lazy val parser = action.parser
def apply(request: Request[A]): Future[SimpleResult] = {
AccountService.getBySubdomain(request.host).map { account =>
action(AccountWrappedRequest(account, request))
} getOrElse {
Future.successful(NotFound(views.html.account_not_found()))
}
}
}
object AccountAction extends ActionBuilder[AccountWrappedRequest] {
def invokeBlock[A](request: Request[A], block: (AccountWrappedRequest[A]) => Future[SimpleResult]) = {
request match {
case req: AccountRequest[A] => block(req)
case _ => Future.successful(BadRequest("400 Invalid Request"))
}
}
override def composeAction[A](action: Action[A]) = Account(action)
}
Then inside the apply() method of another Action (the Authenticated action in my case) you can similarly do:
def apply(request: Request[A]): Future[SimpleResult] = {
request match {
case req: AccountRequest[A] => {
// Do something that requires req.account
val user = User(1, "New User")
action(AuthenticatedWrappedRequest(req.account, user, request))
}
case _ => Future.successful(BadRequest("400 Invalid Request"))
}
}
And you can chain the actions together in the ActionBuilder
override def composeAction[A](action: Action[A]) = Account(Authenticated(action))
If AuthenticatedWrappedRequest is then passed into the controller you would have access to request.account, request.user and all the usual request parameters.
As you can see there are a couple of cases where the response is unknown which would generate a BadRequest. In reality these should never get called as far as I can tell but they are in there just incase.
I would love to have some feedback on this solution as I'm still fairly new to Scala and I'm not sure if there might be a better way to do it with the same result but I hope this is of use to someone too.
I wrote a standalone small (ish) example that does what you're looking for:
https://github.com/aellerton/play-login-example
I gave up trying to use the Security classes that exist in the play framework proper. I'm sure they're good, but I just couldn't understand them.
Brief guide...
In the example code, a controller is declared as using the AuthenticatedRequests trait:
object UserSpecificController extends Controller with AuthenticatedRequests {
...
}
Forcing any page to require authentication (or redirect to get it) is done with the RequireAuthentication action:
def authenticatedIndex = RequireAuthentication { implicit request: AuthenticatedRequest[AnyContent] =>
Ok("This content will be accessible only after logging in)
}
Signing out is done by using the AbandonAuthentication action:
def signOut = AbandonAuthentication { implicit request =>
Ok("You're logged out.").withNewSession
}
Note that for this to work, you must override methods from the AuthenticatedRequests trait, e.g.:
override def authenticationRequired[A](request: Request[A]): Future[SimpleResult] = {
Future.successful(
Redirect(routes.LoginController.showLoginForm).withSession("goto" -> request.path)
)
}
There's more to it; best to see the code.
HTH
Andrew