8000 Parametarize Plans with `Fallible` aka can this particular Plan have a fallible node by arainko · Pull Request #112 · arainko/ducktape · GitHub
[go: up one dir, main page]

Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -4,33 +4,41 @@ import io.github.arainko.ducktape.*

import scala.quoted.*

private[ducktape] enum Configuration derives Debug {
private[ducktape] enum Configuration[+F <: Fallible] {
def tpe: Type[?]

case Const(value: Expr[Any], tpe: Type[?])
case CaseComputed(tpe: Type[?], function: Expr[Any => Any])
case FieldComputed(tpe: Type[?], function: Expr[Any => Any])
case FieldReplacement(source: Expr[Any], name: String, tpe: Type[?])
case Const(value: Expr[Any], tpe: Type[?]) extends Configuration[Nothing]
case CaseComputed(tpe: Type[?], function: Expr[Any => Any]) extends Configuration[Nothing]
case FieldComputed(tpe: Type[?], function: Expr[Any => Any]) extends Configuration[Nothing]
case FieldReplacement(source: Expr[Any], name: String, tpe: Type[?]) extends Configuration[Nothing]
case FallibleConst(value: Expr[Any], tpe: Type[?]) extends Configuration[Fallible]
case FallibleComputed(tpe: Type[?], function: Expr[Any => Any]) extends Configuration[Fallible]
}

private[ducktape] object Configuration {

given debug: Debug[Configuration[Fallible]] = Debug.derived

trait ErrorModifier {
def apply(parent: Plan[Plan.Error] | None.type, plan: Plan.Error)(using Quotes): Configuration | plan.type
def apply(parent: Plan[Plan.Error, Fallible] | None.type, plan: Plan.Error)(using Quotes): Configuration[Nothing] | plan.type
}

object ErrorModifier {
given Debug[ErrorModifier] = Debug.nonShowable

val substituteOptionsWithNone = new ErrorModifier:
def apply(parent: Plan[Plan.Error] | None.type, plan: Plan.Error)(using Quotes): Configuration | plan.type =
def apply(parent: Plan[Plan.Error, Fallible] | None.type, plan: Plan.Error)(using
Quotes
): Configuration[Nothing] | plan.type =
plan.dest.tpe match {
case tpe @ '[Option[a]] => Configuration.Const('{ None }, tpe)
case _ => plan
}

val substituteWithDefaults = new ErrorModifier:
def apply(parent: Plan[Plan.Error] | None.type, plan: Plan.Error)(using Quotes): Configuration | plan.type =
def apply(parent: Plan[Plan.Error, Fallible] | None.type, plan: Plan.Error)(using
Quotes
): Configuration[Nothing] | plan.type =
PartialFunction
.condOpt(parent -> plan.destPath.segments.lastOption) {
case (Plan.BetweenProducts(_, dest, _), Some(Path.Segment.Field(_, fieldName))) =>
Expand All @@ -46,47 +54,54 @@ private[ducktape] object Configuration {

trait FieldModifier {
def apply(
parent: Plan.BetweenProductFunction[Plan.Error] | Plan.BetweenProducts[Plan.Error],
parent: Plan.BetweenProductFunction[Plan.Error, Fallible] | Plan.BetweenProducts[Plan.Error, Fallible],
field: String,
plan: Plan[Plan.Error]
)(using Quotes): Configuration | plan.type
plan: Plan[Plan.Error, Fallible]
)(using Quotes): Configuration[Nothing] | plan.type
}

object FieldModifier {
given Debug[FieldModifier] = Debug.nonShowable
}

enum Instruction derives Debug {
enum Instruction[+F <: Fallible] {
def path: Path
def side: Side
def span: Span

case Static(path: Path, side: Side, config: Configuration, span: Span)
case Static(path: Path, side: Side, config: Configuration[F], span: Span) extends Instruction[F]

case Dynamic(path: Path, side: Side, config: Plan[Plan.Error] | None.type => Either[String, Configuration], span: Span)
case Dynamic(
path: Path,
side: Side,
config: Plan[Plan.Error, Fallible] | None.type => Either[String, Configuration[F]],
span: Span
) extends Instruction[F]

case Bulk(
path: Path,
side: Side,
modifier: FieldModifier,
span: Span
)
) extends Instruction[Nothing]

case Regional(path: Path, side: Side, modifier: ErrorModifier, span: Span)
case Regional(path: Path, side: Side, modifier: ErrorModifier, span: Span) extends Instruction[Nothing]

case Failed(path: Path, side: Side, message: String, span: Span)
case Failed(path: Path, side: Side, message: String, span: Span) extends Instruction[Nothing]
}

object Instruction {
given debug: Debug[Instruction[Fallible]] = Debug.derived

object Failed {
def from(instruction: Instruction, message: String): Instruction.Failed =
def from(instruction: Instruction[Fallible], message: String): Instruction.Failed =
Failed(instruction.path, instruction.side, message, instruction.span)
}
}

def parse[A: Type, B: Type](
configs: Expr[Seq[Field[A, B] | Case[A, B]]]
)(using Quotes): List[Instruction] = {
)(using Quotes): List[Instruction[Nothing]] = {
import quotes.reflect.*

Varargs
Expand All @@ -110,15 +125,15 @@ private[ducktape] object Configuration {
TypeApply(Select(IdentOfType('[Field.type]), "default"), a :: b :: destFieldTpe :: Nil),
PathSelector(path) :: Nil
) =>
def default(parent: Plan[Plan.Error] | None.type) =
def default(parent: Plan[Plan.Error, Fallible] | None.type) =
for {
selectedField <-
path.segments.lastOption
.flatMap(_.narrow[Path.Segment.Field])
.toRight("Selected path's length should be at least 1")
defaults <-
PartialFunction
.condOpt(parent) { case parent: Plan.BetweenProducts[Plan.Error] => parent.dest.defaults }
.condOpt(parent) { case parent: Plan.BetweenProducts[Plan.Error, Fallible] => parent.dest.defaults }
.toRight("Selected field's parent is not a product")
defaultValue <-
defaults
Expand Down Expand Up @@ -233,10 +248,10 @@ private[ducktape] object Configuration {
.map { sourceStruct =>
val modifier = new FieldModifier:
def apply(
parent: Plan.BetweenProductFunction[Plan.Error] | Plan.BetweenProducts[Plan.Error],
parent: Plan.BetweenProductFunction[Plan.Error, Fallible] | Plan.BetweenProducts[Plan.Error, Fallible],
field: String,
plan: Plan[Plan.Error]
)(using Quotes): Configuration | plan.type =
plan: Plan[Plan.Error, Fallible]
)(using Quotes): Configuration[Nothing] | plan.type =
sourceStruct.fields.get(field).match {
case Some(struct) if struct.tpe.repr <:< plan.dest.tpe.repr =>
Configuration.FieldReplacement(sourceExpr, field, struct.tpe)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,10 @@ import scala.collection.immutable.ListMap
import scala.quoted.*
import scala.reflect.TypeTest

private[ducktape] sealed trait Plan[+E <: Plan.Error] {
object Fallible
type Fallible = Fallible.type

private[ducktape] sealed trait Plan[+E <: Plan.Error, +F <: Fallible] {
import Plan.*

def source: Structure
Expand All @@ -18,99 +21,100 @@ private[ducktape] sealed trait Plan[+E <: Plan.Error] {

final def destPath: Path = dest.path

final def narrow[A <: Plan[Plan.Error]](using tt: TypeTest[Plan[Plan.Error], A]): Option[A] = tt.unapply(this)
final def narrow[A <: Plan[Plan.Error, Fallible]](using tt: TypeTest[Plan[Plan.Error, Fallible], A]): Option[A] =
tt.unapply(this)

final def configureAll(configs: List[Configuration.Instruction])(using Quotes): Plan.Reconfigured =
final def configureAll[FF >: F <: Fallible](configs: List[Configuration.Instruction[FF]])(using Quotes): Plan.Reconfigured[FF] =
PlanConfigurer.run(this, configs)

final def refine: Either[NonEmptyList[Plan.Error], Plan[Nothing]] = PlanRefiner.run(this)
final def refine: Either[NonEmptyList[Plan.Error], Plan[Nothing, F]] = PlanRefiner.run(this)
}

private[ducktape] object Plan {
case class Upcast(
source: Structure,
dest: Structure
) extends Plan[Nothing]
) extends Plan[Nothing, Nothing]

case class UserDefined(
case class UserDefined[+F <: Fallible](
source: Structure,
dest: Structure,
transformer: Expr[Transformer[?, ?]]
) extends Plan[Nothing]
transformer: Summoner.UserDefined[F]
) extends Plan[Nothing, F]

case class Derived(
case class Derived[+F <: Fallible](
source: Structure,
dest: Structure,
transformer: Expr[Transformer.Derived[?, ?]]
) extends Plan[Nothing]
transformer: Summoner.Derived[F]
) extends Plan[Nothing, F]

case class Configured(
case class Configured[+F <: Fallible](
source: Structure,
dest: Structure,
config: Configuration
) extends Plan[Nothing]
config: Configuration[F]
) extends Plan[Nothing, F]

case class BetweenProductFunction[+E <: Plan.Error](
case class BetweenProductFunction[+E <: Plan.Error, +F <: Fallible](
source: Structure.Product,
dest: Structure.Function,
argPlans: ListMap[String, Plan[E]]
) extends Plan[E]
argPlans: ListMap[String, Plan[E, F]]
) extends Plan[E, F]

case class BetweenUnwrappedWrapped(
source: Structure,
dest: Structure.ValueClass
) extends Plan[Nothing]
) extends Plan[Nothing, Nothing]

case class BetweenWrappedUnwrapped(
source: Structure.ValueClass,
dest: Structure,
fieldName: String
) extends Plan[Nothing]
) extends Plan[Nothing, Nothing]

case class BetweenSingletons(
source: Structure.Singleton,
dest: Structure.Singleton
) extends Plan[Nothing]
) extends Plan[Nothing, Nothing]

case class BetweenProducts[+E <: Plan.Error](
case class BetweenProducts[+E <: Plan.Error, +F <: Fallible](
source: Structure.Product,
dest: Structure.Product,
fieldPlans: Map[String, Plan[E]]
) extends Plan[E]
fieldPlans: Map[String, Plan[E, F]]
) extends Plan[E, F]

case class BetweenCoproducts[+E <: Plan.Error](
case class BetweenCoproducts[+E <: Plan.Error, +F <: Fallible](
source: Structure.Coproduct,
dest: Structure.Coproduct,
casePlans: Vector[Plan[E]]
) extends Plan[E]
casePlans: Vector[Plan[E, F]]
) extends Plan[E, F]

case class BetweenOptions[+E <: Plan.Error](
case class BetweenOptions[+E <: Plan.Error, +F <: Fallible](
source: Structure.Optional,
dest: Structure.Optional,
plan: Plan[E]
) extends Plan[E]
plan: Plan[E, F]
) extends Plan[E, F]

case class BetweenNonOptionOption[+E <: Plan.Error](
case class BetweenNonOptionOption[+E <: Plan.Error, +F <: Fallible](
source: Structure,
dest: Structure.Optional,
plan: Plan[E]
) extends Plan[E]
plan: Plan[E, F]
) extends Plan[E, F]

case class BetweenCollections[+E <: Plan.Error](
case class BetweenCollections[+E <: Plan.Error, +F <: Fallible](
source: Structure.Collection,
dest: Structure.Collection,
plan: Plan[E]
) extends Plan[E]
plan: Plan[E, F]
) extends Plan[E, F]

case class Error(
source: Structure,
dest: Structure,
message: ErrorMessage,
suppressed: Option[Plan.Error]
) extends Plan[Plan.Error]
) extends Plan[Plan.Error, Nothing]

object Error {
def from(plan: Plan[Plan.Error], message: ErrorMessage, suppressed: Option[Plan.Error]): Plan.Error =
def from(plan: Plan[Plan.Error, Fallible], message: ErrorMessage, suppressed: Option[Plan.Error]): Plan.Error =
Plan.Error(
plan.source,
plan.dest,
Expand All @@ -120,19 +124,23 @@ private[ducktape] object Plan {
}

object Configured {
def from(plan: Plan[Plan.Error], conf: Configuration): Plan.Configured =
def from[F <: Fallible](plan: Plan[Plan.Error, F], conf: Configuration[F]): Plan.Configured[F] =
Plan.Configured(
plan.source,
plan.dest,
conf
)
}

given debug: Debug[Plan[Plan.Error]] = Debug.derived
given debug: Debug[Plan[Plan.Error, Fallible]] = Debug.derived

final case class Reconfigured(
final case class Reconfigured[+F <: Fallible](
errors: List[Plan.Error],
successes: List[(Path, Side)],
result: Plan[Plan.Error]
) derives Debug
result: Plan[Plan.Error, F]
)

object Reconfigured {
given debug: Debug[Reconfigured[Fallible]] = Debug.derived
}
}
Loading
0