use keyring::Entry;
use thiserror::Error;
use crate::oauth::token::Token;
const SERVICE_NAME: &str = "spotify-cli";
const TOKEN_KEY: &str = "oauth_token";
#[derive(Debug, Error)]
pub enum KeyringError {
#[error("Keyring error: {0}")]
Keyring(#[from] keyring::Error),
#[error("Failed to serialize token: {0}")]
Serialize(#[from] serde_json::Error),
#[error("Token not found in keyring")]
NotFound,
}
pub struct KeyringStore {
entry: Entry,
}
impl KeyringStore {
pub fn new() -> Result<Self, KeyringError> {
let entry = Entry::new(SERVICE_NAME, TOKEN_KEY)?;
Ok(Self { entry })
}
pub fn save(&self, token: &Token) -> Result<(), KeyringError> {
let json = serde_json::to_string(token)?;
self.entry.set_password(&json)?;
Ok(())
}
pub fn load(&self) -> Result<Token, KeyringError> {
let json = self.entry.get_password().map_err(|e| match e {
keyring::Error::NoEntry => KeyringError::NotFound,
other => KeyringError::Keyring(other),
})?;
let token = serde_json::from_str(&json)?;
Ok(token)
}
pub fn delete(&self) -> Result<(), KeyringError> {
match self.entry.delete_credential() {
Ok(()) => Ok(()),
Err(keyring::Error::NoEntry) => Ok(()), Err(e) => Err(KeyringError::Keyring(e)),
}
}
pub fn exists(&self) -> bool {
self.entry.get_password().is_ok()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn keyring_error_display() {
let err = KeyringError::NotFound;
let display = format!("{}", err);
assert!(display.contains("not found"));
}
#[test]
fn keyring_error_serialize() {
let json_err = serde_json::from_str::<Token>("invalid").unwrap_err();
let err = KeyringError::Serialize(json_err);
let display = format!("{}", err);
assert!(display.contains("serialize"));
}
}