spotify_cli/storage/
keyring.rs1use keyring::Entry;
9use thiserror::Error;
10
11use crate::oauth::token::Token;
12
13const SERVICE_NAME: &str = "spotify-cli";
14const TOKEN_KEY: &str = "oauth_token";
15
16#[derive(Debug, Error)]
18pub enum KeyringError {
19 #[error("Keyring error: {0}")]
20 Keyring(#[from] keyring::Error),
21
22 #[error("Failed to serialize token: {0}")]
23 Serialize(#[from] serde_json::Error),
24
25 #[error("Token not found in keyring")]
26 NotFound,
27}
28
29pub struct KeyringStore {
31 entry: Entry,
32}
33
34impl KeyringStore {
35 pub fn new() -> Result<Self, KeyringError> {
37 let entry = Entry::new(SERVICE_NAME, TOKEN_KEY)?;
38 Ok(Self { entry })
39 }
40
41 pub fn save(&self, token: &Token) -> Result<(), KeyringError> {
43 let json = serde_json::to_string(token)?;
44 self.entry.set_password(&json)?;
45 Ok(())
46 }
47
48 pub fn load(&self) -> Result<Token, KeyringError> {
50 let json = self.entry.get_password().map_err(|e| match e {
51 keyring::Error::NoEntry => KeyringError::NotFound,
52 other => KeyringError::Keyring(other),
53 })?;
54 let token = serde_json::from_str(&json)?;
55 Ok(token)
56 }
57
58 pub fn delete(&self) -> Result<(), KeyringError> {
60 match self.entry.delete_credential() {
61 Ok(()) => Ok(()),
62 Err(keyring::Error::NoEntry) => Ok(()), Err(e) => Err(KeyringError::Keyring(e)),
64 }
65 }
66
67 pub fn exists(&self) -> bool {
69 self.entry.get_password().is_ok()
70 }
71}
72
73#[cfg(test)]
74mod tests {
75 use super::*;
76
77 #[test]
78 fn keyring_error_display() {
79 let err = KeyringError::NotFound;
80 let display = format!("{}", err);
81 assert!(display.contains("not found"));
82 }
83
84 #[test]
85 fn keyring_error_serialize() {
86 let json_err = serde_json::from_str::<Token>("invalid").unwrap_err();
87 let err = KeyringError::Serialize(json_err);
88 let display = format!("{}", err);
89 assert!(display.contains("serialize"));
90 }
91}