1use std::fmt;
4
5#[derive(Clone, Eq, PartialEq)]
7pub struct ProtectedString {
8 value: String,
9 sensitive: bool,
10}
11
12impl ProtectedString {
13 #[must_use]
15 pub fn plain(value: impl Into<String>) -> Self {
16 Self {
17 value: value.into(),
18 sensitive: false,
19 }
20 }
21
22 #[must_use]
24 pub fn sensitive(value: impl Into<String>) -> Self {
25 Self {
26 value: value.into(),
27 sensitive: true,
28 }
29 }
30
31 #[must_use]
33 pub const fn is_sensitive(&self) -> bool {
34 self.sensitive
35 }
36
37 #[must_use]
39 pub fn expose(&self) -> &str {
40 &self.value
41 }
42
43 #[must_use]
45 pub fn redacted(&self) -> &str {
46 if self.sensitive { "[REDACTED]" } else { &self.value }
47 }
48}
49
50impl fmt::Debug for ProtectedString {
51 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
52 formatter
53 .debug_tuple("ProtectedString")
54 .field(&self.redacted())
55 .finish()
56 }
57}
58
59#[cfg(test)]
60mod tests {
61 use super::ProtectedString;
62
63 #[test]
64 fn sensitive_debug_output_is_redacted() {
65 let value = ProtectedString::sensitive("never-print-this");
66 let debug = format!("{value:?}");
67 assert!(!debug.contains("never-print-this"));
68 assert!(debug.contains("[REDACTED]"));
69 assert_eq!(value.expose(), "never-print-this");
70 }
71}