Created
September 29, 2022 13:31
-
-
Save umazalakain/fd5b8f11c3f4c99d151e1c838142a4bb to your computer and use it in GitHub Desktop.
Staged validation
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import cats.Monad | |
import cats.syntax.all.* | |
object Validation: | |
enum ValidationLevel: | |
case Mandatory | |
case Desirable | |
case Optional | |
export ValidationLevel.* | |
/* We define the type of a field as Validated[V, D, A] where | |
- A is what this field contains | |
- D is how optional this field is | |
- Mandatory: has to be there | |
- Desirable: should be there, otherwise we will alert | |
- Optional: could be there, do nothing if it's not | |
- V is the level we are validating for. | |
- Mandatory: fields can only be missing if they are marked as Mandatory, Desirable or Optional | |
- Desirable: fields can only be missing if they are marked as Desirable or Optional | |
- Optional: fields can only be missing if they are marked as Optional | |
Note that this could (should?) be abstracted over to any preorder. | |
*/ | |
enum Validated[V, D, A]: | |
case Some[V, D, A](a : A) extends Validated[V, D, A] | |
case NoneMM[A]() extends Validated[Mandatory.type, Mandatory.type, A] | |
case NoneMD[A]() extends Validated[Mandatory.type, Desirable.type, A] | |
case NoneMO[A]() extends Validated[Mandatory.type, Optional.type, A] | |
case NoneDD[A]() extends Validated[Desirable.type, Desirable.type, A] | |
case NoneDO[A]() extends Validated[Desirable.type, Optional.type, A] | |
case NoneOO[A]() extends Validated[Optional.type, Optional.type, A] | |
object Validated: | |
def traverse[F[_]: Monad, V, D, A, B](x: Validated[V, D, A])(f: A => F[B]): F[Validated[V, D, B]] = x match | |
case Validated.Some(a) => f(a).map(Validated.Some.apply) | |
case Validated.NoneMM() => Validated.NoneMM().pure[F] | |
case Validated.NoneMD() => Validated.NoneMD().pure[F] | |
case Validated.NoneMO() => Validated.NoneMO().pure[F] | |
case Validated.NoneDD() => Validated.NoneDD().pure[F] | |
case Validated.NoneDO() => Validated.NoneDO().pure[F] | |
case Validated.NoneOO() => Validated.NoneOO().pure[F] | |
def mandatoryToDesirable[D, A]: Validated[Mandatory.type, D, A] => Option[Validated[Desirable.type, D, A]] = | |
case Validated.Some(a) => Some(Validated.Some(a)) | |
case Validated.NoneMM() => None | |
case Validated.NoneMD() => Some(Validated.NoneDD()) | |
case Validated.NoneMO() => Some(Validated.NoneDO()) | |
def desirableToOptional[D, A]: Validated[Desirable.type, D, A] => Option[Validated[Optional.type, D, A]] = | |
case Validated.Some(a) => Some(Validated.Some(a)) | |
case Validated.NoneDD() => None | |
case Validated.NoneDO() => Some(Validated.NoneOO()) | |
final case class Asset[V1](name: Validated[V1, Optional.type, String]): | |
def validate[V2](f: [D, A] => Validated[V1, D, A] => Option[Validated[V2, D, A]]): Option[Asset[V2]] = | |
for name2 <- f(name) | |
yield Asset(name2) | |
final case class Article[V1](title: Validated[V1, Mandatory.type, String], asset: Validated[V1, Desirable.type, Asset[V1]]): | |
def validate[V2](f: [D, A] => Validated[V1, D, A] => Option[Validated[V2, D, A]]): Option[Article[V2]] = | |
for | |
title2 <- f(title) | |
asset2 <- Validated.traverse(asset)(_.validate(f)) | |
asset3 <- f(asset2) | |
yield Article(title2, asset3) | |
val example: Unit = | |
val asset: Asset[Mandatory.type] = Asset(name = Validated.NoneMO()) | |
val stage0: Article[Mandatory.type] = Article(title = Validated.Some("title"), asset = Validated.Some(asset)) | |
val stage1: Option[Article[Desirable.type]] = stage0.validate([D, A] => (v: Validated[Mandatory.type, D, A]) => mandatoryToDesirable[D, A](v)) | |
val stage2: Option[Article[Optional.type]] = stage1.flatMap(_.validate([D, A] => (v: Validated[Desirable.type, D, A]) => desirableToOptional[D, A](v))) | |
assert(stage2.isDefined) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment