Skip to main content

qubit_config/source/
env_config_source.rs

1/*******************************************************************************
2 *
3 *    Copyright (c) 2025 - 2026.
4 *    Haixing Hu, Qubit Co. Ltd.
5 *
6 *    All rights reserved.
7 *
8 ******************************************************************************/
9//! # System Environment Variable Configuration Source
10//!
11//! Loads configuration from the current process's environment variables.
12//!
13//! # Key Transformation
14//!
15//! When a prefix is set, only variables matching the prefix are loaded, and
16//! the prefix is stripped from the key name. The key is then lowercased and
17//! underscores are converted to dots to produce the config key.
18//!
19//! For example, with prefix `APP_`:
20//! - `APP_SERVER_HOST=localhost` → `server.host = "localhost"`
21//! - `APP_SERVER_PORT=8080` → `server.port = "8080"`
22//!
23//! Without a prefix, all environment variables are loaded as-is.
24//!
25//! # Author
26//!
27//! Haixing Hu
28
29use crate::{Config, ConfigResult};
30
31use super::ConfigSource;
32
33/// Configuration source that loads from system environment variables
34///
35/// # Examples
36///
37/// ```rust,ignore
38/// use qubit_config::source::{EnvConfigSource, ConfigSource};
39/// use qubit_config::Config;
40///
41/// // Load all env vars
42/// let source = EnvConfigSource::new();
43///
44/// // Load only vars with prefix "APP_", strip prefix and normalize key
45/// let source = EnvConfigSource::with_prefix("APP_");
46///
47/// let mut config = Config::new();
48/// source.load(&mut config).unwrap();
49/// ```
50///
51/// # Author
52///
53/// Haixing Hu
54#[derive(Debug, Clone)]
55pub struct EnvConfigSource {
56    /// Optional prefix filter; only variables with this prefix are loaded
57    prefix: Option<String>,
58    /// Whether to strip the prefix from the key
59    strip_prefix: bool,
60    /// Whether to convert underscores to dots in the key
61    convert_underscores: bool,
62    /// Whether to lowercase the key
63    lowercase_keys: bool,
64}
65
66impl EnvConfigSource {
67    /// Creates a new `EnvConfigSource` that loads all environment variables.
68    ///
69    /// Keys are loaded as-is (no prefix filtering, no transformation).
70    ///
71    /// # Returns
72    ///
73    /// A source that ingests every `std::env::vars()` entry.
74    #[inline]
75    pub fn new() -> Self {
76        Self {
77            prefix: None,
78            strip_prefix: false,
79            convert_underscores: false,
80            lowercase_keys: false,
81        }
82    }
83
84    /// Creates a new `EnvConfigSource` that filters by prefix and normalizes
85    /// keys.
86    ///
87    /// Only variables with the given prefix are loaded. The prefix is stripped,
88    /// the key is lowercased, and underscores are converted to dots.
89    ///
90    /// # Parameters
91    ///
92    /// * `prefix` - The prefix to filter by (e.g., `"APP_"`)
93    ///
94    /// # Returns
95    ///
96    /// A source with prefix filtering and key normalization enabled.
97    #[inline]
98    pub fn with_prefix(prefix: &str) -> Self {
99        Self {
100            prefix: Some(prefix.to_string()),
101            strip_prefix: true,
102            convert_underscores: true,
103            lowercase_keys: true,
104        }
105    }
106
107    /// Creates a new `EnvConfigSource` with a custom prefix and explicit
108    /// options.
109    ///
110    /// # Parameters
111    ///
112    /// * `prefix` - The prefix to filter by
113    /// * `strip_prefix` - Whether to strip the prefix from the key
114    /// * `convert_underscores` - Whether to convert underscores to dots
115    /// * `lowercase_keys` - Whether to lowercase the key
116    ///
117    /// # Returns
118    ///
119    /// A configured [`EnvConfigSource`].
120    #[inline]
121    pub fn with_options(
122        prefix: &str,
123        strip_prefix: bool,
124        convert_underscores: bool,
125        lowercase_keys: bool,
126    ) -> Self {
127        Self {
128            prefix: Some(prefix.to_string()),
129            strip_prefix,
130            convert_underscores,
131            lowercase_keys,
132        }
133    }
134
135    /// Transforms an environment variable key according to the source's
136    /// settings.
137    ///
138    /// # Parameters
139    ///
140    /// * `key` - Original environment variable name.
141    ///
142    /// # Returns
143    ///
144    /// The key after optional prefix strip, lowercasing, and underscore
145    /// replacement.
146    fn transform_key(&self, key: &str) -> String {
147        let mut result = key.to_string();
148
149        if self.strip_prefix {
150            if let Some(prefix) = &self.prefix {
151                if result.starts_with(prefix.as_str()) {
152                    result = result[prefix.len()..].to_string();
153                }
154            }
155        }
156
157        if self.lowercase_keys {
158            result = result.to_lowercase();
159        }
160
161        if self.convert_underscores {
162            result = result.replace('_', ".");
163        }
164
165        result
166    }
167}
168
169impl Default for EnvConfigSource {
170    #[inline]
171    fn default() -> Self {
172        Self::new()
173    }
174}
175
176impl ConfigSource for EnvConfigSource {
177    fn load(&self, config: &mut Config) -> ConfigResult<()> {
178        for (key, value) in std::env::vars() {
179            // Filter by prefix if set
180            if let Some(prefix) = &self.prefix {
181                if !key.starts_with(prefix.as_str()) {
182                    continue;
183                }
184            }
185
186            let transformed_key = self.transform_key(&key);
187            config.set(&transformed_key, value)?;
188        }
189
190        Ok(())
191    }
192}