A Java library for easily validating configurations before the configuration data is needed.
Instead of getting a value from a configuration when you need it and supplying a default value if it is invalid or missing, this library allows you to validate the entire configuration
at initialization and stop if it is invalid. This library functions as a validation layer on top of whatever means you use to get your configuration data. It enables basic type checking
for all Java primitives, lists, and configuration subsections (maps) as well as validating more advanced "filters" that can be written by you or selected from a provided set of common
filters. Filters also allow you to convert the configuration data into a new type, such as from a String to a URI, to make life easier when you need to access the data later. That also
means that you can refer to configuration data in a "statically typed" way and no longer have to rely on passing string paths into getter methods hoping the return value will be what
you need.
- Immediate validation of entire config
- Works on top of any configuration source
- Customizable filters
- Data type conversion
- "Statically typed" configuration data
- Easy to use
- Underlying limitations of configuration source are carried through
- Nested Lists (see below for details)
<dependency>
<groupId>io.github.ttno1</groupId>
<artifactId>configvalidation</artifactId>
<version>1.0.5</version>
</dependency>
ConfigSpec
- Represents a specification that a config must meet in order to be valid.ConfigNode
- Represents an item in a config that must be present and (optionally) must meet certain requirements.ConfigList
- Similar toConfigNode
but for lists.ConfigFilter
- A functional interface that takes in a config data value and returns whether it is valid or not. Can optionally transform the value into a new type.ConfigWrapper
- An interface that wraps a source of configuration data. This library has default implementations for Apache Commons Config and SnakeYAML.Cfg
- A class with static factory methods for constructingConfigSpec
s,ConfigNode
s, andConfigList
s.ConfigFilters
- A utility class with commonConfigFilters
.
ConfigWrapper wrapper = new SnakeYamlConfigWrapper(new Yaml().load(/*config input stream*/));
Cfg.newSpec()
.addNode("path.to.boolean.node", Cfg.Node.ofBoolean())
.addNode("path.to.integer.node", Cfg.Node.ofInteger())
.addNode("path.to.string.node", Cfg.Node.ofString())
.addNode("path.to.list.of.strings", Cfg.List.ofString())
.addNode("path.to.config.subsection", Cfg.newSpec().addNode(/*etc.*/))
.validate(wrapper)
.handle(result -> {
if(!result.passed()) {
//do something when config is invalid
System.out.println(result.getFailMessage());
}
});
ConfigWrapper wrapper = new SnakeYamlConfigWrapper(new Yaml().load(/*config input stream*/));
Cfg.newSpec()
.addNode("path.to.url.node", Cfg.Node.ofString(ConfigFilters.validURL()))
.addNode("path.to.file.node", Cfg.Node.ofString(ConfigFilters.validPath(FileState.PATH)))
.addNode("path.to.string.node", Cfg.Node.ofString((string) -> {
if(string.contains("mySubString")) {
return ConfigFilterResult.pass(string.toUpperCase());// ConfigFilterResult#pass() takes in the transformed value, it does not have to be the same type
} else {
return ConfigFilterResult.fail("My Fail Message");
}
}))
.validate(wrapper)
.handle(result -> {
if(!result.passed()) {
//do something when config is invalid
System.out.println(result.getFailMessage());
}
});
The easiest way to save the value of a ConfigNode
is with the ConfigFilter#thenRun
or ConfigNode#thenRun
method.
ConfigWrapper wrapper = new SnakeYamlConfigWrapper(new Yaml().load(/*config input stream*/));
String myConfigStringValue = null;// value to be obtained from config (pretend this is a field in a class)
Path myConfigFileValue = null;// value to be obtained from config (pretend this is a field in a class)
Cfg.newSpec()
.addNode("path.to.file.node", Cfg.Node.ofString(ConfigFilters.validPath(FileState.PATH).thenRun((path) -> {myConfigFileValue = path;})))
.addNode("path.to.string.node", Cfg.Node.ofString().thenRun((string) -> {myConfigStringValue = string;}))
.validate(wrapper)
.handle(result -> {
if(!result.passed()) {
//do something when config is invalid
System.out.println(result.getFailMessage());
}
});
In order to use this library with SnakeYAML, Apache Commons Configuration, or Apache Commons Validator (for URL validation), you must include those dependencies separately. For your convenience, here are the maven snippets for those dependencies.
<!--SnakeYAML-->
<dependency>
<groupId>org.yaml</groupId>
<artifactId>snakeyaml</artifactId>
<version>2.2</version>
</dependency>
<!--Commons Configuration-->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-configuration2</artifactId>
<version>2.9.0</version>
</dependency>
<!--Commons Validator-->
<dependency>
<groupId>commons-validator</groupId>
<artifactId>commons-validator</artifactId>
<version>1.7</version>
</dependency>
This library serves only as a validation layer on top of whatever means you use to get your configuration data. If you choose to use a library like Apache Commons Config or SnakeYAML, then the limitations of that library will still apply.
For example, by default SnakeYAML treats all decimals as double
s, so if you attempt to validate a ConfigNode
of type float
, it will not work.
When validating a list of lists (assuming the underlying config library supports nested lists), the inner list cannot have a specific type and will always be a list of objects. This means that for ConfigFilter
purposes, the filter will accept a type of List<List<Object>>
.
Feel free to contribute in any way you please. It is much appreciated.
Feel free to ask in an issue or by contacting me.