ZI
Feat/schema migration 519
zio/zio-blocks#898

Summary

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.

Key Features

🎯 Core Architecture

  • Migration[A, B] - Typed, schema-aware migration wrapper with apply, ++, andThen, reverse
  • DynamicMigration - Fully serializable migration core (no functions/closures) operating on DynamicValue
  • MigrationAction - Sealed trait ADT with 14 action types for all transformation operations
  • MigrationBuilder[A, B] - Fluent builder with macro-based selectors and validation

🔧 Migration Actions (14 types)

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

✨ New APIs

  • Schema.structural[A] - Factory for structural type schemas
  • Migration.builder[A, B] - Fluent migration builder
  • Macro-based selectors: _.fieldName, _.a.b.c syntax for paths
  • SchemaExpr integration for build-time expression evaluation

Files Changed

Core Implementation (shared/src/main/scala)

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)

Scala 3 Macros (shared/src/main/scala-3)

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

Scala 2 Macros (shared/src/main/scala-2)

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

Tests

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

API Examples

// 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)

Verification

✅ Tests

  • 83 migration-specific tests pass
  • All laws verified: Identity, Associativity, Structural Reverse
  • Error handling with path information tested
  • Macro selectors tested for both Scala versions

✅ Cross-Platform

  • Scala 2.13: Compiles and passes all tests
  • Scala 3.5+: Compiles and passes all tests
  • JVM, JS, Native: All compile successfully

✅ Formatting

  • scalafmtCheckAll passes

Implementation Notes

Serialization

  • DynamicMigration contains NO functions or closures
  • All 14 MigrationAction subtypes have hand-written Schema instances
  • Uses manual Binding.Record/Binding.Variant for Scala 2 compatibility
  • Fully serializable for storage in registries, DDL generation, etc.

Validation

  • build() validates root-level field operations against source/target schemas
  • buildPartial() skips validation for advanced use cases
  • Nested path operations skip validation (cannot be statically verified)

Structural Types (JVM-only)

  • ToStructural type class converts nominal types to structural representations
  • Reflection-based deconstructors for runtime structural type handling
  • Rejects recursive types (Scala limitation)

SchemaExpr Integration

  • SchemaExpr.DefaultValue extracts schema default values at build time
  • *Expr overloads evaluate expressions to DynamicValue during builder construction
  • Supports Literal and DefaultValue expression types

/attempt #519 issue #519 /claim #519

Claim

Total prize pool $4,000
Total paid $0
Status Pending
Submitted January 30, 2026
Last updated January 30, 2026

Contributors

OR

Orbin Sunny

@orbin123

100%

Sponsors

ZI

ZIO

@ZIO

$4,000