def update() = AuthAction.async(parse.json) { implicit request =>
val list = request.body.asInstanceOf[JsArray].value
list.foreach( mapping => {
repository.update()
}.andThen {
case Success(value) => repository.update2()
case Failure(exception) => {
BadRequest(errorResponse(Json.toJson(""), "updation failed"))
}
})
}
I have a controller function where I want to wait for a DB repo function ( repository.update2() )to complete and then send a response, but its saying "Found Unit expected Future[Result]"
Your success claus is presumably returning Unit where a Future response is required. Try this:
.andThen {
case Success(value) =>
repository.update2()
Future.successful(Ok("repository updated"))
case Failure(exception) =>
Future.successful(BadRequest(exception.getMessage())))
}
Related
I'm a beginner in Scala. Is there a concise way to write the following nested case statement?
def delete(
client : TwitterRestClient,
userId : Long,
keyword : String) : Unit = {
client.userTimelineForUserId(userId).onComplete{
case Success(value) => {
value.data.filter(_.text.contains(keyword)).foreach(x => {
client.deleteTweet(x.id).onComplete{
case Success(value) => println(s"Success: $value")
case Failure(exception) => println(s"Fail: $exception")
}
})
}
case Failure(exception) => println(s"Fail:$exception")
}
Slightly different way using flatMap, sequencing Futures and using collect instead of filter and map:
def delete(
client : TwitterRestClient,
userId : Long,
keyword : String) : Unit = {
client
.userTimelineForUserId(userId)
.flatMap(ratedData => Future.sequence(ratedData.data.collect {
case tweet if tweet.text.contains(keyword) => client.deleteTweet(tweet.id)
}))
.onComplete {
case Success(value) => println(s"Success: $value")
case Failure(exception) => println(s"Fail: $exception")
}
}
I have only compiled the code, dint run it.
I want to refactor by update action below to look a little more readable and also handle the failure case better
The userService has the following functions:
class UserService {
def getUserByUsername: Future[Option[Int]] // which is the UserId
def getUserById: Future[User]
}
My action looks like:
def update(userId: Int) = Action.async { implicit request =>
request.body.validate[User] match {
case JsSuccess(user, _) => {
userService.getUserByUsername(user.username).map { userId =>
userService.getUserById(userId.get).map { existingUser =>
userService.update(user.username)
Ok
}
}
}
case JsError(err) => Future.sucessful(BadRequest(err))
}
}
How do I handle the situation where getUserByUsername returns a None?
Would this look cleaner if it was in a for comprehension, is it better style?
You have some missing data in your questions such as case classes for the User model, userService class.
also better to attach the original function.
Anyways, I will do something as follows:
def update(userId: Int) = Action { implicit request =>
request.body.validate[User] match {
case JsSuccess(user: User, _) => {
val userId = getUserByUsername(user.username)
userId match {
case Some(userId) => {
for {
_ <- userService.getUserById(userId)
_ <- userService.update(user.username)
} yield Ok
}.recover {
case t: Throwable =>
Metrics.errOnUpdate.increment() // Some metric to monitor
logger.error(s"update userId: $userId failed with ex: ${t.getMessage}") // log the error
InternalServerError(Json.toJson(Json.obj("error" -> "Failure occured on update"))) // return custom made exception to the client
}
case None => Future.successful(NotFound(s"No such user with ${user.username}"))
}
}
case JsError(err) => Future.sucessful(BadRequest(err))
}
}
Note: If .update returns Future, you actually not waiting to update before returning Ok to the user, thus, if its fails, its still returns Ok.
To fix that, use flatMap and then map the value of update response.
You can also separate the recovering for the getUserById and update if you prefer.
Edit:
def update(userId: Int) = Action { implicit request =>
request.body.validate[User] match {
case JsSuccess(user: User, _) => {
getUserByUsername(user.username).flatMap {
case Some(userId) => for {
_ <- userService.getUserById(userId)
_ <- userService.update(user.username)
} yield Ok
case None => Future.successful(NotFound(s"No such user with ${user.username}"))
}
}.recover {
case t: Throwable =>
Metrics.errOnUpdate.increment() // Some metric to monitor
logger.error(s"update userId: $userId failed with ex: ${t.getMessage}") // log the error
InternalServerError(Json.toJson(Json.obj("error" -> "Failure occured on update"))) // return custom made exception to the client
}
}
case JsError(err) => Future.sucessful(BadRequest(err))
}
}
First, you probably need to use Option.fold:
#inline final def fold[B](ifEmpty: => B)(f: A => B)
Then you can do something like this:
def update(userId: Int) = Action.async { implicit request =>
def handleJsonErrors(errors: Seq[(JsPath, collection.Seq[JsonValidationError])]): Future[Result] = ???
def updateUser(userWithoutId: User): Future[Result] = {
for {
userId <- userService.getUserByUsername(userWithoutId.username)
_ <- userService.getUserById(userId.get)
_ <- userService.update(userWithoutId.username)
} yield {
Ok
}
}
request.body.asJson.fold {
Future.successful(BadRequest("Bad json"))
} {
_.validate[User].fold(handleJsonErrors, updateUser).recover {
case NonFatal(ex) =>
InternalServerError
}
}
}
How can I complete akka-http response with different types based on future response?
I am trying to do something like
implicit val textFormat = jsonFormat1(Text.apply)
implicit val userFormat = jsonFormat2(User.apply)
path(Segment) { id =>
get {
complete {
(fooActor ? GetUser(id)).map {
case n: NotFound => Text("Not Found")
case u: User => u
}
}
}
}
but I am getting
type mismatch , expected: ToResponseMarshable , actual Futue[Product with Serializable ]
You could use the onComplete directive:
get {
path(Segment) { id =>
onComplete(fooActor ? GetUser(id)) {
case Success(actorResponse) => actorResponse match {
case _ : NotFound => complete(StatusCodes.NotFound,"Not Found")
case u : User => complete(StatusCodes.OK, u)
}
case Failure(ex) => complete(StatusCodes.InternalServerError, "bad actor")
}
}
}
I have the following function and I would like to return Future[Boolean] but the IDE prompts that I return Unit. I am new in Scala. Can someone point me out what I am doing wrong?
def remove(loginInfo: LoginInfo): Future[Boolean] = {
val result = findObject(loginInfo)
result.onSuccess {
case Some(persistentPasswordInfo) =>
val removeResult = remove(persistentPasswordInfo._id.toString)
removeResult.map {
case Left(ex) => Future.successful(false)
case Right(b) => Future.successful(b)
}
case None => Future.successful(false)
}
}
Replace onSuccess with flatMap. Assuming your remove(x: String) method also returns a Future, that will also need to be flatMapped:
def remove(loginInfo: LoginInfo): Future[Boolean] = {
val result = findObject(loginInfo)
result.flatMap {
case Some(persistentPasswordInfo) =>
val removeResult = remove(persistentPasswordInfo._id.toString)
removeResult.flatMap {
case Left(ex) => Future.successful(false)
case Right(b) => Future.successful(b)
}
case None => Future.successful(false)
}
}
Is there a more elegant way of getting the Int value from Future[Option[Int]] instead of using finalFuture.value.get.get.get?
This is what I have so far:
val finalFuture: Future[Option[Int]] = result.contents
finalFuture.onComplete {
case Success(value) => println(s"Got the callback with value = ", finalFuture.value.get.get.get)
case Failure(e) => e.printStackTrace
}
You could nest the match:
finalFuture.onComplete {
case Success(Some(value)) => println(s"Got the callback with value = ", value)
case Success(None) => ()
case Failure(e) => e.printStackTrace
}
You can use foreach to apply a A => Unit function to the value in Option[A], if it exists.
fut.onComplete {
case Success(opt) => opt.foreach { val =>
println(s"Got the callback with value = {}", val)
}
case Falure(ex) => ex.printStackTrace
}
You can use also the toOption of Try to get Option[Option[Int]] and then flatten to get Option[Int]
def printVal(finalFuture: Future[Option[Int]] ) = finalFuture.onComplete(
_.toOption.flatten.foreach(x=> println (s"got {}",x))
)
EDIT: That assuming you don't care about the stacktrace :)