Skip to content

Commit 6cde5a1

Browse files
committed
Add support for schema/fix class prefix validation and update related tests and documentation
1 parent 7d94c54 commit 6cde5a1

6 files changed

Lines changed: 232 additions & 27 deletions

File tree

CHANGELOG.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,9 @@ New module for schema analysis, validation, and migration coverage checking.
4040
- `StructureValidator` — Validates schema structure (cycles, version ordering, parent chains)
4141
- `ConventionChecker` — Validates naming conventions for types, fields, classes
4242
- `ConventionRules` — Configurable naming rules (STRICT, RELAXED, NONE, or custom)
43-
- Predefined patterns for snake_case, camelCase, class suffixes
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
4446
- Custom validators via `customTypeValidator()` and `customFieldValidator()`
4547

4648
**Type Introspection (`schematools.introspection`):**

aether-datafixers-schema-tools/src/main/java/de/splatgames/aether/datafixers/schematools/validation/ConventionChecker.java

Lines changed: 37 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -114,8 +114,8 @@ public static ValidationResult checkSchema(
114114
if (!rules.isValidSchemaClassName(schemaClassName)) {
115115
result.add(createIssue(
116116
CONVENTION_SCHEMA_CLASS,
117-
"Schema class name '" + schemaClassName + "' should end with '"
118-
+ rules.schemaClassSuffix() + "'",
117+
buildClassNameViolationMessage(schemaClassName, "Schema",
118+
rules.schemaClassPrefix(), rules.schemaClassSuffix()),
119119
schemaLocation,
120120
rules
121121
));
@@ -174,8 +174,8 @@ public static ValidationResult checkFix(
174174
if (!rules.isValidFixClassName(fixClassName)) {
175175
result.add(createIssue(
176176
CONVENTION_FIX_CLASS,
177-
"Fix class name '" + fixClassName + "' should end with '"
178-
+ rules.fixClassSuffix() + "'",
177+
buildClassNameViolationMessage(fixClassName, "Fix",
178+
rules.fixClassPrefix(), rules.fixClassSuffix()),
179179
"Fix@" + fix.toVersion().getVersion(),
180180
rules
181181
));
@@ -215,6 +215,39 @@ private static void checkFieldNames(
215215
}
216216
}
217217

218+
/**
219+
* Builds a descriptive message for class name convention violations.
220+
*
221+
* @param className the actual class name
222+
* @param type the type of class (e.g., "Schema", "Fix")
223+
* @param prefix the expected prefix, or {@code null}
224+
* @param suffix the expected suffix, or {@code null}
225+
* @return the violation message
226+
*/
227+
@NotNull
228+
private static String buildClassNameViolationMessage(
229+
@NotNull final String className,
230+
@NotNull final String type,
231+
final String prefix,
232+
final String suffix
233+
) {
234+
final StringBuilder message = new StringBuilder();
235+
message.append(type).append(" class name '").append(className).append("' should ");
236+
237+
if (prefix != null && suffix != null) {
238+
message.append("start with '").append(prefix)
239+
.append("' and end with '").append(suffix).append("'");
240+
} else if (prefix != null) {
241+
message.append("start with '").append(prefix).append("'");
242+
} else if (suffix != null) {
243+
message.append("end with '").append(suffix).append("'");
244+
} else {
245+
message.append("follow naming conventions");
246+
}
247+
248+
return message.toString();
249+
}
250+
218251
/**
219252
* Creates a validation issue with the appropriate severity based on rules.
220253
*/

aether-datafixers-schema-tools/src/main/java/de/splatgames/aether/datafixers/schematools/validation/ConventionRules.java

Lines changed: 101 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -75,14 +75,14 @@ public final class ConventionRules {
7575
* <ul>
7676
* <li>Type names must be snake_case</li>
7777
* <li>Field names must be snake_case</li>
78-
* <li>Schema class names must end with "Schema"</li>
79-
* <li>DataFix class names must end with "Fix"</li>
78+
* <li>Schema class names must start with "Schema" (e.g., Schema100, Schema200)</li>
79+
* <li>DataFix class names must end with "Fix" (e.g., PlayerNameFix)</li>
8080
* </ul>
8181
*/
8282
public static final ConventionRules STRICT = builder()
8383
.typeNamePattern(Pattern.compile("^[a-z][a-z0-9_]*$"))
8484
.fieldNamePattern(Pattern.compile("^[a-z][a-z0-9_]*$"))
85-
.schemaClassSuffix("Schema")
85+
.schemaClassPrefix("Schema")
8686
.fixClassSuffix("Fix")
8787
.treatViolationsAsErrors(true)
8888
.build();
@@ -128,11 +128,21 @@ public final class ConventionRules {
128128
*/
129129
private final String typeNamePrefix;
130130

131+
/**
132+
* Expected prefix for schema class names (e.g., "Schema"), or {@code null} to skip.
133+
*/
134+
private final String schemaClassPrefix;
135+
131136
/**
132137
* Expected suffix for schema class names (e.g., "Schema"), or {@code null} to skip.
133138
*/
134139
private final String schemaClassSuffix;
135140

141+
/**
142+
* Expected prefix for fix class names, or {@code null} to skip.
143+
*/
144+
private final String fixClassPrefix;
145+
136146
/**
137147
* Expected suffix for fix class names (e.g., "Fix"), or {@code null} to skip.
138148
*/
@@ -162,7 +172,9 @@ public final class ConventionRules {
162172
* @param typeNamePattern pattern for type names
163173
* @param fieldNamePattern pattern for field names
164174
* @param typeNamePrefix required prefix for type names
175+
* @param schemaClassPrefix expected prefix for schema classes
165176
* @param schemaClassSuffix expected suffix for schema classes
177+
* @param fixClassPrefix expected prefix for fix classes
166178
* @param fixClassSuffix expected suffix for fix classes
167179
* @param treatViolationsAsErrors whether violations are errors
168180
* @param customTypeValidator custom type name validator
@@ -173,7 +185,9 @@ private ConventionRules(
173185
final Pattern typeNamePattern,
174186
final Pattern fieldNamePattern,
175187
final String typeNamePrefix,
188+
final String schemaClassPrefix,
176189
final String schemaClassSuffix,
190+
final String fixClassPrefix,
177191
final String fixClassSuffix,
178192
final boolean treatViolationsAsErrors,
179193
final Predicate<String> customTypeValidator,
@@ -183,7 +197,9 @@ private ConventionRules(
183197
this.typeNamePattern = typeNamePattern;
184198
this.fieldNamePattern = fieldNamePattern;
185199
this.typeNamePrefix = typeNamePrefix;
200+
this.schemaClassPrefix = schemaClassPrefix;
186201
this.schemaClassSuffix = schemaClassSuffix;
202+
this.fixClassPrefix = fixClassPrefix;
187203
this.fixClassSuffix = fixClassSuffix;
188204
this.treatViolationsAsErrors = treatViolationsAsErrors;
189205
this.customTypeValidator = customTypeValidator;
@@ -269,33 +285,59 @@ public boolean isValidFieldName(@NotNull final String fieldName) {
269285
/**
270286
* Validates a schema class name against conventions.
271287
*
288+
* <p>Checks both prefix and suffix if configured. For example, with prefix "Schema",
289+
* valid names include: Schema100, Schema200, SchemaV1.</p>
290+
*
272291
* @param className the class name to validate, must not be {@code null}
273292
* @return {@code true} if the name follows conventions
274293
*/
275294
public boolean isValidSchemaClassName(@NotNull final String className) {
276295
Preconditions.checkNotNull(className, "className must not be null");
277296

278-
if (!this.enabled || this.schemaClassSuffix == null) {
297+
if (!this.enabled) {
279298
return true;
280299
}
281300

282-
return className.endsWith(this.schemaClassSuffix);
301+
// Check prefix
302+
if (this.schemaClassPrefix != null && !className.startsWith(this.schemaClassPrefix)) {
303+
return false;
304+
}
305+
306+
// Check suffix
307+
if (this.schemaClassSuffix != null && !className.endsWith(this.schemaClassSuffix)) {
308+
return false;
309+
}
310+
311+
return true;
283312
}
284313

285314
/**
286315
* Validates a fix class name against conventions.
287316
*
317+
* <p>Checks both prefix and suffix if configured. For example, with suffix "Fix",
318+
* valid names include: PlayerNameFix, SwordRenameFix.</p>
319+
*
288320
* @param className the class name to validate, must not be {@code null}
289321
* @return {@code true} if the name follows conventions
290322
*/
291323
public boolean isValidFixClassName(@NotNull final String className) {
292324
Preconditions.checkNotNull(className, "className must not be null");
293325

294-
if (!this.enabled || this.fixClassSuffix == null) {
326+
if (!this.enabled) {
295327
return true;
296328
}
297329

298-
return className.endsWith(this.fixClassSuffix);
330+
// Check prefix
331+
if (this.fixClassPrefix != null && !className.startsWith(this.fixClassPrefix)) {
332+
return false;
333+
}
334+
335+
// Check suffix
336+
if (this.fixClassSuffix != null && !className.endsWith(this.fixClassSuffix)) {
337+
return false;
338+
}
339+
340+
return true;
299341
}
300342

301343
/**
@@ -325,6 +367,15 @@ public String typeNamePrefix() {
325367
return this.typeNamePrefix;
326368
}
327369

370+
/**
371+
* Returns the expected schema class prefix.
372+
*
373+
* @return the prefix, or {@code null} if not required
374+
*/
375+
public String schemaClassPrefix() {
376+
return this.schemaClassPrefix;
377+
}
378+
328379
/**
329380
* Returns the expected schema class suffix.
330381
*
@@ -334,6 +385,15 @@ public String schemaClassSuffix() {
334385
return this.schemaClassSuffix;
335386
}
336387

388+
/**
389+
* Returns the expected fix class prefix.
390+
*
391+
* @return the prefix, or {@code null} if not required
392+
*/
393+
public String fixClassPrefix() {
394+
return this.fixClassPrefix;
395+
}
396+
337397
/**
338398
* Returns the expected fix class suffix.
339399
*
@@ -361,7 +421,9 @@ public static final class Builder {
361421
private Pattern typeNamePattern;
362422
private Pattern fieldNamePattern;
363423
private String typeNamePrefix;
424+
private String schemaClassPrefix;
364425
private String schemaClassSuffix;
426+
private String fixClassPrefix;
365427
private String fixClassSuffix;
366428
private boolean treatViolationsAsErrors = false;
367429
private Predicate<String> customTypeValidator;
@@ -418,6 +480,21 @@ public Builder requireTypePrefix(final String prefix) {
418480
return this;
419481
}
420482

483+
/**
484+
* Sets the expected prefix for schema class names.
485+
*
486+
* <p>For example, with prefix "Schema", valid class names include:
487+
* Schema100, Schema200, SchemaV1.</p>
488+
*
489+
* @param prefix the expected prefix (e.g., "Schema"), or {@code null} to skip
490+
* @return this builder for chaining
491+
*/
492+
@NotNull
493+
public Builder schemaClassPrefix(final String prefix) {
494+
this.schemaClassPrefix = prefix;
495+
return this;
496+
}
497+
421498
/**
422499
* Sets the expected suffix for schema class names.
423500
*
@@ -430,9 +507,24 @@ public Builder schemaClassSuffix(final String suffix) {
430507
return this;
431508
}
432509

510+
/**
511+
* Sets the expected prefix for fix class names.
512+
*
513+
* @param prefix the expected prefix, or {@code null} to skip
514+
* @return this builder for chaining
515+
*/
516+
@NotNull
517+
public Builder fixClassPrefix(final String prefix) {
518+
this.fixClassPrefix = prefix;
519+
return this;
520+
}
521+
433522
/**
434523
* Sets the expected suffix for fix class names.
435524
*
525+
* <p>For example, with suffix "Fix", valid class names include:
526+
* PlayerNameFix, SwordRenameFix.</p>
527+
*
436528
* @param suffix the expected suffix (e.g., "Fix"), or {@code null} to skip
437529
* @return this builder for chaining
438530
*/
@@ -490,7 +582,9 @@ public ConventionRules build() {
490582
this.typeNamePattern,
491583
this.fieldNamePattern,
492584
this.typeNamePrefix,
585+
this.schemaClassPrefix,
493586
this.schemaClassSuffix,
587+
this.fixClassPrefix,
494588
this.fixClassSuffix,
495589
this.treatViolationsAsErrors,
496590
this.customTypeValidator,

aether-datafixers-schema-tools/src/test/java/de/splatgames/aether/datafixers/schematools/validation/ConventionCheckerTest.java

Lines changed: 19 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -155,9 +155,10 @@ void typeNameIssueHasLocation() {
155155

156156
final ValidationResult result = ConventionChecker.checkSchema(schema, ConventionRules.STRICT);
157157

158-
assertThat(result.issues()).isNotEmpty();
159-
assertThat(result.issues().get(0).location()).isPresent();
160-
assertThat(result.issues().get(0).location().orElseThrow()).contains("BadName");
158+
final var typeNameIssues = result.byCode(ConventionChecker.CONVENTION_TYPE_NAME);
159+
assertThat(typeNameIssues).isNotEmpty();
160+
assertThat(typeNameIssues.get(0).location()).isPresent();
161+
assertThat(typeNameIssues.get(0).location().orElseThrow()).contains("BadName");
161162
}
162163

163164
@Test
@@ -169,8 +170,9 @@ void typeNameIssueHasContext() {
169170

170171
final ValidationResult result = ConventionChecker.checkSchema(schema, ConventionRules.STRICT);
171172

172-
assertThat(result.issues()).isNotEmpty();
173-
assertThat(result.issues().get(0).context()).containsKey("typeName");
173+
final var typeNameIssues = result.byCode(ConventionChecker.CONVENTION_TYPE_NAME);
174+
assertThat(typeNameIssues).isNotEmpty();
175+
assertThat(typeNameIssues.get(0).context()).containsKey("typeName");
174176
}
175177

176178
@Test
@@ -199,8 +201,9 @@ void issueMessageContainsPatternInfo() {
199201

200202
final ValidationResult result = ConventionChecker.checkSchema(schema, ConventionRules.STRICT);
201203

202-
assertThat(result.issues()).isNotEmpty();
203-
assertThat(result.issues().get(0).message()).contains("pattern");
204+
final var typeNameIssues = result.byCode(ConventionChecker.CONVENTION_TYPE_NAME);
205+
assertThat(typeNameIssues).isNotEmpty();
206+
assertThat(typeNameIssues.get(0).message()).contains("pattern");
204207
}
205208
}
206209

@@ -295,7 +298,15 @@ void handlesNestedFieldTypes() {
295298
void handlesEmptySchema() {
296299
final Schema schema = MockSchemas.minimal(100);
297300

298-
final ValidationResult result = ConventionChecker.checkSchema(schema, ConventionRules.STRICT);
301+
// Use custom rules that only check type/field names, not schema class names
302+
// (MockSchemas generates class names like "MinimalSchema" that don't match prefix rules)
303+
final ConventionRules rules = ConventionRules.builder()
304+
.typeNamePattern(ConventionRules.STRICT.typeNamePattern())
305+
.fieldNamePattern(ConventionRules.STRICT.fieldNamePattern())
306+
.treatViolationsAsErrors(true)
307+
.build();
308+
309+
final ValidationResult result = ConventionChecker.checkSchema(schema, rules);
299310

300311
assertThat(result.issues()).isEmpty();
301312
}

0 commit comments

Comments
 (0)