All Downloads are FREE. Search and download functionalities are using the official Maven repository.

validation.package.scala Maven / Gradle / Ivy

The newest version!
package ltbs.uniform

import cats.implicits._
import cats.Monoid
import cats.data.Validated

/** Validation and data transformation capabilities used in
  * uniform. 
  */
package object validation
    extends validation.Compat
    with Quantifiable.ToQuantifiableOps
    with QuantifiableInstances
    with Empty.ToEmptyOps
    with EmptyInstances
{
  type Transformation[A, B] = A => Validated[ErrorTree, B]

  /** A validation rule used to check input data. */
  type Rule[A] = Transformation[A,A]

  implicit def ruleMonoidInstance[A] = new Monoid[Rule[A]] {
    def empty: Rule[A] = Rule.alwaysPass[A]
   
    def combine(x: Rule[A],y: Rule[A]): Rule[A] = new Rule[A] {
      import Validated.{Valid, Invalid}
      def apply(in: A) = (x.apply(in),y.apply(in)) match {
        case (Valid(_), Valid(_)) => Valid(in)
        case (Invalid(e), Valid(_)) => Invalid(e)
        case (Valid(_), Invalid(e)) => Invalid(e)
        case (Invalid(e1), Invalid(e2)) => Invalid(e1 |+| e2)
      }
    }
  }

  implicit class RichTransformation[A,B](transformation: Transformation[A,B]) {

    /** check the input and return a Left(ErrorTree) if there is an
      * error or a Right(a) if the data is valid
      */    
    def either(in: A): Either[ErrorTree, B] = transformation.apply(in).toEither

    /** compose a new validation transformation by chaining two together in
      * sequence. The second Transformation will not be executed unless the
      * first passes.
      */
    def followedBy[C](transformationB: Transformation[B,C]): Transformation[A,C] =
      transformation(_) andThen transformationB
  }

  implicit class RichRule[A](rule: Rule[A]) {
    def either(in: A): Either[ErrorTree, A] = rule.apply(in).toEither

    /** compose a new validation rule by chaining two together in
      * sequence. The second Rule will not be executed unless the
      * first passes.
      */
    def followedBy(ruleB: Rule[A]): Rule[A] = FollowedByRule(rule, ruleB)

    /** compose a new validation rule by running two together in
      * parallel. The second Rule will be executed regardless of 
      * if the first passes and any errors aggregated.
      */
    def alongWith(ruleB: Rule[A]): Rule[A] = AlongWithRule(rule, ruleB)    

    /** In the event that the validation is some form of aggregation
      * (such as [[FollowedByRule]] or [[AlongWithRule]]) split it out into all of 
      * its non-aggregate components. 
      * 
      * If the rule is a normal rule return a singleton list with that rule. 
      */
    def subRules: List[Rule[A]] = {
      @annotation.tailrec
      def inner(normal: List[Rule[A]], unsorted: List[Rule[A]]): List[Rule[A]] = {
        unsorted match {
          case (FollowedByRule(a,b)::xs) => inner(normal, a :: b :: xs)
          case (AlongWithRule(a,b)::xs) => inner(normal, a :: b :: xs)
          case x::xs => inner(x::normal, xs)
          case Nil => normal
        }
      }

      inner(Nil, List(rule))
    }
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy