1#![feature(min_specialization)]
7
8use std::{
9 fmt::Display,
10 net::{IpAddr, Ipv4Addr},
11};
12
13pub trait Redact {
14 fn redacted(&self) -> String;
16}
17
18impl<T: Display> Redact for T {
20 default fn redacted(&self) -> String {
21 String::from(DEFAULT_REDACTED_VALUE)
22 }
23}
24
25impl Redact for IpAddr {
27 fn redacted(&self) -> String {
28 match self {
29 IpAddr::V4(x) => x.redacted(),
30 IpAddr::V6(x) => x.redacted(),
31 }
32 }
33}
34
35impl Redact for Ipv4Addr {
36 fn redacted(&self) -> String {
37 let octets = self.octets();
40 format!("{}.{}.{}.xxx", octets[0], octets[1], octets[2])
41 }
42}
43
44const DEFAULT_REDACTED_VALUE: &str = "***";
45
46#[cfg(test)]
47mod tests {
48 use std::net::Ipv6Addr;
49
50 use super::*;
51
52 #[test]
53 fn can_redact_string() {
54 let value = String::from("qax qex qqx");
55 let redacted = value.redacted();
56
57 assert_ne!(value, redacted);
58 assert_eq!(DEFAULT_REDACTED_VALUE, redacted);
59 }
60
61 #[test]
62 fn can_redact_integer() {
63 let value = 1234;
64 let redacted = value.redacted();
65
66 assert_ne!(value.to_string(), redacted);
67 assert_eq!(DEFAULT_REDACTED_VALUE, redacted);
68 }
69
70 #[test]
71 fn can_redact_float() {
72 let value = 12.34;
73 let redacted = value.redacted();
74
75 assert_ne!(value.to_string(), redacted);
76 assert_eq!(DEFAULT_REDACTED_VALUE, redacted);
77 }
78
79 #[test]
80 fn can_redact_ipv4() {
81 let value = Ipv4Addr::new(1, 2, 3, 4);
82 let redacted = value.redacted();
83
84 assert_ne!(value.to_string(), redacted);
85 assert_eq!("1.2.3.xxx", redacted);
86 }
87
88 #[test]
89 fn can_redact_ipv6() {
90 let value = Ipv6Addr::new(1, 2, 3, 4, 5, 6, 7, 8);
91 let redacted = value.redacted();
92
93 assert_ne!(value.to_string(), redacted);
94 assert_eq!(DEFAULT_REDACTED_VALUE, redacted);
95 }
96
97 #[test]
98 fn can_redact_ip() {
99 let value = IpAddr::V4(Ipv4Addr::new(1, 2, 3, 4));
100 let redacted = value.redacted();
101
102 assert_ne!(value.to_string(), redacted);
103 assert_eq!("1.2.3.xxx", redacted);
104 }
105
106 struct CustomSecretStructViaDisplay {
107 tell_noone: String,
108 }
109
110 impl Display for CustomSecretStructViaDisplay {
111 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
112 f.write_str(&self.tell_noone)
113 }
114 }
115
116 #[test]
117 fn can_redact_custom_struct_via_display() {
118 let value = CustomSecretStructViaDisplay {
119 tell_noone: "the secret value".to_owned(),
120 };
121 let redacted = value.redacted();
122
123 assert_ne!(value.to_string(), redacted);
124 assert_eq!(DEFAULT_REDACTED_VALUE, redacted);
125 }
126
127 struct CustomSecretStructViaCustomLogic {
128 first_name: String,
129 last_name: String,
130 }
131
132 impl Display for CustomSecretStructViaCustomLogic {
133 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
134 f.write_fmt(format_args!("{} {}", &self.first_name, &self.last_name))
135 }
136 }
137
138 impl Redact for CustomSecretStructViaCustomLogic {
139 fn redacted(&self) -> String {
140 format!("{}. {}.", &self.first_name[0..1], &self.last_name[0..1])
141 }
142 }
143
144 #[test]
145 fn can_redact_custom_struct_via_custom_logic() {
146 let value = CustomSecretStructViaCustomLogic {
147 first_name: "Firstname".to_owned(),
148 last_name: "Lastname".to_owned(),
149 };
150 let redacted = value.redacted();
151
152 assert_ne!(value.to_string(), redacted);
153 assert_eq!("F. L.", redacted);
154 }
155}