a248 / dazzleconf Goto Github PK
View Code? Open in Web Editor NEWIncredible configuration library
License: GNU Lesser General Public License v3.0
Incredible configuration library
License: GNU Lesser General Public License v3.0
Using @ConfKey
to enforce configuration key conventions is brittle and error-prone. I might want to use this-kind-of-format
with YAML, and thus use @ConfKey("my-key")
on a method name such as "myKey". However, it can be easy to forget this for a single key, which creates a longlasting ugly-looking spot in the configuration.
An interface should be provided to automatically map method names to configuration keys, therefore.
In the same vein of reasoning, we may want to deprecate @ConfKey
entirely. I don't see a reason to keep the annotation. Removing it would make reasoning about a config easier for someone who wants to look at their plugin's source code.
This leads to funny text such as \u003c/hover\u003e
instead of </hover>
.
It may be possible to change this behavior in a compatible fashion.
Hello.
This is similar to an issue discussed some time ago for YAML, except now it is for Hocon.
java.lang.ClassCastException: class java.lang.Integer cannot be cast to class java.lang.String (java.lang.Integer and java.lang.String are in module java.base of loader 'bootstrap')
at libs.space.arim.dazzleconf.ext.hocon.HoconConfigurationFactoryImpl.convertMapToHocon(HoconConfigurationFactoryImpl.java:77) ~[system.jar:?]
at libs.space.arim.dazzleconf.ext.hocon.HoconConfigurationFactoryImpl.convertValueToHocon(HoconConfigurationFactoryImpl.java:88) ~[system.jar:?]
at libs.space.arim.dazzleconf.ext.hocon.HoconConfigurationFactoryImpl.convertValueToHocon(HoconConfigurationFactoryImpl.java:92) ~[system.jar:?]
at libs.space.arim.dazzleconf.ext.hocon.HoconConfigurationFactoryImpl.convertMapToHocon(HoconConfigurationFactoryImpl.java:77) ~[system.jar:?]
at libs.space.arim.dazzleconf.ext.hocon.HoconConfigurationFactoryImpl.writeMap(HoconConfigurationFactoryImpl.java:65) ~[system.jar:?]
at libs.space.arim.dazzleconf.factory.HumanReadableConfigurationFactory.bufferedWriteMap(HumanReadableConfigurationFactory.java:125) ~[system.jar:?]
at libs.space.arim.dazzleconf.factory.HumanReadableConfigurationFactory.writeMap(HumanReadableConfigurationFactory.java:114) ~[system.jar:?]
at libs.space.arim.dazzleconf.factory.ConfigurationFormatFactory.write(ConfigurationFormatFactory.java:184) ~[system.jar:?]
at libs.space.arim.dazzleconf.helper.ConfigurationHelper.reloadConfigData(ConfigurationHelper.java:102) ~[system.jar:?]
Can this be improved? For better experience. ๐
Dotted paths in @ConfKey
tend to be error-prone when the developer mistakenly specifies conflicting keys. For example:
@ConfKey("keyOne")
@ConfDefault.DefaultString("some")
String string();
@ConfKey("keyOne.subKey")
@ConfDefault.DefaultString("other")
String otherString();
If the format syntax is invalid, there's nothing we can do. However, if an individual config value is valid, developers might want to handle it in different ways.
The main behavior of DazzleConf is to fail-fast upon reaching the first invalid value. However, it should also be possible to substitute default values and log such substitutions. Moreover, we might benefit from collecting each invalid value, and including them in an exception message or logging about them all at once, so that users don't have to repeatedly reload their configuration and recheck for errors.
We should add an InvalidValueHandler
which is allowed to create state per config load, with a cleanup callback after the configuration is finished loading. The default handler should be to fail but to collect all the invalid values first, with a reasonable limit on the number of invalid values to inform the user about in a single error message.
Very weird problem I'm encountering. I'm using the Gson extension for dazzleconf. I have the following value in my config interface (HexCrawlerConfig):
@ConfKey("channel")
@ConfDefault.DefaultLong(0)
long channel();
In my .json config file it is defined like so:
"channel": 1184252858728726529
Whenever my config is loaded, it changes the value to be 1 less:
"channel": 1184252858728726528
If I leave it at this new value it chose for it, it stays like this every time I load up. When I set it back to the original value, it overrides it back to this value... Something I figured I'd try was just set the value to be 1 greater than what I need it to be, but it still locks the value to the one with the 8 at the end.
Here is how I am loading the config itself:
public final class JsonDazzleConfHandler<C> extends ConfigurationHelper<C>
{
private volatile C configData;
private JsonDazzleConfHandler(Path configFolder, String fileName, ConfigurationFactory<C> factory)
{
super(configFolder, fileName, factory);
}
public static <C> JsonDazzleConfHandler<C> create(Path configFolder, String fileName, Class<C> configClass)
{
GsonOptions jsonOptions = new GsonOptions.Builder()
.build();
return new JsonDazzleConfHandler<>(
configFolder, fileName,
GsonConfigurationFactory.create(
configClass,
new ConfigurationOptions.Builder().sorter(new AnnotationBasedSorter()).build(),
jsonOptions
)
);
}
public void reload()
{
try
{
configData = reloadConfigData();
}
catch (IOException ex)
{
throw new UncheckedIOException(ex);
}
catch (ConfigFormatSyntaxException ex)
{
configData = getFactory().loadDefaults();
System.err.println("Uh-oh! The syntax of your configuration are invalid.");
ex.printStackTrace();
}
catch (InvalidConfigException ex)
{
configData = getFactory().loadDefaults();
System.err.println("Uh-oh! The values in your configuration are invalid. Check to make sure you have specified the right data types.");
ex.printStackTrace();
}
}
public C get()
{
if(configData == null)
{
throw new IllegalStateException("Configuration has not been loaded yet");
}
return configData;
}
}
JsonDazzleConfHandler<HexCrawlerConfig> configHandler = JsonDazzleConfHandler.create(
Path.of("file path here"), "config.json",
HexCrawlerConfig.class
);
configHandler.reload();
config = configHandler.get();
System.out.println("test: " + config.channel());
config.equals(config)
returns false because of the way proxies generated with java.lang.reflect.Proxy work. This can be easily corrected.
As a pre-requisite for sub-sections in collections, equality should be well-defined.
Moreover, this is a big enough bug that I would consider back-porting it to 1.1.0.
My idea is about utilizing method arguments as ValueSerializer parameters. With them, we can pass some context to the serializer when we are getting the value. Ill give an example:
Lets assume that we have a config with Component message that has sender placeholder:
public interface TestConfig { Component message(@Argument("sender") Component sender); }
And in value serializer, we pass arguments in the map where key is arg name/string defined in @argument, and Value is argument value.
public Component deserialise(FlexibleType value, Map<String, Object> args) throws BadValueException { List<Template> templates = args.entrySet().stream() .map(entry -> Template.of(entry.getKey(), (Component) entry.getValue()) ).toList(); return miniMessage.parse(value.getString(), templates); }
I think this addition would make code much cleaner in some cases, especially if you have placeholders in your message.
But, to be honest, im not sure if its the best solution.
The current @Defaultxxx annotations are limited to the "literal objects" - literal primitive values, literal lists of such values, and values parseable from strings.
This works for simple values of inbuilt types, as well as collections thereof. It works for custom objects where the custom object can be represented as a string, and also collections of these.
However, there is a limitation when using a custom object, which spans configuration options and therefore cannot be a simple string, is specified.
Consider this example, where each KeyModel is represented by a configuration section:
https://hasteb.in/unebetod.js
How would the default annotation be specified? @defaultmap fails because, barring much pain and format-specific coupling, the values of the default-specified map cannot be written as configuration sections.
To solve this, @defaultobject, which specifies the fully qualified name of a static method, can be used. The method @defaultobject points to would be invoked, and its result used to provide the default object.
Hi, i need to add to the interface a type that represents an item from XSeries.XItemStack
but adding the return type as Object does not work, this is the problematic interface:
public interface EventItens {
@Order(1)
Boolean Enabled();
@Order(2)
Object Helmet();
@Order(3)
Object Chestplate();
@Order(4)
Object Leggings();
@Order(5)
Object Boots();
@Order(6)
Map<String, Object> Inventory();
}```
Simple - add a "reload helper" object that allows you to return a config value, and also be reloaded. When reloaded the config value returned will change. I may propose an object in a future PR.
DazzleConf is a very useful library and makes config cooler and easier, but now let's make it even better.
First of all, change how the interface is processed, instead of current way it works it could be where it loads the interface, and looks for method with default
which would have a special default, then it would store these extra defaults when generating the config. and also ability for variable methods, which is not related to the config but to make working with dazzleconf's way of configuration easier. It is simple, we can pass variables with identifier, and there would be an annotation like VariableMethod
and requires the identifier, the method then is no longer a configuration option and is just a variable used by default methods to know what they should return exactly. and third is Double order because integers between 2 integers are always finite, but decimals are always infinite. And lastly. it is annoying when you have to use DefaultObject
so replace/or add a feature where you can add your own default function like when adding config serializable, identified by a string. And most important of all of this, fixing the bug with hashmap. Also, a converter which allows previous options to change location, for example if the type was previously a list.. but now a map, and the map contains the list, it would move the list into its new location. And also make sure all of this doesn't break if their parents are lists.
This will make dazzleconf easier & more beginner friendly. it will reduce the code you have to write, and make it look better, and will also make the library more advanced. So i hope you add this in the next releases, maybe 2.0.
Allow adding empty lines and different styles of comments.
This issue may seem obscure in essence, but it is a backbone for adding more flexibility to DazzleConf in several areas, such as #37, #30, #35.
A configuration definition is the result of reading a config interface and determining its required methods, their return types, default values if present (or if required, see #36); effectively, it is the whole structure of a configuration. It should also include mapped keys (#38).
We can define a configuration definition provider as the interface responsible for creating configuration definitions. When a configuration definition is made, I also propose that the relevant serializer is attached to the definition at the key, rather than retained globally and looked up as required; this would allow wildcard serializers as mentioned in #39.
Here is an example putting this all together:
Configuration<MyConfig> configuration = Configuration
.builder() // Or builderWithoutDefaultSerializers
.withSerializers(...) // ValueSerializer... or Collection<ValueSerializer>
.keyMapping(new DefaultKeyMapper()) // Can be omitted
.definitionProvider(new DefaultDefinitionProvider<>(MyConfig.class))
.instantiator(new DefaultInstantiator()) // Not generic over MyConfig.
.migrations()
.backend(new SnakeYamlBackend(
new ConfigurationFile(path) // Provides methods such as newReader, newWriter, and buffered variants
// Can also provide options here
))
.build(); // Throws IllDefinedConfigException if developer error
// ConfigResult returns elements such as auxiliary keys, whether defaults were written back, migrations performed, etc.
ConfigResult<MyConfig> myConfigResult = configuration.reload();
MyConfig myConfig = myConfigResult.loadedValue();
// Can build out further abstractions related to reloading
Reloadable<MyConfig> reloadable = Reloadable.create(configuration);
reloadable.setBackingInstance(myConfig);
assert reloadable.getBackingInstance() == myConfig;
MyConfig reloadProofConfig = reloadable.getTransparentProvider();
We can use multiple builder steps so that adding the definition provider and instantiator can be done in an ergonomic generics-friendly fashion. The definition provider should be composable so that the configuration is modular: that is, combining non-overlapping configuration definitions must be possible. Loading by multiple files would also be possible by wrapping the backend. The backend in this design proposal, unlike in the current library, is stateless and decoupled from other aspects of DazzleConf.
There are only a couple API methods which return null. These are methods highly unlikely to be used by most API users, so I do not expect any confusion arising from them.
However, it would be better for overall API consistency and clarity to simply declare all return values non-null, rather than have these few exceptions.
Currently, the @defaultOption annotation is sloppy at best for providing non-primitive default objects for configuration values. I propose the use (or at least the option to use) default methods to return objects as default values. This is supplemented by the fact that customized serializers already exist in DazzleConf and more importantly already support deserialization of objects. The only issue being the use of default methods as helper methods in configuration classes, for which i propose an annotation that would allow a default method to be ignored, or a boolean that controls configuration reading accepting default methods as default values.
The ConifgManager example simplifies use of DazzleConf a lot. I copy and paste it to projects i work on. So, why not to include the class in the api itself?
Some formats to consider, and likely corresponding parsing libraries:
Add a ConfigurationSorter which sorts entries alphabetically based on their keys.
We should add easy-to-use migrations to DazzleConf. These allow users to migrate their configuration options from older values as new settings are added.
In particular, migrating one's configuration should be type safe. For example, suppose I have an interface Config
. I want to add a new version, so I copy the interface and create two files, ConfigV1
and Config
, where Config
stands in for the new version of the configuration. DazzleConf should then allow me to write a simple converter:
public class MyMigrator implements ConfigurationMigrator<ConfigV1, Config> {
public Config fromPrevious(ConfigV1 previous) {
// Perform migration
}
}
Detecting the configuration version could be accomplished in a few ways. We could simply attempt to read the configuration according to ConfigV1's definition, and if that fails, move on to the new version, Config. We could perform a differential calculation on the new definition versus the old definition, then use the unique aspects of each version for its detection, starting with newer versions as a fast-path. Or, less appealingly, developers might supply a configuration version as part of the config definition which is supposed to remain untouched by the end user.
This whole framework could even allow us to import from other configuration frameworks. For instance, maybe you wrote an encapsulation-destroying mess of public final fields whose values are loaded via reflection by a different configuration library. You'd still need to depend on the old library for deserializing the old instance, but at least you gain the benefits of DazzleConf going forward, and can stop depending on the old library eventually once end users have migrated.
This way to declare serialisers forces you to use singletones. Those ones are not the best practice in the object oriented programming.
Since there's already a better solution, using ConfigurationOptions, i suggest deprecating @ConfSerialisers annotation, or, at least, showing another way in documentation.
The objective is to support something like this without extra configuration or having to write any ValueSerialisers:
public interface MyConfig {
Map<String, @SubSection MySubSection> sections();
interface MySubSection {
}
}
This takes advantage of the TYPE_USE capability of annotations introduced in Java 8.
Title: If you have an Optional annotated as a config value, not including a key for the value will simply make the Optional return empty.
A configuration with
@ConfigKey("some.key")
Optional<Integer> lol();
some:
otherKey: otherValue
# note the lack of some.key
should load lol() as an empty opt
We should take all the default serialization features of DazzleConf and turn them into value serializers. That way, developers can swap out which serialization mechanisms are used. The default serializers, however, should follow existing DazzleConf practices and require no manual configuration.
This will require us to allow some serializers to match multiple kinds of types. For example, serializing enums must check whether the type is an enum.
Snakeyaml 1.28 adds a way to write comments directly:
https://bitbucket.org/asomov/snakeyaml/wiki/Changes
Presently, it is already possible to write comments using SnakeYamlConfigurationFactory, but this is done through an alternative yaml writer, specifically CommentedWriter.
Some considerations:
useCommentingWriter
therefore be deprecated, with snakeyaml-written comments recommended as a replacement?FlexibleType's getCollection, getList, and getSet methods do not handle the edge case where the input collection has a null element.
Per testing, it is possible to specify null collection elements in Yaml, implicitly and literally. Other configuration formats likely have the same "feature."
It is possible, in rare cases, for API users to obtain the null collection element. Moreover, whatever fix settled on will no longer throw NullPointerException. Fixing this bug will therefore cause a minor behavioral change, so it is scheduled for 1.3.0.
Hello, could you add some docs on how to make the library load configuration defaults from a file (ex. a resource).
This is mentioned on the wiki but there aren't any examples.
Thanks!
Requiring Default Values
On our existing trajectory, DazzleConf 2.0 will be taking a more expansive role with respect to providing default values. We may therefore require the existence of default values when a configuration is defined, e.g. via @DefaultBoolean
, @DefaultInteger
, @DefaultString
and related annotations.
Requiring default values would invalidate the "defaults from a resource file" approach documented in The two ways of using DazzleConf. However, that approach seems to be much rarer (if it exists at all) compared to the mainstream, common style.
Please comment here and describe your use-case if you use DazzleConf 1.x without defaults annotations. We would like to support all library usages when developing DazzleConf 2.x. However, we need your feedback if you use this
Related Plans
Sometimes, software may want to supply a different default value depending on whether a whole fresh configuration is generated versus an individual option was missing due to an upgrade. For example, I might introduce an option whose default setting changes behavior in the new version, but wish to keep backwards compatibility for existing configurations. DazzleConf should address this use-case by defining a default value as well as an "if missing" value. For example, @DefaultString("default for fresh configuration", ifMissing = "default for when the option itself is missing")
.
Migration should also be supported for configurations, with existing values being taken from other configuration options, sections, and even separate files and sub-sections of other files. We further need to account for the dynamic case of multiple new options inheriting from multiple previous options in an interacting fashion, i.e. a M:N relationship between old and new options. Such feature will require caller hooks to handle the specifics of migration.
The following would require an optional bytecode manipulating library:
Implement a ValueSerializer that deserializes interfaces similar to the core behavior of DazzleConf: Simply provide it an interface class and it will deserialize it using any values annotated on the object.
I bug in the wiki.
Error: SnakeYamlConfigurationFactory.create (configClass, ConfigurationOptions.defaults (), yamlOptions)
Fix: new SnakeYamlConfigurationFactory (configClass, ConfigurationOptions.defaults (), yamlOptions)
Static methods in config interfaces should be permitted. Currently they are flagged for IllDefinedConfigException.
The equals implementation should check createSingleElementCollections
, but presently it does not.
Fix timeline:
Risks and assumptions:
Does dazzleconf have a simple way to add values to a map configuration option? I don't meant the default object option, I basically need to dynamically change the configuration values.
Hi, how i can use ValueSerializer?
Currently, nested modular configuration (considering classes not present at compile-time for an application's primary configuration) is difficult to implement. Exposing a functionality to deserialize maps provided using DazzleConf's ValueSerializer
and FlexibleType
would allow for enhanced modularity as subclass configurations would be provided the ability to insert new features to a configuration's deserialization by simply providing an instance of their configuration's class
object and letting the library handle the heavy lifting.
The config is reset when we put a nested section in a map
Provide a means of using alternative dynamic proxy implementations, such as a proxy with generated byte code.
It remains to be seen whether the proxy factory will be considered standard API or more of an internal interface.
How to set options order?
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.