use core::fmt;
use std::str::FromStr;
use std::sync::Arc;
use serde::{Deserialize, Serialize, Serializer};
use url::Url;
#[cfg(test)]
#[path = "secrets_test.rs"]
mod secrets_test;
const DEFAULT_REDACTION_OUTPUT: &str = "<<redacted>>";
type Redactor<T> = Arc<dyn Fn(&T) -> String + Send + Sync + 'static>;
#[derive(Clone, Deserialize)]
#[serde(transparent, bound(deserialize = "T: Deserialize<'de>"))]
pub struct Sensitive<T> {
inner: T,
#[serde(skip)]
redactor: Option<Redactor<T>>,
}
impl<T> Sensitive<T> {
pub fn new(inner: T) -> Self {
Self { inner, redactor: None }
}
pub fn with_redactor<F>(mut self, redactor: F) -> Self
where
F: Fn(&T) -> String + Send + Sync + 'static,
{
self.redactor = Some(Arc::new(redactor));
self
}
pub fn expose_secret(self) -> T {
self.inner
}
pub fn peek_secret(&self) -> &T {
&self.inner
}
fn redact(&self) -> String {
match &self.redactor {
Some(f) => f(&self.inner),
None => DEFAULT_REDACTION_OUTPUT.to_string(),
}
}
pub fn modify_in_place<F, R>(&mut self, f: F) -> R
where
F: FnOnce(&mut T) -> R,
{
f(&mut self.inner)
}
}
impl<T> FromStr for Sensitive<T>
where
T: FromStr,
T::Err: std::fmt::Display,
{
type Err = T::Err;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let value = T::from_str(s)?;
Ok(Sensitive::new(value))
}
}
impl<T> From<T> for Sensitive<T> {
fn from(value: T) -> Self {
Sensitive::new(value)
}
}
impl<T: PartialEq> PartialEq for Sensitive<T> {
fn eq(&self, other: &Self) -> bool {
self.inner.eq(&other.inner)
}
}
impl<T: Eq> Eq for Sensitive<T> {}
impl<T: PartialOrd> PartialOrd for Sensitive<T> {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
self.inner.partial_cmp(&other.inner)
}
}
impl<T: Ord> Ord for Sensitive<T> {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
self.inner.cmp(&other.inner)
}
}
impl<T: std::hash::Hash> std::hash::Hash for Sensitive<T> {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.inner.hash(state)
}
}
impl<T> fmt::Debug for Sensitive<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(&self.redact())
}
}
impl<T> fmt::Display for Sensitive<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(&self.redact())
}
}
impl<T> Serialize for Sensitive<T> {
fn serialize<S: Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
s.serialize_str(&self.redact())
}
}
impl Sensitive<Url> {
pub fn append_route(&mut self, suffix: &str) {
self.modify_in_place(|url: &mut Url| {
*url = url.join(suffix).expect("Failed to construct URL")
});
}
}