use std::sync::OnceLock;
use super::{KeyStore, KeyStoreError};
const SERVICE: &str = "trusty-tools";
const PROBE_ACCOUNT: &str = "__trusty_probe__";
static PROBE_RESULT: OnceLock<bool> = OnceLock::new();
#[derive(Debug)]
pub struct KeyringStore;
impl KeyringStore {
pub fn new() -> Self {
Self
}
pub fn probe_available(&self) -> bool {
*PROBE_RESULT.get_or_init(Self::probe)
}
fn probe() -> bool {
match keyring::Entry::new(SERVICE, PROBE_ACCOUNT) {
Ok(entry) => matches!(entry.get_password(), Ok(_) | Err(keyring::Error::NoEntry)),
Err(_) => false,
}
}
}
impl Default for KeyringStore {
fn default() -> Self {
Self::new()
}
}
impl KeyStore for KeyringStore {
fn get(&self, provider: &str) -> Option<String> {
if !self.probe_available() {
return None;
}
keyring::Entry::new(SERVICE, provider)
.ok()?
.get_password()
.ok()
}
fn set(&self, provider: &str, value: &str) -> Result<(), KeyStoreError> {
if !self.probe_available() {
return Err(KeyStoreError::Keyring(
"keychain backend unavailable".to_string(),
));
}
let entry = keyring::Entry::new(SERVICE, provider)
.map_err(|e| KeyStoreError::Keyring(e.to_string()))?;
entry
.set_password(value)
.map_err(|e| KeyStoreError::Keyring(e.to_string()))
}
fn unset(&self, provider: &str) -> Result<(), KeyStoreError> {
if !self.probe_available() {
return Err(KeyStoreError::Keyring(
"keychain backend unavailable".to_string(),
));
}
let entry = keyring::Entry::new(SERVICE, provider)
.map_err(|e| KeyStoreError::Keyring(e.to_string()))?;
match entry.delete_credential() {
Ok(()) | Err(keyring::Error::NoEntry) => Ok(()),
Err(e) => Err(KeyStoreError::Keyring(e.to_string())),
}
}
fn list(&self) -> Vec<String> {
Vec::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn probe_does_not_panic() {
let store = KeyringStore::new();
let _ = store.probe_available();
}
#[test]
fn probe_is_cached_across_instances() {
let first = KeyringStore::new().probe_available();
let second = KeyringStore::new().probe_available();
assert_eq!(first, second);
}
#[test]
fn list_is_always_empty() {
let store = KeyringStore::new();
assert_eq!(store.list(), Vec::<String>::new());
}
#[test]
fn debug_output_never_contains_values() {
let dbg = format!("{:?}", KeyringStore::new());
assert_eq!(dbg, "KeyringStore");
}
}