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

When no configs are present, Config::new() should default to the structs default #60

Closed
elliott-davis opened this issue Jan 31, 2018 · 8 comments · Fixed by #106
Closed
Labels

Comments

@elliott-davis
Copy link

In an application, sometimes there is no config present and you would just like to use the default values for your struct. If this is the case currently, you get an obtuse error like: invalid type: unit value, expected struct Settings. As an example usage:

#[derive(Debug, Deserialize)]
pub struct Settings {
    db_host: string,
}

impl Default for Settings {
    fn default() -> Self {
        Settings {
            db_host: IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)),
        }
    }
}

impl Settings {
    pub fn new() -> Result<Self, ConfigError> {
        let mut s = Config::new();
       // hand waiving pseudo code
        if config_file.present() {
           s.merge(File::with_name(config_file))?;
       }
       s.try_into()
}
@Michael-F-Bryan
Copy link

Michael-F-Bryan commented Feb 11, 2018

I was looking for this exact feature. Originally I thought you could work around it by converting/serialising your Settings into a Value and swapping out the Config's cache field. But there doesn't seem to be any way to do this...

EDIT: I came up with a hacky workaround but in the long term it'd be nice if Value had a try_from() constructor which accepts anything implementing Serialize.

    pub fn from_env() -> Result<Settings, Error> {
        let mut s = Config::new();

        let defaults = Settings::default();
        s.cache = serde_json::from_value(serde_json::to_value(defaults)?)?;

        let mut env = Environment::new();
        env.separator("__".to_string());
        s.merge(env)?;

        s.try_into().map_err(Error::from)
    }

@elliott-davis
Copy link
Author

@Michael-F-Bryan that sort of worked. However whenever I merged a file my cache override got blown away. After reading through the config merge code I see that the cache is repopulated from ConfigKind on each refresh.

@Michael-F-Bryan
Copy link

I think a better solution would be to have a with_default() method which accepts some default object and if it's a map it'll iterate over the items, calling set_default() for each.

@mehcode
Copy link
Collaborator

mehcode commented Mar 12, 2018

How about:

s.set_defaults::<Settings>()?;

Config::set_defaults<T> accepts a T that is Serialize + Deserialize + Default. Config could transform a generic T that way and set that as default values.

@vorner
Copy link
Contributor

vorner commented Aug 16, 2018

I think there are two issues here, aren't there?

First one is how to use defaults ‒ and the answer for that one is to use the serde's default handling (eg. the default attribute on fields).

The other problem is the annoying unit value error. I managed to work around that one by merging an empty source as the first one, eg config.merge(File::from_str("", FileFormat::Toml))?. Could something like this be already done as part of the constructor? I mean, make sure whatever is tried to be deserialized looks like an empty map to serde, not unit.

@mehcode
Copy link
Collaborator

mehcode commented Oct 5, 2018

I didn't see that before. Thanks for making that clear. This is more of a bug then. Marking to get fixed for 0.10.

@mehcode mehcode added this to the 0.10.0 milestone Oct 5, 2018
@Kirguir
Copy link

Kirguir commented Nov 30, 2018

Creating a config so

let mut s = Config::try_from(&Settings::default())?;

expect that the values will fall into defaults and then can overwrite them.

But ConfigSerializer writes in overrides

self.output.set(&key, value.into())?;

😥

@richard-uk1
Copy link

richard-uk1 commented Mar 17, 2019

This can also be fixed by a call to config.refresh(), which possibly avoids an allocation.

The problem is the deserialization of ValueKind::Nil - it deserializes as (), which serde fails. The solution is to create ValueKind::Table at the root on Config creation.

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

Successfully merging a pull request may close this issue.

6 participants