Last active
December 15, 2015 05:29
-
-
Save AitorATuin/5209043 to your computer and use it in GitHub Desktop.
Validator and Validables in Scala
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 com.logikujo.form | |
| import com.logikujo.Validator._ | |
| import scalaz._ | |
| import Scalaz._ | |
| object Form extends App { | |
| def toInt(s:String) = try { | |
| s.toInt.some | |
| } catch { | |
| case _:Throwable => None | |
| } | |
| def nameInDb(s:String):Boolean = s match { | |
| case "Carlos" => true | |
| case _ => false | |
| } | |
| def isSecure(p:String) = true | |
| def showAccountOrError(acc:List[String] \/ Account) = acc.fold( | |
| { | |
| l => println("Couldnt create account:"); | |
| l.foreach(e => println(" [ERROR]: " + e)) | |
| }, | |
| {_ => println("Account created successfull")}) | |
| case class Account(age:Int, name:String, password:String) | |
| // Age Validator | |
| val isInt = (a:String) => toInt(a). | |
| ?(none[List[String]]). | |
| |(List("Age must be an integer.").some) | |
| // In order to suppor this, we need to make Validator a Monad or maybe Functor ?! | |
| val greater18 = (a:Int) => (a <= 18). | |
| option(List("Your age must be above the legal one.")) | |
| // Name Account Validator | |
| val existName = (s:String) => nameInDb(s). | |
| option(List("User name exists yet.")) | |
| // Password Validator | |
| val passwordSize = (s:String) => (s.size < 10). | |
| option(List("Password is too short.")) | |
| val passwordSecurity = (s:String) => (!isSecure(s)). | |
| option(List("Password is too insecure, please choose another one.")) | |
| // Implicit Validators, may be defined and passed explicitly. | |
| implicit val vAge = isInt.validator | |
| implicit val vPassword = passwordSize.validator <=< passwordSecurity.validator | |
| // Using Applicative Builders (|@|) over Validation[List[String],Account] | |
| val account1 = (("30".validate(vAge).validation ∘ (_.toInt)) | |
| |@| "Carlos".validate(existName.validator).validation | |
| |@| "1234".validate(vPassword).validation) {Account(_,_,_)} | |
| showAccountOrError(account1.disjunction) | |
| // Using Applicative Functors (<*>) over Validation[List[String],Account] | |
| val account2 = "1234".validate(vPassword).validation <*> ( | |
| "Carlos".validate(existName.validator).validation <*> ( | |
| ("30".validate(vAge).validation ∘ (_.toInt)) map Account.curried)) | |
| showAccountOrError(account2.disjunction) | |
| // Using monad over \/. Ahggg, monad doesnt concatenate error Strings, | |
| // as expected just fails on first error. | |
| val account3 = for { | |
| age <- "30".validate(vAge) ∘ (_.toInt) | |
| name <- "Carlos".validate(existName.validator) | |
| pass <- "1234".validate(vPassword) | |
| } yield Account(age,name,pass) | |
| showAccountOrError(account3) | |
| } |
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 com.logikujo | |
| import scalaz._ | |
| import Scalaz._ | |
| import Validator._ | |
| object Test extends App { | |
| val greater18 = (a:Int) => (a <= 18). | |
| option(List("You must have the legal age!")) | |
| val lowerM = ((m:Int,a:Int) => (a >= m). | |
| option(List("You must be eager than the 25 years old"))).curried | |
| implicit val validatorInt = greater18.validator <=< lowerM(25).validator | |
| val ages = List(5,10,15,20,25,30) | |
| (ages zip ages ∘ (a => (~a.validate) | (List()))) foreach { | |
| case (age, Nil) => println("Validating age" + age) | |
| case (age, l) => { | |
| println("Validating age: " + age) | |
| l foreach { s => println(" [ERROR]: " + s)} | |
| } | |
| } | |
| } |
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 com.logikujo | |
| import scalaz._ | |
| import Scalaz._ | |
| import syntax._ | |
| import scala.language.implicitConversions | |
| package object Validator { | |
| trait ValidatorIdOps[A] extends Ops[Validator[A]] { | |
| def validator: Validator[A] = self | |
| } | |
| trait Validator[A] { | |
| val validator:A=>Option[List[String]] | |
| def run(a:A) = ~(validator(a) toSuccess a).disjunction | |
| def apply(a:A):List[String] \/ A = run(a) | |
| def compose(other:Validator[A]):Validator[A] = | |
| Validator((a:A) => validator(a) |+| other.validator(a)) | |
| def <=<(other:Validator[A]):Validator[A] = compose(other) | |
| } | |
| object Validator { | |
| def apply[A](f:A => Option[List[String]]) = new Validator[A] { | |
| val validator = f | |
| } | |
| def validate[A](a:A)(implicit v:Validator[A]) = v.run(a) | |
| } | |
| trait ValidableIdOps[A] extends Ops[Validable[A]] { | |
| def validable:Validable[A] = self | |
| } | |
| trait Validable[A] { | |
| val value:A | |
| def validate(implicit v:Validator[A]) = v.run(v alue) | |
| def apply(implicit v:Validator[A]) = validate(v) | |
| } | |
| object Validable { | |
| def apply[A](a:A):Validable[A] = new Validable[A] { val value = a } | |
| } | |
| implicit def ValidatorFunc[A](f:A => Option[String]):A => Option[List[String]] = | |
| f andThen (_ ∘ (List(_))) | |
| implicit def ToValidable[A](a:A) = Validable(a) | |
| implicit def ToValidatorIdOps[A](f:A => Option[List[String]]) = | |
| new ValidatorIdOps[A] { | |
| def self = Validator(f) | |
| } | |
| implicit def ToValidableIdOps[A](a:A) = new ValidableIdOps[A] { | |
| def self = Validable(a) | |
| } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment