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β
ValidationDsl contains MRule alias that can help with type inference.
Createβ
import cats.Eval
import fields.value.FieldsDsl
import fields.error._
import fields.fail._
import fields.CatsInterop.*
object Validation extends FieldsDsl.BaseAccumulate[Eval, ValidationError] with ValidationError.failWith.Mixin
import Validation._
implicit def error(path: String): ValidationError = ValidationError.Invalid(FieldPath.fromPath(path))
List[Rule](
Rule.valid,
Rule.invalid("Rule.invalid"),
Rule.pure(V.invalid("Rule.pure")),
Rule.effect(Eval.now(V.invalid("Rule.effect"))),
Rule.defer(Rule.invalid("Rule.defer")),
Rule(Eval.later(V.invalid("Rule.apply")))
).map(_.effect.value)
// res0: List[List[ValidationError]] = List(
// List(),
// List(Invalid(.Rule.invalid)),
// List(Invalid(.Rule.pure)),
// List(Invalid(.Rule.effect)),
// List(Invalid(.Rule.defer)),
// List(Invalid(.Rule.apply))
// )
Operationsβ
Rule.invalid("Rule.unwrap").unwrap.value
// res1: List[ValidationError] = List(Invalid(.Rule.unwrap))
Rule.invalid("Rule.effect").effect.value
// res2: List[ValidationError] = List(Invalid(.Rule.effect))
Rule.and(Rule.invalid("Rule.and.1"), Rule.invalid("Rule.and.2")).effect.value
// res3: List[ValidationError] = List(
// Invalid(.Rule.and.1),
// Invalid(.Rule.and.2)
// )
Rule.or(Rule.invalid("Rule.or"), Rule.valid).effect.value
// res4: List[ValidationError] = List()
Rule.when(true)(Rule.invalid("Rule.when")).effect.value
// res5: List[ValidationError] = List(Invalid(.Rule.when))
Rule.whenF(Eval.later(true))(Rule.invalid("Rule.whenF")).effect.value
// res6: List[ValidationError] = List(Invalid(.Rule.whenF))
Rule.ensure(V.invalid("Rule.ensure"))(false).effect.value
// res7: List[ValidationError] = List(Invalid(.Rule.ensure))
Rule.ensureF(V.invalid("Rule.ensure"))(Eval.later(false)).effect.value
// res8: List[ValidationError] = List(Invalid(.Rule.ensure))
Rule.andAll(List(Rule.invalid("Rule.andAll.1"), Rule.invalid("Rule.andAll.2"))).effect.value
// res9: List[ValidationError] = List(
// Invalid(.Rule.andAll.1),
// Invalid(.Rule.andAll.2)
// )
Rule.orAll(List(Rule.invalid("Rule.andAll.1"), Rule.valid)).effect.value
// res10: List[ValidationError] = List()
Rule.modify(Rule.invalid(""))(_ => V.invalid("Rule.modify")).effect.value
// res11: List[ValidationError] = List(Invalid(.Rule.modify))
Rule.modifyM(Rule.invalid(""))(_ => Rule.invalid("Rule.modifyM")).effect.value
// res12: List[ValidationError] = List(Invalid(.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 = ., value = 4)
val rule =
for {
_ <- intF > 4
_ <- intF < 4
_ <- intF !== 4
} yield V.valid
// rule: RuleK[[A >: Nothing <: Any] => Eval[A], [E >: Nothing <: Any] => List[E], ValidationError] = cats.Eval$$anon$3@6c854dcf
Be aware this is experimental and requires yielding V.valid.