I am trying to create my own ActionRefiner to accommodate authentication, but for some reason the compiler won't let me use implicit variables in the refine[A] function... I have the following code:
trait Auth {
object AuthenticatedAction extends ActionBuilder[AuthRequest] with ActionRefiner[Request, AuthRequest] {
def refine[A](request: Request[A])(implicit userCollection: UserCollection, ex: ExecutionContext): Future[Either[Result, AuthRequest[A]]] = {
request.session.get("username") match {
case Some(username) => userCollection.findByEmail(username).map { userOpt =>
userOpt.map(new AuthRequest(_, request)).toRight(Results.Redirect(routes.LoginController.login()))
}
case None => Future.successful(Left(Results.Redirect(routes.LoginController.login())))
}
}
}
}
class AuthRequest[A](val user: User, request: Request[A]) extends WrappedRequest[A](request)
The Scala compiler tells me that the method refine[A](request: R[A]): Future[Either[Result, P[A]]] is not defined. When I remove the implicit variables it registers, but that leaves me with no UserCollection...
So, How do I correctly use the ActionRefiner?
Thanks to this topic I found a way to make it work. Instead of using an implicit, I can define the UserCollection in the trait and pass it from my controller, like so:
trait Auth {
def userCollection: UserCollection
object AuthenticatedAction extends ActionBuilder[AuthRequest] with ActionRefiner[Request, AuthRequest] {
def refine[A](request: Request[A]): Future[Either[Result, AuthRequest[A]]] = {
...
}
}
}
class HomeController #Inject()(val userCollection: UserCollection)(implicit executionContext: ExecutionContext) extends Controller with Auth {
def index = AuthenticatedAction { implicit request =>
Ok(s"Hello ${request.user.name}")
}
}
I just had gotten so used to using implicits that I completely forgot about this.
Related
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: Request[AnyContent] =>
Ok("Ok")
}
And I've got
"play.api.mvc.ActionRefiner[play.api.mvc.Request,controllers.actionbuilders.LangRequest]
does not take parameters"
error at compilation time. How can I use this ActionRefiner? In PlayFramework documentation https://www.playframework.com/documentation/2.4.x/ScalaActionsComposition they show ActionRefiner usages with Filter, Transformer like this:
(UserAction andThen ItemAction(itemId) andThen PermissionCheckAction)
and this does work. But how to use one ActionRefiner?
I'm sorry; this is pretty old, but I am facing a similar problem and wanted to post the solution so others could find it. I suspect it is because the first item in the chain of actions should be an ActionBuilder, in addition to whatever else. ActionTransformer is not a subtype of ActionBuilder, though it can be an argument to ActionBuilder#andThen.
It is correct, the ActionBuilder has to be the first element in the chain based on https://www.playframework.com/documentation/2.4.x/ScalaActionsComposition#Putting-it-all-together
On the other hand, there are scenarios where we need to pass arguments to the ActionRefiner due to interactions with the database (e.g. retrieve products using id). At this point, we need to #Inject dependencies like services to get access to DB and the function is a partial solution. So, we can wrap the function in class to inject the dependencies and also inherent from ActionBuilder due to we can have only one element in the chain and we need to start with an ActionBuilder.
class UserActionBuilder #Inject()(
val defaultBodyParsers: BodyParsers.Default,
userService: UserService
)(implicit val ec: ExecutionContext) {
def refine(userEmail: String): ActionRefiner[Request, UserRequest] with ActionBuilder[UserRequest, AnyContent] =
new ActionRefiner[Request, UserRequest] with ActionBuilder[UserRequest, AnyContent] {
def executionContext: ExecutionContext = ec
override def refine[A](request: Request[A]): Future[Either[Result, UserRequest[A]]] = {
userService.retrieveUserByEmail(userEmail)
.map {
case Some(user) if user.isActive => Right(UserRequest(user, request))
case _ => Left(Results.Forbidden("User is inactive"))
}
.recover { case ex: Throwable =>
Left(Results.InternalServerError(ex.getMessage))
}
}
override def parser: BodyParser[AnyContent] = defaultBodyParsers
}
}
case class UserRequest[A](user: User, request: Request[A])
extends WrappedRequest[A](request)
And then
class UserController #Inject()(
arguments...,
userAction: UserActionBuilder
)(implicit executionContext: ExecutionContext) {
def updateUserPurchase(emailUser: String): Action[UserPurchaseRequest] = {
userAction.refine(emailUser)).async(apiParser.parserOf[UserPurchaseRequest]) { implicit request =>
...
doUpdate(request.body, request.user)
...
}
}
}
since Play Framework 2.4 there is the possibility to use dependency injection (with Guice).
Before I used objects (for example AuthenticationService) in my ActionBuilders:
object AuthenticatedAction extends ActionBuilder[AuthenticatedRequest] {
override def invokeBlock[A](request: Request[A], block: (AuthenticatedRequest[A]) => Future[Result]): Future[Result] = {
...
AuthenticationService.authenticate (...)
...
}
}
Now AuthenticationService is not an object anymore, but a class. How can I still use the AuthenticationService in my ActionBuilder?
Define your action builders inside a trait with the authentication service as an abstract field. Then mix them into your controllers, into which you inject the service. For example:
trait MyActionBuilders {
// the abstract dependency
def authService: AuthenticationService
def AuthenticatedAction = new ActionBuilder[AuthenticatedRequest] {
override def invokeBlock[A](request: Request[A], block(AuthenticatedRequest[A]) => Future[Result]): Future[Result] = {
authService.authenticate(...)
...
}
}
}
and the controller:
#Singleton
class MyController #Inject()(authService: AuthenticationService) extends Controller with MyActionBuilders {
def myAction(...) = AuthenticatedAction { implicit request =>
Ok("authenticated!")
}
}
I didn't like the way one was required to inherit in the above example. But apparently it's possible to simply wrap a object inside class:
class Authentication #Inject()(authService: AuthenticationService) {
object AuthenticatedAction extends ActionBuilder[Request] {
def invokeBlock[A](request: Request[A], block: (Request[A]) => Future[Result]) = {
// Do your thing wit the authService...
block(request)
}
}
}
class YourController #Inject() (val auth: Authentication) extends Controller (
def loggedInUser = auth.AuthenticatedAction(parse.json) { implicit request =>
// ...
}
}
I like accepted answer but for some reason the compiler would not recognize the authService reference. I got around this pretty easily by just sending the service in the method signature, a la...
class Authentication #Inject()(authenticationService: AuthenticationService) extends Controller with ActionBuilders {
def testAuth = AuthenticatedAction(authenticationService).async { implicit request =>
Future.successful(Ok("Authenticated!"))
}
}
My current custom action is this
class UserRequest[A](val user: Option[models.UserProfile],
request: Request[A]) extends WrappedRequest[A](request)
object UserAction extends ActionBuilder[UserRequest] with ActionTransformer[Request, UserRequest] {
def transform[A](request: Request[A]) = Future.successful {
val user = ... get user from session
new UserRequest(user, request)
}
}
Additionally, I want to pass a parameter to UserAction (e.g. role to validate). So in controller I can use it in this way:
def admin = UserAction("admin") { Ok("granted") }
I don't know why you want to do such thing, but you can probably achieve it this way :
class UserRequest[A](val user: Option[models.UserProfile],
request: Request[A]) extends WrappedRequest[A](request)
object UserActionInner extends ActionBuilder[UserRequest] with ActionTransformer[Request, UserRequest] {
def transform[A](request: Request[A]) = Future.successful {
val user = ... get user from session
new UserRequest(user, request)
}
}
object UserAction {
def apply[ A ]( str: String )( block: Request[ A ] => Future[ Result ] ) {
// do something with your str
// Now let UserActionInner do the job
UserActionInner( block )
}
}
// Now you can use it like this.
def admin = UserAction("admin") { Ok("granted") }
Using a case class with a role parameter, instead of an object, should give you the desired effect:
case class UserAction(role: String) extends ActionBuilder[UserRequest] with ActionTransformer[Request, UserRequest] {
override def transform[A](request: Request[A]) = ...
}
How can I use guice when creating a custom Action using ActionBuilder?
It seems to complain with "not found: value MyAction" if I change the ActionBuilder from a object to a class.
I have this but it doesn't work:
case class MyModel(name: String, request: Request[A]) extends WrappedRequest[A](request)
class MyAction #Inject()(userService: UserService) extends ActionBuilder[MyModel] {
def invokeBlock[A](request: Request[A], block: (MyModel[A]) => Future[SimpleResult]) = {
val abc = loadAbc(request)
block(new MyModel(abc, request))
}
def loadAbc(rh: RequestHeader): String {
"abc" // just for testing
}
}
So changing it from an object to a class causes it to fail, and I tried keeping it as an object but then it doesn't compile correctly.
How can I get this to work?
I have it working just fine in my controllers.
With a few minor corrections, what you've got seems to work already. All you've got to do is inject the guice-instantiated instance of MyAction into your controller, and then you can use the instance (rather than trying to use the MyAction class name).
This works with Play 2.3:
import scala.concurrent.Future
import javax.inject.{Inject, Singleton}
import play.api.mvc._
class UserService() {
def loadAbc(rh: RequestHeader) = "abc"
}
class MyModel[A](val name: String, request: Request[A]) extends WrappedRequest[A](request)
class MyAction #Inject()(userService: UserService) extends ActionBuilder[MyModel] {
def invokeBlock[A](request: Request[A], block: (MyModel[A]) => Future[Result]) = {
val abc = userService.loadAbc(request)
block(new MyModel(abc, request))
}
}
#Singleton
class Application #Inject() (myAction: MyAction) extends Controller {
def index = myAction { request =>
Ok(request.name)
}
}
You can't use object because that violates the design of Guice. object is a singleton instantiated by Scala itself and cannot have instance variables, whereas Guice needs to be able to instantiate classes on the fly so that it can inject dependencies.
I think your code should work if you use it like this:
class MyClass #Inject()(myAction: MyAction) {
val echo = myAction { request =>
Ok("Got request [" + request + "]")
}
}
I'd like some help sorting out this scenario. I have an Akka actor where I want to inject a dependency, in this case RemoteFetcher, which I would also like mock in my tests. Like so:
main/src/scala/mypackage/Services.scala
package mypackage
import RemoteFetcherFileSystem._
trait RemoteFetcher {
def fetch( path:String ): Future[Stream[String]]
}
class MyRemoteResourceActor extends Actor with ActorLogging {
def fetchRemote( path:String ) = implicitly[RemoteFetcher].fetch( path )
def receive = {
case FetchRemoteResource( path ) => fetchRemote( path ).map( _.foreach( sender ! _ ) )
}
}
For this to work I have an implicit object that I import into the file above. Would look something like this:
implicit object RemoteFetcherFileSystem extends RemoteFetcher {
def fetchRemote( path:String ) = Future[Stream[String]] { ... reading from file system ... }
}
Now in my tests I have TestActor from the akka-testkit. Here I want to instead import my mock dependency:
implicit object RemoteFetcherMock extends RemoteFetcher {
def fetchRemote( path:String ) = Future[Stream[String]] { ... mock implementation ... }
}
My problem is that to compile Services.scala I need to import the implicit object. But how do I go about to shadow/override this in my test-files. The reason I'm not using implicit arguments is that I want to avoid having to modify all my actors constructor arguments.
I when looking around and reading up on the type class dependency injection pattern and I get it to work according to the tutorials, but I don't get it to work when I want to test and override like in my example.
I'm not sure how to do it with implicits, but typically one could inject instead like so:
trait RemoteFetcherComponent {
def remoteFetcher: RemoteFetcher
trait RemoteFetcher {
def fetch(path: String): Future[Stream[String]]
}
}
trait RemoteFetcherFileSystemComponent extends RemoteFetcherComponent {
val remoteFetcher = RemoteFetcherFileSystem
object RemoteFetcherFileSystem extends RemoteFetcher {
def fetch(path: String): Future[Stream[String]] = ???
}
}
class MyRemoteResourceActor extends Actor with ActorLogging with RemoteFetcherFileSystemComponent {
def fetchRemote(path: String) = remoteFetcher.fetch(path)
def receive = {
case FetchRemoteResource(path) => fetchRemote(path).map( _.foreach(sender ! _))
}
}
val myRemoteResourceActor = new MyRemoteResourceActor()
And then a test value would be defined like so:
trait RemoteFetcherMockComponent extends RemoteFetcherComponent {
def remoteFetcher = RemoteFetcherMock
object RemoteFetcherMock extends RemoteFetcher {
def fetch(path: String): Future[Stream[String]] = ???
}
}
val myMockedResourceActor = new MyRemoteResourceActor with RemoteFetcherMockComponent {
override val remoteFetcher = super[RemoteFetcherMockComponent].remoteFetcher
}
The reason you are having an issue with implicits is because the way you're using it is no different from simply using def fetchRemote(path: String) = RemoteFetcherFileSystem.fetch(path). With the import, you've defined the implementation, rather than allowed it to be injected later.
You could also change the implicitly to an implicit parameter:
trait RemoteFetcher {
def fetch(path: String): Future[Stream[String]]
}
object RemoteFetcher {
implicit val fetcher = RemoteFetcherFileSystem
}
class MyRemoteResourceActor extends Actor with ActorLogging {
def fetchRemote(path: String)(implicit remoteFetcher: RemoteFetcher) = remoteFetcher.fetch(path)
def receive = {
case FetchRemoteResource(path) => fetchRemote(path).map( _.foreach(sender ! _))
}
}
Then you could override the implicit that is resolved in the companion object of RemoteFetcher by simply importing RemoteFetcherMock.
See this post for more information about implicit parameter resolution precedence rules.