Created
May 11, 2014 00:01
-
-
Save IainHull/f44c497998303329a382 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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