Implements #519 /claim #519
The spec proposes flat Vector[MigrationAction] where each action carries its path:
// Spec approach
case class DynamicMigration(actions: Vector[MigrationAction])
case class AddField(at: DynamicOptic, default: SchemaExpr[?]) extends MigrationAction
Per JDG feedback:
“nested migrations can’t be flat, must be lists of lists where top level encodes depth”
This implementation uses a recursive tree:
sealed trait MigrationStep {
case class Record(
fieldActions: Vector[FieldAction], // Actions at this level
nestedFields: Map[String, MigrationStep] // Recursive children
)
}
// Phantom types encode field operations
sealed trait FieldTree
sealed trait Empty extends FieldTree
sealed trait Branch[Name <: String, Op <: Operation, Children <: FieldTree, Siblings <: FieldTree] extends FieldTree
// Builder carries phantom type parameters
final case class MigrationBuilder[A, B, Handled, Provided](...)
Each addTyped/dropTyped call accumulates into the phantom type. At buildTyped, a macro compares these against the actual schema diff:
TypedMigrationBuilderMacro.from[PersonV1].to[PersonV2]
.addTyped(_.address.zip, DynamicValue.string("00000"))
.buildTyped // Compile error if nested field missing
Nb: Macro files are excluded from coverage in build.sbt since macro code executes at comptime and cannot be instrumented for runtime coverage.
| Spec | Implementation | Justification |
|---|---|---|
SchemaExpr[A, ?] |
DynamicValueTransform + PrimitiveConversion |
Separate value transforms from type conversions |
Actions carry at: DynamicOptic |
Tree structure encodes path implicitly | Cleaner nested validation, natural recursion for reverse |
Helmy LuqmanulHakim
@elskow
marianaguzmanguerrero16-dev
@marianaguzmanguerrero16-dev
ZIO
@ZIO