I am migrating to Play 2.6 and have the following API wrapper functions that used to work:
trait API {
self: Controller =>
def api(businessLogic: Request[AnyContent] => Any): Action[AnyContent] = apiWithBody(parse.anyContent)(businessLogic)
def apiWithBody[A](bodyParser: BodyParser[A])(businessLogic: Request[A] => Any): Action[A] = Action(bodyParser) {
implicit request =>
{
val apiResult = businessLogic(request)
val start = new java.util.Date().getTime
val actionDuration = (new java.util.Date().getTime - start)
val response = resultFrom(apiResult, request, actionDuration) // Returns a Result
response
}
}
}
Called by Controller functions like:
object Accounts extends Controller with API {
def all = superUser {
implicit principal =>
api {
request =>
models.Account.all
}
}
}
Where superUser is the principal (user) type "admin".
And get the following compiler error:
[error] type mismatch;
[error] found : play.api.mvc.Action[play.api.mvc.AnyContent]
[error] required: play.api.mvc.Request[?] => play.api.mvc.Result
[error] api {
[error] ^
I'm building with sbt 1.1.5 and Scala 2.11.8.
I am guessing the [?] means the compiler doesn't know what type is required but I don't understand what is wrong. I have searched for this issue but not found the specific answer for this problem.
In addition I'm getting an error:
[error] could not find implicit value for parameter parser: play.api.mvc.BodyParser[Any]
[error] def all = superUser {
[error] ^
that I posted as a separate issue (see could not find implicit value for parameter parser: play.api.mvc.BodyParser[Any]) but might be relevant here?
def superUser[A](f: => Principal => Request[A] => Result)(implicit parser: BodyParser[A]): SecureAction[A] = {
_superUser {
user =>
implicit val principal = data.Principal(user)
Action(parser)(request => f(principal)(request))
}
}
private def _superUser[A](action: String => Action[A]) = {
play.api.mvc.Security.Authenticated(getSuperUser, onUnauthorized)(action)
}
Any help would be appreciated.
Sorry I'm little bit confused here about the architecture as:
Where is the API call? Is it within the model? Are you calling outside the current Play app? Why don't you use the Future API for it? Because then you can recover it. Which then help you with logging and error handling.
The all method get the request, and then it does not return an HTTP response. Why don't you pass on what you need from the request (e.g., the Cookie).
I think the answer to your question is to use your action composition with action. Something like:
def myMethod(queryParam: String) = myDefinedAction compose Action { ??? }
//In case you want to use the Future API, then you need to be async
def myMethod(queryParam: String) = (myDefinedAction compose Action).async {
implicit request =>
apiCall.map{
case _ => Ok("good request")
}.recover{case _ => BadRequest}
}
Related
I've got some code that returns an IO but I need a Effect in http4s.
import cats.effect.{Effect, IO}
class Service[F[_]: Effect] extends Http4sDsl[F] {
val service: HttpService[F] = {
HttpService[F] {
case GET -> Root =>
val data: IO[String] = getData()
data.map(d => Ok(d))
}
}
}
gives
[error] found : cats.effect.IO[F[org.http4s.Response[F]]]
[error] required: F[org.http4s.Response[F]]
[error] data.map(d => Ok(d))
[error] ^
One way we can get around using a concrete IO[A] is using LiftIO[F]:
class Service[F[_]: Effect] extends Http4sDsl[F] {
val service: HttpService[F] = {
HttpService[F] {
case GET -> Root =>
getData().liftIO[F].flatMap(Ok(_))
}
}
}
LiftIO lifts will lift: IO[A] => F[A], but this yields us F[F[Response[F]. In order to get things to compile, we'll flatten on F since it has a Monad (or FlatMap) instance in cats due to our Effect context bounds requirement.
If we want more detail, this is the -Xprint:typer result:
cats.implicits.catsSyntaxFlatten[F, org.http4s.Response[F]](
cats.effect.LiftIO.apply[F](Service.this.evidence$1)
.liftIO[F[org.http4s.Response[F]]](
data.map[F[org.http4s.Response[F]]](
((d: String) => Service.this.http4sOkSyntax(Service.this.Ok)
.apply[String](d)(Service.this.evidence$1,
Service.this.stringEncoder[F](
Service.this.evidence$1, Service.this.stringEncoder$default$2[F]))))))(Service.this.evidence$1).flatten(Service.this.evidence$1)
And at the end of the world when you want to give a concrete effect, for example Service[IO], we get:
val serv: Service[cats.effect.IO] =
new Service[cats.effect.IO]()(effect.this.IO.ioConcurrentEffect)
Where ioConcurrentEffect is the Effect[IO] instance.
It seems that all the good stuff is defined at org.http4s.syntax.AllSyntax trait.
I'm having a problem to return the correct type in a scala play controller method can someone give me a hint here? I'm using for comprehantion to deal with two service methods that returns a Future, and I would like to handle elegantly the result and the errors.
What is the best practice to do this?
def registerUser = Action { implicit request =>
Logger.info("Start play actoin")
RegisterForm.form.bindFromRequest.fold(
formWithErrors => {
BadRequest(views.html.register(formWithErrors))
},
formData => {
val registerResult = for {
reCaptchaOk <- registerUserService.checkRecaptcha(formData.gRecaptchaResponse)
userId <- registerUserService.registerUser(formData) if reCaptchaOk
} yield userId
registerResult.map(
result => Redirect(routes.DashboardController.dashboard).withSession("USER_ID" -> result.toString))
.recover{
e => handleRegisterError(e)
}
})
}
def handleRegisterError(cause: Throwable)(implicit request: Request[_]) : Result = {
val form = RegisterForm.form.bindFromRequest
cause match {
case dae: DataAccessException =>
val globalError = dae.getCause.asInstanceOf[PSQLException].getSQLState match {
case "23505" => GlobalMessages(Seq(GlobalMessage(Messages("errors.db.userAlreadyExists") ,ERROR)))
case _ => GlobalMessages(Seq(GlobalMessage(Messages("errors.system.error"),ERROR)))
}
BadRequest(views.html.register(form,globalError))
case _ =>
BadRequest(views.html.register(form))
}
the error:
[error] (compile:compileIncremental) Compilation failed
[info] Compiling 1 Scala source to C:\repos\scala\SocerGladiatorWeb\target\scala-2.11\classes...
[error] C:\repos\scala\SocerGladiatorWeb\app\controllers\RegisterController.scala:56: type mismatch;
[error] found : Throwable => play.api.mvc.Result
[error] required: PartialFunction[Throwable,?]
[error] e => handleRegisterError(e)
[error] ^
[error] one error found
[error] (compile:compileIncremental) Compilation failed
Short answer
You need a partial function to recover future failures:
def handleRegisterError(implicit request: Request[_]): PartialFunction[Throwable, Result] = {
case dae: DataAccessException =>
val form = RegisterForm.form.bindFromRequest
val globalError = dae.getCause.asInstanceOf[PSQLException].getSQLState match {
case "23505" => GlobalMessages(Seq(GlobalMessage(Messages("errors.db.userAlreadyExists"), ERROR)))
case _ => GlobalMessages(Seq(GlobalMessage(Messages("errors.system.error"), ERROR)))
}
BadRequest(views.html.register(form, globalError))
case _ =>
val form = RegisterForm.form.bindFromRequest
BadRequest(views.html.register(form))
}
then change the controller code to
registerResult
.map { result =>
Redirect(routes.DashboardController.dashboard).withSession("USER_ID" -> result.toString)
}
.recover {
handleRegisterError
}
Also note that you need an async action, i.e.
def registerUser = Action.async { implicit request =>
...
}
because you are not returning a Result but a Future[Result]. You can find more about actions in Play docs.
Details
If you look at the docs of the recover method of Future (see here) you'll see that it needs a pf: PartialFunction[Throwable, U].
Partial functions are just like normal functions but they might reject some values (for instance here, the recover method does not accept all exceptions, but only those specified in the body).
Defining a partial function needs a special syntax. It's very much like pattern matching but with no match expression.
Future(someAsyncWork).recover {
case my: MyException => ....
case _ => ....
}
Here we are using a partial recover function inline, so the type will be inferred automatically but if you want to define the recover as a separate function you need to explicitly state its type.
Advanced
The partial function syntax (pattern matching with no match keyword) is very concise and handy in most situations, but sometimes you need more than that.
For instance, note that using this syntax, we had to duplicate parts of the code (val form = RegisterForm.form.bindFromRequest) in the recover function.
Although in your case there might be better solutions but you can always convert a normal function to a partial function. First you need to define a function of the type Throwable => Option[Result] and then you can use Function#unlift to convert it to the desired partial function.
Also you can directly inherit from a PartialFunction and implement its two methods (apply and isDefinedAt).
UserGetResponse and GeneralResponse are sublclasses of BaseResponse, which is as follows:
abstract class BaseResponse()
And the function I use to retrieve users is as follows:
def userGet(userId: Int)(implicit ec: ExecutionContext): Future[BaseResponse] = Future {
val response = users.get(userId) map { user =>
val userRes = new UserResponse(user.id, user.firstname, user.lastname, user.organisationid, user.email, user.password, user.usertype)
new UserGetResponse(1, "Successful retrieved the user.", userRes)
} getOrElse {
GeneralResponse(0, s"Error retrieving user. User does not exist.")
}
}
where users is another class with methods get, insert etc. defined. I am getting the following compilation error:
type mismatch;
[error] found : Unit
[error] required: package.name.BaseResponse
[error] }
What am I doing wrong?
The closure inside the Future is not returning anything, so the compiler infers that it's return type is Unit, and complains because it should be BaseResponse.
Removing val response = from the beginning of the function (or adding response before the end) should fix it.
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've implemented authorization for my Play Framework (version 2.3.5) application as per the official security documentation:
trait Secured {
def username(request: RequestHeader) = request.session.get(Security.username)
def onUnauthorized(request: RequestHeader) = Results.Redirect(routes.Login.index)
def withAuth(f: => String => Request[AnyContent] => Result) = {
Security.Authenticated(username, onUnauthorized) { user =>
Action(request => f(user)(request))
}
}
}
However, when I decorate my controller actions with the withAuth function like so:
def export = withAuth { username => implicit request =>
val csv = csv()
Ok(csv)
.as("text/csv")
.withHeaders(
CONTENT_DISPOSITION -> ("attachment; filename=export.csv"),
CONTENT_LENGTH -> csv.length.toString
)
}
I get compilation errors in my controller specs2 unit tests:
"export data as CSV" in {
val controller = new controllers.DataController
val result = controller.export().apply(FakeRequest())
headers(result) must havePair("Content-Disposition" -> ("attachment; filename=export.csv"))
}
The call to the headers test helper function fails with the following compiler error message:
type mismatch; found : play.api.libs.iteratee.Iteratee[Array[Byte],play.api.mvc.Result] required: scala.concurrent.Future[play.api.mvc.Result]
Am I doing something wrong? I've tried some of the remedies suggested by other Stackoverflow users but they all seem to rely on setting the body type, but my actions do not have a body type.
It looks as though the call to withAuth obscures some of the types of the Action being wrapped so maybe there is a more type-safe way of expressing the Secured trait?
Try changing this:
val result = controller.export().apply(FakeRequest())
into this:
val result = controller.export().apply(FakeRequest()).run
And then it should work.