#![cfg_attr(docs_rs, feature(doc_cfg, doc_auto_cfg))]
#![doc = include_str!("../README.md")]
#[cfg(feature = "derive")]
pub(crate) mod derive;
mod error;
#[cfg(feature = "keyring")]
pub use keyring;
#[cfg(feature = "keyring")]
use keyring::KeyringEntry;
#[cfg(feature = "command")]
pub use process;
#[cfg(feature = "command")]
use process::Command;
use tracing::debug;
#[doc(inline)]
pub use crate::error::{Error, Result};
#[cfg(any(
all(feature = "tokio", feature = "async-std"),
not(any(feature = "tokio", feature = "async-std"))
))]
compile_error!("Either feature `tokio` or `async-std` must be enabled for this crate.");
#[cfg(any(
all(feature = "rustls", feature = "openssl"),
not(any(feature = "rustls", feature = "openssl"))
))]
compile_error!("Either feature `rustls` or `openssl` must be enabled for this crate.");
#[derive(Clone, Debug, Default, Eq, PartialEq)]
#[cfg_attr(
feature = "derive",
derive(serde::Serialize, serde::Deserialize),
serde(rename_all = "kebab-case", from = "derive::Secret")
)]
pub enum Secret {
#[default]
Empty,
Raw(String),
#[cfg(feature = "command")]
#[cfg_attr(feature = "derive", serde(alias = "cmd"))]
Command(Command),
#[cfg(feature = "keyring")]
Keyring(KeyringEntry),
}
impl Secret {
pub fn new() -> Self {
Default::default()
}
pub fn new_raw(raw: impl ToString) -> Self {
Self::Raw(raw.to_string())
}
#[cfg(feature = "command")]
pub fn new_command(cmd: impl ToString) -> Self {
Self::Command(Command::new(cmd))
}
#[cfg(feature = "keyring")]
pub fn new_keyring_entry(entry: KeyringEntry) -> Self {
Self::Keyring(entry)
}
#[cfg(feature = "keyring")]
pub fn try_new_keyring_entry(
entry: impl TryInto<KeyringEntry, Error = keyring::Error>,
) -> Result<Self> {
let entry = entry.try_into()?;
Ok(Self::new_keyring_entry(entry))
}
pub fn is_empty(&self) -> bool {
*self == Self::Empty
}
pub async fn get(&self) -> Result<String> {
match self {
Self::Empty => {
return Err(Error::GetEmptySecretError);
}
Self::Raw(secret) => {
return Ok(secret.clone());
}
#[cfg(feature = "command")]
Self::Command(cmd) => {
let full_secret = cmd
.run()
.await
.map_err(Error::GetSecretFromCommand)?
.to_string_lossy();
let first_line_secret = full_secret
.lines()
.take(1)
.next()
.ok_or(Error::GetSecretFromCommandEmptyOutputError)?
.to_owned();
Ok(first_line_secret)
}
#[cfg(feature = "keyring")]
Self::Keyring(entry) => {
let secret = entry.get_secret().await?;
Ok(secret)
}
}
}
pub async fn find(&self) -> Result<Option<String>> {
match self {
Self::Empty => {
return Ok(None);
}
Self::Raw(secret) => {
return Ok(Some(secret.clone()));
}
#[cfg(feature = "command")]
Self::Command(cmd) => {
let full_secret = cmd
.run()
.await
.map_err(Error::GetSecretFromCommand)?
.to_string_lossy();
let first_line_secret = full_secret.lines().take(1).next().map(ToOwned::to_owned);
Ok(first_line_secret)
}
#[cfg(feature = "keyring")]
Self::Keyring(entry) => {
let secret = entry.find_secret().await?;
Ok(secret)
}
}
}
pub async fn set(&mut self, secret: impl ToString) -> Result<String> {
match self {
Self::Raw(prev) => {
*prev = secret.to_string();
}
#[cfg(feature = "command")]
Self::Command(_) => {
debug!("cannot change value of command-based secret");
}
#[cfg(feature = "keyring")]
Self::Keyring(entry) => entry.set_secret(secret.to_string()).await?,
Self::Empty => {
debug!("cannot change value of empty secret");
}
}
Ok(secret.to_string())
}
#[cfg(feature = "keyring")]
pub async fn set_if_keyring(&self, secret: impl ToString) -> Result<String> {
if let Self::Keyring(entry) = self {
let secret = secret.to_string();
entry.set_secret(&secret).await?;
return Ok(secret);
}
Ok(secret.to_string())
}
pub async fn delete(&mut self) -> Result<()> {
#[cfg(feature = "keyring")]
if let Self::Keyring(entry) = self {
entry.delete_secret().await?;
}
*self = Self::Empty;
Ok(())
}
#[cfg(feature = "keyring")]
pub async fn delete_if_keyring(&self) -> Result<()> {
if let Self::Keyring(entry) = self {
entry.delete_secret().await?;
}
Ok(())
}
pub fn replace_if_empty(&mut self, new: Self) {
if self.is_empty() {
*self = new
}
}
#[cfg(feature = "keyring")]
pub fn replace_with_keyring_if_empty(&mut self, entry: impl ToString) -> Result<()> {
if self.is_empty() {
*self = Self::try_new_keyring_entry(entry.to_string())?;
}
Ok(())
}
}