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}