Skip to content

Instantly share code, notes, and snippets.

@ambantis
Last active August 9, 2018 14:39
Show Gist options
  • Save ambantis/99804354ef65123209239572ce40f8ab to your computer and use it in GitHub Desktop.
Save ambantis/99804354ef65123209239572ce40f8ab to your computer and use it in GitHub Desktop.
Data validation using just Scala
/* sealed */ trait Color {
def isWarm: Boolean
def isCool: Boolean = !isWarm
}
object Color {
case object Red extends Color { val isWarm = true }
case object Yellow extends Color { val isWarm = true }
case object Orange extends Color { val isWarm = false }
case object Green extends Color { val isWarm = false }
case object Blue extends Color { val isWarm = false }
case object Purple extends Color { val isWarm = false }
}
object PersonTests {
def validName(name: String): Boolean = name.trim.nonEmpty
def validAge(age: Int): Boolean = age > 0 && age < 150
def isCool(color: Color): Boolean = color.isCool
def errors(name: String, age: Int, favorite: Color): List[Throwable] = {
val builder = List.newBuilder[Throwable]
if (!validName(name)) builder += new IllegalArgumentException("invalid name!")
if (!validAge(age)) builder += new IllegalArgumentException("invalid age!")
if (!isCool(favorite)) builder += new IllegalArgumentException("not cool!")
builder.result()
}
}
/** Basic validation with require statements
*
* This is really good for asserting conditions, but can be tricky to use,
* and if there are multiple errors, only the first will be thrown.
*/
final case class Person1(name: String, age: Int, favorite: Color) {
import PersonTests.{isCool, validAge, validName}
require(validName(name), "invalid name")
require(validAge(age), "invalid age")
require(isCool(favorite), "not cool")
}
/** Better validation with scala.util.Try
*
* The constructor is private; you can only create a person via `tryPerson`,
* but if there are multiple errors, only the first will be captured.
*
* Note: making the case class abstract makes the constructor private outside of
* this file.
*/
abstract case class Person2(name: String, age: Int, favorite: Color) {
import PersonTests.{isCool, validAge, validName}
require(validName(name), "invalid name")
require(validAge(age), "invalid age")
require(isCool(favorite), "not cool")
}
object Person2 {
import scala.util.Try
def tryPerson(name: String, age: Int, favorite: Color): Try[Person2] =
Try(new Person2(name, age, favorite) {})
}
/** Even better validation with scala Either.
*
* This will capture all the errors, but working with Either in 2.11 can be a bit
* clunky and capturing all the errors needs low level code.
*/
abstract case class Person3(name: String, age: Int, favorite: Color)
object Person3 {
def eitherPerson(name: String, age: Int, favorite: Color): Either[List[Throwable], Person3] =
PersonTests.errors(name, age, favorite) match {
case Nil => Right(new Person3(name, age, favorite) {})
case errors => Left(errors)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment