Codex Handbook
keyring-store/src/lib.rs 226 lines
use keyring::Entry;use keyring::Error as KeyringError;use std::error::Error;use std::fmt;use std::fmt::Debug;use tracing::trace;#[derive(Debug)]pub enum CredentialStoreError {    Other(KeyringError),}impl CredentialStoreError {    pub fn new(error: KeyringError) -> Self {        Self::Other(error)    }    pub fn message(&self) -> String {        match self {            Self::Other(error) => error.to_string(),        }    }    pub fn into_error(self) -> KeyringError {        match self {            Self::Other(error) => error,        }    }}impl fmt::Display for CredentialStoreError {    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {        match self {            Self::Other(error) => write!(f, "{error}"),        }    }}impl Error for CredentialStoreError {}/// Shared credential store abstraction for keyring-backed implementations.pub trait KeyringStore: Debug + Send + Sync {    fn load(&self, service: &str, account: &str) -> Result<Option<String>, CredentialStoreError>;    fn save(&self, service: &str, account: &str, value: &str) -> Result<(), CredentialStoreError>;    fn delete(&self, service: &str, account: &str) -> Result<bool, CredentialStoreError>;}#[derive(Debug, Clone, Copy)]pub struct DefaultKeyringStore;impl KeyringStore for DefaultKeyringStore {    fn load(&self, service: &str, account: &str) -> Result<Option<String>, CredentialStoreError> {        trace!("keyring.load start, service={service}, account={account}");        let entry = Entry::new(service, account).map_err(CredentialStoreError::new)?;        match entry.get_password() {            Ok(password) => {                trace!("keyring.load success, service={service}, account={account}");                Ok(Some(password))            }            Err(keyring::Error::NoEntry) => {                trace!("keyring.load no entry, service={service}, account={account}");                Ok(None)            }            Err(error) => {                trace!("keyring.load error, service={service}, account={account}, error={error}");                Err(CredentialStoreError::new(error))            }        }    }    fn save(&self, service: &str, account: &str, value: &str) -> Result<(), CredentialStoreError> {        trace!(            "keyring.save start, service={service}, account={account}, value_len={}",            value.len()        );        let entry = Entry::new(service, account).map_err(CredentialStoreError::new)?;        match entry.set_password(value) {            Ok(()) => {                trace!("keyring.save success, service={service}, account={account}");                Ok(())            }            Err(error) => {                trace!("keyring.save error, service={service}, account={account}, error={error}");                Err(CredentialStoreError::new(error))            }        }    }    fn delete(&self, service: &str, account: &str) -> Result<bool, CredentialStoreError> {        trace!("keyring.delete start, service={service}, account={account}");        let entry = Entry::new(service, account).map_err(CredentialStoreError::new)?;        match entry.delete_credential() {            Ok(()) => {                trace!("keyring.delete success, service={service}, account={account}");                Ok(true)            }            Err(keyring::Error::NoEntry) => {                trace!("keyring.delete no entry, service={service}, account={account}");                Ok(false)            }            Err(error) => {                trace!("keyring.delete error, service={service}, account={account}, error={error}");                Err(CredentialStoreError::new(error))            }        }    }}pub mod tests {    use super::CredentialStoreError;    use super::KeyringStore;    use keyring::Error as KeyringError;    use keyring::credential::CredentialApi as _;    use keyring::mock::MockCredential;    use std::collections::HashMap;    use std::sync::Arc;    use std::sync::Mutex;    use std::sync::PoisonError;    #[derive(Default, Clone, Debug)]    pub struct MockKeyringStore {        credentials: Arc<Mutex<HashMap<String, Arc<MockCredential>>>>,    }    impl MockKeyringStore {        pub fn credential(&self, account: &str) -> Arc<MockCredential> {            let mut guard = self                .credentials                .lock()                .unwrap_or_else(PoisonError::into_inner);            guard                .entry(account.to_string())                .or_insert_with(|| Arc::new(MockCredential::default()))                .clone()        }        pub fn saved_value(&self, account: &str) -> Option<String> {            let credential = {                let guard = self                    .credentials                    .lock()                    .unwrap_or_else(PoisonError::into_inner);                guard.get(account).cloned()            }?;            credential.get_password().ok()        }        pub fn set_error(&self, account: &str, error: KeyringError) {            let credential = self.credential(account);            credential.set_error(error);        }        pub fn contains(&self, account: &str) -> bool {            let guard = self                .credentials                .lock()                .unwrap_or_else(PoisonError::into_inner);            guard.contains_key(account)        }    }    impl KeyringStore for MockKeyringStore {        fn load(            &self,            _service: &str,            account: &str,        ) -> Result<Option<String>, CredentialStoreError> {            let credential = {                let guard = self                    .credentials                    .lock()                    .unwrap_or_else(PoisonError::into_inner);                guard.get(account).cloned()            };            let Some(credential) = credential else {                return Ok(None);            };            match credential.get_password() {                Ok(password) => Ok(Some(password)),                Err(KeyringError::NoEntry) => Ok(None),                Err(error) => Err(CredentialStoreError::new(error)),            }        }        fn save(            &self,            _service: &str,            account: &str,            value: &str,        ) -> Result<(), CredentialStoreError> {            let credential = self.credential(account);            credential                .set_password(value)                .map_err(CredentialStoreError::new)        }        fn delete(&self, _service: &str, account: &str) -> Result<bool, CredentialStoreError> {            let credential = {                let guard = self                    .credentials                    .lock()                    .unwrap_or_else(PoisonError::into_inner);                guard.get(account).cloned()            };            let Some(credential) = credential else {                return Ok(false);            };            let removed = match credential.delete_credential() {                Ok(()) => Ok(true),                Err(KeyringError::NoEntry) => Ok(false),                Err(error) => Err(CredentialStoreError::new(error)),            }?;            let mut guard = self                .credentials                .lock()                .unwrap_or_else(PoisonError::into_inner);            guard.remove(account);            Ok(removed)        }    }}