Question

In my scala code, I have some nested Try() match {}, which look ugly:

import scala.util._

Try(convertJsonToObject[User]) match {
  case Success(userJsonObj) =>
    Try(saveToDb(userJsonObj.id)) match {
      case Success(user) => Created("User saved")
      case _ => InternalServerError("database error")
    }
  case _ => BadRequest("bad input")
}

Is there any better way of writing such code?

Was it helpful?

Solution

There's a bunch of ways to solve this problem. I'll give you one possibility. Consider this cleaned up version of your code:

trait Result
case class BadRequest(message:String) extends Result
case class InternalServerError(message:String) extends Result
case class Created(message:String) extends Result

def processRequest(json:String):Result = {
  val result = 
    for{
      user <- Try(parseJson(json))
      savedUser <- Try(saveToDb(user))
    } yield Created("saved")

  result.recover{
    case jp:JsonParsingException => BadRequest(jp.getMessage)
    case other => InternalServerError(other.getMessage)
  }.get
}

def parseJson(json:String):User = ...
def saveToDb(user:User):User = ...

The caveat to this code is that it assumes that you can differentiate the json parsing failure from the db failure by the exception each might yield. Not a bad assumption to make though. This code is very similar to a java try/catch block that catches different exception types and returns different results based on catching those different types.

One other nice thing about this approach is that you could just define a standard recovery Partial Function for all kinds of possible exceptions and use it throughout your controllers (which I'm assuming this code is) to eliminate duplicate code. Something like this:

object ExceptionHandling{
  val StandardRecovery:PartialFunction[Throwable,Result] = {
    case jp:JsonParsingException => BadRequest(jp.getMessage)
    case sql:SQLException => InternalServerError(sql.getMessage)
    case other => InternalServerError(other.getMessage)   
  }
} 

And then in your controller:

import ExceptionHandling._
result.recover(StandardRecovery).get

OTHER TIPS

Another approach is to define implicit reads for User (if using Play Framework) and then doing something like

someData.validate[User].map { user =>
  saveToDb(user.id) match { // you can return Try from saveToDb
    case Success(savedUser) => Created("User saved")
    case Failure(exception) => InternalServerError("Database Error")
  }
}.recoverTotal {
    e => BadRequest(JsError.toFlatJson(e))
}    
Try(convertJsonToObject[User]).map([your code]).toOption.getOrElse(fallback)
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top