Skip to main content

gonfig/
environment.rs

1use crate::{
2    error::Result,
3    source::{ConfigSource, Source},
4    Prefix,
5};
6use serde_json::{json, Map, Value};
7use std::any::Any;
8use std::collections::HashMap;
9use std::env;
10
11/// Environment variable configuration source.
12///
13/// The `Environment` struct provides a flexible way to read configuration values
14/// from environment variables. It supports prefixes, custom separators, case sensitivity
15/// control, and field-specific mappings.
16///
17/// # Examples
18///
19/// ## Basic Usage
20///
21/// ```rust
22/// use gonfig::{Environment, ConfigBuilder};
23/// use serde::Deserialize;
24///
25/// #[derive(Deserialize)]
26/// struct Config {
27///     database_url: String,
28///     port: u16,
29/// }
30///
31/// std::env::set_var("APP_DATABASE_URL", "postgres://localhost/db");
32/// std::env::set_var("APP_PORT", "5432");
33///
34/// let config: Config = ConfigBuilder::new()
35///     .add_source(Box::new(Environment::new().with_prefix("APP")))
36///     .build()
37///     .unwrap();
38/// ```
39///
40/// ## Advanced Configuration
41///
42/// ```rust
43/// use gonfig::Environment;
44///
45/// let env = Environment::new()
46///     .with_prefix("MYAPP")
47///     .separator("__")  // Use double underscore
48///     .case_sensitive(true)
49///     .override_with("database_url", "postgres://override/db")
50///     .with_field_mapping("db_url", "CUSTOM_DB_CONNECTION");
51/// ```
52#[derive(Debug, Clone)]
53pub struct Environment {
54    prefix: Option<Prefix>,
55    separator: String,
56    case_sensitive: bool,
57    overrides: HashMap<String, String>,
58    field_mappings: HashMap<String, String>,
59    nested: bool,
60}
61
62impl Default for Environment {
63    fn default() -> Self {
64        Self {
65            prefix: None,
66            separator: "_".to_string(),
67            case_sensitive: false,
68            overrides: HashMap::new(),
69            field_mappings: HashMap::new(),
70            nested: false,
71        }
72    }
73}
74
75impl Environment {
76    /// Create a new environment variable source with default settings.
77    ///
78    /// Default configuration:
79    /// - No prefix
80    /// - Separator: `"_"`
81    /// - Case sensitive: `false` (environment variables are converted to uppercase)
82    /// - No overrides or field mappings
83    ///
84    /// # Examples
85    ///
86    /// ```rust
87    /// use gonfig::Environment;
88    ///
89    /// let env = Environment::new();
90    /// ```
91    pub fn new() -> Self {
92        Self::default()
93    }
94
95    /// Set the environment variable prefix.
96    ///
97    /// When a prefix is set, environment variables will be expected in the format
98    /// `{PREFIX}{SEPARATOR}{FIELD_NAME}`. For example, with prefix "APP" and
99    /// separator "_", a field named `database_url` would map to `APP_DATABASE_URL`.
100    ///
101    /// # Examples
102    ///
103    /// ```rust
104    /// use gonfig::Environment;
105    ///
106    /// let env = Environment::new().with_prefix("MYAPP");
107    /// // Will look for MYAPP_* environment variables
108    /// ```
109    pub fn with_prefix(mut self, prefix: impl Into<String>) -> Self {
110        self.prefix = Some(Prefix::new(prefix));
111        self
112    }
113
114    /// Set the separator used between prefix and field names.
115    ///
116    /// The default separator is `"_"`. This affects how environment variable
117    /// names are constructed from the prefix and field names.
118    ///
119    /// # Examples
120    ///
121    /// ```rust
122    /// use gonfig::Environment;
123    ///
124    /// let env = Environment::new()
125    ///     .with_prefix("APP")
126    ///     .separator("__");  // Results in APP__FIELD_NAME
127    /// ```
128    pub fn separator(mut self, sep: impl Into<String>) -> Self {
129        self.separator = sep.into();
130        self
131    }
132
133    /// Control case sensitivity for environment variable names.
134    ///
135    /// When `false` (default), all environment variable names are converted
136    /// to uppercase. When `true`, the exact case is preserved.
137    ///
138    /// # Examples
139    ///
140    /// ```rust
141    /// use gonfig::Environment;
142    ///
143    /// let env = Environment::new()
144    ///     .with_prefix("app")
145    ///     .case_sensitive(true);
146    /// // Will look for app_field_name instead of APP_FIELD_NAME
147    /// ```
148    pub fn case_sensitive(mut self, sensitive: bool) -> Self {
149        self.case_sensitive = sensitive;
150        self
151    }
152
153    /// Override a specific field with a hardcoded value.
154    ///
155    /// This is useful for providing default values or overriding environment
156    /// variables programmatically. Overrides take precedence over actual
157    /// environment variables.
158    ///
159    /// # Examples
160    ///
161    /// ```rust
162    /// use gonfig::Environment;
163    ///
164    /// let env = Environment::new()
165    ///     .override_with("debug", "true")
166    ///     .override_with("timeout", "30");
167    /// ```
168    pub fn override_with(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
169        self.overrides.insert(key.into(), value.into());
170        self
171    }
172
173    /// Map a specific field to a custom environment variable name.
174    ///
175    /// This allows you to override the default environment variable naming
176    /// for specific fields. The mapping takes precedence over the standard
177    /// prefix and separator rules.
178    ///
179    /// # Examples
180    ///
181    /// ```rust
182    /// use gonfig::Environment;
183    ///
184    /// let env = Environment::new()
185    ///     .with_prefix("APP")
186    ///     .with_field_mapping("database_url", "DATABASE_CONNECTION_STRING");
187    /// // database_url will read from DATABASE_CONNECTION_STRING instead of APP_DATABASE_URL
188    /// ```
189    pub fn with_field_mapping(
190        mut self,
191        field_name: impl Into<String>,
192        env_key: impl Into<String>,
193    ) -> Self {
194        self.field_mappings
195            .insert(field_name.into(), env_key.into());
196        self
197    }
198
199    /// Enable nested mode to convert flat environment variable keys into nested structures.
200    ///
201    /// When enabled, environment variables with the configured separator (default: `_`) will be split
202    /// into nested paths. For example, `APP_HTTP_PORT=9000` becomes `{"http": {"port": 9000}}`.
203    ///
204    /// This is essential for properly overriding nested configuration file values with
205    /// environment variables when using the Deep merge strategy.
206    ///
207    /// # Examples
208    ///
209    /// ```rust
210    /// use gonfig::{Environment, ConfigBuilder, MergeStrategy};
211    ///
212    /// // With nested=true, APP_HTTP_PORT will override http.port in config file
213    /// let env = Environment::new()
214    ///     .with_prefix("APP")
215    ///     .nested(true);
216    /// ```
217    pub fn nested(mut self, nested: bool) -> Self {
218        self.nested = nested;
219        self
220    }
221
222    fn build_env_key(&self, path: &[&str]) -> String {
223        let mut parts = Vec::new();
224
225        if let Some(prefix) = &self.prefix {
226            parts.push(prefix.as_str().to_string());
227        }
228
229        for part in path {
230            parts.push(part.to_string());
231        }
232
233        let key = parts.join(&self.separator);
234
235        if self.case_sensitive {
236            key
237        } else {
238            key.to_uppercase()
239        }
240    }
241
242    /// Normalize a key for storage in the flat map based on nested mode setting.
243    ///
244    /// In nested mode, preserves the original case for proper splitting.
245    /// In flat mode, converts to lowercase for backward compatibility.
246    fn normalize_key(&self, key: &str) -> String {
247        if self.nested {
248            key.to_string()
249        } else {
250            key.to_lowercase()
251        }
252    }
253
254    fn parse_env_value(value: &str) -> Value {
255        if let Ok(b) = value.parse::<bool>() {
256            return json!(b);
257        }
258
259        if let Ok(n) = value.parse::<i64>() {
260            return json!(n);
261        }
262
263        if let Ok(n) = value.parse::<f64>() {
264            return json!(n);
265        }
266
267        if value.starts_with('[') && value.ends_with(']') {
268            if let Ok(arr) = serde_json::from_str::<Vec<Value>>(value) {
269                return json!(arr);
270            }
271        }
272
273        if value.starts_with('{') && value.ends_with('}') {
274            if let Ok(obj) = serde_json::from_str::<Value>(value) {
275                return obj;
276            }
277        }
278
279        json!(value)
280    }
281
282    /// Recursively insert a value into a nested map structure based on a path of keys.
283    ///
284    /// This helper function takes a flat key path (e.g., ["http", "server", "port"])
285    /// and creates the necessary nested structure in the map, inserting the value
286    /// at the deepest level.
287    fn insert_nested(map: &mut Map<String, Value>, parts: &[String], value: Value) {
288        if parts.is_empty() {
289            return;
290        }
291
292        if parts.len() == 1 {
293            // Base case: insert the value at this key
294            map.insert(parts[0].clone(), value);
295            return;
296        }
297
298        // Recursive case: get or create the nested object
299        let key = parts[0].clone();
300        match map.entry(key) {
301            serde_json::map::Entry::Occupied(mut occ) => {
302                if let Value::Object(ref mut nested) = occ.get_mut() {
303                    Self::insert_nested(nested, &parts[1..], value);
304                } else {
305                    // Replace non-object with a new object containing the nested value
306                    let mut new_map = Map::new();
307                    Self::insert_nested(&mut new_map, &parts[1..], value);
308                    *occ.get_mut() = Value::Object(new_map);
309                }
310            }
311            serde_json::map::Entry::Vacant(vac) => {
312                let mut new_map = Map::new();
313                Self::insert_nested(&mut new_map, &parts[1..], value);
314                vac.insert(Value::Object(new_map));
315            }
316        }
317    }
318
319    pub fn collect_for_struct(
320        &self,
321        struct_name: &str,
322        fields: &[(&str, Option<&str>)],
323    ) -> HashMap<String, Value> {
324        let mut result = HashMap::new();
325
326        for (field_name, field_override) in fields {
327            let env_key = if let Some(override_name) = field_override {
328                override_name.to_string()
329            } else if let Some(prefix) = &self.prefix {
330                format!(
331                    "{}_{}_{}_{}",
332                    prefix.as_str().to_uppercase(),
333                    struct_name.to_uppercase(),
334                    field_name.to_uppercase(),
335                    ""
336                )
337                .trim_end_matches('_')
338                .to_string()
339            } else {
340                format!(
341                    "{}_{}",
342                    struct_name.to_uppercase(),
343                    field_name.to_uppercase()
344                )
345            };
346
347            if let Some(override_value) = self.overrides.get(&env_key) {
348                result.insert(
349                    field_name.to_string(),
350                    Self::parse_env_value(override_value),
351                );
352            } else if let Ok(value) = env::var(&env_key) {
353                result.insert(field_name.to_string(), Self::parse_env_value(&value));
354            }
355        }
356
357        result
358    }
359
360    pub fn collect_with_flat_keys(&self) -> Result<Value> {
361        let mut flat_map = HashMap::new();
362
363        // First collect from environment variables
364        for (key, value) in env::vars() {
365            if let Some(prefix) = &self.prefix {
366                let prefix_str = if self.case_sensitive {
367                    prefix.as_str().to_string()
368                } else {
369                    prefix.as_str().to_uppercase()
370                };
371
372                let key_check = if self.case_sensitive {
373                    key.clone()
374                } else {
375                    key.to_uppercase()
376                };
377
378                if key_check.starts_with(&prefix_str) {
379                    let trimmed = key_check[prefix_str.len()..].trim_start_matches(&self.separator);
380                    let key_for_map = self.normalize_key(trimmed);
381                    flat_map.insert(key_for_map, Self::parse_env_value(&value));
382                }
383            } else {
384                flat_map.insert(key.to_lowercase(), Self::parse_env_value(&value));
385            }
386        }
387
388        // Then apply overrides (overrides take precedence)
389        for (override_key, override_value) in &self.overrides {
390            if let Some(prefix) = &self.prefix {
391                let prefix_str = if self.case_sensitive {
392                    prefix.as_str().to_string()
393                } else {
394                    prefix.as_str().to_uppercase()
395                };
396
397                let key_check = if self.case_sensitive {
398                    override_key.clone()
399                } else {
400                    override_key.to_uppercase()
401                };
402
403                if key_check.starts_with(&prefix_str) {
404                    let trimmed = key_check[prefix_str.len()..].trim_start_matches(&self.separator);
405                    let key_for_map = self.normalize_key(trimmed);
406                    flat_map.insert(key_for_map, Self::parse_env_value(override_value));
407                }
408            } else {
409                flat_map.insert(
410                    override_key.to_lowercase(),
411                    Self::parse_env_value(override_value),
412                );
413            }
414        }
415
416        // Convert flat keys into nested structures if enabled
417        let mut result = Map::new();
418        for (key, value) in flat_map {
419            if self.nested {
420                // Split on separator to create nested structure
421                let parts: Vec<&str> = key.split(&self.separator).collect();
422                if parts.len() == 1 {
423                    // Single part, insert directly (lowercase it)
424                    result.insert(key.to_lowercase(), value);
425                } else {
426                    // Multiple parts, create nested structure
427                    // Lowercase each part individually
428                    let lowercase_parts: Vec<String> =
429                        parts.iter().map(|p| p.to_lowercase()).collect();
430                    Self::insert_nested(&mut result, &lowercase_parts, value);
431                }
432            } else {
433                // Keep keys flat (backward compatible behavior)
434                result.insert(key.to_lowercase(), value);
435            }
436        }
437
438        Ok(Value::Object(result))
439    }
440}
441
442impl ConfigSource for Environment {
443    fn source_type(&self) -> Source {
444        Source::Environment
445    }
446
447    fn collect(&self) -> Result<Value> {
448        if !self.field_mappings.is_empty() {
449            // Use field mappings when available
450            let mut result = Map::new();
451
452            // First collect using field mappings
453            for (field_name, env_key) in &self.field_mappings {
454                // Check overrides first, then environment
455                if let Some(override_value) = self.overrides.get(env_key) {
456                    result.insert(field_name.clone(), Self::parse_env_value(override_value));
457                } else if let Ok(value) = env::var(env_key) {
458                    result.insert(field_name.clone(), Self::parse_env_value(&value));
459                }
460            }
461
462            // Then collect any prefixed variables not in mappings
463            if let Some(prefix) = &self.prefix {
464                for (key, value) in env::vars() {
465                    let prefix_str = if self.case_sensitive {
466                        prefix.as_str().to_string()
467                    } else {
468                        prefix.as_str().to_uppercase()
469                    };
470
471                    let key_check = if self.case_sensitive {
472                        key.clone()
473                    } else {
474                        key.to_uppercase()
475                    };
476
477                    if key_check.starts_with(&prefix_str)
478                        && !self.field_mappings.values().any(|v| v == &key)
479                    {
480                        let trimmed =
481                            key_check[prefix_str.len()..].trim_start_matches(&self.separator);
482                        let field_name = trimmed.to_lowercase();
483                        if !result.contains_key(&field_name) {
484                            result.insert(field_name, Self::parse_env_value(&value));
485                        }
486                    }
487                }
488            }
489
490            Ok(Value::Object(result))
491        } else {
492            self.collect_with_flat_keys()
493        }
494    }
495
496    fn has_value(&self, key: &str) -> bool {
497        let env_key = self.build_env_key(&[key]);
498        self.overrides.contains_key(&env_key) || env::var(&env_key).is_ok()
499    }
500
501    fn get_value(&self, key: &str) -> Option<Value> {
502        let env_key = self.build_env_key(&[key]);
503
504        if let Some(override_value) = self.overrides.get(&env_key) {
505            Some(Self::parse_env_value(override_value))
506        } else {
507            env::var(&env_key).ok().map(|v| Self::parse_env_value(&v))
508        }
509    }
510
511    fn as_any(&self) -> &dyn Any {
512        self
513    }
514}