Reputation: 63201
Consider a nested structure in which the relevant attributes are as follows:
case class Validation { sql: Option[SqlDataSource] }
case class SqlDataSource { dfh: Option[DataFrameHolder] }
case class DataFrameHolder { sql: Option[String] }
The naive way that I am working with this presently is:
val ssql = expVal.sql.getOrElse(
vc.dfh.map(_.sql
.getOrElse(throw new IllegalStateException(s"$logMsg CompareDF: Missing sql container"))
).getOrElse(throw new IllegalStateException(s"$logMsg CompareDF: dfh missing sql"))
.sql.getOrElse(throw new IllegalStateException(s"$logMsg CompareDF: Missing sql")))
While this does get the job done it is also reader-unfriendly and developer unfriendly (tough to get the nesting correctly). Any thoughts on better ways to handle this?
Update thanks for the great answers - this will help clean up and simplify the exception handling code moving forward.
Upvotes: 1
Views: 334
Reputation: 22895
If you want fail fast semantics, I would use for comprehension with Try
s.
final case class Validation(ql: Option[SqlDataSource])
final case class SqlDataSource(dfh: Option[DataFrameHolder])
final case class DataFrameHolder(sql: Option[String])
val expVal = Validation(
ql = Some(
SqlDataSource(
dfh = Some(
DataFrameHolder(
sql = Some("Hello, World!")
)
)
)
)
)
implicit class OptionOps[T](private val op: Option[T]) {
def toTry(ex: => Throwable): Try[T] = op match {
case Some(t) => Success(t)
case None => Failure(ex)
}
}
val ssql: Try[String] = for {
ql <- expVal.ql.toTry(new IllegalStateException("CompareDF: Missing sql container"))
dfh <- ql.dfh.toTry(new IllegalStateException("CompareDF: dfh missing sql"))
sql <- dfh.sql.toTry(new IllegalStateException("CompareDF: Missing sql"))
} yield sql
Upvotes: 2
Reputation: 51271
The answers from Luis Miguel Mejía Suárez and Xavier Guihot are both quite good, but there's no need to build your own toTry()
method. Either
already offers one and, since Either
is also right-biased, it can be used in the for-comprehension.
import util.Try
val ssql: Try[String] = (for {
ql <- expVal.ql.toRight(new IllegalStateException("yada-yada"))
dfh <- ql.dfh .toRight(new IllegalStateException("yoda-yoda"))
sql <- dfh.sql .toRight(new IllegalStateException("yeah-yeah"))
} yield sql).toTry
Upvotes: 3
Reputation: 61736
I would suggest a very similar solution to Binzi Cao's answer but only using the standard library with the scala.util.Try
monad:
def toTry[T](x: Option[T], message: String): Try[T] =
x.map(Success(_)).getOrElse(Failure(new IllegalStateException(message)))
(for {
sqlDataSource <- toTry(validation.sql, s"Missing sql container")
dataFrameHolder <- toTry(sqlDataSource.dfh, s"dfh missing sql")
sql <- toTry(dataFrameHolder.sql, s"Missing sql")
} yield sql)
.get
The for-comprehension produces a Try
. Applying .get
on the produced Try
will either return the content of the Try
(the String
within dataFrameHolder
) if i's a Success[String]
or throw the exception if it's a Failure[IllegalStateException]
.
Upvotes: 3
Reputation: 1085
You might want to use the cats lib to make your code more functional. You can convert Option
to Either
as below:
import cats.implicits._
def toEither[T](s: Option[T], error: String) = {
s.liftTo[Either[String, ?]](error)
}
def runEither = {
val result =
for {
sqlDataSource <- toEither(validation.sql, s"Missing sql container")
dataFrameHolder <- toEither(sqlDataSource.dfh, s"dfh missing sql")
sql <- toEither(dataFrameHolder.sql, s"Missing sql")
} yield sql
result match {
case Right(r) => r
case Left(e) => throw new Exception(e)
}
}
Upvotes: 3