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}