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

Think about interaction with clap to allow loading/overriding config values from CLI args #19

Open
LukasKalbertodt opened this issue Oct 23, 2022 · 4 comments

Comments

@LukasKalbertodt
Copy link
Owner

Often it's useful to allow users to override some (or all) configuration values via command line. Confique should work nicely for that use case. The currently best way to do it is probably to convert the CLI values to a partial type (manually) and then add it via Builder::preloaded. I would like to investigate whether this can be made more convenient and with less duplicate code.

If this improvement has to be CLI-library specific, I am pretty sure I only want to support clap. I only ever use clap with the derive feature and I think it's the most mature library.

Just to throw some random ideas into this issue, maybe one can annotate config fields with #[config(clap(...))] and if any are annotated this way, we will generate an additional type containing the fields annotated that way that has the derive(clap::*) on it. And has a method to convert it to a partial config type. This extra type can then be flattened into the main clap type. But again, haven't thought about this too deeply yet.

@ssokolow
Copy link

For the record, I also use Gumdrop in my actix-web projects, where I don't need the path arguments to support accepting non-UTF8-able POSIX paths (i.e. where they're just going to be a path to a server root folder or config file) and I want to benefit from the reduced compile times and smaller output binaries.

@LukasKalbertodt
Copy link
Owner Author

Fair point. It's certainly worth considering how one could support multiple cli libraries. Or even be completely library-agnostic.

@hmuendel
Copy link

hmuendel commented Feb 6, 2023

I would also love to have an integration with clap. I followed your workaround described above and I find it quite ok. However I was not able to derive Config directly on my clap cli flag struct, because of defaults. I totally agree with your view, that the finally parsed config should not contain options any longer, but this lead me to have options in my clap struct to see if the user provided the flag. I expect the flag to take precedence over all other config options (since it is the most direct user input). So I had another struct with options removed and config derived on it, which I could then construct from the partials.
But this misses out on the documentation benefits from clap.

I didn't see a better way of doing it for now, but I still find your crate awesome and clean and can live with the solution. Just wanted to raise the point of defaults in clap and confique.

@Nemo157
Copy link

Nemo157 commented Jul 19, 2024

A different kind of approach to support overriding via the command line is to have a general flag that accepts a fragment of config and applies it, this is the approach taken by cargo --config and jj --config-toml. This seems relatively simple to do now with confique, and is maybe just worth adding an example of, I used something like:

#[derive(confique::Config, Debug)]
#[config(partial_attr(derive(Clone, Debug)))]
#[config(partial_attr(serde(deny_unknown_fields)))]
struct Config {
    ...
}

type Partial = <Config as confique::Config>::Partial;

impl Config {
    fn load(fragments: Vec<Partial>) -> Result<Self> {
        let dirs = ProjectDirs::from(...).ok_or_eyre("cannot get config directory")?;
        let mut builder = Config::builder();
        // reverse so that later fragments take precedence
        for fragment in fragments.into_iter().rev() {
            builder = builder.preloaded(fragment)
        }
        Ok(builder.env().file(dirs.config_dir().join("config.toml")).load()?)
    }
}

#[derive(Debug, Parser)]
struct App {
    /// Config overrides to apply, these should be fragments of the config file.
    #[arg(long = "config-toml", value_name = "TOML", value_parser = toml::from_str::<Partial>)]
    config_fragments: Vec<config::Partial>,

    ...
}

fn main() -> Result<()> {
    let app = App::parse();
    let config = Config::load(app.config_fragments)?;
    tracing::trace!(?config, "loaded config");

    ...
}

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

No branches or pull requests

4 participants