I'm getting the following compilation error in the future recover line:
type mismatch; found : scala.concurrent.Future[Any] required:
scala.concurrent.Future[play.api.mvc.Result]
I'm returning Ok() which is a Result object, so why is the compiler complaining?
class Test2 extends Controller {
def test2 = Action.async { request =>
val future = Future { 2 }
println(1)
future.map { result => {
println(2)
Ok("Finished OK")
}
}
future.recover { case _ => { // <-- this line throws an error
println(3)
Ok("Failed")
}
}
}
}
If you take closer look at the Future.recover method you'll see that partial function should have subtype of future's type, in your case Int, because you apply recover to original Future 'future'. To fix it you should apply it to mapped:
future.map {
result => {
println(2)
Ok("Finished OK")
}
}.recover {
case _ => {
println(3)
Ok("Failed")
}
}
You forget to chain, so do like Nyavro wrote, or, if you like another style, then just introduce an intermediate variable.
def test2 = Action.async { request =>
val future = Future { 2 }
println(1)
val futureResult = future.map { result => {
println(2)
Ok("Finished OK")
}}
futureResult.recover { case _ => {
println(3)
Ok("Failed")
}}
}
Related
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
}
}
}
def myMethod(myType: String) :Future[Future[Either[List[MyError], MyClass]]] {
for {
first <- runWithSeq(firstSource)
}
yield {
runWithSeq(secondSource)
.map {s ->
val mine = MyClass(s.head, lars)
val errors = myType match {
case "all" => Something.someMethod(mine)
}
(s, errors)
}
.map { x =>
x._2.leftMap(xs => {
addInfo(x._1.head, xs.toList)
}).toEither
}
}
}
for {
myStuff <- myMethod("something")
} yield {
myStuff.collect {
case(Left(errors), rowNumber) =>
MyCaseClass(errors, None) //compilation error here
}
}
I get compilation error on MyCaseClass that expected: List[MyError], found: Any
The signature of MyCaseClass is:
case class MyCaseClass(myErrors: List[ValidationError])
How can I fix this such that I can correctly call MyCaseClass inside the yield?
Your code example doesn't make much sense, and doesn't compile, but if runWithSeq() returns a Future then you should be able to eliminate the double Future return type like so.
for {
_ <- runWithSeq(firstSource)
scnd <- runWithSeq(secondSource)
} yield { ...
Your example is pretty hard to paste and fix
Abstact example for this
Class C may be whatever you want
def test(testval: Int)(implicit ec: ExecutionContext): Future[Future[Either[String, Int]]] = {
Future(Future{
if (testval % 2 == 0) Right(testval) else Left("Smth wrong")
})
}
implicit class FutureEitherExt[A, B](ft: Future[Either[A, B]]) {
def EitherMatch[C](f1: A => C, f2: B => C)(implicit ec: ExecutionContext): Future[C] = {
ft.map {
case Left(value) => f1(value)
case Right(value) => f2(value)
}
}
}
val fl: Future[Either[String, Int]] = test(5).flatten
val result: Future[String] = fl.EitherMatch(identity, _.toString)
Given these three methods that return Future[Either[String,Int]]:
def greater(x:Int): Future[Either[String,Int]] = Future {
if (x>0)
Right(x)
else
Left("Is not greater")
}
def less(x:Int): Future[Either[String,Int]] = Future {
if (x<0)
Right(x)
else
Left("Is not less")
}
def zero(x:Int): Future[Either[String,Int]] = Future {
if (x==0)
Right(x)
else
Left("Is not zero")
}
The following method that invokes the three above throws a compilation error:
def evaluate(x:Int): Future[Either[String,Int]] = {
val future = greater(x)
future.flatMap { either =>
either.right.flatMap { response => // <-- error in this line
val future2 = less(response)
future2.map { either2 =>
either2.right.map { response2 => zero(response2) }
}
}
}
}
The error:
type mismatch; found : scala.util.Either[String,Nothing] required:
scala.concurrent.Future[Either[String,Int]]
How to fix the evaluate method?
Classic case of Monad transformers.
Use EitherT from cats
val result =
for {
a <- EitherT(greater(x))
b <- EitherT(less(a))
c <- EitherT(zero(b))
} yield c
final value: result.value
Why EitherT?
Either is inside the another seemingly monad Future. So, EitherT can be used.
I have a certain flatMap that I use at about 20 places. And I am sure it will be 20 more in the future. It is to throw an exception when a Option is empty.
Example:
def get(serverId: UUID, sessionId: UUID) = authAction.async { implicit request =>
val user = request.user.get
serverService.findByIdAndUserId(serverId, user.id.get) flatMap { s =>
if (s.isEmpty) {
Future.failed(new NotFoundException)
} else {
Future.successful(s.get)
}
} flatMap { _ =>
serverSessionService.findByIdAndServerId(sessionId, serverId)
} flatMap { s =>
if (s.isEmpty) {
Future.failed(new NotFoundException)
} else {
Future.successful(s.get)
}
} map { s =>
Ok(Json.toJson(s))
}
}
I am doing the flatMap for Option checking twice in one controller method...
How can I isolate this part:
flatMap { s =>
if (s.isEmpty) {
Future.failed(new NotFoundException)
} else {
Future.successful(s.get)
}
}
Here's an approach using implicit class:
implicit class OptionFuture[T](f: Future[Option[T]]) {
def optionFuture(t: Throwable): Future[T] =
f.flatMap{
case Some(x) => Future.successful(x)
case _ => Future.failed(t)
}
}
Future{ Some(1) }.optionFuture(new Exception("failed"))
// Success(1)
Future{ None }.optionFuture(new Exception("failed"))
// Failure(java.lang.Exception: failed)
I would propose to add implicit class to fail if empty:
implicit class FutureFailer[T <: Option[_]](f: Future[T]) {
def failIfEmpty = {
f.flatMap {
case None => Future.failed(new NotFoundException)
case k => Future.successful(k)
}
}
}
Future.successful(Option.empty[String]).failIfEmpty.
flatMap(_ => Future.successful(Option.empty[String])).failIfEmpty
Following code when written using generic give a compilation error.
Without Generic
def getData(id: String) = Action.async {
val items = getItems(id)
sendResult(items)
}
private def sendResult(result: Future[Any]) = {
result.map {
items => {
try {
val itemStr = items.asInstanceOf[String]
Ok(itemStr)
} catch {
case t: ClassCastException => InternalServerError(s"Casting Exception while processing output $t")
}
}
}.recover {
case t:TimeoutException => InternalServerError("Api Timed out")
case t: Throwable => InternalServerError(s"Exception in the api $t")
}
}
With Generic
def getData(id: String) = Action.async {
val items = getItems(id)
sendResult[String](items)
}
private def sendResult[T](result: Future[Any]) = {
result.map {
items => {
try {
val itemStr = items.asInstanceOf[T]
Ok(itemStr)
} catch {
case t: ClassCastException => InternalServerError(s"Casting Exception while processing output $t")
}
}
}.recover {
case t:TimeoutException => InternalServerError("Api Timed out")
case t: Throwable => InternalServerError(s"Exception in the api $t")
}
}
The code is part of play app's contorller method. First one works fine. Second one gives following compilation error
Cannot write an instance of T to HTTP response. Try to define a
Writeable[T] [error] Ok(itemStr) [error]
Using Any with a generic function doesn't make much sense.
private def sendResult[T](result: Future[Any])
// Should better be
private def sendResult[T](result: Future[T])
// ... also remove the unsafe cast
Then this T needs to be provided an instance of Writeable, so it can be written a Array[Byte] over network.
// Either ...
private def sendResult[T: Writeable](result: Future[T])
// ... or ...
private def sendResult[T](result: Future[T])(implicit w: Writeable[T])
The Ok(...) is calling the method apply[C](content: C)(implicit writeable: Writeable[C]): Result on Status class. You need to have an implicit value for Writeable[T] in scope.
As a side note, rather than using Future[Any], you may as well use Future[T] and remove the cast. Also you can use Writes for JSON serialization.
private def sendResult[T](result: Future[T])(implicit writeable: Writes[T]) = {
result.map {
items => {
Ok(Json.toJson(items))
}
}.recover {
case t:TimeoutException => InternalServerError("Api Timed out")
case t: Throwable => InternalServerError(s"Exception in the api $t")
}
}