pub const CREDENTIALED_PROVIDERS: [&str; 5] =
["openai", "anthropic", "gemini", "xai", "openrouter"];
pub trait StoredCredentials {
fn secret_for(&self, provider: &str) -> Option<String>;
}
pub struct EnvironmentOnly;
impl StoredCredentials for EnvironmentOnly {
fn secret_for(&self, _provider: &str) -> Option<String> {
None
}
}
pub(crate) fn key_for(
provider: &str,
environment: &dyn Fn(&str) -> Option<String>,
stored: &dyn StoredCredentials,
) -> Option<String> {
environment(&format!("{}_API_KEY", provider.to_ascii_uppercase()))
.filter(|key| !key.trim().is_empty())
.or_else(|| stored.secret_for(provider))
.filter(|key| !key.trim().is_empty())
}
#[cfg(test)]
mod tests {
use super::*;
use std::collections::HashMap;
struct Saved(HashMap<&'static str, &'static str>);
impl StoredCredentials for Saved {
fn secret_for(&self, provider: &str) -> Option<String> {
self.0.get(provider).map(|secret| secret.to_string())
}
}
#[test]
fn the_environment_wins_and_an_empty_variable_is_not_a_key() {
let stored = Saved(HashMap::from([
("openai", "stored-openai"),
("openrouter", "stored-openrouter"),
]));
let environment = |name: &str| match name {
"OPENAI_API_KEY" => Some("exported-openai".to_string()),
"OPENROUTER_API_KEY" => Some(" ".to_string()),
"ANTHROPIC_API_KEY" => Some("exported-anthropic".to_string()),
_ => None,
};
assert_eq!(
key_for("openai", &environment, &stored).as_deref(),
Some("exported-openai")
);
assert_eq!(
key_for("openrouter", &environment, &stored).as_deref(),
Some("stored-openrouter")
);
assert_eq!(
key_for("anthropic", &environment, &stored).as_deref(),
Some("exported-anthropic")
);
assert_eq!(key_for("gemini", &environment, &stored), None);
}
#[test]
fn a_stored_login_is_asked_for_by_provider_name() {
let asked = std::cell::RefCell::new(Vec::new());
struct Recording<'a>(&'a std::cell::RefCell<Vec<String>>);
impl StoredCredentials for Recording<'_> {
fn secret_for(&self, provider: &str) -> Option<String> {
self.0.borrow_mut().push(provider.to_string());
None
}
}
for provider in CREDENTIALED_PROVIDERS {
key_for(provider, &|_| None, &Recording(&asked));
}
assert_eq!(
asked.into_inner(),
vec!["openai", "anthropic", "gemini", "xai", "openrouter"]
);
}
}