|
| 1 | +# Aether Datafixers v0.2.0 — Testkit, Extended Rules, Diagnostics, and Performance |
| 2 | + |
| 3 | +Extended rules, testkit module, migration diagnostics, and high-performance APIs. |
| 4 | + |
| 5 | +--- |
| 6 | + |
| 7 | +## Highlights in v0.2.0 |
| 8 | + |
| 9 | +- **Testkit Module** — New `aether-datafixers-testkit` module with fluent test data builders, custom AssertJ assertions, and test harnesses for DataFix, Schema, and migration testing. |
| 10 | +- **Extended Rewrite Rules** — Convenience methods for batch operations, field grouping/flattening, path-based operations, and conditional rules. |
| 11 | +- **Migration Diagnostics** — Opt-in diagnostic system for structured reports with timing, snapshots, and warnings. |
| 12 | +- **High-Performance APIs** — Batch transformations and single-pass conditionals for optimized migrations. |
| 13 | +- **Performance Optimizations** — Internal improvements with memoized path parsing, pre-allocated lists, and reduced allocations. |
| 14 | + |
| 15 | +--- |
| 16 | + |
| 17 | +## Installation |
| 18 | + |
| 19 | +> [!TIP] |
| 20 | +> All Aether artifacts are available on **Maven Central** — no extra repository required. |
| 21 | +
|
| 22 | +### Maven |
| 23 | + |
| 24 | +```xml |
| 25 | +<dependency> |
| 26 | + <groupId>de.splatgames.aether.datafixers</groupId> |
| 27 | + <artifactId>aether-datafixers-core</artifactId> |
| 28 | + <version>0.2.0</version> |
| 29 | +</dependency> |
| 30 | +``` |
| 31 | + |
| 32 | +**Using the BOM** |
| 33 | + |
| 34 | +```xml |
| 35 | +<dependencyManagement> |
| 36 | + <dependencies> |
| 37 | + <dependency> |
| 38 | + <groupId>de.splatgames.aether.datafixers</groupId> |
| 39 | + <artifactId>aether-datafixers-bom</artifactId> |
| 40 | + <version>0.2.0</version> |
| 41 | + <type>pom</type> |
| 42 | + <scope>import</scope> |
| 43 | + </dependency> |
| 44 | + </dependencies> |
| 45 | +</dependencyManagement> |
| 46 | + |
| 47 | +<dependencies> |
| 48 | + <!-- No version needed --> |
| 49 | + <dependency> |
| 50 | + <groupId>de.splatgames.aether.datafixers</groupId> |
| 51 | + <artifactId>aether-datafixers-core</artifactId> |
| 52 | + </dependency> |
| 53 | +</dependencies> |
| 54 | +``` |
| 55 | + |
| 56 | +### Gradle (Groovy) |
| 57 | + |
| 58 | +```groovy |
| 59 | +dependencies { |
| 60 | + implementation 'de.splatgames.aether.datafixers:aether-datafixers-core:0.2.0' |
| 61 | + // Or with BOM: |
| 62 | + implementation platform('de.splatgames.aether.datafixers:aether-datafixers-bom:0.2.0') |
| 63 | + implementation 'de.splatgames.aether.datafixers:aether-datafixers-core' |
| 64 | +} |
| 65 | +``` |
| 66 | + |
| 67 | +### Gradle (Kotlin) |
| 68 | + |
| 69 | +```kotlin |
| 70 | +dependencies { |
| 71 | + implementation("de.splatgames.aether.datafixers:aether-datafixers-core:0.2.0") |
| 72 | + // Or with BOM: |
| 73 | + implementation(platform("de.splatgames.aether.datafixers:aether-datafixers-bom:0.2.0")) |
| 74 | + implementation("de.splatgames.aether.datafixers:aether-datafixers-core") |
| 75 | +} |
| 76 | +``` |
| 77 | + |
| 78 | +--- |
| 79 | + |
| 80 | +## What's New |
| 81 | + |
| 82 | +### Testkit Module |
| 83 | + |
| 84 | +New module `aether-datafixers-testkit` for testing migrations: |
| 85 | + |
| 86 | +```xml |
| 87 | +<dependency> |
| 88 | + <groupId>de.splatgames.aether.datafixers</groupId> |
| 89 | + <artifactId>aether-datafixers-testkit</artifactId> |
| 90 | + <version>0.2.0</version> |
| 91 | + <scope>test</scope> |
| 92 | +</dependency> |
| 93 | +``` |
| 94 | + |
| 95 | +**Features:** |
| 96 | +- `TestData` — Fluent test data builders for Gson and Jackson |
| 97 | +- `AetherAssertions` — Custom AssertJ assertions for `Dynamic`, `DataResult`, `Typed` |
| 98 | +- `DataFixTester` — Test harness for individual DataFix implementations |
| 99 | +- `MigrationTester` — Test harness for complete migration chains |
| 100 | +- `SchemaTester` — Test harness for Schema validation |
| 101 | +- `QuickFix` — Factory methods for common fix patterns |
| 102 | +- `MockSchemas` — Factory for mock Schema instances |
| 103 | +- `RecordingContext` / `AssertingContext` — Test contexts |
| 104 | + |
| 105 | +**Example:** |
| 106 | +```java |
| 107 | +// Create test data fluently |
| 108 | +Dynamic<JsonElement> input = TestData.gson().object() |
| 109 | + .put("name", "Alice") |
| 110 | + .put("level", 10) |
| 111 | + .build(); |
| 112 | + |
| 113 | +// Test a DataFix |
| 114 | +DataFixTester.forFix(myFix) |
| 115 | + .withInput(input) |
| 116 | + .forType("player") |
| 117 | + .expectOutput(expected) |
| 118 | + .verify(); |
| 119 | + |
| 120 | +// Test a full migration chain |
| 121 | +MigrationTester.forFixer(myFixer) |
| 122 | + .forType(PLAYER) |
| 123 | + .withInput(v1Data) |
| 124 | + .from(1).to(5) |
| 125 | + .expectOutput(v5Data) |
| 126 | + .verify(); |
| 127 | +``` |
| 128 | + |
| 129 | +### Extended Rewrite Rules |
| 130 | + |
| 131 | +New convenience methods in `Rules` class: |
| 132 | + |
| 133 | +| Rule | Purpose | |
| 134 | +|------|---------| |
| 135 | +| `dynamicTransform(name, ops, fn)` | Custom Dynamic transformation | |
| 136 | +| `setField(ops, field, value)` | Set field (overwrites existing) | |
| 137 | +| `renameFields(ops, map)` | Batch rename multiple fields | |
| 138 | +| `removeFields(ops, fields...)` | Batch remove multiple fields | |
| 139 | +| `groupFields(ops, target, fields...)` | Group fields into nested object | |
| 140 | +| `flattenField(ops, field)` | Flatten nested object to root | |
| 141 | +| `moveField(ops, source, target)` | Move field between paths | |
| 142 | +| `copyField(ops, source, target)` | Copy field (keeps original) | |
| 143 | +| `transformFieldAt(ops, path, fn)` | Transform at nested path | |
| 144 | +| `renameFieldAt(ops, path, newName)` | Rename at nested path | |
| 145 | +| `removeFieldAt(ops, path)` | Remove at nested path | |
| 146 | +| `addFieldAt(ops, path, value)` | Add at nested path | |
| 147 | +| `ifFieldExists(ops, field, rule)` | Conditional on existence | |
| 148 | +| `ifFieldMissing(ops, field, rule)` | Conditional on absence | |
| 149 | +| `ifFieldEquals(ops, field, value, rule)` | Conditional on value | |
| 150 | + |
| 151 | +**Example:** |
| 152 | +```java |
| 153 | +Rules.seq( |
| 154 | + Rules.renameFields(ops, Map.of("playerName", "name", "xp", "experience")), |
| 155 | + Rules.groupFields(ops, "position", "x", "y", "z"), |
| 156 | + Rules.ifFieldMissing(ops, "version", Rules.setField(ops, "version", d -> d.createInt(1))) |
| 157 | +) |
| 158 | +``` |
| 159 | + |
| 160 | +### Migration Diagnostics |
| 161 | + |
| 162 | +New opt-in diagnostic system for capturing structured reports: |
| 163 | + |
| 164 | +**API:** |
| 165 | +- `DiagnosticOptions` — Configuration for diagnostic capture |
| 166 | +- `DiagnosticContext` — Context interface for diagnostic migrations |
| 167 | +- `MigrationReport` — Immutable report with timing, fixes, rules, warnings, and snapshots |
| 168 | + |
| 169 | +**Features:** |
| 170 | +- Zero overhead when diagnostics are not enabled |
| 171 | +- Configurable snapshot capture with truncation limits |
| 172 | +- Per-fix and per-rule timing measurements |
| 173 | +- Warning emission from DataFix implementations |
| 174 | + |
| 175 | +**Presets:** |
| 176 | +- `DiagnosticOptions.defaults()` — Full diagnostics with snapshots and rule details |
| 177 | +- `DiagnosticOptions.minimal()` — Timing only, minimal overhead |
| 178 | + |
| 179 | +### High-Performance APIs |
| 180 | + |
| 181 | +**BatchTransform:** |
| 182 | +```java |
| 183 | +Rules.batch(ops, batch -> batch |
| 184 | + .rename("oldName", "newName") |
| 185 | + .remove("deprecated") |
| 186 | + .set("version", d -> d.createInt(2)) |
| 187 | + .transform("count", d -> d.createInt(d.asInt(0) + 1)) |
| 188 | + .addIfMissing("created", d -> d.createLong(System.currentTimeMillis())) |
| 189 | +) |
| 190 | +``` |
| 191 | + |
| 192 | +**Single-Pass Conditionals:** |
| 193 | +```java |
| 194 | +Rules.conditionalTransform(ops, |
| 195 | + d -> d.get("type").asString("").equals("legacy"), |
| 196 | + d -> d.set("migrated", d.createBoolean(true)) |
| 197 | +) |
| 198 | +``` |
| 199 | + |
| 200 | +### Performance Optimizations |
| 201 | + |
| 202 | +Internal optimizations with no API changes: |
| 203 | +- Path parsing uses character-based parsing with memoization cache |
| 204 | +- `DataFixRegistry.getFixes()` pre-allocates result list |
| 205 | +- `DataFixerImpl` moves validation to registration time |
| 206 | +- Reduced allocations in hot paths |
| 207 | + |
| 208 | +--- |
| 209 | + |
| 210 | +## Changelog |
| 211 | + |
| 212 | +**New in 0.2.0** |
| 213 | + |
| 214 | +- Testkit module with fluent builders, assertions, and test harnesses |
| 215 | +- Extended rewrite rules for batch, grouping, path, and conditional operations |
| 216 | +- Migration diagnostics system with timing and snapshots |
| 217 | +- High-performance batch transformations |
| 218 | +- Single-pass conditional APIs |
| 219 | +- Performance optimizations (memoization, pre-allocation) |
| 220 | +- Comprehensive documentation updates |
| 221 | + |
| 222 | +**Full Changelog:** [v0.1.0...v0.2.0](https://github.com/aether-framework/aether-datafixers/compare/v0.1.0...v0.2.0) |
| 223 | + |
| 224 | +--- |
| 225 | + |
| 226 | +## Roadmap (next) |
| 227 | + |
| 228 | +- **0.3.x** |
| 229 | + - Additional codec implementations |
| 230 | + - Schema validation enhancements |
| 231 | + - Migration dry-run mode |
| 232 | + |
| 233 | +- **1.0.x** |
| 234 | + - Stable API surface |
| 235 | + - Comprehensive documentation |
| 236 | + - Production-ready release |
| 237 | + |
| 238 | +--- |
| 239 | + |
| 240 | +## License |
| 241 | + |
| 242 | +**MIT** — see `LICENSE`. |
0 commit comments