Skip to main content

apm_core/
credentials.rs

1pub fn resolve(name: &str, keychain_service: Option<&str>) -> anyhow::Result<String> {
2    if let Ok(v) = std::env::var(name) {
3        if !v.is_empty() {
4            return Ok(v);
5        }
6    }
7    #[cfg(target_os = "macos")]
8    if let Some(service) = keychain_service {
9        let out = std::process::Command::new("security")
10            .args(["find-generic-password", "-s", service, "-w"])
11            .output()?;
12        if out.status.success() {
13            let val = String::from_utf8(out.stdout)?.trim().to_string();
14            if !val.is_empty() {
15                return Ok(val);
16            }
17        }
18    }
19    #[cfg(not(target_os = "macos"))]
20    let _ = keychain_service;
21    anyhow::bail!("credential {:?} not found in environment or keychain", name);
22}
23
24#[cfg(test)]
25mod tests {
26    use super::*;
27    use std::sync::Mutex;
28
29    static ENV_LOCK: Mutex<()> = Mutex::new(());
30
31    #[test]
32    fn prefers_env_var() {
33        let _g = ENV_LOCK.lock().unwrap();
34        std::env::set_var("TEST_CRED_0038", "from-env");
35        let result = resolve("TEST_CRED_0038", Some("some-service"));
36        std::env::remove_var("TEST_CRED_0038");
37        assert_eq!(result.unwrap(), "from-env");
38    }
39
40    #[test]
41    fn empty_env_var_is_skipped() {
42        let _g = ENV_LOCK.lock().unwrap();
43        std::env::set_var("TEST_CRED_0038_EMPTY", "");
44        let result = resolve("TEST_CRED_0038_EMPTY", None);
45        std::env::remove_var("TEST_CRED_0038_EMPTY");
46        assert!(result.is_err());
47    }
48
49    #[test]
50    fn missing_credential_fails() {
51        let _g = ENV_LOCK.lock().unwrap();
52        std::env::remove_var("TEST_CRED_0038_MISSING");
53        let result = resolve("TEST_CRED_0038_MISSING", None);
54        assert!(result.is_err());
55        assert!(result.unwrap_err().to_string().contains("TEST_CRED_0038_MISSING"));
56    }
57}