My controller action code looks like this:
def addIngredient() = Action { implicit request =>
val boundForm = ingredientForm.bindFromRequest
boundForm.fold(
formWithErrors => BadRequest(views.html.Admin.index(formWithErrors)),
value => {
Async {
val created = Service.addIngredient(value.name, value.description)
created map { ingredient =>
Redirect(routes.Admin.index()).flashing("success" -> "Ingredient '%s' added".format(ingredient.name))
}
// TODO on exception do the following
// BadRequest(views.html.Admin.index(boundForm.copy(errors = Seq(FormError("", ex.getMessage())))))
}
})
}
My Service.addIngredient(...) returns a Promise[Ingredient] but can also throw a custom ValidationException. When this exception is thrown I would like to return the commented code.
Currently the page renders as 500 and in the logs I have:
play - Waiting for a promise, but got an error: Ingredient with name 'test' already exists. services.ValidationException: Ingredient with name 'test' already exists.
Two questions:
I'd say a pure functional way would have been to use a type that can hold valid and error states.
For that, you can use Validation form scalaz
But if don't need more than that from scalaz (you will ^^), you could use a very simple stuff using a Promise[Either[String, Ingredient]]
as the result and its fold
method in the Async block. That is, map
to convert the value when the promise is redeemed and fold
on what is redeemed.
The good point => no exception => every thing is typed check :-)
EDIT
It might need a bit of information more, here are the two options: try catch, thanks to @kheraud) and Either. Didn't put the Validation
, ask me if needed.
object Application extends Controller {
def index = Action {
Ok(views.html.index("Your new application is ready."))
}
//Using Try Catch
// What was missing was the wrapping of the BadRequest into a Promise since the Async
// is requiring such result. That's done using Promise.pure
def test1 = Async {
try {
val created = Promise.pure(new {val name:String = "myname"})
created map { stuff =>
Redirect(routes.Application.index()).flashing("success" -> "Stuff '%s' show".format(stuff.name))
}
} catch {
case _ => {
Promise.pure(Redirect(routes.Application.index()).flashing("error" -> "an error occurred man"))
}
}
}
//Using Either (kind of Validation)
// on the Left side => a success value with a name
val success = Left(new {val name:String = "myname"})
// on the Right side the exception message (could be an Exception instance however => to keep the stack)
val fail = Right("Bang bang!")
// How to use that
// I simulate your service using Promise.pure that wraps the Either result
// so the return type of service should be Promise[Either[{val name:String}, String]] in this exemple
// Then while mapping (that is create a Promise around the convert content), we folds to create the right Result (Redirect in this case).
// the good point => completely compiled time checked ! and no wrapping with pure for the error case.
def test2(trySuccess:Boolean) = Async {
val created = Promise.pure(if (trySuccess) success else fail)
created map { stuff /* the either */ =>
stuff.fold(
/*success case*/s => Redirect(routes.Application.index()).flashing("success" -> "Stuff '%s' show".format(s.name)),
/*the error case*/f => Redirect(routes.Application.index()).flashing("error" -> f)
)
}
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With