1use std::fmt::{Debug, Formatter};
7
8#[cfg(feature = "arbitrary")]
9use arbitrary::Arbitrary;
10#[cfg(feature = "bounded-static")]
11use bounded_static::ToStatic;
12#[cfg(feature = "serde")]
13use serde::{Deserialize, Serialize};
14
15#[cfg_attr(feature = "arbitrary", derive(Arbitrary))]
17#[cfg_attr(feature = "bounded-static", derive(ToStatic))]
18#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
19#[allow(clippy::derived_hash_with_manual_eq)]
22#[derive(Clone, Eq, Hash, PartialEq)]
23pub struct Secret<T>(T);
24
25impl<T> Secret<T> {
26 pub fn new(inner: T) -> Self {
28 Self(inner)
29 }
30
31 pub fn declassify(&self) -> &T {
33 &self.0
34 }
35}
36
37impl<T> From<T> for Secret<T> {
38 fn from(value: T) -> Self {
39 Self::new(value)
40 }
41}
42
43impl<T> Debug for Secret<T>
44where
45 T: Debug,
46{
47 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
48 #[cfg(not(debug_assertions))]
49 return write!(f, "/* REDACTED */");
50 #[cfg(debug_assertions)]
51 return self.0.fmt(f);
52 }
53}
54
55#[cfg(test)]
56mod tests {
57 use crate::{
58 command::{Command, CommandBody},
59 core::{AString, Atom, Literal, Quoted},
60 };
61
62 #[test]
63 #[cfg(not(debug_assertions))]
64 #[allow(clippy::redundant_clone)]
65 fn test_that_secret_is_redacted() {
66 use super::Secret;
67 use crate::auth::{AuthMechanism, AuthenticateData};
68
69 let secret = Secret("xyz123");
70 let got = format!("{:?}", secret);
71 println!("{}", got);
72 assert!(!got.contains("xyz123"));
73
74 println!("-----");
75
76 let tests = vec![
77 CommandBody::login("alice", "xyz123")
78 .unwrap()
79 .tag("A")
80 .unwrap(),
81 CommandBody::authenticate_with_ir(AuthMechanism::Plain, b"xyz123".as_ref())
82 .tag("A")
83 .unwrap(),
84 ];
85
86 for test in tests.into_iter() {
87 let got = format!("{:?}", test);
88 println!("Debug: {}", got);
89 assert!(got.contains("/* REDACTED */"));
90 assert!(!got.contains("xyz123"));
91 assert!(!got.contains("eHl6MTIz"));
92
93 println!();
94 }
95
96 println!("-----");
97
98 let tests = [
99 AuthenticateData(Secret::new(b"xyz123".to_vec())),
100 AuthenticateData(Secret::from(b"xyz123".to_vec())),
101 ];
102
103 for test in tests {
104 let got = format!("{:?}", test);
105 println!("Debug: {}", got);
106 assert!(got.contains("/* REDACTED */"));
107 assert!(!got.contains("xyz123"));
108 assert!(!got.contains("eHl6MTIz"));
109 }
110 }
111
112 #[test]
113 fn test_that_secret_has_no_side_effects_on_eq() {
114 assert_ne!(
115 Command::new(
116 "A",
117 CommandBody::login(
118 AString::from(Atom::try_from("user").unwrap()),
119 AString::from(Atom::try_from("pass").unwrap()),
120 )
121 .unwrap(),
122 ),
123 Command::new(
124 "A",
125 CommandBody::login(
126 AString::from(Atom::try_from("user").unwrap()),
127 AString::from(Quoted::try_from("pass").unwrap()),
128 )
129 .unwrap(),
130 )
131 );
132
133 assert_ne!(
134 Command::new(
135 "A",
136 CommandBody::login(
137 Literal::try_from("").unwrap(),
138 Literal::try_from("A").unwrap(),
139 )
140 .unwrap(),
141 ),
142 Command::new(
143 "A",
144 CommandBody::login(
145 Literal::try_from("").unwrap(),
146 Literal::try_from("A").unwrap().into_non_sync(),
147 )
148 .unwrap(),
149 )
150 );
151 }
152}