use std::fmt;
use serde::{Deserialize, Serialize};
#[derive(Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(transparent)]
pub struct Secret(String);
impl Secret {
pub fn new(value: impl Into<String>) -> Self {
Self(value.into())
}
pub fn expose(&self) -> &str {
&self.0
}
pub fn is_empty(&self) -> bool {
self.0.is_empty()
}
}
impl fmt::Debug for Secret {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("Secret([redacted])")
}
}
impl fmt::Display for Secret {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("[redacted]")
}
}
impl From<String> for Secret {
fn from(value: String) -> Self {
Self(value)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn a_secret_never_prints_itself() {
let secret = Secret::new("tskey-api-example1CNTRL-secretpart");
assert!(!format!("{secret:?}").contains("secretpart"));
assert!(!format!("{secret}").contains("secretpart"));
assert!(!format!("{:?}", Some(secret.clone())).contains("secretpart"));
assert_eq!(secret.expose(), "tskey-api-example1CNTRL-secretpart");
}
#[test]
fn a_secret_is_the_plain_string_to_serde() {
let json = r#""tskey-api-example1CNTRL-secretpart""#;
let secret: Secret = serde_json::from_str(json).expect("a string is a secret");
assert_eq!(secret.expose(), "tskey-api-example1CNTRL-secretpart");
assert_eq!(serde_json::to_string(&secret).expect("it serialises"), json);
}
}