Fzzy Config

Validation

The Fzzy Config validation system is applied to every setting either implicitly or explicitly by the creator.

Settings are tightly managed and fail-soft; no catastrophic failure if the config changes and the old file isn't valid, or if a user modifies the .toml directly and makes a mistake.

Every read, write, and update is checked for errors, and problems are reported and either automatically corrected or a fallback is used.

Validation Options

Below is a list of types that Fzzy Config has validation tools for.


Validation Manipulation

ValidatedFields can be manipulated to enhance their effectiveness in more niche situations

Validation Concepts

What is it?

"Validation" is a bit of a misnomer, as the toolset does much more than that. It's a label stemming from the base class ValidatedField. To borrow from the KDoc for Entry, validation:

  • serializes contents
  • deserializes input
  • validates updates
  • corrects errors
  • provides widgets
  • applies inputs
  • supplies outputs
  • creates instances
  • manages flags
  • accepts listeners

Providing Validation

Every setting that appears in a Config GUI is backed by validation. Even when you don't explicitly provide it, Fzzy Config will wrap supported types with validation in the background.

For advanced control of your settings, like Minecraft GameOption, explicitly define the validation for your setting using a ValidatedField class. This grants you the ability to:

  • Provide input restrictions
  • Suggest inputs to users
  • Define the widget used in-GUI
  • Attach listeners, conditions, and feature flags to the setting

Mapping

Validation can be mapped to another convertible type, like Mojang's Codecs. The mapped-from validation type will be used for serialization, GUI widgets, suggestions and corrections, and so on, but in-code you can interact directly with the mapped-to type.

Example: Character

Fzzy Config doesn't have built in validation for Characters. With mapping, we can easily build our own.

//Starting with a ValidatedInt, which characters map easily to, we define validation that bounds the int to the valid character range
//Then using map, we map the int to and from a Char, just like Codec mapping.
//This provides a ValidatedField<Char>, so calling get() will provide a character!
ValidatedField<Character> validatedCharacter = new ValidatedInt(0, Character.MAX_VALUE, Character.MIN_VALUE).map(
i -> (char)i,
c -> Character.getNumericValue(c)
);

Conditional Settings

Validation can be wrapped with conditions that define whether the setting is "active" or not. You can check:

  • Whether a certain mod is loaded
  • The status of another config setting; ValidatedBoolean or ValidatedTriState can be used directly
  • Etc. Etc.
// conditions should supply live values. Validated fields are a convenient mechanism to do that. A plain boolean won't update in-GUI until changes are applied.
ValidatedBoolean validatedBooleanGate = new ValidatedBoolean();
//create a conditional validation with toCondition. Note that the type is no longer ValidatedInt directly.
ValidatedCondition<Int> validatedConditionInt = (new ValidatedInt(5, 100, 0)).toCondition(validatedBooleanGate, Text.literal("Gate must be true"), () -> 0);

Listeners

You can listen to changes made to any ValidatedField by supplying a consumer of that field.

Listening works on both server and client and will trigger on config load. The listeners apply any time the stored value is set (with a couple exceptions, see example below).

//boolean with an attached listener that setups some system when the setting is flipped to true.
ValidatedBoolean listenerBoolean = (new ValidatedBoolean(false)).withListener(lb -> {
if (lb.get())
setupSomething
});
//the below example has two booleans that toggle each other on/off, providing a sort of "cross-linked" setting where only one can be active at a time.
//it's important to note the usage of validateAndSetFlagged here, which lets the partner setting be updated "quietly", otherwise you will deadlock and stack overflow
//QUIET = no listeners applied
//UPDATE = applies updates to the config manager system, syncing/saving/etc any changes made by the listeners
ValidatedBoolean aBool = (new ValidatedBoolean(false)).withListener(a -> {
if (a.get())
bBool.validateAndSetFlagged(false, EntryFlag.Flag.QUIET, EntryFlag.Flag.UPDATE);
});
ValidatedBoolean bBool = (new ValidatedBoolean(false)).withListener(b -> {
if (b.get())
aBool.validateAndSetFlagged(false, EntryFlag.Flag.QUIET, EntryFlag.Flag.UPDATE);
});

Codecs

Any ValidatedField can be used to generate a Codec of the validation's type. The resulting Codec will be backed by the validation present in the field.

//this codec will parse integers, clamping the allowed value to 0 to 100.
Codec<Int> intCodec = (new ValidatedInt(5, 100, 0)).codec();