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
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.
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.
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).