Reputation: 2603
I have a repo method that returns a future:
def insert(newUser: User): Future[User]
This method can fail for different reasons, however, I want to be able to catch one of these reasons and rethrow others. If I am simply wrapping the call with Try
Try(userRepository.insert(usr)) match {
case Success(newUserFuture) => newUserFuture
case Failure(e) if e.getCause.getMessage.contains("duplicate key") => {
logger.warn(e.getMessage)
throw DuplicateEntityException("duplicate user record")
}
case Failure(e) => {
logger.error(e.getMessage)
throw e
}
}
As a result, I am getting Try[Future[User]]
, and in this situations, neither of the Failures will ever happen, since the Future is successfully created.
And at this point, I am not sure how to deal with it. Ideally, I want to get Future[Either[String, User]]
out of it (or something similar).
How should I deal with this situation, or am I in completely wrong direction?
EDIT following recommendations Possible solution I've tried something like this:
userRepository.insert(usr).map(newUser => Right(newUser)) recoverWith {
case e: PSQLException if e.getMessage.contains("duplicate key") =>
Future.successful(Left("duplicate user record"))
}
Upvotes: 2
Views: 1481
Reputation: 4133
try something like that:
def futureToFutureTry[T](f: Future[T]): Future[Try[T]] =
f.map(Success(_)).recover {
case e: Throwable => {
Failure(e)
}
}
...
userRepository.insert(usr) map futureToFutureTry
for checking
def checkOperations[A](operations: List[Try[A]]): Future[List[A]] = {
Future {
if (!operations.filter(_.isFailure).isEmpty) {
// there were some error
} else {
// all operations are ok
}
}
Upvotes: 0
Reputation: 14825
Future
will handles the exceptions for you. You need not wrap future code with Try
.
Future
does the job of the Try
as well.
This line of code is not necessary
Try(userRepository.insert(usr))
I hope your insert method is something like this
def insert(newUser: User): Future[User] = Future { doSomeDBCall() }
In the above method. If doSomeDBCall()
fails. The exception will be caught by the future safely and will be delivered to caller as Failure(exception)
. So you need not wrap the Future
with a Try
Catch specific exceptions and throw others (propagate others).
You can use recoverWith
and handle exceptions and propagate exceptions which you are not interested in.
In the below code SQLException
is handled and other exceptions are propagated to the caller.
userRepository.insert(usr).recoverWith {
case ex: SQLException => Future.successful(defaultValue)
case ex => Future.failed(ex)
}
Upvotes: 4
Reputation: 8202
I think you can reverse your approach, and use a guard in the case clean the code up a little. First up is to have userRepository.insert(usr)
return a Future[Try[User]]
, which allows you to just map the future, e.g.
def insert(user: User): Future[Try[User]]
which can then be used like this:
userRepository.insert(user).map {
case Success(insertedUser) => Right(insertedUser)
case Failure(ex) if ex.getCause.getMessage.contains("duplicate key") => throw DuplicateEntityException("duplicate user record")
case Failure(ex) => throw ex
}
The result of this will be a Future[User]
that you can attach callbacks to (see http://docs.scala-lang.org/overviews/core/futures.html) to either do something else with the user or deal with the exception.
Upvotes: 1