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.
- Int / Long / Short / Byte / Double / Float
- Booleans
- Lists / Sets / Maps
- Enums
- Mathematical Expressions
- Colors
- Identifiers
- TagKey
- Ingredient
- Arbitrary Objects
- Choices of Non-Enum types
- Pairs of Values
- Keybinds
- TriStates
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.
Heads up!
If you add a setting of an unsupported type, you will need to create custom validation for it, or it will not appear in GUIs nor will it be serialized in the config files.
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 ⤴
Heads up!
Added in Fzzy Config 0.5.0
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.
Heads up!
The in-game widget for this will still be based on an int, so will be a slider or a number entry box. In this case dedicated validation with a character selection box would be better.
//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
orValidatedTriState
can be used directly - Etc. Etc.
Heads up!
A conditional setting can have more conditions added with withCondition
. All conditions must pass for the setting to be "active"
// 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 listenersValidatedBoolean 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();