mod cached;
mod mapped;
mod providers;
pub mod encodings;
use std::sync::Arc;
pub use cached::CachedSecret;
pub use encodings::SecretMap;
pub use mapped::{FnMap, MappedSecret, TryFnMap};
pub use providers::{EnvVarSecret, ProvidedSecret};
#[cfg(feature = "fs")]
pub use providers::{FileBytes, FileSecret};
use secrecy::ExposeSecret as _;
use serde::{Deserialize, Serialize};
use crate::{
error::Error,
platform::{MaybeSendBoxFuture, MaybeSendSync},
};
#[derive(Debug, Clone)]
pub struct SecretString(secrecy::SecretString);
impl<'de> Deserialize<'de> for SecretString {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
let s = String::deserialize(deserializer)?;
Ok(Self::new(s))
}
}
impl Serialize for SecretString {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
serializer.serialize_str(self.0.expose_secret())
}
}
impl SecretString {
#[must_use]
pub fn new(secret: impl AsRef<str>) -> Self {
SecretString(secret.as_ref().into())
}
#[must_use]
pub fn expose_secret(&self) -> &str {
self.0.expose_secret()
}
}
impl From<String> for SecretString {
fn from(value: String) -> Self {
Self::new(value)
}
}
impl From<&str> for SecretString {
fn from(value: &str) -> Self {
Self::new(value)
}
}
#[derive(Debug, Clone)]
pub struct SecretBytes(secrecy::SecretBox<[u8]>);
impl SecretBytes {
#[must_use]
pub fn new(secret: Vec<u8>) -> Self {
SecretBytes(secrecy::SecretBox::new(secret.into_boxed_slice()))
}
#[must_use]
pub fn expose_secret(&self) -> &[u8] {
self.0.expose_secret()
}
}
#[derive(Debug, Clone)]
pub struct SecretOutput<T: Clone> {
pub value: T,
pub identity: Option<String>,
}
pub trait Secret: MaybeSendSync {
type Output: Clone + MaybeSendSync;
fn get_secret_value(&self)
-> MaybeSendBoxFuture<'_, Result<SecretOutput<Self::Output>, Error>>;
fn mapped<M>(self, map: M) -> MappedSecret<Self, M>
where
Self: Sized,
M: SecretMap<In = Self::Output>,
{
MappedSecret::new(self, map)
}
fn map<F, B>(self, f: F) -> MappedSecret<Self, FnMap<F, Self::Output>>
where
Self: Sized,
F: Fn(Self::Output) -> B + MaybeSendSync,
B: Clone + MaybeSendSync,
{
MappedSecret::new(self, FnMap::new(f))
}
fn try_map<F, B>(self, f: F) -> MappedSecret<Self, TryFnMap<F, Self::Output>>
where
Self: Sized,
F: Fn(Self::Output) -> Result<B, Error> + MaybeSendSync,
B: Clone + MaybeSendSync,
{
MappedSecret::new(self, TryFnMap::new(f))
}
}
impl<T: Secret + ?Sized> Secret for &T {
type Output = T::Output;
fn get_secret_value(
&self,
) -> MaybeSendBoxFuture<'_, Result<SecretOutput<Self::Output>, Error>> {
(**self).get_secret_value()
}
}
impl<T: Secret + ?Sized> Secret for Box<T> {
type Output = T::Output;
fn get_secret_value(
&self,
) -> MaybeSendBoxFuture<'_, Result<SecretOutput<Self::Output>, Error>> {
(**self).get_secret_value()
}
}
impl<T: Secret + ?Sized> Secret for Arc<T> {
type Output = T::Output;
fn get_secret_value(
&self,
) -> MaybeSendBoxFuture<'_, Result<SecretOutput<Self::Output>, Error>> {
(**self).get_secret_value()
}
}
#[derive(Debug, Clone)]
pub struct WithIdentity<S: Secret> {
inner: S,
identity: String,
}
impl<S: Secret> WithIdentity<S> {
pub fn new(inner: S, identity: impl Into<String>) -> Self {
Self {
inner,
identity: identity.into(),
}
}
}
impl<S: Secret> Secret for WithIdentity<S> {
type Output = S::Output;
fn get_secret_value(
&self,
) -> MaybeSendBoxFuture<'_, Result<SecretOutput<Self::Output>, Error>> {
Box::pin(async move {
let mut output = self.inner.get_secret_value().await?;
output.identity = Some(self.identity.clone());
Ok(output)
})
}
}
#[cfg(test)]
mod tests {
use super::*;
#[tokio::test]
async fn test_with_identity() {
let secret = ProvidedSecret::new(SecretString::new("secret"));
let with_id = WithIdentity::new(secret, "my-id");
let output = with_id.get_secret_value().await.unwrap();
assert_eq!(output.value.expose_secret(), "secret");
assert_eq!(output.identity.unwrap(), "my-id");
}
#[tokio::test]
async fn erased_secret_dispatches() {
let secret: Arc<dyn Secret<Output = SecretString>> =
Arc::new(ProvidedSecret::new(SecretString::new("erased")));
let output = secret.get_secret_value().await.unwrap();
assert_eq!(output.value.expose_secret(), "erased");
}
}