1#![cfg_attr(docs_rs, feature(doc_cfg, doc_auto_cfg))]
2#![doc = include_str!("../README.md")]
3
4mod error;
5mod service;
6
7use std::sync::Arc;
8
9pub use keyring_native as native;
10use tracing::debug;
11
12#[doc(inline)]
13pub use crate::{
14 error::{Error, Result},
15 service::{get_global_service_name, set_global_service_name},
16};
17
18#[cfg(any(
19 all(feature = "tokio", feature = "async-std"),
20 not(any(feature = "tokio", feature = "async-std"))
21))]
22compile_error!("Either feature `tokio` or `async-std` must be enabled for this crate.");
23
24#[cfg(any(
25 all(feature = "rustls", feature = "openssl"),
26 not(any(feature = "rustls", feature = "openssl"))
27))]
28compile_error!("Either feature `rustls` or `openssl` must be enabled for this crate.");
29
30#[derive(Clone, Debug)]
35#[cfg_attr(
36 feature = "derive",
37 derive(serde::Serialize, serde::Deserialize),
38 serde(try_from = "String", into = "String")
39)]
40pub struct KeyringEntry {
41 pub key: String,
43
44 entry: Arc<native::Entry>,
46}
47
48impl Eq for KeyringEntry {}
49
50impl PartialEq for KeyringEntry {
51 fn eq(&self, other: &Self) -> bool {
54 self.key == other.key
55 }
56}
57
58impl KeyringEntry {
59 pub fn try_new(key: impl ToString) -> Result<Self> {
61 Self::try_from(key.to_string())
62 }
63
64 pub async fn get_secret(&self) -> Result<String> {
66 let key = &self.key;
67 debug!(key, "get keyring secret");
68
69 let entry = self.entry.clone();
70 let secret = spawn_blocking(move || entry.get_password())
71 .await?
72 .map_err(|err| Error::GetSecretError(err, key.clone()))?;
73
74 Ok(secret)
75 }
76
77 pub async fn find_secret(&self) -> Result<Option<String>> {
82 let key = &self.key;
83 debug!(key, "find keyring secret");
84
85 let entry = self.entry.clone();
86 let secret = spawn_blocking(move || entry.get_password()).await?;
87
88 match secret {
89 Err(native::Error::NoEntry) => Ok(None),
90 Err(err) => Err(Error::FindSecretError(err, key.clone())),
91 Ok(secret) => Ok(Some(secret)),
92 }
93 }
94
95 pub async fn set_secret(&self, secret: impl ToString) -> Result<()> {
97 let key = &self.key;
98 debug!(key, "set keyring secret");
99
100 let secret = secret.to_string();
101 let entry = self.entry.clone();
102 spawn_blocking(move || entry.set_password(&secret))
103 .await?
104 .map_err(|err| Error::SetSecretError(err, key.clone()))?;
105
106 Ok(())
107 }
108
109 pub async fn try_with_secret(self, secret: impl ToString) -> Result<Self> {
115 self.set_secret(secret).await?;
116 Ok(self)
117 }
118
119 pub async fn delete_secret(&self) -> Result<()> {
121 let key = &self.key;
122 debug!(key, "delete keyring secret");
123
124 let entry = self.entry.clone();
125 spawn_blocking(move || entry.delete_credential())
126 .await?
127 .map_err(|err| Error::DeleteSecretError(err, key.clone()))?;
128
129 Ok(())
130 }
131}
132
133impl TryFrom<String> for KeyringEntry {
134 type Error = Error;
135
136 fn try_from(key: String) -> Result<Self> {
142 let service = get_global_service_name();
143
144 let entry = match native::Entry::new(service, &key) {
145 Ok(entry) => Ok(Arc::new(entry)),
146 Err(err) => Err(Error::BuildEntryError(err, key.clone())),
147 }?;
148
149 Ok(Self { key, entry })
150 }
151}
152
153impl From<KeyringEntry> for String {
154 fn from(entry: KeyringEntry) -> Self {
156 entry.key
157 }
158}
159
160#[cfg(feature = "async-std")]
162async fn spawn_blocking<T: Send + 'static>(f: impl Fn() -> T + Send + 'static) -> Result<T> {
163 Ok(async_std::task::spawn_blocking(f).await)
164}
165
166#[cfg(feature = "tokio")]
168async fn spawn_blocking<T: Send + 'static>(f: impl Fn() -> T + Send + 'static) -> Result<T> {
169 Ok(tokio::task::spawn_blocking(f).await?)
170}