Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Is there a way to load only a certain prefix? #386

Closed
tomhillgreyridge opened this issue Jul 6, 2023 · 7 comments · Fixed by #412
Closed

Is there a way to load only a certain prefix? #386

tomhillgreyridge opened this issue Jul 6, 2023 · 7 comments · Fixed by #412

Comments

@tomhillgreyridge
Copy link

If I have a config file as follows

plugin1:
  value1: "a value"
  value2: "another value"
plugin2:
  username: "a username"

is it possible to load just the plugin1 settings directly into a data class of the following form somehow

data class Plugin1Settings(val value1: String, val value2: String)

or is there no way to load the data without having a "wrapper" object like this

data class Settings(val plugin1: Plugin1Settings)

As you can probably tell from the example, the use-case is for people adding 3rd party plugins where

  • You want the configuration to just go in the main configuration file with everything else
  • The plugin can't modify the "main" settings class used by the application (since it is a 3rd party plugin)
  • It therefore needs to only load its own configuration but from a config file containing other stuff - using a prefix to identify which plugin the configuration relates to

Obviously, the workaround is to create a wrapper class called settings which contains the actual Plugin1Settings with the right property name, but it's a slightly awkward way of doing things. I was hoping there might be some way of doing something along the lines of

ConfigLoader.builder.addResourceSource("application.yaml").loadConfigOrThrow<Plugin1Settings>(prefix = "plugin1")

i.e. load just the data with the given prefix into a class of this type, thus removing the need for the wrapper.

@sksamuel
Copy link
Owner

sksamuel commented Sep 3, 2023

So if you could specify the prefix when you add the file source, would be that sufficient ?

@tomhillgreyridge
Copy link
Author

That would probably work. My opinion is that it would be nicer if it was done at the loadConfigOrThrow stage (as that means you could have a set of sources containing configuration and load a subset of the data from across all of them without duplicating the prefix every time you added the source), but if that is a much harder implementation, then setting it per file would also work.

It's basically useful if you want to extract some information out of someone else's config file without needing to write matching data classes for every single element. Instead you'd just write data classes for the bits you wanted and then provide the "path" to the start element if that makes any sense.

@cloudshiftchris
Copy link

cloudshiftchris commented Oct 24, 2023

Have a similar use case, migrating from Spring Boot where @ConfigurationProperties are bound by prefix.

This is really a separation of concerns - loading (once) of underlying properties (property sources, etc), and then - separately - binding properties to objects, based on a prefix.

In Spring the former is the "Environment" - all the resolved properties, with a PropertyResolver interface for consumers to access them; and way-overly-complex Binder objects that allow for something like this (my code, much more direct than the magical incantations of @ConfigurationProperties):

public inline fun <reified T : Any> Environment.bindConfigurationProperties(prefix: String): T {
    return Binder.get(this).bindOrCreate(prefix, Bindable.of(T::class.java))
}

Ideally we'd be able to do something like:

    val config = ConfigLoaderBuilder.default()
        .addResourceSource("/application-staging.props", optional = true, allowEmpty = true)
        .withExplicitSealedTypes()
        .build()

// above exists today,  sort of: ideally we could get a "Configuration" or "Environment" object back that can then be used as below (in addition to existing uses)

config.bind<MyConfigObject>(prefix = "abc")  // this would have the same semantics as config.loadConfigOrThrow<MyConfigObject>(), except that the config would already be loaded

Selective binding supports a wide range of use cases - one of the more important ones is allowing different components to have their own configuration (and possibly multiple, separately configured instances), without coupling everything into a global config.

@jogro
Copy link

jogro commented Dec 10, 2023

I like the concept suggested by @tomhillgreyridge.

In addition (I'm not sure if this is part of the original suggestion), I would like to have the possibility to load "leaf" nodes, for example:

val plugin1Value2 = configLoader.loadConfigOrThrow<String>(prefix = "plugin1.value2")

I would also like to have the possibility to deal with nullable values, for example:

val plugin1Value2 = configLoader.loadConfigOrThrow<String?>(prefix = "plugin1.value2")

@rocketraman
Copy link
Contributor

rocketraman commented Mar 17, 2024

Looking for this capability as well. I'm building a system (a simplified no-annotation Spring Boot) in which configurations are modular, and the possible prefixes are not known by the configuration system in advance.

@lenalebt
Copy link

Just in case anybody wants it, I just built this in a project of mine:

class PrefixPreprocessor(val prefix: String) : Preprocessor {
  override fun process(
    node: Node,
    context: DecoderContext,
  ): ConfigResult<Node> {
    return node.atPath(prefix).valid()
  }
}

To be used like this:

inline fun <reified T> loadConfig(prefix: String? = null): T {
  val loader =
    ConfigLoaderBuilder.default()
      .addResourceSource("/config.yaml")
      .also {
        if (prefix != null) {
          it.addPreprocessor(PrefixPreprocessor(prefix))
        }
      }

  return loader.build().loadConfigOrThrow()
}

I won't find time to make this into a PR anytime soon, but maybe it helps others already like this (or someone else makes it into a PR) :-)

@rocketraman
Copy link
Contributor

// above exists today, sort of: ideally we could get a "Configuration" or "Environment" object back that can then be used as below (in addition to existing uses)

config.bind(prefix = "abc") // this would have the same semantics as config.loadConfigOrThrow(), except that the config would already be loaded


Selective binding supports a wide range of use cases - one of the more important ones is allowing different components to have their own configuration (and possibly multiple, separately configured instances), without coupling everything into a global config.

Agree with this from @cloudshiftchris . At the moment, if one uses a preprocessor approach like @lenalebt above (thank you!), things work as expected, but loading of the property sources happens multiple times. This also results in some suboptimal behavior like a report printing per prefix.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

6 participants