Skip to content

Commit 7ba89dc

Browse files
authored
Merge pull request #11 from aether-framework/feature/migration-diagnostics
Enable optional diagnostics framework for migration tracking
2 parents 86044ae + aba0951 commit 7ba89dc

22 files changed

Lines changed: 3866 additions & 4 deletions

File tree

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ inspired by Minecraft's DataFixer Upper (DFU), with a focus on **simplicity**, *
1010

1111
---
1212

13-
## ✨ Features (v0.2.0)
13+
## ✨ Features (v0.1.0)
1414

1515
-**Schema-Based Versioning** — Define data types per version with `Schema` and `TypeRegistry`
1616
-**Forward Patching** — Apply `DataFix` instances sequentially to migrate data across versions
@@ -62,7 +62,7 @@ AetherDataFixer fixer = new DataFixerRuntimeFactory()
6262

6363
```java
6464
Dynamic<?> updated = fixer.update(
65-
TypeReference.of("player"),
65+
new TypeReference("player"),
6666
inputDynamic,
6767
fromVersion,
6868
toVersion
Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
/*
2+
* Copyright (c) 2025 Splatgames.de Software and Contributors
3+
*
4+
* Permission is hereby granted, free of charge, to any person obtaining a copy
5+
* of this software and associated documentation files (the "Software"), to deal
6+
* in the Software without restriction, including without limitation the rights
7+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8+
* copies of the Software, and to permit persons to whom the Software is
9+
* furnished to do so, subject to the following conditions:
10+
*
11+
* The above copyright notice and this permission notice shall be included in all
12+
* copies or substantial portions of the Software.
13+
*
14+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20+
* SOFTWARE.
21+
*/
22+
23+
package de.splatgames.aether.datafixers.api.diagnostic;
24+
25+
import de.splatgames.aether.datafixers.api.fix.DataFixerContext;
26+
import org.jetbrains.annotations.NotNull;
27+
28+
/**
29+
* A diagnostic-aware context for capturing detailed migration information.
30+
*
31+
* <p>{@code DiagnosticContext} extends {@link DataFixerContext} with capabilities
32+
* for capturing comprehensive diagnostic data during migration operations.
33+
* When passed to a data fixer, it enables opt-in collection of timing information,
34+
* applied fixes, rule applications, and data snapshots.</p>
35+
*
36+
* <h2>Usage Example</h2>
37+
* <pre>{@code
38+
* // Create a diagnostic context with default options
39+
* DiagnosticContext context = DiagnosticContext.create();
40+
*
41+
* // Or with custom options
42+
* DiagnosticContext context = DiagnosticContext.create(
43+
* DiagnosticOptions.builder()
44+
* .captureSnapshots(true)
45+
* .captureRuleDetails(true)
46+
* .build()
47+
* );
48+
*
49+
* // Run migration with diagnostics
50+
* Dynamic<?> result = fixer.update(type, input, fromVersion, toVersion, context);
51+
*
52+
* // Access the report
53+
* MigrationReport report = context.getReport();
54+
* System.out.println(report.toSummary());
55+
* }</pre>
56+
*
57+
* <h2>Opt-in Behavior</h2>
58+
* <p>Diagnostic collection is opt-in. Only when a {@code DiagnosticContext}
59+
* is explicitly passed to the data fixer will diagnostic data be captured.
60+
* This ensures zero overhead for normal operations.</p>
61+
*
62+
* <h2>Thread Safety</h2>
63+
* <p>Implementations should be designed for single-threaded use during a
64+
* single migration operation. The resulting {@link MigrationReport} is
65+
* immutable and thread-safe.</p>
66+
*
67+
* @author Erik Pförtner
68+
* @see DataFixerContext
69+
* @see MigrationReport
70+
* @see DiagnosticOptions
71+
* @since 0.2.0
72+
*/
73+
public interface DiagnosticContext extends DataFixerContext {
74+
75+
/**
76+
* Returns whether diagnostic collection is enabled.
77+
*
78+
* <p>This method allows the data fixer to check if it should emit
79+
* diagnostic events. For {@code DiagnosticContext} implementations,
80+
* this typically returns {@code true}.</p>
81+
*
82+
* @return {@code true} if diagnostics are enabled
83+
*/
84+
boolean isDiagnosticEnabled();
85+
86+
/**
87+
* Returns the report builder for recording diagnostic events.
88+
*
89+
* <p>This method is intended for internal use by the data fixer
90+
* implementation to record events during migration.</p>
91+
*
92+
* @return the report builder, never {@code null}
93+
*/
94+
@NotNull
95+
MigrationReport.Builder reportBuilder();
96+
97+
/**
98+
* Returns the completed migration report.
99+
*
100+
* <p>This method should be called after the migration completes to
101+
* retrieve the diagnostic information. Calling this method finalizes
102+
* the report building process.</p>
103+
*
104+
* @return the migration report, never {@code null}
105+
* @throws IllegalStateException if called before migration starts
106+
*/
107+
@NotNull
108+
MigrationReport getReport();
109+
110+
/**
111+
* Returns the diagnostic options controlling what data is captured.
112+
*
113+
* @return the diagnostic options, never {@code null}
114+
*/
115+
@NotNull
116+
DiagnosticOptions options();
117+
118+
/**
119+
* Creates a new diagnostic context with default options.
120+
*
121+
* <p>Default options enable full diagnostic capture including snapshots
122+
* and rule details. See {@link DiagnosticOptions#defaults()}.</p>
123+
*
124+
* @return a new diagnostic context
125+
*/
126+
@NotNull
127+
static DiagnosticContext create() {
128+
return create(DiagnosticOptions.defaults());
129+
}
130+
131+
/**
132+
* Creates a new diagnostic context with the specified options.
133+
*
134+
* @param options the diagnostic options
135+
* @return a new diagnostic context
136+
* @throws NullPointerException if options is {@code null}
137+
*/
138+
@NotNull
139+
static DiagnosticContext create(@NotNull final DiagnosticOptions options) {
140+
// Use ServiceLoader or direct instantiation
141+
// For now, we'll use direct instantiation via the core module
142+
// This will be resolved at runtime by the core implementation
143+
try {
144+
final Class<?> implClass = Class.forName(
145+
"de.splatgames.aether.datafixers.core.diagnostic.DiagnosticContextImpl"
146+
);
147+
return (DiagnosticContext) implClass
148+
.getConstructor(DiagnosticOptions.class)
149+
.newInstance(options);
150+
} catch (final ReflectiveOperationException e) {
151+
throw new IllegalStateException(
152+
"Failed to create DiagnosticContext. " +
153+
"Ensure aether-datafixers-core is on the classpath.",
154+
e
155+
);
156+
}
157+
}
158+
}
Lines changed: 215 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,215 @@
1+
/*
2+
* Copyright (c) 2025 Splatgames.de Software and Contributors
3+
*
4+
* Permission is hereby granted, free of charge, to any person obtaining a copy
5+
* of this software and associated documentation files (the "Software"), to deal
6+
* in the Software without restriction, including without limitation the rights
7+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8+
* copies of the Software, and to permit persons to whom the Software is
9+
* furnished to do so, subject to the following conditions:
10+
*
11+
* The above copyright notice and this permission notice shall be included in all
12+
* copies or substantial portions of the Software.
13+
*
14+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20+
* SOFTWARE.
21+
*/
22+
23+
package de.splatgames.aether.datafixers.api.diagnostic;
24+
25+
import org.jetbrains.annotations.NotNull;
26+
27+
/**
28+
* Configuration options for migration diagnostics.
29+
*
30+
* <p>{@code DiagnosticOptions} controls what diagnostic data is captured
31+
* during a migration operation. This allows fine-tuning the balance between
32+
* diagnostic detail and performance/memory overhead.</p>
33+
*
34+
* <h2>Usage Example</h2>
35+
* <pre>{@code
36+
* DiagnosticOptions options = DiagnosticOptions.builder()
37+
* .captureSnapshots(true)
38+
* .captureRuleDetails(true)
39+
* .maxSnapshotLength(5000)
40+
* .prettyPrintSnapshots(true)
41+
* .build();
42+
*
43+
* DiagnosticContext context = DiagnosticContext.create(options);
44+
* }</pre>
45+
*
46+
* <h2>Presets</h2>
47+
* <ul>
48+
* <li>{@link #defaults()} - Full diagnostics with snapshots and rule details</li>
49+
* <li>{@link #minimal()} - Only timing information, no snapshots or rule details</li>
50+
* </ul>
51+
*
52+
* @param captureSnapshots whether to capture before/after data snapshots
53+
* @param captureRuleDetails whether to capture individual rule application details
54+
* @param maxSnapshotLength maximum length for snapshot strings (0 for unlimited)
55+
* @param prettyPrintSnapshots whether to format snapshots for readability
56+
* @author Erik Pförtner
57+
* @see DiagnosticContext
58+
* @see MigrationReport
59+
* @since 0.2.0
60+
*/
61+
public record DiagnosticOptions(
62+
boolean captureSnapshots,
63+
boolean captureRuleDetails,
64+
int maxSnapshotLength,
65+
boolean prettyPrintSnapshots
66+
) {
67+
68+
/**
69+
* Default maximum snapshot length.
70+
*/
71+
public static final int DEFAULT_MAX_SNAPSHOT_LENGTH = 10000;
72+
73+
/**
74+
* Creates default diagnostic options with full diagnostics enabled.
75+
*
76+
* <p>Default settings:</p>
77+
* <ul>
78+
* <li>{@code captureSnapshots} = {@code true}</li>
79+
* <li>{@code captureRuleDetails} = {@code true}</li>
80+
* <li>{@code maxSnapshotLength} = {@code 10000}</li>
81+
* <li>{@code prettyPrintSnapshots} = {@code true}</li>
82+
* </ul>
83+
*
84+
* @return default diagnostic options
85+
*/
86+
@NotNull
87+
public static DiagnosticOptions defaults() {
88+
return new DiagnosticOptions(true, true, DEFAULT_MAX_SNAPSHOT_LENGTH, true);
89+
}
90+
91+
/**
92+
* Creates minimal diagnostic options with only timing information.
93+
*
94+
* <p>Minimal settings:</p>
95+
* <ul>
96+
* <li>{@code captureSnapshots} = {@code false}</li>
97+
* <li>{@code captureRuleDetails} = {@code false}</li>
98+
* <li>{@code maxSnapshotLength} = {@code 0}</li>
99+
* <li>{@code prettyPrintSnapshots} = {@code false}</li>
100+
* </ul>
101+
*
102+
* @return minimal diagnostic options
103+
*/
104+
@NotNull
105+
public static DiagnosticOptions minimal() {
106+
return new DiagnosticOptions(false, false, 0, false);
107+
}
108+
109+
/**
110+
* Creates a new builder for constructing diagnostic options.
111+
*
112+
* @return a new builder instance
113+
*/
114+
@NotNull
115+
public static Builder builder() {
116+
return new Builder();
117+
}
118+
119+
/**
120+
* Builder for constructing {@link DiagnosticOptions} instances.
121+
*
122+
* <p>All settings default to the values from {@link DiagnosticOptions#defaults()}.</p>
123+
*/
124+
public static final class Builder {
125+
126+
private boolean captureSnapshots = true;
127+
private boolean captureRuleDetails = true;
128+
private int maxSnapshotLength = DEFAULT_MAX_SNAPSHOT_LENGTH;
129+
private boolean prettyPrintSnapshots = true;
130+
131+
private Builder() {
132+
}
133+
134+
/**
135+
* Sets whether to capture before/after data snapshots.
136+
*
137+
* <p>When enabled, the migration report will include JSON representations
138+
* of the data before and after each fix is applied. This is useful for
139+
* debugging but increases memory usage.</p>
140+
*
141+
* @param captureSnapshots {@code true} to capture snapshots
142+
* @return this builder
143+
*/
144+
@NotNull
145+
public Builder captureSnapshots(final boolean captureSnapshots) {
146+
this.captureSnapshots = captureSnapshots;
147+
return this;
148+
}
149+
150+
/**
151+
* Sets whether to capture individual rule application details.
152+
*
153+
* <p>When enabled, the migration report will include details about each
154+
* {@link de.splatgames.aether.datafixers.api.rewrite.TypeRewriteRule}
155+
* application, including timing and match status.</p>
156+
*
157+
* @param captureRuleDetails {@code true} to capture rule details
158+
* @return this builder
159+
*/
160+
@NotNull
161+
public Builder captureRuleDetails(final boolean captureRuleDetails) {
162+
this.captureRuleDetails = captureRuleDetails;
163+
return this;
164+
}
165+
166+
/**
167+
* Sets the maximum length for snapshot strings.
168+
*
169+
* <p>Snapshots exceeding this length will be truncated with a
170+
* {@code "... (truncated)"} suffix. Set to 0 for unlimited length.</p>
171+
*
172+
* @param maxSnapshotLength maximum length in characters (0 for unlimited)
173+
* @return this builder
174+
* @throws IllegalArgumentException if maxSnapshotLength is negative
175+
*/
176+
@NotNull
177+
public Builder maxSnapshotLength(final int maxSnapshotLength) {
178+
if (maxSnapshotLength < 0) {
179+
throw new IllegalArgumentException("maxSnapshotLength must be non-negative");
180+
}
181+
this.maxSnapshotLength = maxSnapshotLength;
182+
return this;
183+
}
184+
185+
/**
186+
* Sets whether to format snapshots for readability.
187+
*
188+
* <p>When enabled, JSON snapshots will be pretty-printed with indentation.
189+
* When disabled, snapshots will be compact single-line JSON.</p>
190+
*
191+
* @param prettyPrintSnapshots {@code true} to pretty-print snapshots
192+
* @return this builder
193+
*/
194+
@NotNull
195+
public Builder prettyPrintSnapshots(final boolean prettyPrintSnapshots) {
196+
this.prettyPrintSnapshots = prettyPrintSnapshots;
197+
return this;
198+
}
199+
200+
/**
201+
* Builds the diagnostic options.
202+
*
203+
* @return the constructed diagnostic options
204+
*/
205+
@NotNull
206+
public DiagnosticOptions build() {
207+
return new DiagnosticOptions(
208+
this.captureSnapshots,
209+
this.captureRuleDetails,
210+
this.maxSnapshotLength,
211+
this.prettyPrintSnapshots
212+
);
213+
}
214+
}
215+
}

0 commit comments

Comments
 (0)