Created
June 16, 2012 16:37
-
-
Save teigen/2941885 to your computer and use it in GitHub Desktop.
This file contains hidden or 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
import validation._ | |
object Demo extends App { | |
object validateMaps extends FormValidations[Map[String, String]](_.get) | |
import validateMaps._ | |
def int(s:String) = try{ Right(s.toInt) } catch { case _ => Left("required.int") } | |
def positiveInt(i:Int) = if(i > 0) Right(i) else Left("required.positive") | |
case class Data(a:Int, b:Int) | |
val data = | |
Validator.ok(Data.apply _ curried) <*> | |
Field("a").is(int) <*> | |
Field("b").is(int).is(positiveInt) | |
println(Field("b").is(int).is(positiveInt)(Map("b" -> "-4"))) | |
println(data(Map("a" -> "1", "b" -> "2"))) | |
println(data(Map("a" -> "x", "b" -> "-4"))) | |
println(data(Map())) | |
} |
This file contains hidden or 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
package validation | |
trait Validations[X]{ | |
object Validator { | |
def pure[A](validation:Validation[A]) = Validator(_ => validation) | |
def ok[A](value:A) = pure(Ok(value)) | |
} | |
case class Validator[A](run:X => Validation[A]) extends (X => Validation[A]){ | |
def apply(x:X) = run(x) | |
def map[B](f:A => B) = Validator[B](run(_).map(f)) | |
def flatMap[B](f:A => Validator[B]) = Validator[B](x => run(x).flatMap(a => f(a)(x))) | |
def <*>[B,C](next:Validator[B])(implicit ev:A <:< (B => C)) = Validator[C](x => run(x) <*> next(x)) | |
def | [B >: A](next: => Validator[B]) = Validator(x => run(x) orElse next(x)) | |
} | |
} | |
class FormValidations[X](field:X => String => Option[String]) extends Validations[X]{ | |
object Field { | |
implicit def validator[A](f:Field[A]) = Validator(f.validator) | |
def apply(name:String) = new Field[String](name, field(_)(name).map(Ok(_)).getOrElse(Fail.input(name, "", "required.input"))) | |
} | |
class Field[A](val name:String, val validator:X => Validation[A]){ | |
def is[B](f:A => Either[String, B]) = | |
new Field(name, x => validator(x).flatMap(a => f(a).fold(fail => Fail.input(name, field(x)(name).getOrElse(""), fail), Ok(_)))) | |
} | |
} | |
sealed trait Validation[+A]{ | |
def map[B](f:A => B):Validation[B] = this match { | |
case Ok(value) => Ok(f(value)) | |
case f:Fail => f | |
} | |
def flatMap[B](f:A => Validation[B]):Validation[B] = this match { | |
case Ok(value) => f(value) | |
case f:Fail => f | |
} | |
def <*> [B, C](next:Validation[B])(implicit ev:A <:< (B => C)):Validation[C] = (this, next) match { | |
case (Ok(f), Ok(b)) => Ok(f(b)) | |
case (Fail(left), Fail(right)) => Fail(left ::: right) | |
case (f:Fail, _) => f | |
case (_, f:Fail) => f | |
} | |
def orElse[B >: A](next: => Validation[B]) = if(isSuccess) this else next | |
def isSuccess = this match { | |
case _:Fail => false | |
case _ => true | |
} | |
} | |
object Fail { | |
def input(key:String, value:String, message:String) = Fail(List(InputError(key, value, message))) | |
} | |
case class Fail(fails:List[InputError]) extends Validation[Nothing] | |
case class Ok[A](value:A) extends Validation[A] | |
case class InputError(key:String, value:String, message:String) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment