Skip to content

Instantly share code, notes, and snippets.

@teigen
Created June 16, 2012 16:37
Show Gist options
  • Save teigen/2941885 to your computer and use it in GitHub Desktop.
Save teigen/2941885 to your computer and use it in GitHub Desktop.
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()))
}
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