how to map the failed projection of a Future - scala

In the following code, the function is expected to return instance of Future[Result] but I am unable to do so. The code queries database, the database returns Future[User]. I think I am able to map the success part of the future but not the failure part correctly. Please see the comment at the end of the function.
def addUser = silhouette.UserAwareAction.async{ implicit request => {
val body: AnyContent = request.body
val jsonBody: Option[JsValue] = body.asJson
//check for message body. It should be json
jsonBody match {
case Some(json) => { //got json in message body.
val readableString: String = Json.prettyPrint(json)
println(s"received Json ${readableString}")
val userProfile: Option[UserProfile] = json.asOpt[UserProfile] //check if json conforms with UserProfile structure
userProfile match {
case Some(profile) => { //json conforms to UserProfile.
println(s"received profile ${profile}")
val loginInfo = LoginInfo(CredentialsProvider.ID, profile.externalProfileDetails.email)
println(s"checking if the user with the following details exists ${loginInfo}")
val userFuture: Future[Option[User]] = userRepo.find(loginInfo) // userFuture will eventually contain the result of database query i.e Some(user) or None
userFuture.map { user:Option[User] => { //Future successful
case Some(user) => { //duplicate user
println("duplicate user" + user)
Future { Ok(Json.toJson(JsonResultError("duplicate user"))) }
}
case None => { //unique user
Future { Ok(Json.toJson(JsonResultSuccess("Not duplicate user"))) }
}
}
}
/***This is the part I am unable to code. I suppose the addUser method expect that the last statement (its return value) is Future{....} but it seems that they way I have coded it, it is not the case. If I remove this code and just type Future { Ok(Json.toJson(JsonResultSuccess("Internal Server Error"))) } then code compiles. But that logic is not correct because then this message will be sent all the time, not when the future fails!***/
val userFailedFuture:Future[Throwable] = userFuture.failed
userFailedFuture.map {x=> Future { Ok(Json.toJson(JsonResultSuccess("Internal Server Error"))) }}
}
//Json doesn't conform to UserProfile
case None => Future { Ok(Json.toJson(JsonResultError("Invalid profile"))) } /*TODOM - Standardise error messages. Use as constants*/
}
}
//message body is not json. Error.
case None => Future { Ok(Json.toJson(JsonResultError("Invalid Body Type. Need Json"))) }/*TODOM - Standardise error messages. Use as constants*/
}
}
}

You don't have to wrap your result in future, as you are already evaluating from future value simple this will do:
futureValue.map{value => //RESULT
}
And handling errors in Future, it is recommended to use recover with map like:
futureValue.map{value => //RESULT
}.recover{case ex: Exception => //RESULT
}
You don't need to wrap your result in Future, if it is already inside a map or recover block. As the final result outside the map and recover of Future will be Future[Result]. So if you wrap another Future it will become Future[Future[Result]].

So in case Some(user) you can use Future.successful instead of only Future (the same in None case). Moreover, this part of a code that you try to catch you should put in None case. After that everything should be okay :) And you don't need to put other Futures or Ok(Json.toJson). It's enough to do it in userFuture.map

Agree with answer by #geek94 .
One amendments:
It's not clear what library you are using but in most cases there is no need to call future.recover explicitly. Every decent http library (e.g. akka-http) treats failed future as InternalServerError.

Related

why scala method didn't work in a Line-by-Line sequence?

I'm going to achieve a simple method about login authentication. validate is a POST route
def validate: play.api.mvc.Action[play.api.mvc.AnyContent] = Action {
implicit request ⇒
var isSuccess: Some[Boolean] = Some(false)
val data = login_Form.bindFromRequest().get
models.user.validate(data.name, data.psd).map(ccc =>
if (ccc) {
Logger.info("1.1")
isSuccess = Some(true)
} else {
Logger.info("1.2")
isSuccess = Some(false)
}
)
if (isSuccess.get) {
Logger.info("2.1")
Ok(views.html.index.render())
} else {
Logger.info("2.2")
Ok(views.html.login("Login failed"));
}
}
models.user.validate return a Future(Boolean), but it didn't make my idea come true. The logs just print 2.2 and then can not login. I don't know what's my problem. I do this in Play2.4, Scala 2.11.7, sbt 0.13.8, and run the application in typesafe Activator.
The Future will be resolved in the future.
As such, the code you run on it (in map) does not execute immediately.
You have to either
move the Ok call into the map as well (and have your method return a Future[Result] instead of the Result directly), or
Await.result your booolean (to block the call, waiting for the validate call to complete)
Other observations:
why is isSuccess optional (not not just a simple Boolean)?
don't use var unless you really have to. Prefer to assign the resolved value to a val once you know it instead of initializing a var with a temporary placeholder.
avoid calling get, better handle the case where data is missing as well
Untested rewrite:
def validate = Action.async { implicit request ⇒
login_Form.bindFromRequest() match {
case Some(data) => models.user.validate(data.name, data.psd).map(okay =>
if (okay) {
Logger.info("1.1")
Ok(views.html.index.render())
} else {
Logger.info("1.2")
Ok(views.html.login("Login failed"));
}
)
case None =>
Future.successful(Ok(view.html.login("Invalid form posted"))
}
}

Unable to find Writable inside Action.async in play framework

Appears I am missing something but here is what I have got (posting only relevant piece). where MyService.save returns Future[Option[MyCompany] ].
def myPost = Action.async(parse.json) { request =>
val mn = Json.fromJson[MyEntity](request.body)
mn.map{
case m : MyEntity => MyService.save(m).map{f=>
f.map(mm=> Ok(mm ) )
}
}.recoverTotal {
e => Future { BadRequest("Detected error:" + JsError.toFlatJson(e)) }
}
}
Although I have defined
implicit val companyWriter: Writes[MyCompany] = (...)
And this implicit is in the scope, it shows compile error
Cannot write an instance of MyCompany to HTTP response. Try to define
a Writeable[MyCompany]
FYI: This writer is used elsewhere where I do Json.toJson(myCompany) and over there it finds and works fine.
Anything in particular to async Ok that it's missing?
EDIT
It appears that Ok() method cannot figure out the MyCompany needs to be transformed to json. following seems to have worked.
Ok(Json.toJson(mm) )
Is this because arguments to Ok can vary? Separately there are too many "map" in the above code. Any recommendation for improvement and making it more concise ?
Your compiler error is about a Writeable, not a Writes. Writeables are used to convert whatever you have to something that can be written to an HTTP response, Writes are used to marshall objects to JSON. The names can be a little confusing.
As for style...
def myPost = Action.async(parse.json) { request =>
request.body.validate[MyEntity] map { myEntity =>
MyService.save(myEntity).map { maybeCompany =>
maybeCompany match {
case Some(company) => Ok(Json.toJson(company))
case None => NoContent // or whatever's appropriate
}
}
} recoverTotal { t =>
Future { BadRequest("Detected error: " + JsError.toFlatJson(e)) }
}
}

Spray routing 404 response

I have a service which returns an Option[ProductDoc] in a Future (as an akka ask)
How do I respond in spray routing so that a valid product repsonds with a product but an unknown but well formed one returns a 404?
I want the code to fill in the gap here :
get {
path("products" / PathElement) { productID:String =>
val productFuture = (productService ? ProductService.Get(productID)).mapTo[Option[ProductDoc]]
// THE CODE THAT GOES HERE SO THAT
// IF PRODUCT.ISDEFINED RETURN PRODUCT ELSE REJECT
}
}
The only way I can get to work is with this abomination :
get {
path(PathElement) { productID:String =>
val productFuture = (productService ? ProductService.Get(productID)).mapTo[Option[ProductDoc]]
provide(productFuture).unwrapFuture.hflatMap {
case x => provide(x)
} { hResponse:shapeless.::[Option[ProductDoc], HNil] =>
hResponse.head match {
case Some(product) => complete(product)
case None => reject
}
}
}
}
This can't be the correct way to achieve this, surely? This seems like a pretty simple pattern that must have been solved by someone already!
Spray already has support for your use case: An option value None is marshalled to an EmptyEntity by default. This is probably what you were seeing before you made any changes: a 200 with an empty document. There's a directive which converts an empty document into a 404, rejectEmptyResponse, which you wrap around parts of your route where you want this behavior.
Your route would then look just like this:
rejectEmptyResponse {
path("products" / PathElement) { productID:String =>
val productFuture = // same as before
complete(productFuture)
}
}
Of course, you can put the rejectEmptyResponse inside the path depending on whether you want to wrap more route parts with it.
More info:
https://github.com/spray/spray/blob/master/spray-routing/src/main/scala/spray/routing/directives/MiscDirectives.scala#L117
http://spray.io/documentation/spray-routing/key-concepts/rejections/#empty-rejections.
I has the same problem a few days ago and came to this solution:
I added this method to my actor
def failIfEmpty[T](item: Future[Option[T]], id: String) = {
(item map {
case Some(t) => t
case None => throw NotFoundException(Message(s"id '$id' could not be found",`ERROR`))
}) pipeTo sender
}
Of course you can choose an Exception you like, NotFoundException is one of my own...
Call this on your result to answer to the ask from your actor (this is an example for using ReactiveMongo, replace collection.find(query).headOption with your Future[Option]):
failIfEmpty(collection.find(query).headOption, id)
Then add an ExceptionHandler to your service (where your route is defined) like the following:
implicit val klaraExceptionHandler = ExceptionHandler.fromPF {
case InternalServerErrorException(messages) => complete(InternalServerError, messages)
case NotFoundException(message) => complete(NotFound, message)
case ValidationException(messages) => complete(PreconditionFailed, messages)
[and so on]
}
This way you can handle several different errors that my occur in your future-results.

How to handle exceptions in a playframework 2 Async block (scala)

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:
Is it a bad idea to return this exception from my service, is there a better/more scala way to handle this case?
How do I catch the exception?
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)
)
}
}
}
Can't you just catch the exception in your Async block ?
Async {
try {
val created = Service.addIngredient(value.name, value.description)
created map { ingredient =>
Redirect(routes.Admin.index()).flashing("success" -> "Ingredient '%s' added".format(ingredient.name))
}
} catch {
case _ => {
Promise.pure(Redirect(routes.Admin.index()).flashing("error" -> "Error while addin ingrdient"))
}
}
}

Inserting a document with reactivemongo and fetching it afterwards

When inserting a value into a persistence layer and returning the result object it is usually a good practice to fetch the newly created entity instead of returning the input data again.
When I try to do this in Scala using reactivemongo I stumble over my language skills.
def create(user: User): Future[User] = {
val newUser = user.createOID()
collection.insert(newUser).map {
case ok if ok.ok => {
for {
createdUser <- this.findOne(BSONDocument("_id" -> newUser._id))
} yield {
createdUser match {
case None => throw new RuntimeException("Could not find just created user")
case Some(x) => x
}
}
}
case error => throw new RuntimeException(error.message)
}
}
Where findOne has the signature:
def findOne(query: BSONDocument): Future[Option[User]]
I get the following error:
[error] found : scala.concurrent.Future[models.User]
[error] required: models.User
[error] createdUser <- this.findOne(BSONDocument("_id" -> newUser._id))
[error] ^
If I return the newUser object everything is fine.
I think I have a general misunderstanding what is happening here - maybe there is a better way to fetch the created object in one shot.
I would say that idiomatic Play/Scala way to do that is the following
def create(user: User): Future[Option[User]] = {
val newUser = user.createOID()
for {
nu <- collection.insert(newUser)
createdUser <- findOne(BSONDocument("_id" -> newUser._id))
} yield {
createdUser
}
}
Notice that this does return Future[Option[User]] and not Future[User] as in your code. I believe that Option[User] is definitely the way to go in this case as it actually tells clients of this method that it's not guaranteed that insertion will succeed (and thus runtime exception is not required as client will do .map on the result of this method — avoid using exceptions if you can deal with them gracefully).
You might also check nu for being ok within yield.

Resources