voa_config/config/
base.rs

1//! The unified configuration object representation.
2
3use std::{collections::BTreeMap, fmt::Display};
4
5use libc::geteuid;
6use log::{debug, error, trace};
7use serde::{Deserialize, Serialize};
8
9use crate::{
10    TechnologySettings,
11    config::technology::TechnologySettingsDefaults,
12    core::{Context, Os, Purpose},
13    file::{ConfigLoader, LoadMode},
14};
15
16/// An amalgamation of [`Os`], [`Purpose`] and [`Context`].
17#[derive(Debug, Deserialize, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)]
18pub struct OsPurposeContext {
19    os: Os,
20
21    /// The target purpose for this override.
22    purpose: Purpose,
23
24    /// The context for this override.
25    context: Context,
26}
27
28impl OsPurposeContext {
29    /// Creates a new [`OsPurposeContext`]
30    pub fn new(os: Os, purpose: Purpose, context: Context) -> Self {
31        Self {
32            os,
33            purpose,
34            context,
35        }
36    }
37}
38
39impl Display for OsPurposeContext {
40    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
41        write!(f, "{}/{}/{}", self.os, self.purpose, self.context)
42    }
43}
44
45/// The unified configuration view for a system.
46///
47/// Contains the fully resolved OS-level and context-level technology settings.
48#[derive(Debug)]
49pub struct VoaConfig {
50    /// The built-in default settings for all supported backend technologies.
51    defaults: TechnologySettings,
52
53    /// Per [`Os`] settings for all supported backend technologies.
54    oses: BTreeMap<Os, TechnologySettings>,
55
56    /// Per [`Os`]/[`Purpose`]/[`Context`] settings for all supported backend technologies.
57    contexts: BTreeMap<OsPurposeContext, TechnologySettings>,
58}
59
60impl VoaConfig {
61    /// Creates a new [`VoaConfig`] by loading all available configuration files from system-wide
62    /// and possible user-specific locations.
63    ///
64    /// # Note
65    ///
66    /// Depending on the effective uid of the calling user, the configurations are loaded only from
67    /// system mode locations (i.e. uid < 1000), or from system mode and user mode locations (i.e.
68    /// uid >= 1000).
69    pub fn load() -> Self {
70        let euid = unsafe { geteuid() };
71        trace!("Config::load called with process user id {euid}");
72
73        let loader = if euid < 1000 {
74            debug!("⤷ Using system mode configuration file locations");
75            ConfigLoader::load(LoadMode::System)
76        } else {
77            debug!("⤷ Using user mode configuration file locations");
78            ConfigLoader::load(LoadMode::User)
79        };
80
81        Self::from(loader)
82    }
83
84    /// Returns the technology settings for an OS, or the built-in default.
85    pub fn settings_for_os_or_default(&self, os: &Os) -> &TechnologySettings {
86        self.oses.get(os).unwrap_or(&self.defaults)
87    }
88
89    /// Returns the technology settings for a specific context, an OS or the built-in default.
90    pub fn settings_for_context_or_default(
91        &self,
92        os: &Os,
93        purpose: &Purpose,
94        context: &Context,
95    ) -> &TechnologySettings {
96        self.contexts
97            .get(&OsPurposeContext::new(
98                os.clone(),
99                purpose.clone(),
100                context.clone(),
101            ))
102            .unwrap_or(self.oses.get(os).unwrap_or(&self.defaults))
103    }
104
105    /// Returns a reference to the map of [`Os`] and [`TechnologySettings`].
106    pub fn oses(&self) -> &BTreeMap<Os, TechnologySettings> {
107        &self.oses
108    }
109
110    /// Returns a reference to the map of [`OsPurposeContext`] and [`TechnologySettings`].
111    pub fn contexts(&self) -> &BTreeMap<OsPurposeContext, TechnologySettings> {
112        &self.contexts
113    }
114}
115
116impl From<ConfigLoader> for VoaConfig {
117    fn from(value: ConfigLoader) -> Self {
118        let defaults = TechnologySettings::default();
119        let mut oses: BTreeMap<Os, TechnologySettings> = BTreeMap::new();
120        let mut contexts: BTreeMap<OsPurposeContext, TechnologySettings> = BTreeMap::new();
121
122        // Extract all OS default technology settings from default configuration files.
123        for (os, config_file) in value.defaults() {
124            if let Some(config_technology_settings) = config_file.default_technology_settings() {
125                match TechnologySettings::from_config_with_defaults(
126                    config_technology_settings,
127                    TechnologySettingsDefaults {
128                        built_in_defaults: &defaults,
129                        os_defaults: None,
130                    },
131                ) {
132                    Ok(technology_settings) => {
133                        oses.insert(os.clone(), technology_settings);
134                    }
135                    Err(error) => {
136                        error!(
137                            "Unable to create default technology settings for OS {os} due to error: {error}"
138                        );
139                    }
140                }
141            }
142        }
143        // Extract all OS default technology settings from drop-in configuration files.
144        //
145        // Here, any OS default settings found in drop-ins will have a higher priority and will
146        // override previously found ones.
147        // However, for populating unset fields in these drop-ins, previously found OS default
148        // technology settings will be considered.
149        for (os, config_files) in value.drop_ins() {
150            for config_file in config_files.iter() {
151                if let Some(config_technology_settings) = config_file.default_technology_settings()
152                {
153                    match TechnologySettings::from_config_with_defaults(
154                        config_technology_settings,
155                        TechnologySettingsDefaults {
156                            built_in_defaults: &defaults,
157                            os_defaults: oses.get(os),
158                        },
159                    ) {
160                        Ok(technology_settings) => {
161                            oses.insert(os.clone(), technology_settings);
162                        }
163                        Err(error) => {
164                            error!(
165                                "Unable to create default technology settings for OS {os} due to error: {error}"
166                            );
167                        }
168                    }
169                }
170            }
171        }
172
173        // Extract all context-level technology settings from default configuration files.
174        for (os, config_file) in value.defaults() {
175            // NOTE: Each ConfigOsSettings can only contain a list of unique context overrides.
176            for context_settings in config_file.contexts().iter() {
177                let os_purpose_context = OsPurposeContext::new(
178                    os.clone(),
179                    context_settings.purpose().clone(),
180                    context_settings.context().clone(),
181                );
182
183                let technology_settings = match TechnologySettings::from_config_with_defaults(
184                    context_settings.config_technology_settings(),
185                    TechnologySettingsDefaults {
186                        built_in_defaults: &defaults,
187                        os_defaults: oses.get(os),
188                    },
189                ) {
190                    Ok(settings) => settings,
191                    Err(error) => {
192                        error!(
193                            "Unable to create technology settings for OS/purpose/context {os_purpose_context} due to an error: {error}"
194                        );
195                        continue;
196                    }
197                };
198
199                contexts.insert(os_purpose_context, technology_settings);
200            }
201        }
202
203        // Extract all context-level technology settings from default configuration files.
204        //
205        // Context-level settings found in drop-in configuration files have a higher
206        // priority than those found in default configuration files or previously found drop-in
207        // configuration files (with lower priority).
208        // Context-level settings of lower priority are overridden.
209        for (os, config_files) in value.drop_ins() {
210            for config_file in config_files.iter() {
211                // NOTE: Each ConfigOsSettings can only contain a list of unique context overrides.
212                for context_settings in config_file.contexts().iter() {
213                    let os_purpose_context = OsPurposeContext::new(
214                        os.clone(),
215                        context_settings.purpose().clone(),
216                        context_settings.context().clone(),
217                    );
218
219                    let technology_settings = match TechnologySettings::from_config_with_defaults(
220                        context_settings.config_technology_settings(),
221                        TechnologySettingsDefaults {
222                            built_in_defaults: &defaults,
223                            os_defaults: oses.get(os),
224                        },
225                    ) {
226                        Ok(technology_settings) => technology_settings,
227                        Err(error) => {
228                            error!(
229                                "Unable to create technology settings for OS/purpose/context {os_purpose_context} due to an error: {error}"
230                            );
231                            continue;
232                        }
233                    };
234
235                    contexts.insert(os_purpose_context, technology_settings);
236                }
237            }
238        }
239
240        Self {
241            defaults,
242            oses,
243            contexts,
244        }
245    }
246}