Skip to content

Instantly share code, notes, and snippets.

@IainHull
Created May 11, 2014 00:01
Show Gist options
  • Save IainHull/f44c497998303329a382 to your computer and use it in GitHub Desktop.
Save IainHull/f44c497998303329a382 to your computer and use it in GitHub Desktop.
sealed trait Validation[A] {
def map[B](f: A => B): Validation[B] = this match {
case Success(a) => Success(f(a))
case Failures(errors) => Failures(errors)
}
def flatMap[B](f: A => Validation[B]): Validation[B] = this match {
case Success(a) => f(a)
case Failures(errors) => Failures(errors)
}
def filter(p: A => Boolean) = this match {
case Success(a) => if (p(a)) this else Failures(s"Predicate does not hold for '${a}'")
case Failures(errors) => Failures(errors)
}
def && [B, C](b: Validation[B])(implicit builder: ValidationBuilder[A, B, C]): Validation[C] = {
builder.and(this, b)
}
}
case class Success[A](a: A) extends Validation[A]
case class Failures[A](errors: List[String]) extends Validation[A]
object Failures {
def apply[A](error: String): Failures[A] = Failures(List(error))
}
case class ValidationBuilder[A, B, C](join: (A, B) => C) {
def and(a: Validation[A], b: Validation[B]): Validation[C] = {
(a, b) match {
case (Success(a), Success(b)) => Success(join(a, b))
case (Failures(e1), Failures(e2)) => Failures(e1 ++ e2)
case (Failures(e1), Success(_)) => Failures(e1)
case (Success(_), Failures(e2)) => Failures(e2)
}
}
}
trait NonTuppleValidationBuilder {
implicit def nonProduct[A, B]: ValidationBuilder[A, B, (A, B)] = ValidationBuilder {
(a: A, b: B) => (a, b)
}
}
trait TuppleValidationBuilder extends NonTuppleValidationBuilder {
implicit def a1_b2[A, B1, B2]: ValidationBuilder[A, (B1, B2), (A, B1, B2)] = ValidationBuilder {
(a: A, b: (B1, B2)) => (a, b._1, b._2)
}
implicit def a1_b3[A, B1, B2, B3]: ValidationBuilder[A, (B1, B2, B3), (A, B1, B2, B3)] = ValidationBuilder {
(a: A, b: (B1, B2, B3)) => (a, b._1, b._2, b._3)
}
implicit def a1_b4[A, B1, B2, B3, B4]: ValidationBuilder[A, (B1, B2, B3, B4), (A, B1, B2, B3, B4)] = ValidationBuilder {
(a: A, b: (B1, B2, B3, B4)) => (a, b._1, b._2, b._3, b._4)
}
implicit def a2_b1[A1, A2, B]: ValidationBuilder[(A1, A2), B, (A1, A2, B)] = ValidationBuilder {
(a: (A1, A2), b: B) => (a._1, a._2, b)
}
implicit def a3_b1[A1, A2, A3, B]: ValidationBuilder[(A1, A2, A3), B, (A1, A2, A3, B)] = ValidationBuilder {
(a: (A1, A2, A3), b: B) => (a._1, a._2, a._3, b)
}
implicit def a4_b1[A1, A2, A3, A4, B]: ValidationBuilder[(A1, A2, A3, A4), B, (A1, A2, A3, A4, B)] = ValidationBuilder {
(a: (A1, A2, A3, A4), b: B) => (a._1, a._2, a._3, a._4, b)
}
}
object ValidationBuilder extends TuppleValidationBuilder
trait Method
object Get extends Method
object Post extends Method
object Test {
def parseMethod(name: String): Validation[Method] = {
name.toUpperCase match {
case "GET" => Success(Get)
case "POST" => Success(Post)
case _ => Failures(s"Not a valid http method: ${name}")
}
}
def parsePath[A](supportedPaths: Map[String, A])(path: String): Validation[A] = {
if (!path.startsWith("/")) {
Failures(s"Path '${path}' does not start with a '/'")
} else if (path.endsWith("/")) {
Failures(s"Path '${path}' does not start with a '/'")
} else {
supportedPaths.get(path).map(Success(_)).getOrElse(Failures(s"Path '$path' is not supported"))
}
}
val testPaths = Map("/one" -> 1, "/two" -> 2, "/three" -> 3)
def validatePathMethod(op: Int, m: Method): Validation[Int] = {
(op, m) match {
case (1, Get) => Success(1)
case (2, Post) => Success(2)
case (3, _) => Success(3)
case _ => Failures(s"Operation '${op}' does not support method '${m}'")
}
}
def validate(method: String, path: String): Validation[Int] = {
for {
(m, o) <- parseMethod(method) && parsePath(testPaths)(path)
_ <- validatePathMethod(o, m)
} yield o
}
def main(args: Array[String]) = {
println(validate("foo", "bar")) // Failures(List(Not a valid http method: foo, Path 'bar' does not start with a '/'))
println(validate("GET", "bar")) // Failures(List(Path 'bar' does not start with a '/'))
println(validate("GET", "/one")) // Success(1)
println(validate("GET", "/two")) // Failures(List(Operation '2' does not support method 'Get$@35e146ca'))
println(validate("POST", "/two")) // Success(2)
println(validate("GET", "/three")) // Success(3)
println(validate("POST", "/three")) // Success(3)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment