Skip to content

Instantly share code, notes, and snippets.

@AitorATuin
Last active December 15, 2015 05:29
Show Gist options
  • Select an option

  • Save AitorATuin/5209043 to your computer and use it in GitHub Desktop.

Select an option

Save AitorATuin/5209043 to your computer and use it in GitHub Desktop.
Validator and Validables in Scala
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)
}
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)}
}
}
}
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