better_config_loader/
ini.rs

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