use serde::de::DeserializeOwned;

/// Loads configuration from `basename` and environment variables with the given prefix.
///
/// The file source follows the `config` crate lookup rules, so `etc/app`
/// will resolve to formats such as `etc/app.toml`. Environment variables
/// override file values and use `__` as nested separator.
pub fn load_config<T>(basename: &str, env_prefix: &str) -> Result<T, config::ConfigError>
where
    T: DeserializeOwned,
{
    config::Config::builder()
        .add_source(config::File::with_name(basename).required(false))
        .add_source(
            config::Environment::with_prefix(env_prefix)
                .separator("__")
                .try_parsing(true),
        )
        .build()?
        .try_deserialize::<T>()
}

#[cfg(test)]
mod tests {
    use super::load_config;
    use serde::Deserialize;
    use std::io::Write;

    #[derive(Debug, Deserialize)]
    struct App {
        server: Server,
    }

    #[derive(Debug, Deserialize)]
    struct Server {
        port: u16,
    }

    #[test]
    fn loads_toml_file() {
        let dir = tempfile::tempdir().expect("tempdir");
        let file_path = dir.path().join("app.toml");
        let mut file = std::fs::File::create(&file_path).expect("create config");
        writeln!(file, "[server]\nport = 8080").expect("write config");

        let basename = file_path.with_extension("");
        let app: App =
            load_config(basename.to_str().expect("utf8 path"), "RSZERO_TEST").expect("load config");

        assert_eq!(app.server.port, 8080);
    }
}
