This PR implements Issue #519: A pure, algebraic migration system for ZIO Schema 2 that represents structural transformations between schema versions as first-class, serializable data.
Migration[A, B] - Typed, schema-aware migration wrapper with apply, ++, andThen, reverseDynamicMigration - Fully serializable migration core (no functions/closures) operating on DynamicValueMigrationAction - Sealed trait ADT with 14 action types for all transformation operationsMigrationBuilder[A, B] - Fluent builder with macro-based selectors and validation| Category | Actions |
|---|---|
| Record | AddField, DropField, Rename, TransformValue |
| Optional | Mandate, Optionalize |
| Enum | RenameCase, TransformCase |
| Collection/Map | TransformElements, TransformKeys, TransformValues |
| Field Merging | Join, Split |
| Type Conversion | ChangeType |
Schema.structural[A] - Factory for structural type schemasMigration.builder[A, B] - Fluent migration builder_.fieldName, _.a.b.c syntax for pathsSchemaExpr integration for build-time expression evaluation| File | Changes | Description |
|---|---|---|
Migration.scala |
New | Typed migration wrapper with apply, composition, reverse |
DynamicMigration.scala |
Modified | Untyped core + Schema instance for serialization |
MigrationAction.scala |
Modified | 14 action types + Schema instances for all subtypes |
MigrationBuilder.scala |
Modified | Builder with DynamicOptic methods, SchemaExpr overloads, validation |
MigrationError.scala |
New | 8 error types with path information |
SchemaExpr.scala |
Modified | Added DefaultValue subtype for migrations |
DynamicOptic.scala |
Existing | Path representation (already had Schema support) |
ToStructural.scala |
Existing | Structural type conversion (JVM-only) |
| File | Changes | Description |
|---|---|---|
MigrationMacros.scala |
Modified | 10 macro implementations for selector extraction |
MigrationBuilderVersionSpecific.scala |
Modified | 10 inline def methods with macro delegation |
SchemaCompanionVersionSpecific.scala |
Modified | Added Schema.structural[A] factory |
| File | Changes | Description |
|---|---|---|
MigrationMacros.scala |
Modified | 10 whitebox macro implementations |
MigrationBuilderVersionSpecific.scala |
Modified | 10 macro method declarations |
SchemaCompanionVersionSpecific.scala |
Modified | Added Schema.structural[A] factory |
| File | Changes | Description |
|---|---|---|
MigrationSpec.scala |
New | Typed migration tests (apply, reverse, composition) |
DynamicMigrationSpec.scala |
Modified | 54 tests for all action types, laws, errors |
MigrationBuilderMacroSpec.scala (Scala 2) |
Modified | Macro selector tests |
MigrationBuilderMacroSpec.scala (Scala 3) |
Modified | Macro selector tests |
// Define structural type for old version
type PersonV0 = { def firstName: String; def lastName: String }
implicit val v0Schema: Schema[PersonV0] = Schema.structural[PersonV0]
// Current version
case class Person(fullName: String, age: Int) derives Schema
// Build migration with macro selectors
val migration = Migration.builder[PersonV0, Person]
.addField(_.age, DynamicValue.int(0))
.renameField(_.firstName, _.fullName)
.dropField(_.lastName, DynamicValue.string(""))
.build
// Apply migration
migration(oldPerson) // Right(Person("John", 0))
// Reverse migration
migration.reverse(newPerson) // Right(oldPerson)
scalafmtCheckAll passesDynamicMigration contains NO functions or closuresMigrationAction subtypes have hand-written Schema instancesBinding.Record/Binding.Variant for Scala 2 compatibilitybuild() validates root-level field operations against source/target schemasbuildPartial() skips validation for advanced use casesToStructural type class converts nominal types to structural representationsSchemaExpr.DefaultValue extracts schema default values at build time*Expr overloads evaluate expressions to DynamicValue during builder constructionLiteral and DefaultValue expression types/attempt #519 issue #519 /claim #519
Orbin Sunny
@orbin123
ZIO
@ZIO