Skip to content

Instantly share code, notes, and snippets.

@erikkaplun
Created January 6, 2015 18:35
Show Gist options
  • Save erikkaplun/187ce9dc144db9812038 to your computer and use it in GitHub Desktop.
Save erikkaplun/187ce9dc144db9812038 to your computer and use it in GitHub Desktop.
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