diff --git a/ducktape/src/main/scala/io/github/arainko/ducktape/internal/Plan.scala b/ducktape/src/main/scala/io/github/arainko/ducktape/internal/Plan.scala index ecce4032..e7d0df5e 100644 --- a/ducktape/src/main/scala/io/github/arainko/ducktape/internal/Plan.scala +++ b/ducktape/src/main/scala/io/github/arainko/ducktape/internal/Plan.scala @@ -40,7 +40,7 @@ case class FieldPlan[+E <: Erroneous, +F <: Fallible](sourceField: String | None } object FieldPlan { - def empty[E <: Erroneous, F <: Fallible](plan: Plan[E ,F]): FieldPlan[E, F] = FieldPlan(None, plan) + def empty[E <: Erroneous, F <: Fallible](plan: Plan[E, F]): FieldPlan[E, F] = FieldPlan(None, plan) } private[ducktape] object Plan { @@ -76,13 +76,55 @@ private[ducktape] object Plan { source: Structure.Product, dest: Structure.Function, argPlans: VectorMap[String, Plan[E, F]] - ) extends Plan[E, F] + ) extends Plan[E, F] { + inline def updateEach[EE >: E <: Erroneous, FF >: F <: Fallible]( + inline f: Plan[E, F] => Plan[EE, FF] + ): BetweenProductFunction[EE, FF] = + copy(argPlans = argPlans.transform((_, argPlan) => f(argPlan))) + + inline def updateOrElse[EE >: E <: Erroneous, FF >: F <: Fallible](argName: String)( + inline f: Plan[E, F] => Plan[EE, FF], + alt: Plan.Error + ): BetweenProductFunction[Erroneous, FF] = { + val updatedArgPlan = + argPlans + .get(argName) + .map(f) + .getOrElse(alt) + + copy(argPlans = argPlans.updated(argName, updatedArgPlan)) + } + } case class BetweenTupleFunction[+E <: Erroneous, +F <: Fallible]( source: Structure.Tuple, dest: Structure.Function, argPlans: VectorMap[String, Plan[E, F]] - ) extends Plan[E, F] + ) extends Plan[E, F] { + inline def updateEach[EE >: E <: Erroneous, FF >: F <: Fallible]( + inline f: Plan[E, F] => Plan[EE, FF] + ): BetweenTupleFunction[EE, FF] = + copy(argPlans = argPlans.transform((_, argPlan) => f(argPlan))) + + inline def updateByNameOrElse[EE >: E <: Erroneous, FF >: F <: Fallible](argName: String)( + inline f: Plan[E, F] => Plan[EE, FF], + inline alt: Plan.Error + ): Plan[Erroneous, FF] = { + argPlans + .get(argName) + .map(argPlan => copy(argPlans = argPlans.updated(argName, f(argPlan)))) + .getOrElse(alt) + } + + inline def updateByIndexOrElse[EE >: E <: Erroneous, FF >: F <: Fallible](argIdx: Int)( + inline f: Plan[E, F] => Plan[EE, FF], + alt: Plan.Error + ): Plan[Erroneous, FF] = + argPlans.toVector + .lift(argIdx) + .map((name, fieldPlan) => copy(argPlans = argPlans.updated(name, f(fieldPlan)))) + .getOrElse(alt) + } case class BetweenUnwrappedWrapped( source: Structure, @@ -99,14 +141,20 @@ private[ducktape] object Plan { source: Structure.Wrapped[?], dest: Structure, plan: Plan[E, Nothing] - ) extends Plan[E, Fallible] + ) extends Plan[E, Fallible] { + inline def update[EE >: E <: Erroneous](inline f: Plan[E, Nothing] => Plan[EE, Nothing]): BetweenFallibleNonFallible[EE] = + copy(plan = f(plan)) + } case class BetweenFallibles[+E <: Erroneous]( source: Structure.Wrapped[?], dest: Structure, mode: TransformationMode.FailFast[?], plan: Plan[E, Fallible] - ) extends Plan[E, Fallible] + ) extends Plan[E, Fallible] { + inline def update[EE >: E <: Erroneous](inline f: Plan[E, Fallible] => Plan[EE, Fallible]): BetweenFallibles[EE] = + copy(plan = f(plan)) + } case class BetweenSingletons( source: Structure.Singleton, @@ -117,13 +165,55 @@ private[ducktape] object Plan { source: Structure.Product, dest: Structure.Product, fieldPlans: VectorMap[String, FieldPlan[E, F]] - ) extends Plan[E, F] + ) extends Plan[E, F] { + inline def updateEach[EE >: E <: Erroneous, FF >: F <: Fallible]( + inline f: Plan[E, F] => Plan[EE, FF] + ) = + copy(fieldPlans = fieldPlans.transform((_, argPlan) => argPlan.update(f))) + + inline def updateOrElse[EE >: E <: Erroneous, FF >: F <: Fallible](name: String)( + inline f: Plan[E, F] => Plan[EE, FF], + alt: Plan.Error + ) = { + val updatedArgPlan = + fieldPlans + .get(name) + .map(_.update(f)) + .getOrElse(FieldPlan.empty(alt)) + + copy(fieldPlans = fieldPlans.updated(name, updatedArgPlan)) + } + } case class BetweenProductTuple[+E <: Erroneous, +F <: Fallible]( source: Structure.Product, dest: Structure.Tuple, plans: Vector[Plan[E, F]] - ) extends Plan[E, F] + ) extends Plan[E, F] { + inline def updateEach[EE >: E <: Erroneous, FF >: F <: Fallible]( + inline f: Plan[E, F] => Plan[EE, FF] + ) = + copy(plans = plans.map(argPlan => f(argPlan))) + + // inline def updateByNameOrElse[EE >: E <: Erroneous, FF >: F <: Fallible](argName: String)( + // inline f: Plan[E, F] => Plan[EE, FF], + // inline alt: Plan.Error + // ): Plan[Erroneous, FF] = { + // plans + // .get(argName) + // .map(argPlan => copy(argPlans = argPlans.updated(argName, f(argPlan)))) + // .getOrElse(alt) + // } + + inline def updateByIndexOrElse[EE >: E <: Erroneous, FF >: F <: Fallible](argIdx: Int)( + inline f: Plan[E, F] => Plan[EE, FF], + alt: Plan.Error + ): Plan[Erroneous, FF] = + plans + .lift(argIdx) + .map(fieldPlan => copy(plans = plans.updated(argIdx, f(fieldPlan)))) + .getOrElse(alt) + } case class BetweenTupleProduct[+E <: Erroneous, +F <: Fallible]( source: Structure.Tuple, @@ -205,4 +295,5 @@ private[ducktape] object Plan { object Reconfigured { given debug: Debug[Reconfigured[Fallible]] = Debug.derived } + } diff --git a/ducktape/src/main/scala/io/github/arainko/ducktape/internal/PlanConfigurer.scala b/ducktape/src/main/scala/io/github/arainko/ducktape/internal/PlanConfigurer.scala index 6cc82f25..491cb62f 100644 --- a/ducktape/src/main/scala/io/github/arainko/ducktape/internal/PlanConfigurer.scala +++ b/ducktape/src/main/scala/io/github/arainko/ducktape/internal/PlanConfigurer.scala @@ -4,6 +4,8 @@ import io.github.arainko.ducktape.internal.Configuration.Instruction import io.github.arainko.ducktape.internal.Path.Segment import scala.quoted.* +import io.github.arainko.ducktape.Transformer +import javax.xml.crypto.dsig.Transform private[ducktape] object PlanConfigurer { import Plan.* @@ -96,20 +98,18 @@ private[ducktape] object PlanConfigurer { case (fieldPlan, index @ sourceFields(segment.name)) => parent.copy(plans = plans.updated(index, recurse(fieldPlan, tail, parent, config))) }.getOrElse(Plan.Error.from(parent, ErrorMessage.InvalidFieldAccessor(segment.name, config.span), None)) - case parent @ BetweenProductFunction(sourceTpe, destTpe, argPlans) => - val argPlan = - argPlans - .get(segment.name) - .map(argPlan => recurse(argPlan, tail, parent, config)) - .getOrElse(Plan.Error.from(parent, ErrorMessage.InvalidArgAccessor(segment.name, config.span), None)) - parent.copy(argPlans = argPlans.updated(segment.name, argPlan)) + case parent @ BetweenProductFunction(sourceTpe, destTpe, argPlans) => + parent.updateOrElse(segment.name)( + recurse(_, tail, parent, config), + Plan.Error.from(parent, ErrorMessage.InvalidArgAccessor(segment.name, config.span), None) + ) case parent @ BetweenTupleFunction(source, dest, argPlans) if config.side.isDest => - argPlans - .get(segment.name) - .map(argPlan => parent.copy(argPlans = argPlans.updated(segment.name, recurse(argPlan, tail, parent, config)))) - .getOrElse(Plan.Error.from(parent, ErrorMessage.InvalidArgAccessor(segment.name, config.span), None)) + parent.updateByNameOrElse(segment.name)( + recurse(_, tail, parent, config), + Plan.Error.from(parent, ErrorMessage.InvalidArgAccessor(segment.name, config.span), None) + ) case paren: Upcast => recurse(paren.alt, segments, parent, config) @@ -166,13 +166,10 @@ private[ducktape] object PlanConfigurer { case parent @ BetweenTupleFunction(source, dest, plans) if config.side.isSource => Logger.debug(ds"Matched $parent") - plans.toVector - .lift(index) - .map((name, fieldPlan) => parent.copy(argPlans = plans.updated(name, recurse(fieldPlan, tail, parent, config)))) - .getOrElse( - Plan.Error - .from(parent, ErrorMessage.InvalidTupleAccesor(index, config.span), None) - ) + parent.updateByIndexOrElse(index)( + recurse(_, tail, parent, config), + Plan.Error.from(parent, ErrorMessage.InvalidTupleAccesor(index, config.span), None) + ) case paren: Upcast => recurse(paren.alt, segments, parent, config) @@ -311,10 +308,10 @@ private[ducktape] object PlanConfigurer { case plan: Configured[F] => plan case plan: BetweenProductFunction[Erroneous, F] => - plan.copy(argPlans = plan.argPlans.transform((_, argPlan) => regional(argPlan, modifier, plan))) + plan.updateEach(regional(_, modifier, plan)) case plan: BetweenTupleFunction[Erroneous, F] => - plan.copy(argPlans = plan.argPlans.transform((_, argPlan) => regional(argPlan, modifier, plan))) + plan.updateEach(regional(_, modifier, plan)) case plan: BetweenUnwrappedWrapped => plan