Skip to content

Instantly share code, notes, and snippets.

@davegurnell
Created May 28, 2014 15:03
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 3 You must be signed in to fork a gist
  • Save davegurnell/b88bb34eb743793ebaa6 to your computer and use it in GitHub Desktop.
Save davegurnell/b88bb34eb743793ebaa6 to your computer and use it in GitHub Desktop.
Simple example of a functional data validation using combinators
import scala.util.{ Try, Success, Failure }
case class ValidationError(field: Seq[String], message: String) {
def prefix(name: String) = this.copy(field = name +: field)
}
def validator[A](func: A => Seq[ValidationError]): Validator[A] =
new Validator[A] {
def apply(value: A) = func(value)
}
trait Validator[A] extends (A => Seq[ValidationError]) {
def and(that: Validator[A]) = validator[A] { value =>
this(value) ++ that(value)
}
def orElse(that: Validator[A]) = validator[A] { value =>
this(value) match {
case Seq() => that(value)
case other => other
}
}
}
def pass =
Seq.empty[ValidationError]
def fail(message: String) =
Seq(ValidationError(Seq.empty, message))
def lte(minValue: Int) = validator[Int] { value =>
if (value <= minValue) pass else fail(s"$value must be <= $minValue")
}
def gte(maxValue: Int) = validator[Int] { value =>
if (value >= maxValue) pass else fail(s"$value must be >= $maxValue")
}
def nonEmpty = validator[String] { value =>
if (value.isEmpty) fail("must not be empty") else pass
}
def field[A, B](field: String, func: A => B)(implicit inner: Validator[B]) = validator[A] { value =>
inner(func(value)).map(_ prefix field)
}
case class Address(street: String, city: String)
case class Person(name: String, age: Int, address: Address)
implicit val addressOk =
field("street", (a: Address) => a.street) {
nonEmpty
} and
field("city", (a: Address) => a.city) {
nonEmpty
}
implicit val personOk =
field("name", (p: Person) => p.name) {
nonEmpty
} and
field("age", (p: Person) => p.age) {
gte(0)
} and
field("address", (p: Person) => p.address)
personOk(Person("", -1, Address("", "")))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment