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

play.api.data.mapping.Rule.scala Maven / Gradle / Ivy

The newest version!
package play.api.data.mapping

trait RuleLike[I, O] {
  /**
   * Apply the Rule to `data`
   * @param data The data to validate
   * @return The Result of validating the data
   */
  def validate(data: I): VA[O]
}

object RuleLike {
  implicit def zero[O]: RuleLike[O, O] = Rule[O, O](Success.apply)
}

/**
 * A Rule is
 */
trait Rule[I, O] extends RuleLike[I, O] {

  /**
   * Compose two Rules
   * {{{
   *   val r1: Rule[JsValue, String] = // implementation
   *   val r2: Rule[String, Date] = // implementation
   *   val r = r1.compose(r2)
   *
   * }}}
   * @param path a prefix for the errors path if the result is a `Failure`
   * @param sub the second Rule to apply
   * @return The combination of the two Rules
   */
  def compose[P](path: Path)(sub: => RuleLike[O, P]): Rule[I, P] =
    this.flatMap { o => Rule(_ => sub.validate(o)) }.repath(path ++ _)

  def flatMap[B](f: O => Rule[I, B]): Rule[I, B] =
    Rule { d =>
      this.validate(d)
        .map(f)
        .fold(
          es => Failure(es),
          r => r.validate(d))
    }

  /**
   * Create a new Rule that try `this` Rule, and apply `t` if it fails
   * {{{
   *   val rb: Rule[JsValue, A] = From[JsValue]{ __ =>
   *     ((__ \ "name").read[String] ~ (__ \ "foo").read[Int])(B.apply _)
   *   }
   *
   *   val rc: Rule[JsValue, A] = From[JsValue]{ __ =>
   *     ((__ \ "name").read[String] ~ (__ \ "bar").read[Int])(C.apply _)
   *   }
   *   val rule = rb orElse rc orElse Rule(_ => typeFailure)
   * }}}
   * @param t an alternative Rule
   * @return a Rule
   */
  def orElse[OO >: O](t: => RuleLike[I, OO]): Rule[I, OO] =
    Rule(d => this.validate(d) orElse t.validate(d))

  // would be nice to have Kleisli in play
  def compose[P](sub: => RuleLike[O, P]): Rule[I, P] = compose(Path)(sub)
  def compose[P](m: Mapping[ValidationError, O, P]): Rule[I, P] = compose(Rule.fromMapping(m))

  /**
   * Create a new Rule the validate `this` Rule and `r2` simultaneously
   * If `this` and `r2` both fail, all the error are returned
   * {{{
   *   val valid = Json.obj(
   *      "firstname" -> "Julien",
   *      "lastname" -> "Tournay")
   *   val composed = notEmpty |+| minLength(3)
   *   (Path \ "firstname").read(composed).validate(valid) // Success("Julien")
   *  }}}
   */
  def |+|[OO <: O](r2: RuleLike[I, OO]) = Rule[I, O] { v =>
    (this.validate(v) *> r2.validate(v)).fail.map {
      _.groupBy(_._1).map {
        case (path, errs) =>
          path -> errs.flatMap(_._2)
      }.toSeq
    }
  }

  /**
   * This methods allows you to modify the Path of errors (if the result is a Failure) when aplying the Rule
   */
  def repath(f: Path => Path): Rule[I, O] =
    Rule { d =>
      this.validate(d).fail.map {
        _.map {
          case (p, errs) => f(p) -> errs
        }
      }
    }

}

object Rule {
  import scala.language.experimental.macros

  def gen[I, O]: Rule[I, O] = macro MappingMacros.rule[I, O]

  /**
   * Turn a `A => Rule[B, C]` into a `Rule[(A, B), C]`
   * {{{
   *   val passRule = From[JsValue] { __ =>
   *      ((__ \ "password").read(notEmpty) ~ (__ \ "verify").read(notEmpty))
   *        .tupled.compose(Rule.uncurry(Rules.equalTo[String]).repath(_ => (Path \ "verify")))
   *    }
   * }}}
   */
  def uncurry[A, B, C](f: A => Rule[B, C]): Rule[(A, B), C] =
    Rule { case (a, b) => f(a).validate(b) }

  import play.api.libs.functional._

  implicit def zero[O] = toRule(RuleLike.zero[O])

  def apply[I, O](m: Mapping[(Path, Seq[ValidationError]), I, O]) = new Rule[I, O] {
    def validate(data: I): VA[O] = m(data)
  }

  def toRule[I, O](r: RuleLike[I, O]) = new Rule[I, O] {
    def validate(data: I): VA[O] = r.validate(data)
  }

  def fromMapping[I, O](f: Mapping[ValidationError, I, O]) =
    Rule[I, O](f(_: I).fail.map(errs => Seq(Path -> errs)))

  implicit def applicativeRule[I] = new Applicative[({ type λ[O] = Rule[I, O] })#λ] {
    override def pure[A](a: A): Rule[I, A] =
      Rule(_ => Success(a))

    override def map[A, B](m: Rule[I, A], f: A => B): Rule[I, B] =
      Rule(d => m.validate(d).map(f))

    override def apply[A, B](mf: Rule[I, A => B], ma: Rule[I, A]): Rule[I, B] =
      Rule { d =>
        val a = ma.validate(d)
        val f = mf.validate(d)
        (f *> a).viaEither { _.right.flatMap(x => f.asEither.right.map(_(x))) }
      }
  }

  implicit def functorRule[I] = new Functor[({ type λ[O] = Rule[I, O] })#λ] {
    def fmap[A, B](m: Rule[I, A], f: A => B): Rule[I, B] = applicativeRule[I].map(m, f)
  }

  implicit def functorExtractorRule[I, O]: VariantExtractor[({ type λ[O] = Rule[I, O] })#λ] =
    VariantExtractor.functor[({ type λ[O] = Rule[I, O] })#λ](functorRule)

  // XXX: Helps the compiler a bit
  import play.api.libs.functional.syntax._
  implicit def cba[I] = functionalCanBuildApplicative[({ type λ[O] = Rule[I, O] })#λ]
  implicit def fbo[I, O] = toFunctionalBuilderOps[({ type λ[O] = Rule[I, O] })#λ, O] _
  implicit def ao[I, O] = toApplicativeOps[({ type λ[O] = Rule[I, O] })#λ, O] _
  implicit def f[I, O] = toFunctorOps[({ type λ[O] = Rule[I, O] })#λ, O] _
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy