wowlab_types/types/
sensitive.rs1#[derive(Clone, Default, Eq, Hash, PartialEq)]
11pub struct Sensitive<T>(T);
12
13impl<T> Sensitive<T> {
14 #[must_use]
16 pub fn new(value: T) -> Self {
17 Self(value)
18 }
19
20 #[must_use]
22 pub fn expose(&self) -> &T {
23 &self.0
24 }
25
26 #[must_use]
28 pub fn into_inner(self) -> T {
29 self.0
30 }
31}
32
33impl<T> std::fmt::Debug for Sensitive<T> {
34 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
35 f.debug_tuple("Sensitive").field(&"[REDACTED]").finish()
36 }
37}
38
39impl<T> std::fmt::Display for Sensitive<T> {
40 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
41 f.write_str("[REDACTED]")
42 }
43}
44
45impl<T> From<T> for Sensitive<T> {
46 fn from(value: T) -> Self {
47 Self::new(value)
48 }
49}
50
51#[cfg(test)]
52mod tests {
53 use googletest::prelude::*;
54
55 use super::*;
56
57 #[gtest]
58 fn debug_redacts_string_inner() -> Result<()> {
59 let secret = Sensitive::new("secret".to_string());
60 let rendered = format!("{secret:?}");
61
62 verify_that!(rendered, eq("Sensitive(\"[REDACTED]\")"))?;
63
64 verify_that!(rendered, not(contains_substring("secret")))
65 }
66
67 #[gtest]
68 fn display_redacts_string_inner() -> Result<()> {
69 let secret = Sensitive::new("secret".to_string());
70 let rendered = format!("{secret}");
71
72 verify_that!(rendered, eq("[REDACTED]"))?;
73
74 verify_that!(rendered, not(contains_substring("secret")))
75 }
76
77 #[gtest]
78 fn clone_and_equality_do_not_require_exposure() -> Result<()> {
79 let token = Sensitive::new("opaque-token".to_string());
80 let cloned = token.clone();
81
82 verify_that!(&cloned, eq(&token))?;
83 verify_that!(
84 format!("{cloned:?}"),
85 not(contains_substring("opaque-token"))
86 )?;
87
88 verify_that!(format!("{cloned}"), not(contains_substring("opaque-token")))
89 }
90
91 #[gtest]
92 fn empty_and_non_jwt_strings_remain_opaque_values() -> Result<()> {
93 let empty = Sensitive::new(String::new());
94 let arbitrary = Sensitive::new("not-a-jwt\nwith-control".to_string());
95
96 verify_that!(empty.expose(), eq(""))?;
97 verify_that!(arbitrary.expose(), eq("not-a-jwt\nwith-control"))?;
98 verify_that!(
99 format!("{arbitrary:?}"),
100 not(contains_substring("not-a-jwt"))
101 )?;
102
103 verify_that!(format!("{arbitrary}"), eq("[REDACTED]"))
104 }
105
106 #[gtest]
107 fn debug_redacts_non_string_inner() -> Result<()> {
108 let secret = Sensitive::new(1234u32);
109 let rendered = format!("{secret:?}");
110
111 verify_that!(rendered, eq("Sensitive(\"[REDACTED]\")"))?;
112
113 verify_that!(rendered, not(contains_substring("1234")))
114 }
115
116 #[gtest]
117 fn expose_returns_inner_ref() -> Result<()> {
118 verify_that!(Sensitive::new(42).expose(), eq(&42))
119 }
120
121 #[gtest]
122 fn into_inner_returns_owned() -> Result<()> {
123 verify_that!(Sensitive::new(42).into_inner(), eq(42))
124 }
125
126 #[gtest]
127 fn from_wraps_value() -> Result<()> {
128 verify_that!(Sensitive::from("x".to_string()).expose(), eq("x"))
129 }
130}