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:
// This implementation
sealed trait MigrationStep {
case class Record(
fieldActions: Vector[FieldAction], // Actions at this level
nestedFields: Map[String, MigrationStep] // Recursive children
)
}
sealed trait FieldAction {
case class Add(name: String, defaultValue: DynamicValue) // No path, just name
}
This enables compile-time validation of deeply nested paths:
case class PersonV1(name: String, address: Address)
case class PersonV2(name: String, address: AddressV2)
case class AddressV2(street: String, city: String, zip: String) // nested field added
// .build validates that address.zip is handled, not just top-level fields
MigrationBuilder.from[PersonV1].to[PersonV2]
.add(_.address.zip, DynamicValue.Primitive("00000"))
.build // Compile error if nested field missing
| 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
ZIO
@ZIO