/claim #519

What this does

Adds a pure, serializable migration system for ZIO Schema 2, built on top of the existing DynamicValue and DynamicOptic infrastructure. The design follows the same architectural pattern as the existing Patch/DynamicPatch system — typed wrapper over an untyped core, path-based actions, sequential composition.

Components

MigrationAction — sealed ADT with 12 action types:

  • Record: AddField, DropField, RenameField, TransformValue, Mandate, Optionalize, ChangeFieldType
  • Enum: RenameCase, TransformCase
  • Collections: TransformElements, TransformKeys, TransformValues

Every action carries a DynamicOptic path and implements reverse for structural inversion.

DynamicMigration — the untyped core. A Vector[MigrationAction] applied sequentially to a DynamicValue. Fully serializable (no closures, no reflection). Supports ++ composition and reverse.

Migration[A, B] — typed wrapper with sourceSchema and targetSchema. Converts A → DynamicValue → apply actions → DynamicValue → B. Supports ++/andThen composition and reverse.

MigrationBuilder[A, B] — fluent builder with convenience methods for all operations. DynamicOptic-based overloads for the underlying implementation, plus typed convenience methods (e.g. addField("age", 0), changeFieldType("total", "Long", "Int")).

MigrationError — error type with DynamicOptic path info for diagnostics.

Laws

  • Identity: Migration.identity[A].apply(a) == Right(a)
  • Associativity: (m1 ++ m2) ++ m3 produces the same result as m1 ++ (m2 ++ m3)
  • Structural reverse: m.reverse.reverse == m
  • Best-effort semantic inverse: m.apply(a) == Right(b) => m.reverse.apply(b) == Right(a) when sufficient info exists

Tests

42 tests covering:

  • All individual action types (add, drop, rename, mandate, optionalize, type change, case rename, case transform)
  • Sequential composition and associativity
  • Structural reverse and semantic roundtrips (single-step and multi-step)
  • Typed end-to-end migrations (PersonV1 → PersonV2, composition chains A → B → C)
  • Collection operations (sequence element transform, map value transform, empty sequence handling)
  • Primitive type conversion edge cases (Int→String, String→Int, invalid conversions)
  • Error reporting with path information, non-record errors, missing field errors

Cross-compiled and tested on Scala 3.7.4 (JVM).

Demo

https://github.com/user-attachments/assets/placeholder

Example

case class PersonV1(firstName: String, lastName: String)
case class PersonV2(firstName: String, lastName: String, age: Int)
val migration = Migration.newBuilder[PersonV1, PersonV2]
.addField("age", 0)
.build
migration(PersonV1("John", "Doe"))
// Right(PersonV2("John", "Doe", 0))
migration.reverse(PersonV2("John", "Doe", 42))
// Right(PersonV1("John", "Doe"))

What’s next

This is the foundation. Future work to complete the full spec includes:

  • Macro-based selector expressions (_.name, _.address.street) for the builder methods
  • Schema.structural[T] for refinement/structural types
  • Full SchemaExpr integration for richer transforms
  • Join/Split actions
  • .build validation (checking source fields are fully accounted for)

Claim

Total prize pool $8,000
Total paid $0
Status Pending
Submitted March 06, 2026
Last updated March 06, 2026

Contributors

LE

legendary658

@legendary658

100%

Sponsors

MA

marianaguzmanguerrero16-dev

@marianaguzmanguerrero16-dev

$4,000
ZI

ZIO

@ZIO

$4,000