Skip to content

Rubric Validation

Oliver Yasuna edited this page Jul 1, 2026 · 1 revision

Rubric — Validation

Rubric never fails a load because a value is invalid. Instead, invalid values are corrected — replaced with a suggested or default value — and the correction is logged on LoadResult.corrections(). This is the "correct-and-log" model.

Why not just reject

The alternatives are worse:

  • Reject the load and drop the user into a broken game state. Users hate this. Mod authors hate this.
  • Reject the entry and use its default but silently. Now the user's config file has a value they can't tell was ignored.

Correct-and-log picks up the pieces without losing information: the running config is always internally consistent (guaranteed valid), and every fix is recorded for the mod author to surface.

Rubric loads and applies corrections. What you do with them is up to you.

Built-in validators

Every constraint annotation attaches a validator:

Annotation Behavior on invalid
@Range Clamps to [min, max].
@OneOf Resets to the entry's default value.
@Pattern Resets to default.
@Length Truncates over-long collections; resets over-long strings to default.
@NotNull Resets to default.

Custom validators

Implement com.oliveryasuna.mc.rubric.validation.Validator<T>:

public interface Validator<T> {
    ValidationResult validate(T value);

    default ValidationResult validateRaw(Object value) {
        try {
            @SuppressWarnings("unchecked")
            final T typed = (T) value;
            return validate(typed);
        } catch(ClassCastException e) {
            return ValidationResult.invalid(ValidationIssue.of("type mismatch"));
        }
    }
}

Return ValidationResult.ok() for good values. Return ValidationResult.invalid(ValidationIssue.corrected("message", replacement)) when the value should be corrected to a specific replacement, or ValidationResult.invalid(ValidationIssue.of("message")) when the entry should be reset to its default.

Registering a custom validator

  • Annotation configs: write a matching annotation and update AnnotationSchemaReader to attach your validator. (Or, more commonly, wrap your logic into @Pattern's regex, @Length's bounds, etc.)
  • Builder configs: call .validator(new MyValidator()) on the ConfigSpec.EntryBuilder.

The correction record

LoadResult.corrections() returns a List<Correction>. Each entry has:

  • path — dotted path of the corrected value.
  • rejected — the value as read from the file.
  • replacement — what Rubric substituted.
  • reason — the message from the offending validator.

Typical usage: log every correction on manager.load(), or surface them in a toast/notification on first world load.

final LoadResult result = manager.load();
for(final Correction c : result.corrections()) {
    LOGGER.warn("[mymod] corrected {}: {} -> {} ({})",
            c.path(), c.rejected(), c.replacement(), c.reason());
}

Corrections cause a write-back on load — the file is rewritten with the corrected values so the next boot doesn't re-log the same problem.

When correct-and-log isn't enough

Some mod packs and CI environments want a strict mode: any correction = load failed = boot aborted. That's the intent of the validation.strict knob in Self-config. When set, Rubric will (once wired) throw on non-empty corrections instead of applying them.

Similarly, validation.dumpCorrections (once wired) writes a sibling <config>.corrections.log on every load with corrections, for post-hoc inspection.

Both are placeholders in the current release — the flags exist, the enforcement doesn't yet. Follow the repo issues for progress.

Corrector internals

For the curious: Corrector.correct(Schema, instance) walks every SchemaEntry, runs each attached Validator against the value read from the instance, and — on invalid — writes back either the first suggested replacement or the entry's defaultValue. It always returns a list of Corrections and never throws — a faulty validator that raises is treated as a hard failure for that entry and the default is used.

What next

  • Annotations — the annotation set includes every built-in validator.
  • ConceptsValidator, ValidationResult, ValidationIssue, Correction.

Clone this wiki locally