Skip to main content

Rule

When using validation syntax result type of validation will be Rule[F, V, E] where F, V, E is your Effect, Validated and Error respectively.

No need to worry about additional allocations as Rule is tagged type for F[V[E]]

You can easily convert between Rule[F, V, E] and F[V[E]] back and forth for free

Syntax​

Module​

ValidationModule contains MRule alias that can help with type inference.

Create​

import cats.Eval
import jap.fields._
import jap.fields.error._
import jap.fields.fail._
import jap.fields.CatsInterop.fromCatsMonadDefer

object Validation extends AccumulateVM[Eval, ValidationError] with CanFailWithValidationError
import Validation._

def error(path: String) = ValidationError.Invalid(FieldPath.fromPath(path))

List[MRule](
Rule.valid,
Rule.invalid(error("Rule.invalid")),
Rule.pure(V.invalid(error("Rule.pure"))),
Rule.effect(Eval.now(V.invalid(error("Rule.effect")))),
Rule.defer(Rule.invalid(error("Rule.defer"))),
Rule(Eval.later(V.invalid(error("Rule.apply"))))
).map(_.effect.value)
// res0: List[Accumulate[ValidationError]] = List(
// Valid,
// Invalid(
// errors = List(
// Invalid(path = FieldPath(parts = List(Path(value = "Rule.invalid"))))
// )
// ),
// Invalid(
// errors = List(
// Invalid(path = FieldPath(parts = List(Path(value = "Rule.pure"))))
// )
// ),
// Invalid(
// errors = List(
// Invalid(path = FieldPath(parts = List(Path(value = "Rule.effect"))))
// )
// ),
// Invalid(
// errors = List(
// Invalid(path = FieldPath(parts = List(Path(value = "Rule.defer"))))
// )
// ),
// Invalid(
// errors = List(
// Invalid(path = FieldPath(parts = List(Path(value = "Rule.apply"))))
// )
// )
// )

Operations​

Rule.invalid("Rule.unwrap").unwrap.value
// res1: Accumulate[String] = Invalid(errors = List("Rule.unwrap"))
Rule.invalid("Rule.effect").effect.value
// res2: Accumulate[String] = Invalid(errors = List("Rule.effect"))
Rule.and(Rule.invalid("Rule.and.1"), Rule.invalid("Rule.and.2")).effect.value
// res3: Accumulate[String] = Invalid(
// errors = List("Rule.and.1", "Rule.and.2")
// )
Rule.or(Rule.invalid("Rule.or"), Rule.valid).effect.value
// res4: Accumulate[String] = Valid
Rule.when(true)(Rule.invalid("Rule.when")).effect.value
// res5: Accumulate[String] = Invalid(errors = List("Rule.when"))
Rule.whenF(Eval.later(true))(Rule.invalid("Rule.whenF")).effect.value
// res6: Accumulate[String] = Invalid(errors = List("Rule.whenF"))
Rule.ensure(V.invalid("Rule.ensure"))(false).effect.value
// res7: Accumulate[String] = Invalid(errors = List("Rule.ensure"))
Rule.ensureF(V.invalid("Rule.ensure"))(Eval.later(false)).effect.value
// res8: Accumulate[String] = Invalid(errors = List("Rule.ensure"))
Rule.andAll(List(Rule.invalid("Rule.andAll.1"), Rule.invalid("Rule.andAll.2"))).effect.value
// res9: Accumulate[String] = Invalid(
// errors = List("Rule.andAll.1", "Rule.andAll.2")
// )
Rule.orAll(List(Rule.invalid("Rule.andAll.1"), Rule.valid)).effect.value
// res10: Accumulate[String] = Valid
Rule.modify(Rule.invalid(""))(_ => V.invalid("Rule.modify")).effect.value
// res11: Accumulate[String] = Invalid(errors = List("Rule.modify"))
Rule.modifyM(Rule.invalid(""))(_ => Rule.invalid("Rule.modifyM")).effect.value
// res12: Accumulate[String] = Invalid(errors = List("Rule.modifyM"))

For-comprehension​

Because Rule has custom map and flatMap you can also define validations like this:

val intF = Field(4)
// intF: Field[Int] = Field(path = FieldPath(parts = List()), value = 4)
val rule =
for {
_ <- intF > 4
_ <- intF < 4
_ <- intF !== 4
} yield V.valid
// rule: Rule[[A >: Nothing <: Any] => Eval[A], [E >: Nothing <: Any] => Accumulate[E], ValidationError] = cats.Eval$$anon$3@21145fa4

Be aware this is experimental and requires yielding V.valid.