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::{
24    Path,
25    PathBuf,
26};
27
28use crate::{
29    Config,
30    ConfigError,
31    ConfigResult,
32};
33
34use super::{
35    ConfigSource,
36    config_source::load_transactionally,
37};
38
39/// Configuration source that loads from `.env` format files
40///
41/// # Examples
42///
43/// ```rust
44/// use qubit_config::source::{EnvFileConfigSource, ConfigSource};
45/// use qubit_config::Config;
46///
47/// let temp_dir = tempfile::tempdir().unwrap();
48/// let path = temp_dir.path().join(".env");
49/// std::fs::write(&path, "PORT=8080\n").unwrap();
50/// let source = EnvFileConfigSource::from_file(path);
51/// let mut config = Config::new();
52/// source.load(&mut config).unwrap();
53/// let port = config.get::<String>("PORT").unwrap();
54/// assert_eq!(port, "8080");
55/// ```
56///
57#[derive(Debug, Clone)]
58pub struct EnvFileConfigSource {
59    path: PathBuf,
60}
61
62impl EnvFileConfigSource {
63    /// Creates a new `EnvFileConfigSource` from a file path
64    ///
65    /// # Parameters
66    ///
67    /// * `path` - Path to the `.env` file
68    #[inline]
69    pub fn from_file<P: AsRef<Path>>(path: P) -> Self {
70        Self {
71            path: path.as_ref().to_path_buf(),
72        }
73    }
74}
75
76impl ConfigSource for EnvFileConfigSource {
77    fn load(&self, config: &mut Config) -> ConfigResult<()> {
78        load_transactionally(self, config)
79    }
80
81    fn load_into(&self, config: &mut Config) -> ConfigResult<()> {
82        let iter = dotenvy::from_path_iter(&self.path).map_err(|e| {
83            ConfigError::IoError(std::io::Error::other(format!(
84                "Failed to read .env file '{}': {}",
85                self.path.display(),
86                e
87            )))
88        })?;
89
90        for item in iter {
91            let (key, value) = item.map_err(|e| {
92                ConfigError::ParseError(format!("Failed to parse .env file '{}': {}", self.path.display(), e))
93            })?;
94            config.set(&key, value)?;
95        }
96
97        Ok(())
98    }
99}