use std::fmt;
use serde::{Deserialize, Serialize};
pub(crate) const REDACTED: &str = "<redacted>";
#[derive(Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
#[serde(transparent)]
pub struct Secret<T = String>(T);
impl<T> Secret<T> {
pub const fn new(value: T) -> Self {
Self(value)
}
pub const fn expose(&self) -> &T {
&self.0
}
pub fn into_inner(self) -> T {
self.0
}
}
impl<T> From<T> for Secret<T> {
fn from(value: T) -> Self {
Self(value)
}
}
impl<T> fmt::Debug for Secret<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(REDACTED)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn debug_is_always_redacted() {
let secret = Secret::new("lip_supersecret".to_owned());
assert_eq!(format!("{secret:?}"), REDACTED);
assert!(!format!("{secret:?}").contains("lip_supersecret"));
}
#[test]
fn expose_returns_the_inner_value() {
let secret = Secret::new("abc");
assert_eq!(secret.expose(), &"abc");
assert_eq!(Secret::from("abc").into_inner(), "abc");
}
#[test]
fn serializes_transparently_as_the_inner_value() {
let secret = Secret::new("abc".to_owned());
assert_eq!(serde_json::to_string(&secret).unwrap(), "\"abc\"");
let back: Secret<String> = serde_json::from_str("\"abc\"").unwrap();
assert_eq!(back.expose(), "abc");
}
}