Throwing exception is not idiomatic way to deal with errors in Scala, because of variety of reasons, like:
- Scala does not have
checked
exceptions concept like Java, all exceptions from Scala perspective are unchecked
- so compiler will not force you to handle certain exception, hence it easier to do a mistake and forget to handle exception;
- Exceptions considered as side effects and function throwing them are considered as impure. It does not mean they bad or not properly designed, it means, they should be handled in a special manner, hence might make your code more complicated without meaningful reason. (it is long discussion out of scope of this question);
- Error should be an return value - if you would like to force client side to handle expected error, proper result type should be choose;
So in you case you can go with next possible options:
In case of using plain Either
the solution will look like:
def validateSyntax(statement: String): Either[String, Unit] = {
if (!is_valid(statement)) Left("syntax is not valid") else Right(())
}
val myStatements = List("foo =! bar", "print(foo + bar)")
val validatedStatements = myStatements.map(statement => statement -> validateSyntax(statement))
val allErrors = validatedStatements.collect {
case (statement, Left(error)) => s"Statement: `$statement` is invalid because of $error"
}
if(allErrors.nonEmpty) {
println(s"Statements not valid. All errors:\n - ${allErrors.mkString("\n - ")}")
sys.exit(-1)
}
which produce next example output:
Statements not valid. All errors:
- Statement: `foo =! bar` is invalid because of syntax is not valid
- Statement: `print(foo + bar)` is invalid because of syntax is not valid
If you would like to produce more precise error message, and validate each statement over more then one rule, then cats
Validated
mentioned before might be the option to go. Please, see implementation example below:
import cats.data._
import cats.data.Validated._
import cats.instances.string._
import cats.instances.unit._
import cats.syntax.show._
import cats.syntax.validated._
import cats.syntax.foldable._
type InvalidNec[T] = Invalid[NonEmptyChain[T]]
type ValidatedStatement = (String, ValidatedNec[String, String])
type InvalidStatement = (String, InvalidNec[String])
implicit def listShow[T](implicit show: Show[T]): Show[List[T]] = Show.show(_.map(show.show).mkString("\n"))
implicit val invalidNecShow: Show[InvalidNec[String]] = Show.show(_.e.mkString_("\n - ", "\n - ", ""))
implicit val invalidStatementShow: Show[InvalidStatement] = Show.show {
case (statement, invalid) => show"Statement `$statement` is invalid, because of next errors: $invalid"
}
def validateRule1(statement: String): ValidatedNec[String, Unit] = {
val validCondition: Boolean = false // put validation condition here
if(validCondition) ().validNec else "Statement does not satisfy first rule1".invalidNec
}
def validateRule2(statement: String): ValidatedNec[String, Unit] = {
val validCondition: Boolean = false // put validation condition here
if(validCondition) ().validNec else "Statement does not satisfy first rule2".invalidNec
}
def validateSyntax(statement: String): ValidatedNec[String, String] = {
(validateRule1(statement) combine validateRule2(statement)).map(_ => statement)
}
val myStatements = List("foo =! bar", "print(foo + bar)")
val validatedStatements: List[ValidatedStatement] = myStatements.map(statement => statement -> validateSyntax(statement))
val invalidStatements: List[InvalidStatement] = validatedStatements.collect {
case (statement, invalid: InvalidNec[String]) => statement -> invalid
}
if (invalidStatements.nonEmpty) {
println(show"$invalidStatements")
sys.exit(-1)
}
Which produce next output result (just an example):
Statement `foo =! bar` is invalid, because of next errors:
- Statement does not satisfy first rule1
- Statement does not satisfy first rule2
Statement `print(foo + bar)` is invalid, because of next errors:
- Statement does not satisfy first rule1
- Statement does not satisfy first rule2
Hope this helps!