Skip to content

Commit c93eafe

Browse files
authored
Merge pull request #17 from aether-framework/feature/schema-tooling
Improve schema validation and documentation
2 parents 30ea043 + 9f9f849 commit c93eafe

64 files changed

Lines changed: 16742 additions & 4 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

CHANGELOG.md

Lines changed: 51 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,53 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
66

77
---
88

9-
## [0.3.0] - 2026-01-07
9+
## [0.3.0] - 2026-01-08
1010

1111
### Added
1212

13+
#### Schema Tools Module (`aether-datafixers-schema-tools`)
14+
15+
New module for schema analysis, validation, and migration coverage checking.
16+
17+
**Schema Diffing (`schematools.diff`):**
18+
- `SchemaDiffer` — Fluent API for comparing two schemas
19+
- `SchemaDiff` — Immutable result with added/removed/common types
20+
- `TypeDiff` — Field-level changes for types present in both schemas
21+
- `FieldDiff` — Individual field change (ADDED, REMOVED, MODIFIED, UNCHANGED)
22+
- `DiffKind` — Enumeration of change types
23+
- Optional field-level diffing via `includeFieldLevel(true)`
24+
- Type filtering via `ignoreTypes(...)`
25+
26+
**Migration Analysis (`schematools.analysis`):**
27+
- `MigrationAnalyzer` — Fluent API for analyzing migration paths
28+
- `MigrationPath` — Complete migration sequence with all steps
29+
- `MigrationStep` — Single version transition with optional DataFix and SchemaDiff
30+
- `FixCoverage` — Analysis result showing fix coverage for schema changes
31+
- `CoverageGap` — Represents a schema change without corresponding DataFix
32+
- Coverage gap reasons: TYPE_ADDED, TYPE_REMOVED, TYPE_MODIFIED, FIELD_ADDED, FIELD_REMOVED, FIELD_TYPE_CHANGED
33+
- Orphan fix detection (fixes without schema changes)
34+
35+
**Schema Validation (`schematools.validation`):**
36+
- `SchemaValidator` — Fluent API for validating schemas
37+
- `ValidationResult` — Immutable collection of validation issues
38+
- `ValidationIssue` — Single issue with severity, code, message, location, context
39+
- `IssueSeverity` — ERROR, WARNING, INFO levels
40+
- `StructureValidator` — Validates schema structure (cycles, version ordering, parent chains)
41+
- `ConventionChecker` — Validates naming conventions for types, fields, classes
42+
- `ConventionRules` — Configurable naming rules (STRICT, RELAXED, NONE, or custom)
43+
- Schema class prefix/suffix validation (e.g., "Schema" prefix for Schema100, Schema200)
44+
- Fix class prefix/suffix validation (e.g., "Fix" suffix for PlayerNameFix)
45+
- Predefined patterns for snake_case, camelCase
46+
- Custom validators via `customTypeValidator()` and `customFieldValidator()`
47+
48+
**Type Introspection (`schematools.introspection`):**
49+
- `TypeIntrospector` — Utility for analyzing type structures
50+
- `TypeStructure` — Normalized, comparable representation of a Type
51+
- `FieldInfo` — Field metadata (name, path, optionality, type)
52+
- `TypeKind` — Classification (PRIMITIVE, LIST, OPTIONAL, PRODUCT, SUM, FIELD, etc.)
53+
- Recursive field extraction with hierarchical paths
54+
- Structural equality comparison
55+
1356
#### CLI Module (`aether-datafixers-cli`)
1457

1558
New command-line interface for data migration without writing Java code.
@@ -47,6 +90,13 @@ New command-line interface for data migration without writing Java code.
4790
- `BootstrapLoadException` — Bootstrap class loading failures
4891
- `FormatParseException` — Input parsing failures
4992

93+
### Documentation
94+
95+
- Added comprehensive Schema Tools documentation (diffing, analysis, validation, introspection)
96+
- Added CLI module documentation (commands, format handlers, examples)
97+
- Updated glossary with Schema Tools terminology
98+
- Updated installation guide with new modules
99+
50100
---
51101

52102
## [0.2.0] - 2026-01-07

README.md

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ inspired by Minecraft's DataFixer Upper (DFU), with a focus on **simplicity**, *
1818
-**Codec System** — Bidirectional transformation between typed Java objects and dynamic representations
1919
-**Type Safety** — Strong typing with `TypeReference` identifiers for data routing
2020
-**Testkit** — Fluent test data builders, custom assertions, and test harnesses for DataFix testing
21+
-**CLI Tool** — Migrate and validate data files from the command line with batch processing
22+
-**Schema Tools** — Schema diffing, validation, migration analysis, and type introspection
2123
-**Migration Diagnostics** — Opt-in structured reports with timing, applied fixes, and snapshots
2224
-**Extended Rewrite Rules** — Batch operations, path-based transforms, conditional rules
2325
-**High-Performance APIs**`Rules.batch()` for single-pass multi-operation transforms
@@ -31,6 +33,8 @@ inspired by Minecraft's DataFixer Upper (DFU), with a focus on **simplicity**, *
3133
- **aether-datafixers-core** — Default implementations of the API interfaces
3234
- **aether-datafixers-codec** — Codec implementations for serialization formats
3335
- **aether-datafixers-testkit** — Testing utilities for DataFix, Schema, and migration testing
36+
- **aether-datafixers-cli** — Command-line interface for data migration and validation
37+
- **aether-datafixers-schema-tools** — Schema analysis, validation, diffing, and introspection
3438
- **aether-datafixers-examples** — Practical examples demonstrating real-world usage
3539
- **aether-datafixers-bom** — Bill of Materials for coordinated dependency management
3640

@@ -418,8 +422,10 @@ mvn test
418422
- **Performance optimizations** — Path caching, optimized fix registry, reduced allocations
419423

420424
- **v0.3.0** (next)
421-
- **CLI module** — Migrate files and print/export a migration report (batch-friendly)
422-
- **Schema tooling** — Runtime schema validation + diff utilities between versions
425+
- **CLI module** — Migrate files from the command line with batch processing and reports
426+
- **Schema Tools module** — Schema diffing, migration analysis, validation, and introspection
427+
- **Fix coverage analysis** — Detect schema changes without corresponding DataFixes
428+
- **Convention checking** — Enforce naming conventions for types, fields, and classes
423429

424430
- **v0.4.0**
425431
- **Spring Boot integration** — Auto-configuration for DataFixer in Spring apps

aether-datafixers-api/src/main/java/de/splatgames/aether/datafixers/api/schema/SchemaRegistry.java

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,9 @@
2828
import org.jetbrains.annotations.NotNull;
2929
import org.jetbrains.annotations.Nullable;
3030

31+
import java.util.Set;
32+
import java.util.stream.Stream;
33+
3134
/**
3235
* A registry for managing {@link Schema} instances across data versions.
3336
*
@@ -152,4 +155,29 @@ default void freeze() {
152155
default boolean isFrozen() {
153156
return false;
154157
}
158+
159+
/**
160+
* Returns a stream of all registered schemas.
161+
*
162+
* <p>The returned stream provides access to all schemas in this registry.
163+
* The order of schemas in the stream is implementation-dependent.</p>
164+
*
165+
* @return a stream of all schemas, never {@code null}
166+
* @since 0.3.0
167+
*/
168+
@NotNull
169+
Stream<Schema> stream();
170+
171+
/**
172+
* Returns the set of all registered versions.
173+
*
174+
* <p>The returned set is a snapshot of the registered versions at the time
175+
* of the call. Modifications to the registry after this call will not be
176+
* reflected in the returned set.</p>
177+
*
178+
* @return an unmodifiable set of all registered versions, never {@code null}
179+
* @since 0.3.0
180+
*/
181+
@NotNull
182+
Set<DataVersion> versions();
155183
}

aether-datafixers-api/src/main/java/de/splatgames/aether/datafixers/api/type/TypeRegistry.java

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,19 @@ public interface TypeRegistry {
8989
*/
9090
boolean has(@NotNull final TypeReference ref);
9191

92+
/**
93+
* Returns all registered type references.
94+
*
95+
* <p>The returned set is a snapshot of the registered references at the time
96+
* of the call. Modifications to the registry after this call will not be
97+
* reflected in the returned set.</p>
98+
*
99+
* @return an unmodifiable set of all registered type references, never {@code null}
100+
* @since 0.3.0
101+
*/
102+
@NotNull
103+
java.util.Set<TypeReference> references();
104+
92105
/**
93106
* Retrieves a type by its reference, throwing if not found.
94107
*

aether-datafixers-api/src/test/java/de/splatgames/aether/datafixers/api/schema/SchemaTest.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -345,6 +345,12 @@ public Type<?> get(@NotNull TypeReference ref) {
345345
public boolean has(@NotNull TypeReference ref) {
346346
return types.containsKey(ref);
347347
}
348+
349+
@NotNull
350+
@Override
351+
public java.util.Set<TypeReference> references() {
352+
return java.util.Set.copyOf(types.keySet());
353+
}
348354
}
349355

350356
private static class TestSchema extends Schema {

aether-datafixers-bom/pom.xml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,11 @@
3737
<artifactId>aether-datafixers-testkit</artifactId>
3838
<version>${project.version}</version>
3939
</dependency>
40+
<dependency>
41+
<groupId>de.splatgames.aether.datafixers</groupId>
42+
<artifactId>aether-datafixers-schema-tools</artifactId>
43+
<version>${project.version}</version>
44+
</dependency>
4045
<dependency>
4146
<groupId>de.splatgames.aether.datafixers</groupId>
4247
<artifactId>aether-datafixers-cli</artifactId>

aether-datafixers-core/src/main/java/de/splatgames/aether/datafixers/core/fix/DataFixerBuilder.java

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -202,4 +202,22 @@ public DataFixer build() {
202202
this.registry.freeze();
203203
return new DataFixerImpl(this.currentVersion, this.registry, this.defaultContext);
204204
}
205+
206+
/**
207+
* Returns the internal fix registry.
208+
*
209+
* <p>This method provides access to the underlying {@link DataFixRegistry}
210+
* for analysis and introspection purposes. The registry may be mutable
211+
* if {@link #build()} has not been called yet.</p>
212+
*
213+
* <p><b>Warning:</b> Modifying the returned registry after build() has been
214+
* called will have no effect, as build() creates an immutable copy.</p>
215+
*
216+
* @return the fix registry, never {@code null}
217+
* @since 0.3.0
218+
*/
219+
@NotNull
220+
public DataFixRegistry getFixRegistry() {
221+
return this.registry;
222+
}
205223
}

aether-datafixers-core/src/main/java/de/splatgames/aether/datafixers/core/schema/SimpleSchemaRegistry.java

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,9 @@
3232
import java.util.Collections;
3333
import java.util.Map;
3434
import java.util.NavigableMap;
35+
import java.util.Set;
3536
import java.util.TreeMap;
37+
import java.util.stream.Stream;
3638

3739
/**
3840
* A simple {@link TreeMap}-based implementation of {@link SchemaRegistry}.
@@ -136,4 +138,16 @@ public void freeze() {
136138
public boolean isFrozen() {
137139
return this.frozen;
138140
}
141+
142+
@Override
143+
@NotNull
144+
public Stream<Schema> stream() {
145+
return this.schemas.values().stream();
146+
}
147+
148+
@Override
149+
@NotNull
150+
public Set<DataVersion> versions() {
151+
return this.frozen ? this.schemas.keySet() : Set.copyOf(this.schemas.keySet());
152+
}
139153
}

aether-datafixers-core/src/main/java/de/splatgames/aether/datafixers/core/type/SimpleTypeRegistry.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131

3232
import java.util.HashMap;
3333
import java.util.Map;
34+
import java.util.Set;
3435

3536
/**
3637
* A simple {@link HashMap}-based implementation of {@link TypeRegistry}.
@@ -85,6 +86,12 @@ public boolean has(@NotNull final TypeReference ref) {
8586
return this.types.containsKey(ref);
8687
}
8788

89+
@NotNull
90+
@Override
91+
public Set<TypeReference> references() {
92+
return this.frozen ? this.types.keySet() : Set.copyOf(this.types.keySet());
93+
}
94+
8895
@Override
8996
public void freeze() {
9097
if (!this.frozen) {
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<project xmlns="http://maven.apache.org/POM/4.0.0"
3+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
4+
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
5+
<modelVersion>4.0.0</modelVersion>
6+
7+
<parent>
8+
<groupId>de.splatgames.aether.datafixers</groupId>
9+
<artifactId>aether-datafixers</artifactId>
10+
<version>0.3.0-SNAPSHOT</version>
11+
</parent>
12+
13+
<artifactId>aether-datafixers-schema-tools</artifactId>
14+
<packaging>jar</packaging>
15+
16+
<name>Aether Datafixers :: Schema Tools</name>
17+
<description>Schema analysis, validation, and migration path analysis tools for Aether Datafixers.</description>
18+
19+
<dependencies>
20+
<!-- Aether Datafixers modules -->
21+
<dependency>
22+
<groupId>de.splatgames.aether.datafixers</groupId>
23+
<artifactId>aether-datafixers-api</artifactId>
24+
</dependency>
25+
<dependency>
26+
<groupId>de.splatgames.aether.datafixers</groupId>
27+
<artifactId>aether-datafixers-core</artifactId>
28+
</dependency>
29+
30+
<!-- JetBrains annotations -->
31+
<dependency>
32+
<groupId>org.jetbrains</groupId>
33+
<artifactId>annotations</artifactId>
34+
</dependency>
35+
36+
<!-- Guava for utilities -->
37+
<dependency>
38+
<groupId>com.google.guava</groupId>
39+
<artifactId>guava</artifactId>
40+
</dependency>
41+
42+
<!-- Testing -->
43+
<dependency>
44+
<groupId>org.junit.jupiter</groupId>
45+
<artifactId>junit-jupiter</artifactId>
46+
<scope>test</scope>
47+
</dependency>
48+
<dependency>
49+
<groupId>org.assertj</groupId>
50+
<artifactId>assertj-core</artifactId>
51+
<scope>test</scope>
52+
</dependency>
53+
<dependency>
54+
<groupId>de.splatgames.aether.datafixers</groupId>
55+
<artifactId>aether-datafixers-testkit</artifactId>
56+
<scope>test</scope>
57+
</dependency>
58+
<dependency>
59+
<groupId>de.splatgames.aether.datafixers</groupId>
60+
<artifactId>aether-datafixers-codec</artifactId>
61+
<scope>test</scope>
62+
</dependency>
63+
</dependencies>
64+
65+
<build>
66+
<plugins>
67+
<plugin>
68+
<groupId>org.apache.maven.plugins</groupId>
69+
<artifactId>maven-compiler-plugin</artifactId>
70+
</plugin>
71+
<plugin>
72+
<groupId>org.apache.maven.plugins</groupId>
73+
<artifactId>maven-enforcer-plugin</artifactId>
74+
</plugin>
75+
<plugin>
76+
<groupId>org.apache.maven.plugins</groupId>
77+
<artifactId>maven-surefire-plugin</artifactId>
78+
</plugin>
79+
</plugins>
80+
</build>
81+
</project>

0 commit comments

Comments
 (0)