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}