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