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

validation.Rule.scala Maven / Gradle / Ivy

The newest version!
package ltbs.uniform
package validation

import cats.implicits._
import cats.Order
import cats.data.Validated

object Rule extends Quantifiable.ToQuantifiableOps {

  def error(errorMsg: String, args: Any*): ErrorTree =
    ErrorTree.oneErr(ErrorMsg(errorMsg, args:_*))

  /** A custom rule from a predicate and an error tree */  
  def condError[A](predicate: A => Boolean, errorTree: ErrorTree): Rule[A] = {
    a: A => Validated.cond(predicate(a), a, errorTree)
  }

  /** A custom rule from a predicate and an error message */
  def cond[A](predicate: A => Boolean, errorMsg: String, args: Any*): Rule[A] =
    condError(predicate, error(errorMsg, args: _*))

  /** A custom rule from a predicate and an error message to be
    * applied to a supplied path */
  def condAtPath[A](pathHead: String, pathTail: String*)(predicate: A => Boolean, errorMsg: String, args: Any*): Rule[A] =
    condError(predicate, error(errorMsg, args: _*).prefixWithMany(pathHead :: pathTail.toList))

  case class MinLength[A: Quantifiable] private (len: Int, errorMsg: String) extends Rule[A] {
    def apply(in: A): Validated[ErrorTree, A] =
      Validated.cond(in.qty >= len, in, error(errorMsg, in.qty, len))
  }

  /** Assert that there are at least `len` elements */  
  def minLength[A: Quantifiable](len: Int, errorMsg: String = "minLength"): Rule[A] =
    MinLength[A](len, errorMsg)

  case class MaxLength[A: Quantifiable] private (len: Int, errorMsg: String) extends Rule[A] {
    def apply(in: A): Validated[ErrorTree, A] =
      Validated.cond(in.qty <= len, in, error(errorMsg, in.qty, len))
  }

  /** Assert that there are no more than `len` elements */
  def maxLength[A: Quantifiable](len: Int, errorMsg: String = "maxLength"): Rule[A] =
    MaxLength[A](len, errorMsg)

  case class AlwaysPass[A] private () extends Rule[A] {
    def apply(in: A): Validated[ErrorTree, A] =
      Validated.Valid(in)
  }

  /** The simplest validation rule - accepts any value */
  def alwaysPass[A]: Rule[A] = AlwaysPass[A]()

  case class AlwaysFail[A] private () extends Rule[A] {
    def apply(in: A): Validated[ErrorTree, A] =
      Validated.Invalid(error("impossible-to-continue"))
  }

  /** The simplest validation rule - accepts any value */
  def alwaysFail[A]: Rule[A] = AlwaysFail[A]()


  case class LengthBetween[A: Quantifiable] private (min: Int, max: Int) extends Rule[A] {
    def apply(in: A): Validated[ErrorTree, A] =
      minLength[A](min).apply(in) andThen (maxLength[A](max).apply(_))
  }

  /** Assert that there are at least `len` elements, but no more than `max` */
  def lengthBetween[A: Quantifiable](min: Int, max: Int): Rule[A] = LengthBetween[A](min, max)

  case class NonEmpty[A: Empty] private (errorMsg: String) extends Rule[A] {
    def apply(in: A): Validated[ErrorTree, A] =
      Validated.cond(!in.isEmpty, in, error(errorMsg))
  }

  /** Assert that the input is not empty (in the sense of a monoid being 'empty') */
  def nonEmpty[A: Empty](errorMsg: String): Rule[A] = NonEmpty[A](errorMsg)

  /** Assert that the input is not empty (in the sense of a monoid being 'empty') */  
  def nonEmpty[A: Empty]: Rule[A] = nonEmpty("required")

  case class MatchesRegex private (regex: String, errorMsg: String) extends Rule[String] {
    def apply(in: String): Validated[ErrorTree, String] =
      Validated.cond(in matches regex, in, error(errorMsg))
  }

  /** Assert that the input string matches the given regular expression  */  
  def matchesRegex(regex: String, errorMsg: String = "format"): Rule[String] =
    MatchesRegex(regex, errorMsg)

  case class Min[A: Order] private (minValue: A, errorMsg: String) extends Rule[A]{
    def apply(in: A): Validated[ErrorTree, A] =
      Validated.cond(in >= minValue, in, error(errorMsg))
  }

  /** Assert that the input value must come after, or in the same
    * position as, `minValue` if the elements were put in order */
  def min[A: Order](minValue: A, errorMsg: String = "min"): Rule[A] =
    Min[A](minValue, errorMsg)

  case class Max[A: Order] private (maxValue: A, errorMsg: String) extends Rule[A]{
    def apply(in: A): Validated[ErrorTree, A] =
      Validated.cond(in <= maxValue, in, error(errorMsg))
  }

  /** Assert that the input value must come before, or in the same
    * position as, `maxValue` if the elements were put in order */  
  def max[A: Order](maxValue: A, errorMsg: String = "max"): Rule[A] =
    Max[A](maxValue, errorMsg)

  case class Between[A: Order] private (minValue: A, maxValue: A) extends Rule[A] {
    def apply(in: A): Validated[ErrorTree, A] =
      min(minValue).apply(in) andThen (max(maxValue).apply(_))
  }

  /** Assert that the input value must come after, or in the same
    * position as, `minValue` but before or in the same position as
    * `maxValue` if the elements were put in order.
    */
  def between[A: Order](minValue: A, maxValue: A): Rule[A] =
    Between[A](minValue, maxValue)

  case class ForEachInList[A](inner: Rule[A]) extends Rule[List[A]] {
    def apply(in: List[A]): Validated[ErrorTree, List[A]] =
      in.map { inner }.sequence
  }

  def forEachInList[A](inner: Rule[A]): Rule[List[A]] =
    ForEachInList[A](inner)

  case class In[A](allowed: Seq[A], errorMsg: String) extends Rule[A] {
    def apply(in: A): Validated[ErrorTree, A] =
      Validated.cond(allowed.contains(in), in, error(errorMsg))
  }

  def in[A](allowed: Seq[A], errorMsg: String = "not-in-allowed-list"): Rule[A] =
    In(allowed, errorMsg)

  case class NotIn[A](disallowed: Seq[A], errorMsg: String) extends Rule[A] {
    def apply(in: A): Validated[ErrorTree, A] =
      Validated.cond(!disallowed.contains(in), in, error(errorMsg))
  }

  def notIn[A](disallowed: Seq[A], errorMsg: String = "in-disallowed-list"): Rule[A] =
    NotIn(disallowed, errorMsg)


}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy