use std::collections::BTreeMap;
pub const SERVICE: &str = "dev.leviath.lev";
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, serde::Serialize, serde::Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum CredentialStoreKind {
#[default]
File,
Keychain,
}
impl CredentialStoreKind {
pub fn is_keychain(self) -> bool {
matches!(self, Self::Keychain)
}
}
pub fn provider_account(provider: &str) -> String {
format!("provider/{provider}")
}
pub fn mcp_account(server: &str) -> String {
format!("mcp/{server}")
}
pub trait CredentialStore: Send + Sync {
fn get(&self, account: &str) -> Result<Option<String>, String>;
fn set(&self, account: &str, secret: &str) -> Result<(), String>;
fn delete(&self, account: &str) -> Result<bool, String>;
fn read_all(&self, accounts: &[String]) -> BTreeMap<String, String> {
accounts
.iter()
.filter_map(|a| match self.get(a) {
Ok(Some(secret)) => Some((a.clone(), secret)),
_ => None,
})
.collect()
}
}
#[derive(Debug, Default)]
pub struct MemoryStore {
entries: std::sync::Mutex<BTreeMap<String, String>>,
}
impl MemoryStore {
pub fn new() -> Self {
Self::default()
}
}
impl CredentialStore for MemoryStore {
fn get(&self, account: &str) -> Result<Option<String>, String> {
Ok(self.lock().get(account).cloned())
}
fn set(&self, account: &str, secret: &str) -> Result<(), String> {
self.lock().insert(account.to_string(), secret.to_string());
Ok(())
}
fn delete(&self, account: &str) -> Result<bool, String> {
Ok(self.lock().remove(account).is_some())
}
}
impl MemoryStore {
fn lock(&self) -> std::sync::MutexGuard<'_, BTreeMap<String, String>> {
self.entries
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn accounts_are_namespaced_by_kind() {
assert_eq!(provider_account("anthropic"), "provider/anthropic");
assert_eq!(mcp_account("github"), "mcp/github");
assert_ne!(provider_account("x"), mcp_account("x"));
}
#[test]
fn file_is_the_default_backend() {
assert_eq!(CredentialStoreKind::default(), CredentialStoreKind::File);
assert!(!CredentialStoreKind::default().is_keychain());
assert!(CredentialStoreKind::Keychain.is_keychain());
}
#[test]
fn the_kind_round_trips_through_config_syntax() {
#[derive(serde::Serialize, serde::Deserialize, PartialEq, Debug)]
struct Section {
credential_store: CredentialStoreKind,
}
let parsed: Section = toml::from_str("credential_store = \"keychain\"\n").unwrap();
assert_eq!(parsed.credential_store, CredentialStoreKind::Keychain);
assert_eq!(
toml::to_string(&Section {
credential_store: CredentialStoreKind::File
})
.unwrap()
.trim(),
"credential_store = \"file\""
);
assert!(toml::from_str::<Section>("credential_store = \"vault\"\n").is_err());
}
#[test]
fn the_memory_store_round_trips_and_reports_absence() {
let store = MemoryStore::new();
assert_eq!(store.get("a").unwrap(), None);
store.set("a", "one").unwrap();
assert_eq!(store.get("a").unwrap().as_deref(), Some("one"));
store.set("a", "two").unwrap();
assert_eq!(store.get("a").unwrap().as_deref(), Some("two"));
assert!(store.delete("a").unwrap(), "it was there");
assert!(!store.delete("a").unwrap(), "and now it is not");
assert!(format!("{:?}", MemoryStore::default()).contains("MemoryStore"));
}
#[test]
fn read_all_returns_only_the_accounts_that_exist() {
let store = MemoryStore::new();
store.set(&provider_account("openai"), "sk-openai").unwrap();
let found = store.read_all(&[
provider_account("openai"),
provider_account("google"),
mcp_account("github"),
]);
assert_eq!(found.len(), 1);
assert_eq!(found[&provider_account("openai")], "sk-openai");
}
#[test]
fn read_all_skips_an_entry_the_store_cannot_return() {
struct Broken;
impl CredentialStore for Broken {
fn get(&self, account: &str) -> Result<Option<String>, String> {
if account == "good" {
Ok(Some("v".into()))
} else {
Err("locked".into())
}
}
fn set(&self, _: &str, _: &str) -> Result<(), String> {
Ok(())
}
fn delete(&self, _: &str) -> Result<bool, String> {
Ok(false)
}
}
let found = Broken.read_all(&["good".into(), "bad".into()]);
assert_eq!(found.len(), 1, "the readable entry still comes back");
assert!(found.contains_key("good"));
assert!(Broken.set("good", "v").is_ok());
assert!(!Broken.delete("good").unwrap());
}
#[test]
fn the_memory_store_survives_a_poisoned_lock() {
let store = std::sync::Arc::new(MemoryStore::new());
store.set("a", "one").unwrap();
let poisoner = std::sync::Arc::clone(&store);
let _ = std::thread::spawn(move || {
let _held = poisoner.lock();
panic!("poison the lock");
})
.join();
assert_eq!(store.get("a").unwrap().as_deref(), Some("one"));
}
}