Skip to main content

qubit_config/source/
env_file_config_source.rs

1/*******************************************************************************
2 *
3 *    Copyright (c) 2025 - 2026 Haixing Hu.
4 *
5 *    SPDX-License-Identifier: Apache-2.0
6 *
7 *    Licensed under the Apache License, Version 2.0.
8 *
9 ******************************************************************************/
10//! # `.env` File Configuration Source
11//!
12//! Loads configuration from `.env` format files (as used by dotenv tools).
13//!
14//! # Format
15//!
16//! The `.env` format supports:
17//! - `KEY=VALUE` assignments
18//! - `# comment` lines
19//! - Quoted values: `KEY="value with spaces"` or `KEY='value'`
20//! - Export prefix: `export KEY=VALUE` (the `export` keyword is ignored)
21//!
22
23use std::path::{Path, PathBuf};
24
25use crate::{Config, ConfigError, ConfigResult};
26
27use super::ConfigSource;
28
29/// Configuration source that loads from `.env` format files
30///
31/// # Examples
32///
33/// ```rust
34/// use qubit_config::source::{EnvFileConfigSource, ConfigSource};
35/// use qubit_config::Config;
36///
37/// let temp_dir = tempfile::tempdir().unwrap();
38/// let path = temp_dir.path().join(".env");
39/// std::fs::write(&path, "PORT=8080\n").unwrap();
40/// let source = EnvFileConfigSource::from_file(path);
41/// let mut config = Config::new();
42/// source.load(&mut config).unwrap();
43/// let port = config.get::<String>("PORT").unwrap();
44/// assert_eq!(port, "8080");
45/// ```
46///
47#[derive(Debug, Clone)]
48pub struct EnvFileConfigSource {
49    path: PathBuf,
50}
51
52impl EnvFileConfigSource {
53    /// Creates a new `EnvFileConfigSource` from a file path
54    ///
55    /// # Parameters
56    ///
57    /// * `path` - Path to the `.env` file
58    #[inline]
59    pub fn from_file<P: AsRef<Path>>(path: P) -> Self {
60        Self {
61            path: path.as_ref().to_path_buf(),
62        }
63    }
64}
65
66impl ConfigSource for EnvFileConfigSource {
67    fn load(&self, config: &mut Config) -> ConfigResult<()> {
68        let iter = dotenvy::from_path_iter(&self.path).map_err(|e| {
69            ConfigError::IoError(std::io::Error::other(format!(
70                "Failed to read .env file '{}': {}",
71                self.path.display(),
72                e
73            )))
74        })?;
75
76        for item in iter {
77            let (key, value) = item.map_err(|e| {
78                ConfigError::ParseError(format!(
79                    "Failed to parse .env file '{}': {}",
80                    self.path.display(),
81                    e
82                ))
83            })?;
84            config.set(&key, value)?;
85        }
86
87        Ok(())
88    }
89}