Created
January 6, 2015 18:35
-
-
Save erikkaplun/187ce9dc144db9812038 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 scala.util.Try | |
import scalaz._, Scalaz._ | |
object InputValidation { | |
type Va[A] = Validation[String, A] | |
type VaNel[A] = ValidationNel[String, A] | |
type Vali[A, B] = Kleisli[Va, A, B] | |
type ValiNel[A, B] = Kleisli[VaNel, A, B] | |
/** Wraps a validator in Kleisli so that it could be piped from/into another Kleisli wrapped validator */ | |
def validator[In, Out](fn: In => Va[Out]): Vali[In, Out] = Kleisli[Va, In, Out](fn) | |
def validatorNel[In, Out](fn: In => VaNel[Out]): ValiNel[In, Out] = Kleisli[VaNel, In, Out](fn) | |
import scalaz.Validation.FlatMap._ | |
// for Kleisli and its >=> | |
implicit val vaBind = new Bind[Va] { | |
def map[A, B](fa: Va[A])(f: A => B): Va[B] = fa.map(f) | |
def bind[A, B](fa: Va[A])(f: A => Va[B]): Va[B] = | |
fa.flatMap(f) | |
} | |
implicit val vaNelBind = new Bind[VaNel] { | |
def map[A, B](fa: VaNel[A])(f: A => B): VaNel[B] = fa.map(f) | |
def bind[A, B](fa: VaNel[A])(f: A => VaNel[B]): VaNel[B] = | |
fa.flatMap(f) | |
} | |
import shapeless._, contrib.scalaz._, syntax.std.tuple._ | |
import syntax.std.function._, ops.function._ | |
import shapeless.ops.hlist._ | |
private object toValidationNel extends Poly1 { | |
implicit def apply1[T] = at[Va [T]](_.toValidationNel) | |
implicit def apply2[T] = at[VaNel[T]](identity) | |
} | |
/** | |
* Usage: | |
* | |
* val postal = "12345".some | |
* val country = "US".some | |
* | |
* val params = ( | |
* postal |> nonEmpty("postal is required"), | |
* country |> nonEmpty("country is required") >=> validCountry("country must be a valid 2 letter ISO country code") | |
* ) | |
* | |
* withValidation(params) { (postal: String, country: String) => | |
* println(s"postal = $postal, country = $country") | |
* } | |
*/ | |
def withValidation[P <: Product, F, L1 <: HList, L2 <: HList, L3 <: HList, R](params: P)(block: F)( | |
implicit | |
gen: Generic.Aux[P, L1], | |
mp: Mapper.Aux[toValidationNel.type, L1, L2], | |
seq: Sequencer.Aux[L2, VaNel[L3]], | |
fn: FnToProduct.Aux[F, L3 => R] | |
): VaNel[R] = { | |
sequence(gen.to(params).map(toValidationNel)).map(block.toProduct) | |
} | |
object Validations { | |
/** Type class for types that can be parsed from string */ | |
trait Read[T] { def parseOpt(raw: String): Option[T] } | |
object Read { | |
implicit val canParseInt = new Read[Int] { | |
def parseOpt(raw: String) = | |
(raw matches raw"\d+") option raw.toInt | |
} | |
implicit val canParseDouble = new Read[Double] { | |
def parseOpt(raw: String) = | |
(raw matches raw"(\d+)*\.(\d+)") option raw.toDouble | |
} | |
implicit val canParseBoolean = new Read[Boolean] { | |
def parseOpt(raw: String) = raw.toLowerCase |> { raw => | |
(raw matches raw"0|1|true|false|on|off|yes|no") option (Set("1", "true", "on", "yes") contains raw) | |
} | |
} | |
import reactivemongo.bson.BSONObjectID | |
implicit val canParseObjectId = new Read[BSONObjectID] { | |
def parseOpt(raw: String) = | |
BSONObjectID.parse(raw).toOption | |
} | |
} | |
def canParse[T: Read](msg: String) = validator { raw: String => | |
implicitly[Read[T]].parseOpt(raw).toSuccess(msg) | |
} | |
import scala.language.higherKinds | |
/** Maps from Kleisli[M, A, B] to Kleisli[M, F[A], F[B]] */ | |
def traverseKleisli[M[_]: Applicative, F[_]: Traverse, A, B](k: Kleisli[M, A, B]) = | |
Kleisli[M, F[A], F[B]](k.traverse) | |
/** Usage: | |
* val params = ( | |
* offset |> optional(isIntegral("offset must be integral if set")), | |
* limit |> optional(isIntegral("limit must be integral if set")) | |
* ) | |
*/ | |
def optional[M[_]: Applicative, A, B](fn: Kleisli[M, A, B]) = | |
traverseKleisli[M, Option, A, B](fn) | |
/** just an alias for `_.toSuccess(msg)` to complement `optional` */ | |
def required[T](msg: String) = validator { opt: Option[T] => | |
opt.toSuccess(msg) | |
} | |
def nonEmptyString(msg: String) = validator { str: String => | |
(str.size > 0 option str).toSuccess(msg) | |
} | |
def default[T](value: T) = validator { raw: Option[T] => | |
(raw | value).success[String] | |
} | |
def defaultNel[T](value: T) = validatorNel { raw: Option[T] => | |
(raw | value).success[String].toValidationNel | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment