better_config_loader/
ini.rs

1use better_config_core::{merge_with_env_uppercase, AbstractConfig, Error, misc};
2use ini::Ini;
3use std::collections::{HashMap, HashSet};
4
5/// Indicates that structure can be initialized from INI file.
6pub trait IniConfig<T = HashMap<String, String>>: AbstractConfig<T> {
7    /// Load specified INI file and initialize the structure.
8    ///
9    /// # Arguments
10    /// * `target` - Path to the INI file.
11    ///
12    /// # Errors
13    /// * `Error::LoadFileError` - If the specified INI file cannot be loaded or parsed.
14    fn load(target: Option<String>) -> Result<T, Error>
15    where
16        T: Default,
17        HashMap<String, String>: Into<T>,
18        Self: Sized,
19    {
20        Self::load_with_override(target, &HashSet::new())
21    }
22
23    /// Load specified INI file with explicit control over which keys should not be overridden.
24    ///
25    /// # Arguments
26    /// * `target` - Path to the INI file.
27    /// * `excluded_keys` - Keys that should not be overridden by environment variables.
28    ///
29    /// # Errors
30    /// * `Error::LoadFileError` - If the specified INI file cannot be loaded or parsed.
31    fn load_with_override(target: Option<String>, excluded_keys: &HashSet<String>) -> Result<T, Error>
32    where
33        T: Default,
34        HashMap<String, String>: Into<T>,
35        Self: Sized,
36    {
37        let target = target.or(Some("config.ini".to_string()));
38
39        let mut ini_map = HashMap::new();
40
41        if let Some(target) = target {
42            let file_paths = misc::validate_and_split_paths(&target)?;
43
44            for file_path in file_paths {
45                // Check file accessibility before reading
46                misc::check_file_accessibility(&file_path)?;
47
48                let ini = Ini::load_from_file(&file_path)
49                    .map_err(|e| Error::IoError {
50                        operation: format!("load INI file '{}'", file_path),
51                        source: Some(Box::new(e)),
52                    })?;
53
54                for (section, props) in ini.iter() {
55                    let section_prefix = match section {
56                        Some(s) => format!("{}.", s),
57                        None => String::new(),
58                    };
59
60                    for (key, value) in props.iter() {
61                        ini_map.insert(format!("{}{}", section_prefix, key), value.to_string());
62                    }
63                }
64            }
65        }
66
67        // Apply environment variable override with excluded keys
68        let ini_map = merge_with_env_uppercase(ini_map, None, excluded_keys);
69
70        Ok(ini_map.into())
71    }
72}