winbrew_database\config/
lookup.rs

1use anyhow::Result;
2use std::borrow::Cow;
3
4use super::error::{ConfigError, ConfigResult};
5use super::types::{Config, ConfigSection, ConfigSource};
6
7struct SectionSpec {
8    title: &'static str,
9    entries: &'static [EntrySpec],
10}
11
12struct EntrySpec {
13    key: &'static str,
14    full_key: &'static str,
15    value: for<'a> fn(&'a Config) -> Cow<'a, str>,
16}
17
18const SECTION_SPECS: &[SectionSpec] = &[
19    SectionSpec {
20        title: "Core",
21        entries: &[
22            EntrySpec {
23                key: "log_level",
24                full_key: "core.log_level",
25                value: core_log_level,
26            },
27            EntrySpec {
28                key: "file_log_level",
29                full_key: "core.file_log_level",
30                value: core_file_log_level,
31            },
32            EntrySpec {
33                key: "auto_update",
34                full_key: "core.auto_update",
35                value: core_auto_update,
36            },
37            EntrySpec {
38                key: "confirm_remove",
39                full_key: "core.confirm_remove",
40                value: core_confirm_remove,
41            },
42            EntrySpec {
43                key: "default_yes",
44                full_key: "core.default_yes",
45                value: core_default_yes,
46            },
47            EntrySpec {
48                key: "color",
49                full_key: "core.color",
50                value: core_color,
51            },
52        ],
53    },
54    SectionSpec {
55        title: "Paths",
56        entries: &[
57            EntrySpec {
58                key: "root",
59                full_key: "paths.root",
60                value: paths_root,
61            },
62            EntrySpec {
63                key: "packages",
64                full_key: "paths.packages",
65                value: paths_packages,
66            },
67            EntrySpec {
68                key: "data",
69                full_key: "paths.data",
70                value: paths_data,
71            },
72            EntrySpec {
73                key: "logs",
74                full_key: "paths.logs",
75                value: paths_logs,
76            },
77            EntrySpec {
78                key: "cache",
79                full_key: "paths.cache",
80                value: paths_cache,
81            },
82        ],
83    },
84];
85
86fn core_log_level(config: &Config) -> Cow<'_, str> {
87    Cow::Borrowed(config.core.log_level.as_str())
88}
89
90fn core_file_log_level(config: &Config) -> Cow<'_, str> {
91    Cow::Borrowed(config.core.file_log_level.as_str())
92}
93
94fn core_auto_update(config: &Config) -> Cow<'_, str> {
95    Cow::Owned(config.core.auto_update.to_string())
96}
97
98fn core_confirm_remove(config: &Config) -> Cow<'_, str> {
99    Cow::Owned(config.core.confirm_remove.to_string())
100}
101
102fn core_default_yes(config: &Config) -> Cow<'_, str> {
103    Cow::Owned(config.core.default_yes.to_string())
104}
105
106fn core_color(config: &Config) -> Cow<'_, str> {
107    Cow::Owned(config.core.color.to_string())
108}
109
110fn paths_root(config: &Config) -> Cow<'_, str> {
111    Cow::Borrowed(config.paths.root.as_str())
112}
113
114fn paths_packages(config: &Config) -> Cow<'_, str> {
115    Cow::Borrowed(config.paths.packages.as_str())
116}
117
118fn paths_data(config: &Config) -> Cow<'_, str> {
119    Cow::Borrowed(config.paths.data.as_str())
120}
121
122fn paths_logs(config: &Config) -> Cow<'_, str> {
123    Cow::Borrowed(config.paths.logs.as_str())
124}
125
126fn paths_cache(config: &Config) -> Cow<'_, str> {
127    Cow::Borrowed(config.paths.cache.as_str())
128}
129
130fn find_entry(key: &str) -> Option<&'static EntrySpec> {
131    SECTION_SPECS
132        .iter()
133        .flat_map(|section| section.entries.iter())
134        .find(|entry| entry.full_key == key)
135}
136
137impl Config {
138    pub fn sections(&self) -> Vec<ConfigSection> {
139        SECTION_SPECS
140            .iter()
141            .map(|section| ConfigSection {
142                title: section.title.to_string(),
143                entries: section
144                    .entries
145                    .iter()
146                    .map(|entry| (entry.key.to_string(), (entry.value)(self).into_owned()))
147                    .collect(),
148            })
149            .collect()
150    }
151
152    pub fn effective_value(&self, key: &str) -> ConfigResult<(String, ConfigSource)> {
153        self.lookup_effective(key)?
154            .ok_or_else(|| ConfigError::UnknownKey {
155                key: key.trim().to_string(),
156            })
157    }
158
159    pub fn effective_optional_value(
160        &self,
161        key: &str,
162    ) -> ConfigResult<Option<(String, ConfigSource)>> {
163        self.lookup_effective(key)
164    }
165
166    pub fn effective_sections(&self) -> Result<Vec<ConfigSection>> {
167        let mut sections = Vec::new();
168
169        for section in SECTION_SPECS {
170            let mut entries = Vec::with_capacity(section.entries.len());
171
172            for entry in section.entries {
173                let display_value = if let Some(value) = self.env.value(entry.full_key) {
174                    format!("{value} [env override]")
175                } else {
176                    (entry.value)(self).into_owned()
177                };
178
179                entries.push((entry.key.to_string(), display_value));
180            }
181
182            sections.push(ConfigSection {
183                title: section.title.to_string(),
184                entries,
185            });
186        }
187
188        Ok(sections)
189    }
190
191    pub fn get_value(&self, key: &str) -> ConfigResult<Option<String>> {
192        let key = key.trim();
193
194        if key.is_empty() {
195            return Err(ConfigError::EmptyKey);
196        }
197
198        Ok(find_entry(key).map(|entry| (entry.value)(self).into_owned()))
199    }
200
201    fn lookup_effective(&self, key: &str) -> ConfigResult<Option<(String, ConfigSource)>> {
202        let key = key.trim();
203
204        if key.is_empty() {
205            return Err(ConfigError::EmptyKey);
206        }
207
208        let Some(entry) = find_entry(key) else {
209            return Ok(None);
210        };
211
212        if let Some(value) = self.env.value(key) {
213            return Ok(Some((value.to_string(), ConfigSource::Env)));
214        }
215
216        Ok(Some(((entry.value)(self).into_owned(), ConfigSource::File)))
217    }
218}