codecov_rust_mcp/
env_file.rs1use std::path::{Path, PathBuf};
4
5pub fn load_dotenv() {
12 let exe = std::env::current_exe().ok();
13 let xdg = std::env::var_os("XDG_CONFIG_HOME").map(PathBuf::from);
14 let home = std::env::var_os("HOME").map(PathBuf::from);
15 load_dotenv_from(exe.as_deref(), xdg.as_deref(), home.as_deref());
16}
17
18pub(crate) fn load_dotenv_from(exe: Option<&Path>, xdg: Option<&Path>, home: Option<&Path>) {
20 let _ = dotenvy::dotenv();
21 if let Some(path) = exe.and_then(repo_env_path_from_exe) {
22 let _ = dotenvy::from_path(path);
23 }
24 if let Some(path) = user_config_env_path_from(xdg, home) {
25 let _ = dotenvy::from_path(path);
26 }
27}
28
29#[must_use]
31pub(crate) fn repo_env_path_from_exe(exe: &Path) -> Option<PathBuf> {
32 let release_or_debug = exe.parent()?;
33 let target = release_or_debug.parent()?;
34 if target.file_name()?.to_str()? != "target" {
35 return None;
36 }
37 let root = target.parent()?;
38 let path = root.join(".env");
39 Path::new(&path).is_file().then_some(path)
40}
41
42#[must_use]
44pub(crate) fn user_config_env_path_from(
45 xdg: Option<&Path>,
46 home: Option<&Path>,
47) -> Option<PathBuf> {
48 let base = xdg
49 .map(Path::to_path_buf)
50 .or_else(|| home.map(|h| h.join(".config")))?;
51 let path = base.join("codecov-rust-mcp").join(".env");
52 path.is_file().then_some(path)
53}
54
55#[cfg(test)]
56mod tests {
57 use super::*;
58 use std::fs;
59 use std::time::{SystemTime, UNIX_EPOCH};
60
61 fn temp_root(tag: &str) -> PathBuf {
62 let nanos = SystemTime::now()
63 .duration_since(UNIX_EPOCH)
64 .expect("time")
65 .as_nanos();
66 let root = std::env::temp_dir().join(format!("codecov-rust-mcp-env-{tag}-{nanos}"));
67 fs::create_dir_all(&root).expect("temp root");
68 root
69 }
70
71 #[test]
72 fn repo_env_path_from_exe_finds_dotenv() {
73 let root = temp_root("repo");
74 let exe = root.join("target").join("debug").join("codecov-rust-mcp");
75 fs::create_dir_all(exe.parent().expect("parent")).expect("dirs");
76 let env_path = root.join(".env");
77 fs::write(&env_path, "CODECOV_TOKEN=from-repo\n").expect("write env");
78 assert_eq!(repo_env_path_from_exe(&exe), Some(env_path));
79 let _ = fs::remove_dir_all(root);
80 }
81
82 #[test]
83 fn repo_env_path_from_exe_rejects_non_target() {
84 let root = temp_root("notarget");
85 let exe = root.join("bin").join("debug").join("codecov-rust-mcp");
86 fs::create_dir_all(exe.parent().expect("parent")).expect("dirs");
87 fs::write(root.join(".env"), "x=1\n").expect("write");
88 assert!(repo_env_path_from_exe(&exe).is_none());
89 let _ = fs::remove_dir_all(root);
90 }
91
92 #[test]
93 fn repo_env_path_from_exe_missing_file() {
94 let root = temp_root("missing");
95 let exe = root.join("target").join("release").join("codecov-rust-mcp");
96 fs::create_dir_all(exe.parent().expect("parent")).expect("dirs");
97 assert!(repo_env_path_from_exe(&exe).is_none());
98 let _ = fs::remove_dir_all(root);
99 }
100
101 #[test]
102 fn user_config_env_path_from_xdg() {
103 let root = temp_root("xdg");
104 let path = root.join("codecov-rust-mcp").join(".env");
105 fs::create_dir_all(path.parent().expect("parent")).expect("dirs");
106 fs::write(&path, "CODECOV_TOKEN=from-xdg\n").expect("write");
107 assert_eq!(
108 user_config_env_path_from(Some(root.as_path()), None),
109 Some(path)
110 );
111 let _ = fs::remove_dir_all(root);
112 }
113
114 #[test]
115 fn user_config_env_path_from_home() {
116 let home = temp_root("home");
117 let path = home.join(".config").join("codecov-rust-mcp").join(".env");
118 fs::create_dir_all(path.parent().expect("parent")).expect("dirs");
119 fs::write(&path, "CODECOV_TOKEN=from-home\n").expect("write");
120 assert_eq!(
121 user_config_env_path_from(None, Some(home.as_path())),
122 Some(path)
123 );
124 assert!(user_config_env_path_from(None, None).is_none());
125 let _ = fs::remove_dir_all(home);
126 }
127
128 #[test]
129 fn load_dotenv_from_loads_repo_and_user_files() {
130 let root = temp_root("load");
131 let exe = root.join("target").join("debug").join("codecov-rust-mcp");
132 fs::create_dir_all(exe.parent().expect("parent")).expect("dirs");
133 fs::write(root.join(".env"), "# repo env\n").expect("repo env");
134
135 let xdg = temp_root("load-xdg");
136 let user_env = xdg.join("codecov-rust-mcp").join(".env");
137 fs::create_dir_all(user_env.parent().expect("parent")).expect("dirs");
138 fs::write(&user_env, "# user env\n").expect("user env");
139
140 load_dotenv_from(Some(exe.as_path()), Some(xdg.as_path()), None);
141 load_dotenv();
142
143 let _ = fs::remove_dir_all(root);
144 let _ = fs::remove_dir_all(xdg);
145 }
146}