use std::fmt::{Debug, Formatter};
#[cfg(feature = "arbitrary")]
use arbitrary::Arbitrary;
#[cfg(feature = "bounded-static")]
use bounded_static::ToStatic;
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
#[cfg_attr(feature = "arbitrary", derive(Arbitrary))]
#[cfg_attr(feature = "bounded-static", derive(ToStatic))]
#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
#[allow(clippy::derived_hash_with_manual_eq)]
#[derive(Clone, Eq, Hash, PartialEq)]
pub struct Secret<T>(T);
impl<T> Secret<T> {
pub fn new(inner: T) -> Self {
Self(inner)
}
pub fn declassify(&self) -> &T {
&self.0
}
}
impl<T> From<T> for Secret<T> {
fn from(value: T) -> Self {
Self::new(value)
}
}
impl<T> Debug for Secret<T>
where
T: Debug,
{
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
#[cfg(not(debug_assertions))]
return write!(f, "/* REDACTED */");
#[cfg(debug_assertions)]
return self.0.fmt(f);
}
}
#[cfg(test)]
mod tests {
#[cfg(feature = "ext_literal")]
use crate::core::Literal;
use crate::{
command::{Command, CommandBody},
core::{AString, Atom, Quoted},
};
#[test]
#[cfg(not(debug_assertions))]
#[allow(clippy::redundant_clone)]
fn test_that_secret_is_redacted() {
use super::Secret;
#[cfg(feature = "ext_sasl_ir")]
use crate::auth::AuthMechanism;
use crate::auth::AuthenticateData;
let secret = Secret("xyz123");
let got = format!("{:?}", secret);
println!("{}", got);
assert!(!got.contains("xyz123"));
println!("-----");
let tests = vec![
CommandBody::login("alice", "xyz123")
.unwrap()
.tag("A")
.unwrap(),
#[cfg(feature = "ext_sasl_ir")]
CommandBody::authenticate_with_ir(AuthMechanism::PLAIN, b"xyz123".as_ref())
.tag("A")
.unwrap(),
];
for test in tests.into_iter() {
let got = format!("{:?}", test);
println!("Debug: {}", got);
assert!(got.contains("/* REDACTED */"));
assert!(!got.contains("xyz123"));
assert!(!got.contains("eHl6MTIz"));
println!();
}
println!("-----");
let tests = [
AuthenticateData(Secret::new(b"xyz123".to_vec())),
AuthenticateData(Secret::from(b"xyz123".to_vec())),
];
for test in tests {
let got = format!("{:?}", test);
println!("Debug: {}", got);
assert!(got.contains("/* REDACTED */"));
assert!(!got.contains("xyz123"));
assert!(!got.contains("eHl6MTIz"));
}
}
#[test]
fn test_that_secret_has_no_side_effects_on_eq() {
assert_ne!(
Command::new(
"A",
CommandBody::login(
AString::from(Atom::try_from("user").unwrap()),
AString::from(Atom::try_from("pass").unwrap()),
)
.unwrap(),
),
Command::new(
"A",
CommandBody::login(
AString::from(Atom::try_from("user").unwrap()),
AString::from(Quoted::try_from("pass").unwrap()),
)
.unwrap(),
)
);
#[cfg(feature = "ext_literal")]
assert_ne!(
Command::new(
"A",
CommandBody::login(
Literal::try_from("").unwrap(),
Literal::try_from("A").unwrap(),
)
.unwrap(),
),
Command::new(
"A",
CommandBody::login(
Literal::try_from("").unwrap(),
Literal::try_from("A").unwrap().into_non_sync(),
)
.unwrap(),
)
);
}
}