Skip to content

1.x Config specification

Guillaume R edited this page Apr 11, 2017 · 7 revisions

Introduction

When dealing with configurations files that are modified by the end user, software should detect invalid values and react (for instance, use default values instead of the invalid ones).

While it's possible to load the config and then retrieve and check each value individually with custom functions, this approach is heavy and boring to code.

It would be nice if we could delegate most of the work to the config library, wouldn't it? Well, that's exactly what the config specification is for!

The ConfigSpec

Concept

The idea is to define how the config values must be, provide some default values, and then let the library check the configuration and even correct it (with the default values).

In NightConfig, this is done with a ConfigSpec object.

Defining values

The class ConfigSpec provides a lot of define methods to define how the values must be. Example:

ConfigSpec spec = new ConfigSpec();

// Basic, general methods:
// They just specify that a value must exist and be of the correct type
spec.define("path.of.the.value", defaultValue);
spec.define(Arrays.asList("path", "as", "list"), defaultValue);

// Specific methods for precise specifications:
// Defines that a value must be in a list:
List<Character> acceptableValues = Arrays.asList('a', 'b', 'c');
spec.defineInList("myChar", defaultChar, acceptableValues);

// Defines that a value must be between two values:
spec.defineInRange("myInt", defaultInt, 0, 50);// myInt must be between 0 and 50 (included) to be considered valid

// Defines a list value and a validator to check the elements of the list:
spec.defineList("myList", defaultList, element -> (element instanceof String) && ((String)element).contains("abc"));// myList must be a list of strings, and each string must contains "abc"

More advanced methods, not explained here, are also provided! Look at the class.

Important: You have to define all the values of your configuration, because each unspecified value will be considered invalid during the check.

Checking and correcting configurations

Once you've created the ConfigSpec and defined all your values, you can check if your config is conform to the specification with the isCorrect(Config) method.

Even better: you can correct the config automatically, with the correct(Config) method! A correction does the following things:

  • Any unspecified entry is removed.
  • Any missing entry is added with the default value.
  • Any specified but invalid value is replaced by its default value.

The correct(Config) method returns the total number of added, removed and modified values, so that you can inform the user of what happened. If you want more information about what's happening during the correction, you can provide a CorrectionListener.

Example

ConfigSpec spec = new ConfigSpec();
spec.define("server.name", "Bob");
spec.define("server.ip", "127.0.0.1");
spec.define("server.local", true);
spec.define("server.port", 12345, 1024, 65535);

// Later:
Config config = new HoconConfig().readFrom(myConfigFile);
int nCorrected = spec.correct(config, (action, path, incorrectValue, correctedValue) -> {
    switch(action) {
        case ADD:
            System.out.println("Entry " + path + " = " + correctedValue + " added in the config");
            break;
        case REMOVE:
            System.out.println("Entry " + path + " removed from the config");
            break;
        case REPLACE:
            System.out.println("Entry " + path + ": value " + incorrectValue + " replaced by " + correctedValue);
            break;
    }
});
System.out.println(nCorrected + " correction(s) applied.");