secret/
lib.rs

1#![cfg_attr(docs_rs, feature(doc_cfg, doc_auto_cfg))]
2#![doc = include_str!("../README.md")]
3
4#[cfg(feature = "derive")]
5pub(crate) mod derive;
6mod error;
7
8#[cfg(feature = "keyring")]
9pub use keyring;
10#[cfg(feature = "keyring")]
11use keyring::KeyringEntry;
12#[cfg(feature = "command")]
13pub use process;
14#[cfg(feature = "command")]
15use process::Command;
16use tracing::debug;
17
18#[doc(inline)]
19pub use crate::error::{Error, Result};
20
21#[cfg(any(
22    all(feature = "tokio", feature = "async-std"),
23    not(any(feature = "tokio", feature = "async-std"))
24))]
25compile_error!("Either feature `tokio` or `async-std` must be enabled for this crate.");
26
27#[cfg(any(
28    all(feature = "rustls", feature = "openssl"),
29    not(any(feature = "rustls", feature = "openssl"))
30))]
31compile_error!("Either feature `rustls` or `openssl` must be enabled for this crate.");
32
33/// The secret.
34///
35/// A secret can be retrieved either from a raw string, from a shell
36/// command or from a keyring entry.
37#[derive(Clone, Debug, Default, Eq, PartialEq)]
38#[cfg_attr(
39    feature = "derive",
40    derive(serde::Serialize, serde::Deserialize),
41    serde(rename_all = "kebab-case", from = "derive::Secret")
42)]
43pub enum Secret {
44    /// The secret is empty.
45    #[default]
46    Empty,
47
48    /// The secret is contained in a raw string.
49    ///
50    /// This variant is not safe to use and therefore not
51    /// recommended. Yet it works well for testing purpose.
52    Raw(String),
53
54    /// The secret is exposed by the given shell command.
55    ///
56    /// This variant takes the secret from the first line returned by
57    /// the given shell command.
58    ///
59    /// See [process-lib](https://crates.io/crates/process-lib).
60    #[cfg(feature = "command")]
61    #[cfg_attr(feature = "derive", serde(alias = "cmd"))]
62    Command(Command),
63
64    /// The secret is contained in the user's global keyring at the
65    /// given entry.
66    ///
67    /// See [keyring-lib](https://crates.io/crates/keyring-lib).
68    #[cfg(feature = "keyring")]
69    Keyring(KeyringEntry),
70}
71
72impl Secret {
73    /// Creates a new empty secret.
74    pub fn new() -> Self {
75        Default::default()
76    }
77
78    /// Creates a new secret from the given raw string.
79    pub fn new_raw(raw: impl ToString) -> Self {
80        Self::Raw(raw.to_string())
81    }
82
83    /// Creates a new secret from the given shell command.
84    #[cfg(feature = "command")]
85    pub fn new_command(cmd: impl ToString) -> Self {
86        Self::Command(Command::new(cmd))
87    }
88
89    /// Creates a new secret from the given keyring entry.
90    #[cfg(feature = "keyring")]
91    pub fn new_keyring_entry(entry: KeyringEntry) -> Self {
92        Self::Keyring(entry)
93    }
94
95    /// Tries to create a new secret from the given entry.
96    #[cfg(feature = "keyring")]
97    pub fn try_new_keyring_entry(
98        entry: impl TryInto<KeyringEntry, Error = keyring::Error>,
99    ) -> Result<Self> {
100        let entry = entry.try_into()?;
101        Ok(Self::new_keyring_entry(entry))
102    }
103
104    /// Returns `true` if the secret is empty.
105    pub fn is_empty(&self) -> bool {
106        *self == Self::Empty
107    }
108
109    /// Gets the secret value.
110    ///
111    /// The command-based secret execute its shell command and returns
112    /// the output, and the keyring-based secret retrieves the value
113    /// from the global keyring using its inner key.
114    pub async fn get(&self) -> Result<String> {
115        match self {
116            Self::Empty => {
117                return Err(Error::GetEmptySecretError);
118            }
119            Self::Raw(secret) => {
120                return Ok(secret.clone());
121            }
122            #[cfg(feature = "command")]
123            Self::Command(cmd) => {
124                let full_secret = cmd
125                    .run()
126                    .await
127                    .map_err(Error::GetSecretFromCommand)?
128                    .to_string_lossy();
129
130                let first_line_secret = full_secret
131                    .lines()
132                    .take(1)
133                    .next()
134                    .ok_or(Error::GetSecretFromCommandEmptyOutputError)?
135                    .to_owned();
136
137                Ok(first_line_secret)
138            }
139            #[cfg(feature = "keyring")]
140            Self::Keyring(entry) => {
141                let secret = entry.get_secret().await?;
142                Ok(secret)
143            }
144        }
145    }
146
147    /// Finds the secret value.
148    ///
149    /// Like [`Secret::get`], but returns [`None`] if the secret value
150    /// is not found or empty.
151    pub async fn find(&self) -> Result<Option<String>> {
152        match self {
153            Self::Empty => {
154                return Ok(None);
155            }
156            Self::Raw(secret) => {
157                return Ok(Some(secret.clone()));
158            }
159            #[cfg(feature = "command")]
160            Self::Command(cmd) => {
161                let full_secret = cmd
162                    .run()
163                    .await
164                    .map_err(Error::GetSecretFromCommand)?
165                    .to_string_lossy();
166
167                let first_line_secret = full_secret.lines().take(1).next().map(ToOwned::to_owned);
168
169                Ok(first_line_secret)
170            }
171            #[cfg(feature = "keyring")]
172            Self::Keyring(entry) => {
173                let secret = entry.find_secret().await?;
174                Ok(secret)
175            }
176        }
177    }
178
179    /// Updates the secret value.
180    ///
181    /// This is only applicable for raw secrets and keyring-based
182    /// secrets. A secret value cannot be changed for command-base
183    /// secrets, since the value is the output of the command.
184    pub async fn set(&mut self, secret: impl ToString) -> Result<String> {
185        match self {
186            Self::Raw(prev) => {
187                *prev = secret.to_string();
188            }
189            #[cfg(feature = "command")]
190            Self::Command(_) => {
191                debug!("cannot change value of command-based secret");
192            }
193            #[cfg(feature = "keyring")]
194            Self::Keyring(entry) => entry.set_secret(secret.to_string()).await?,
195            Self::Empty => {
196                debug!("cannot change value of empty secret");
197            }
198        }
199
200        Ok(secret.to_string())
201    }
202
203    /// Updates the secret value of the keyring-based secret only.
204    ///
205    /// This function as no effect on other secret variants.
206    #[cfg(feature = "keyring")]
207    pub async fn set_if_keyring(&self, secret: impl ToString) -> Result<String> {
208        if let Self::Keyring(entry) = self {
209            let secret = secret.to_string();
210            entry.set_secret(&secret).await?;
211            return Ok(secret);
212        }
213
214        Ok(secret.to_string())
215    }
216
217    /// Deletes the secret value and make the current secret empty.
218    pub async fn delete(&mut self) -> Result<()> {
219        #[cfg(feature = "keyring")]
220        if let Self::Keyring(entry) = self {
221            entry.delete_secret().await?;
222        }
223
224        *self = Self::Empty;
225
226        Ok(())
227    }
228
229    /// Deletes the secret value of keyring-based secrets only.
230    ///
231    /// This function has no effect on other variants.
232    #[cfg(feature = "keyring")]
233    pub async fn delete_if_keyring(&self) -> Result<()> {
234        if let Self::Keyring(entry) = self {
235            entry.delete_secret().await?;
236        }
237
238        Ok(())
239    }
240
241    /// Replaces empty secret variant with the given one.
242    ///
243    /// This function has no effect on other variants.
244    pub fn replace_if_empty(&mut self, new: Self) {
245        if self.is_empty() {
246            *self = new
247        }
248    }
249
250    /// Replaces empty secret variant with a keyring one.
251    ///
252    /// This function has no effect on other variants.
253    #[cfg(feature = "keyring")]
254    pub fn replace_with_keyring_if_empty(&mut self, entry: impl ToString) -> Result<()> {
255        if self.is_empty() {
256            *self = Self::try_new_keyring_entry(entry.to_string())?;
257        }
258
259        Ok(())
260    }
261}