support_kit/config/
config_sources.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
use bon::builder;
use figment::{Figment, Provider};
use std::fmt::Debug;
use std::path::PathBuf;

use crate::{Configuration, Environment};

use super::{ConfigDefinition, ConfigFile, ConfigFormat, ConfigManifest};

#[derive(Clone, Debug, bon::Builder)]
#[builder(derive(Clone))]
pub struct ConfigSources {
    #[builder(default, into)]
    file: ConfigFile,
    env: Option<Environment>,
}

impl ConfigSources {
    pub fn manifest(&self) -> ConfigManifest {
        let mut definitions = Vec::new();
        let definition = ConfigDefinition::builder()
            .maybe_env(self.env)
            .file(self.file.clone());

        for path in canonical_paths() {
            let file_definition = definition.clone().path(path);

            definitions.push(file_definition.clone().format(ConfigFormat::Yaml).build());
            definitions.push(file_definition.clone().format(ConfigFormat::Json).build());
            definitions.push(file_definition.clone().format(ConfigFormat::Toml).build());
        }

        definitions.push(definition.env_var((self.file.clone(), self.env)).build());

        ConfigManifest::builder().definitions(definitions).build()
    }

    pub fn sources(&self) -> figment::Result<ConfigManifest> {
        let manifest_builder = Self::builder().file(self.file.clone());
        let mut root_manifest = manifest_builder.clone().build().manifest();

        let next_env = match self.env {
            Some(env) => Some(env),
            None => {
                let figment = Figment::new().merge(&root_manifest);

                figment.extract::<Configuration>()?.environment
            }
        };

        let env_manifest = manifest_builder.env(next_env.unwrap_or_default());

        root_manifest.merge(env_manifest.build().manifest());

        Ok(root_manifest)
    }
}

impl Provider for ConfigSources {
    fn metadata(&self) -> figment::Metadata {
        Default::default()
    }

    fn data(
        &self,
    ) -> Result<figment::value::Map<figment::Profile, figment::value::Dict>, figment::Error> {
        let sources = self.sources()?;

        Ok(Figment::new().merge(sources).data()?)
    }
}

pub fn canonical_paths() -> Vec<PathBuf> {
    let mut paths = vec![PathBuf::new()];

    paths.extend(dirs::config_dir());
    paths.extend(dirs::home_dir());
    paths.reverse();

    paths
}

#[test]
fn basic_manifest_matches() {
    for format in ConfigFormat::all() {
        figment::Jail::expect_with(|jail| {
            jail.create_file(format!("support-kit.{format}"), "")?;
            jail.create_file(format!("support-kit.production.{format}"), "")?;

            let sources = ConfigSources::builder().file("support-kit").build();

            assert_eq!(
                sources.manifest().known(),
                ConfigManifest::builder()
                    .definitions(bon::vec![
                        ConfigDefinition::builder()
                            .file("support-kit")
                            .format(format)
                            .build(),
                        ConfigDefinition::builder().env_var("support-kit").build(),
                    ])
                    .build()
            );

            Ok(())
        });
    }
}

#[test]
fn env_specific_manifest_matches() {
    for env in Environment::all() {
        for format in ConfigFormat::all() {
            figment::Jail::expect_with(|jail| {
                jail.create_file(format!("support-kit.{format}"), "")?;
                jail.create_file(format!("support-kit.{env}.{format}"), "")?;

                let sources = ConfigSources::builder()
                    .file("support-kit")
                    .env(env)
                    .build();

                assert_eq!(
                    sources.manifest().known(),
                    ConfigManifest::builder()
                        .definitions(bon::vec![
                            ConfigDefinition::builder().format(format).env(env).build(),
                            ConfigDefinition::builder()
                                .env_var(("support-kit", env))
                                .build(),
                        ])
                        .build()
                );

                Ok(())
            });
        }
    }
}