Scala: Error handling and exception matching - scala

Given the following custom Exception ...
trait ServiceException extends RuntimeException {
val errorCode: Int
}
object ServiceException {
def apply(
message: String, _errorCode: Int
): ServiceException = new RuntimeException(message) with ServiceException {
val errorCode: Int = _errorCode
}
def apply(
message: String, cause: Throwable, _errorCode: Int
): ServiceException = new RuntimeException(message, cause) with ServiceException {
val errorCode: Int = _errorCode
}
}
... and the following method returning a Future ...
myService.doSomethingAndReturnFuture.map {
...
}.recover {
case ServiceException(5) =>
Logger.debug("Error 5")
// this does not work
// case e: ServiceException(5) =>
// Logger.debug(s"Error 5: ${e.getMessage}")
case NonFatal(e) =>
Logger.error("error doing something", e)
}
... how do I get the error message from ServiceException?

You will need an unapply for your described match to work, which should be defined in the companion object.
object ServiceException {
//... apply methods
def unapply(ex: ServiceException) = Some(ex.errorCode)
}
And then you can match.
recover {
case se#ServiceException(5) => println(s"Error 5: ${se.getMessage}")
case _ => println("Some other error")
}
You could also include the message in the unapply.
def unapply(ex: ServiceException) = Some((ex.errorCode, ex.getMessage))
and then match like this:
recover {
case ServiceException(5, msg) => println(s"Error 5: $msg")
case _ => println("Some other error")
}
As an alternative you can also do it without the unapply. Then it could look like:
recover {
case se: ServiceException if se.errorCode == 5 => println(s"Error 5: ${se.getMessage}")
case _ => println("Some other error")
}

People like case classes.
This does not exactly align with your givens, but for example:
scala> trait ServiceException { _: RuntimeException => def errorCode: Int }
defined trait ServiceException
scala> case class MyX(errorCode: Int, msg: String, cause: Exception = null) extends RuntimeException(msg, cause) with ServiceException
defined class MyX
scala> def i: Int = throw MyX(42, "Help!")
i: Int
scala> import concurrent._ ; import ExecutionContext.Implicits._
import concurrent._
import ExecutionContext.Implicits._
scala> Future(i) recover { case MyX(code, m, err) => println(m) ; -1 }
res11: scala.concurrent.Future[Int] = scala.concurrent.impl.Promise$DefaultPromise#7d17ee50
scala> Help!
scala> .value
res12: Option[scala.util.Try[Int]] = Some(Success(-1))

Related

Unable to recover exception in Future in Scala

The following Scala code uses cats EitherT to wrap results in a Future[Either[ServiceError, T]]:
package com.example
import com.example.AsyncResult.AsyncResult
import cats.implicits._
import scala.concurrent.ExecutionContext.Implicits.global
class ExternalService {
def doAction(): AsyncResult[Int] = {
AsyncResult.success(2)
}
def doException(): AsyncResult[Int] = {
println("do exception")
throw new NullPointerException("run time exception")
}
}
class ExceptionExample {
private val service = new ExternalService()
def callService(): AsyncResult[Int] = {
println("start callService")
val result = for {
num <- service.doException()
} yield num
result.recoverWith {
case ex: Throwable =>
println("recovered exception")
AsyncResult.success(99)
}
}
}
object ExceptionExample extends App {
private val me = new ExceptionExample()
private val result = me.callService()
result.value.map {
case Right(value) => println(value)
case Left(error) => println(error)
}
}
AsyncResult.scala contains:
package com.example
import cats.data.EitherT
import cats.implicits._
import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.Future
object AsyncResult {
type AsyncResult[T] = EitherT[Future, ServiceError, T]
def apply[T](fe: => Future[Either[ServiceError, T]]): AsyncResult[T] = EitherT(fe)
def apply[T](either: Either[ServiceError, T]): AsyncResult[T] = EitherT.fromEither[Future](either)
def success[T](res: => T): AsyncResult[T] = EitherT.rightT[Future, ServiceError](res)
def error[T](error: ServiceError): AsyncResult[T] = EitherT.leftT[Future, T](error)
def futureSuccess[T](fres: => Future[T]): AsyncResult[T] = AsyncResult.apply(fres.map(res => Right(res)))
def expectTrue(cond: => Boolean, err: => ServiceError): AsyncResult[Boolean] = EitherT.cond[Future](cond, true, err)
def expectFalse(cond: => Boolean, err: => ServiceError): AsyncResult[Boolean] = EitherT.cond[Future](cond, false, err)
}
ServiceError.scala contains:
package com.example
sealed trait ServiceError {
val detail: String
}
In ExceptionExample, if it call service.doAction() it prints 2 as expected, but if it call service.doException() it throws an exception, but I expected it to print "recovered exception" and "99".
How do I recover from the exception correctly?
That is because doException is throwing exception inline. If you want to use Either, you have to return Future(Left(exception)) rather than throwing it.
I think, you are kinda overthinking this. It does not look like you need Either here ... or cats for that matter.
Why not do something simple, like this:
class ExternalService {
def doAction(): Future[Int] = Future.successful(2)
def doException(): AsyncResult[Int] = {
println("do exception")
Future.failed(NullPointerException("run time exception"))
// alternatively: Future { throw new NullPointerExceptioN() }
}
class ExceptionExample {
private val service = new ExternalService()
def callService(): AsyncResult[Int] = {
println("start callService")
val result = for {
num <- service.doException()
} yield num
// Note: the aboive is equivalent to just
// val result = service.doException
// You can write it as a chain without even needing a variable:
// service.doException.recover { ... }
result.recover { case ex: Throwable =>
println("recovered exception")
Future.successful(99)
}
}
I tend to agree that it seems a bit convoluted, but for the sake of the exercise, I believe there are a couple of things that don't quite click.
The first one is the fact that you are throwing the Exception instead of capturing it as part of the semantics of Future. ie. You should change your method doException from:
def doException(): AsyncResult[Int] = {
println("do exception")
throw new NullPointerException("run time exception")
}
To:
def doException(): AsyncResult[Int] = {
println("do exception")
AsyncResult(Future.failed(new NullPointerException("run time exception")))
}
The second bit that is not quite right, would be the recovery of the Exception. When you call recoverWith on an EitherT, you're defining a partial function from the Left of the EitherT to another EitherT. In your case, that'd be:
ServiceError => AsyncResult[Int]
If what you want is to recover the failed future, I think you'll need to explicitly recover on it. Something like:
AsyncResult {
result.value.recover {
case _: Throwable => {
println("recovered exception")
Right(99)
}
}
}
If you really want to use recoverWith, then you could write this instead:
AsyncResult {
result.value.recoverWith {
case _: Throwable =>
println("recovered exception")
Future.successful(Right(99))
}
}

How to Right and Left from a Future

I have a function as follows:
def bar(x : Int) : Either[String, Future[Option[Foo]]] = {
Goo() recover { case e => Left("Some error string") }
}
As you can see, if the Future fails then it will hit the partial function inside the recover body. This will return Left and satisfies the left part of the Either type. What I am stuck on is how to return the Right if the Goo future completes successfully.
I tried the following:
def bar(x : Int) : Either[String, Future[Option[Foo]]] = {
Goo().map(x => Right(Future.successful(x))) recover { case e => Left("Some error string") }
}
However, I get a type error indicating that the return type for bar is Future[Either[String, Future[Foo]]].
How can I return a Right(x) where x is some value of type Foo?
UPDATE
def bar(x : Int) : Future[Either[String, Option[Foo]]] = {
Goo().map(x => Right(x)) recover { case e => Left("Some error string") }
}
You didn't define Goo, but i'll assume for the moment the following:
import scala.concurrent.Future
import scala.concurrent.ExecutionContext.Implicits.global
case class Foo(n: Int)
/** Takes a string and returns a future int-parsing of it */
def Goo(s: String): Future[Foo] = Future {
Foo(java.lang.Integer.parseInt(s)) // will throw an exception on non-int
}
Then if you want bar(s: String) to return Either[String, Option[Foo]] where the Option is a Some[Foo] if the number is parseable and positive, or None if parseable but non-positive and the String is an explanation of why it failed to parse, you could do:
import scala.concurrent.Await
import scala.concurrent.duration.Duration
import scala.util.control.NonFatal
def bar(s: String): Future[Either[String, Option[Foo]]] = {
Goo(s).map { foo: Foo =>
Right(if (foo.n > 0) Some(foo) else None)
}.recover {
case NonFatal(e) => Left("Failed to parse %s: %s".format(s, e))
}
}
voila:
scala> Await.result(bar("4"), Duration.Inf)
res1: Either[String,Option[Foo]] = Right(Some(Foo(4)))
scala> Await.result(bar("-4"), Duration.Inf)
res2: Either[String,Option[Foo]] = Right(None)
scala> Await.result(bar("four"), Duration.Inf)
res3: Either[String,Option[Foo]] = Left(Failed to parse four: java.lang.NumberFormatException: For input string: "four")

Play Framework: How to implement proper error handling

I have a Play application with several modules, each of which has its own exception set. Here are three examples:
Module common:
package services.common
trait CommonErrors {
final case class NotFound(id: String) extends Exception(s"object $id not found")
final case class InvalidId(id: String) extends Exception(s"$id is an invalid id")
...
// `toJson` is just an extension method that converts an exception to JSON
def toResult(e: Exception): Result = e match {
case NotFound => Results.NotFound(e.toJson)
case InvalidId => Results.BadRequest(e.toJson)
case _ => Results.InternalError(e.toJson)
}
}
Module auth:
package services.auth
trait AuthErrors {
final case class UserNotFound(e: NotFound) extends Exception(s"user ${e.id} not found")
final case class UserAlreadyExists(email: String) extends Exception(s"user identified by $email already exists")
...
// `toJson` is just an extension method that converts an exception to JSON
def toResult(e: Exception): Result = e match {
case UserNotFound => Results.NotFound(e.toJson)
case UserAlreadyExists => Results.BadRequest(e.toJson)
case _ => Results.InternalError(e.toJson)
}
}
Module other:
trait OtherErrors {
final case class AnotherError(s: String) extends Exception(s"another error: $s")
...
// `toJson` is just an extension method that converts an exception to JSON
def toResult(e: Exception): Result = e match {
case AnotherError => Results.BadRequest(e.toJson)
...
case _ => Results.InternalError(e.toJson)
}
}
As you can see, each trait defines a set of exceptions and provides a method to convert that exception to a JSON response like this:
{
"status": 404,
"code": "not_found",
"message": "user 123456789123456789123456 not found",
"request": "https://myhost.com/users/123456789123456789123456"
}
What I'm trying to achieve is to have each module defining its exceptions, reusing the ones defined in the common module, and mixin the exception traits as needed:
object Users extends Controller {
val errors = new CommonErrors with AuthErrors with OtherErrors {
// here I have to override `toResult` to make the compiler happy
override def toResult(e: Exception) = super.toResult
}
def find(id: String) = Action { request =>
userService.find(id).map { user =>
Ok(success(user.toJson))
}.recover { case e =>
errors.toResult(e) // this returns the appropriate result
}
}
}
If you look at how I've overridden toResult, I always return super.toResult, which corresponds to the implementation contained in trait OtherErrors... and this implementation might miss some patterns that are expected to be found in CommonErrors.toResult.
For sure I'm missing something... so the question is: what's the design pattern to fix the issue with multiple implementations of toResult?
You could use Stackable Trait pattern. I'll replaced your .toJson with .getMessage for simplification reason:
define base trait:
trait ErrorsStack {
def toResult(e: Exception): Result = e match {
case _ => Results.InternalServerError(e.getMessage)
}
}
and stackable traits:
trait CommonErrors extends ErrorsStack {
case class NotFound(id: String) extends Exception(s"object $id not found")
case class InvalidId(id: String) extends Exception(s"$id is an invalid id")
override def toResult(e: Exception): Result = e match {
case e: NotFound => Results.NotFound(e.getMessage)
case e: InvalidId => Results.BadRequest(e.getMessage)
case _ => super.toResult(e)
}
}
trait AuthErrors extends ErrorsStack {
case class UserNotFound(id: String) extends Exception(s"user $id not found")
case class UserAlreadyExists(email: String) extends Exception(s"user identified by $email already exists")
override def toResult(e: Exception): Result = e match {
case e: UserNotFound => Results.NotFound(e.getMessage)
case e: UserAlreadyExists => Results.BadRequest(e.getMessage)
case _ => super.toResult(e)
}
}
trait OtherErrors extends ErrorsStack {
case class AnotherError(s: String) extends Exception(s"another error: $s")
override def toResult(e: Exception): Result = e match {
case e: AnotherError => Results.BadRequest(e.getMessage)
case _ => super.toResult(e)
}
}
so if we have some stack
val errors = new CommonErrors with AuthErrors with OtherErrors
and defined some helper
import java.nio.charset.StandardCharsets.UTF_8
import play.api.libs.iteratee.Iteratee
import concurrent.duration._
import scala.concurrent.Await
def getResult(ex: Exception) = {
val res = errors.toResult(ex)
val body = new String(Await.result(res.body.run(Iteratee.consume()), 5 seconds), UTF_8)
(res.header.status, body)
}
following code
import java.security.GeneralSecurityException
getResult(errors.UserNotFound("Riddle"))
getResult(errors.UserAlreadyExists("Weasley"))
getResult(errors.NotFound("Gryffindor sword"))
getResult(errors.AnotherError("Snape's death"))
getResult(new GeneralSecurityException("Marauders's map"))
will produce reasonable output
res0: (Int, String) = (404,user Riddle not found)
res1: (Int, String) = (400,user identified by Weasley already exists)
res2: (Int, String) = (404,object Gryffindor sword not found)
res3: (Int, String) = (400,another error: Snape's death)
res4: (Int, String) = (500,Marauders's map)
also we can refactor this code, pulling case classed from traits, and make function's more composable:
type Resolver = PartialFunction[Exception, Result]
object ErrorsStack {
val resolver: Resolver = {
case e => Results.InternalServerError(e.getMessage)
}
}
trait ErrorsStack {
def toResult: Resolver = ErrorsStack.resolver
}
object CommonErrors {
case class NotFound(id: String) extends Exception(s"object $id not found")
case class InvalidId(id: String) extends Exception(s"$id is an invalid id")
val resolver: Resolver = {
case e: NotFound => Results.NotFound(e.getMessage)
case e: InvalidId => Results.BadRequest(e.getMessage)
}
}
trait CommonErrors extends ErrorsStack {
override def toResult = CommonErrors.resolver orElse super.toResult
}
object AuthErrors {
case class UserNotFound(id: String) extends Exception(s"user $id not found")
case class UserAlreadyExists(email: String) extends Exception(s"user identified by $email already exists")
val resolver: Resolver = {
case e: UserNotFound => Results.NotFound(e.getMessage)
case e: UserAlreadyExists => Results.BadRequest(e.getMessage)
}
}
trait AuthErrors extends ErrorsStack {
override def toResult = AuthErrors.resolver orElse super.toResult
}
object OtherErrors {
case class AnotherError(s: String) extends Exception(s"another error: $s")
val resolver: Resolver = {
case e: AnotherError => Results.BadRequest(e.getMessage)
}
}
trait OtherErrors extends ErrorsStack {
override def toResult = OtherErrors.resolver orElse super.toResult
}

scala's Try elegant on error behavior

Is there a more elegant way of doing this in scala?
def doTheDangerousThing(): Try[Result] = {
val result = Try(dangerousOp)
if (result.isFailure) {
println("error")
}
result
}
I think your if statement is perfectly valid. Here is another alternative:
def doTheDangerousThing(): Try[Result] = Try(dangerousOp) recoverWith {
case exception => println("error"); Failure(exception)
}
Something like this:
def doTheDangerousThing[Result](dangerousOp: =>Result): Try[Result] = Try(dangerousOp) match {
case o # Failure(_) => println("error"); o
case _ => _
}
Not sure if this is more idiomatic, but sometimes I find that placing the recoverWith in this manner improves readability for me:
def doDangerousThing(): Try[Result] = Try {
dangerousOp
} recoverWith {
case t: Throwable => println("error"); Failure(t)
}
My preferred,
def doTheDangerousThing(): Option[Result] = Try (dangerousOp) toOption
If the Try is successful you will get a Some(value), if it fails a None.
For a large compilation on Try uses, have a look at Try introduced in Scala 2.10.0 .
There are ways. For instance:
def doTheDangerousThing(): Try[Result] = {
val result = Try(dangerousOp)
result.failed foreach { _ =>
println("error")
}
result
}
Or, if you don't want to repeat result all through, then:
def doTheDangerousThing(): Try[Result] = {
Try(dangerousOp) recover {
case ex => println("error"); throw ex
}
}
Well, I suppose you could do something like this:
def doTheDangerousThing(): Option[Result] =
Try(dangerousOp) match {
case Success(result) => Some(result)
case Failure(e) => None //might want to log the error as well
}
In some cases I love to use two-step approach which will allow me a more granular error message control:
def retrieveData(dataId: String): Try[String] = {
Try {
Option(someApi(dataId))
.getOrElse(throw SomeApiFailedException("invalid dataId"))
} recoverWith {
case e: SomeApiFailedException => Failure(e)
case e: Throwable => Failure(SomeApiFailedException("failed retrieve dataId"))
}
}
case class SomeApiFailedException(err: String) extends RuntimeException(err)
I could choose from either of the three implementations, depending on whether I want to:
Simply propagate it upwards ( doTheDangerousThing1 )
Ignore the error ( doTheDangerousThing2 )
Intercept the error while propagating it upwards ( doTheDangerousThing3 )
Here is the code:
import scala.util.{Try,Success,Failure}
object temp {
type Result = Int
def dangerousOp = {
val r = scala.util.Random.nextInt(10)
if (r > 5) r else throw new RuntimeException("Failed on " + r)
}
def logMessage[T](t: T) = println(t)
def doTheDangerousThing1(): Try[Result] = Try(dangerousOp)
def doTheDangerousThing2(): Option[Result] = {
Try(dangerousOp) match {
case Success(r) => Option(r)
case _ => None
}
}
def doTheDangerousThing3(): Try[Result] = {
Try(dangerousOp) match {
case t # Success(r) => t
case t # _ => logMessage("failed: "+t); t
}
}
}
Inside the REPL
scala> doTheDangerousThing1
res0: scala.util.Try[Result] = Success(9)
scala> doTheDangerousThing1
res1: scala.util.Try[Result] = Success(9)
scala> doTheDangerousThing2
res2: Option[Result] = None
scala> doTheDangerousThing2
res3: Option[Result] = Some(7)
scala> doTheDangerousThing3
failed: Failure(java.lang.RuntimeException: Failed on 0)
res4: scala.util.Try[Result] = Failure(java.lang.RuntimeException: Failed on 0)
scala> doTheDangerousThing3
failed: Failure(java.lang.RuntimeException: Failed on 0)
res5: scala.util.Try[Result] = Failure(java.lang.RuntimeException: Failed on 0)

Return same value for multiple exceptions

Given a block that catches more than one exception is it possible to handle multiple exceptions without putting the desired value in every case block? e.g. it would be nice if something like this worked:
val foo: Int = try {
//do stuff that results in an Int
} catch {
case e: SomeException => //do something if this gets thrown
case e: SomeOtherException => //do something different if this gets thrown
0
}
But that results in a compile error (type mismatch; found : Unit required: Int). I could put the default in each throwable case e: SomeException => {/*do something if this gets thrown*/; 0} - but that just seems like code smell so I'm hoping there is a more elegant solution.
You could simply wrap the exception handling:
val foo: Int = try {
//do stuff that results in an Int
17
} catch { case t: Throwable => t match {
case e: SomeException => //do something if this gets thrown
case e: SomeOtherException => //do something different if this gets thrown
}
42
}
You can take advantage of partial functions to do your error handling using the built in Try
val foo: Int ={
val value = Try{
//stuff
}
unwrap(0, value){
case x: SomeException => doStuff()
case x: OtherExcetion => doMoreStuff()
}
}
def unwrap[A](ret: A, value: Try[A])(f: Failure[A] => Unit): A = value match{
case Success(x) => x
case x: Failure => f(x); ret
}
and voila, you've handled it quite well.
The catch keyword expects a PartialFunction, which can easily be chained with andThen:
scala> val pf1: PartialFunction[Throwable, Unit] = { case _: IllegalArgumentException => println("pf1") }
pf1: PartialFunction[Throwable,Unit] = <function1>
scala> val pf2: PartialFunction[Unit, Int] = { case _ => println("pf2"); 0}
pf2: PartialFunction[Unit,Int] = <function1>
scala> try throw new IllegalArgumentException catch pf1 andThen pf2
pf1
pf2
res0: Int = 0
scala> try throw new NoSuchElementException catch pf1 andThen pf2
java.util.NoSuchElementException
The second PartialFunction is only executed when the first one matched its argument, which can be a problem when you want to catch other exceptions (that also should not return the default value). But for this case, there is orElse:
scala> val pf3: PartialFunction[Throwable, Int] = { case _ => println("pf3"); 1}
pf3: PartialFunction[Throwable,Int] = <function1>
scala> try throw new NoSuchElementException catch pf1 andThen pf2 orElse pf3
pf3
res2: Int = 1
You can use the Try object to wrap your possibly failing code, and then compose the outcome like this
val foo: Int = (Try {
//do stuff that results in an Int
} recover {
//here we handle the recovering
handleFail andThen defaultInt
}).get
val handleFail: PartialFunction[Throwable, Unit] = {
case e: SomeException => //do something if this gets thrown
case e: SomeOtherException => //do something different if this gets thrown
val defaultInt: PartialFunction[Unit, Int] = { case _ => 0 }