use std::fmt;
const REDACTED: &str = "Secret(<redacted>)";
pub struct Secret<T>(T);
impl<T> Secret<T> {
pub fn new(value: T) -> Self {
Self(value)
}
pub fn expose(&self) -> &T {
&self.0
}
pub fn into_inner(self) -> T {
self.0
}
}
impl<T> fmt::Debug for Secret<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(REDACTED)
}
}
impl<T> fmt::Display for Secret<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(REDACTED)
}
}
mod not_serialize_not_clone {
use super::Secret;
const _ASSERT_NOT_SERIALIZE: fn() = || {
trait AmbiguousIfImpl<A> {
fn some_item() {}
}
impl<T: ?Sized> AmbiguousIfImpl<()> for T {}
impl<T: ?Sized + serde::Serialize> AmbiguousIfImpl<u8> for T {}
let _ = <Secret<String> as AmbiguousIfImpl<_>>::some_item;
};
const _ASSERT_NOT_CLONE: fn() = || {
trait AmbiguousIfImpl<A> {
fn some_item() {}
}
impl<T> AmbiguousIfImpl<()> for T {}
impl<T: Clone> AmbiguousIfImpl<u8> for T {}
let _ = <Secret<String> as AmbiguousIfImpl<_>>::some_item;
};
const _ASSERT_NOT_DESERIALIZE: fn() = || {
trait AmbiguousIfImpl<A> {
fn some_item() {}
}
impl<T: ?Sized> AmbiguousIfImpl<()> for T {}
impl<T: serde::de::DeserializeOwned> AmbiguousIfImpl<u8> for T {}
let _ = <Secret<String> as AmbiguousIfImpl<_>>::some_item;
};
}
#[cfg(test)]
mod tests {
use super::*;
fn specimens() -> Vec<String> {
let mut v: Vec<String> = [
"",
"a",
"ab",
"secret",
"ghp_16C7e42F292c6912E7710c838347Ae178B4a",
concat!("xo", "xb", "-2314151234-2321313111-QwErTyUiOpAsDf"),
"eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIxIn0.dBjftJeZ4CVP",
"-----BEGIN RSA PRIVATE KEY-----\nMIIEow==\n",
"パスワード",
"Secret(<redacted>)",
]
.iter()
.map(|s| s.to_string())
.collect();
let mut state: u64 = 0x2545_F491_4F6C_DD1D;
for len in 1..=64 {
let mut s = String::with_capacity(len);
for _ in 0..len {
state = state.wrapping_mul(6364136223846793005).wrapping_add(1);
let byte = ((state >> 33) % 94) as u8 + 33; s.push(byte as char);
}
v.push(s);
}
v
}
#[test]
fn debug_and_display_are_value_independent() {
let baseline_debug = format!("{:?}", Secret::new(String::new()));
let baseline_display = format!("{}", Secret::new(String::new()));
for value in specimens() {
let s = Secret::new(value.clone());
assert_eq!(
format!("{s:?}"),
baseline_debug,
"Debug varied with the value: {value:?}"
);
assert_eq!(
format!("{s}"),
baseline_display,
"Display varied with the value: {value:?}"
);
}
}
#[test]
fn rendering_contains_no_substring_of_the_value() {
const MIN_DISCLOSURE_LEN: usize = 3;
const MIN_CREDENTIAL_LEN: usize = 8;
let baseline = format!("{:?} {}", Secret::new(String::new()), Secret::new(""));
for value in specimens() {
let chars: Vec<char> = value.chars().collect();
if chars.len() < MIN_CREDENTIAL_LEN {
continue;
}
let rendered = format!("{:?} {}", Secret::new(value.clone()), Secret::new(&value));
for start in 0..chars.len() {
for end in (start + MIN_DISCLOSURE_LEN)..=chars.len() {
let candidate: String = chars[start..end].iter().collect();
if baseline.contains(&candidate) {
continue; }
assert!(
!rendered.contains(&candidate),
"rendering {rendered:?} disclosed {candidate:?} from {value:?}"
);
}
}
}
}
#[test]
fn expose_returns_the_raw_value() {
let s = Secret::new("raw-key".to_string());
assert_eq!(s.expose(), "raw-key");
let bytes = Secret::new(vec![1u8, 2, 3]);
assert_eq!(bytes.expose(), &[1u8, 2, 3]);
}
#[test]
fn into_inner_moves_the_value() {
let s = Secret::new("raw-key".to_string());
assert_eq!(s.into_inner(), "raw-key");
}
#[test]
fn compile_time_assertions_hold() {
assert_eq!(REDACTED, "Secret(<redacted>)");
}
}