#![feature(min_specialization)]
use std::{
fmt::Display,
net::{IpAddr, Ipv4Addr},
};
pub trait Redact {
fn redacted(&self) -> String;
}
impl<T: Display> Redact for T {
default fn redacted(&self) -> String {
String::from(DEFAULT_REDACTED_VALUE)
}
}
impl Redact for IpAddr {
fn redacted(&self) -> String {
match self {
IpAddr::V4(x) => x.redacted(),
IpAddr::V6(x) => x.redacted(),
}
}
}
impl Redact for Ipv4Addr {
fn redacted(&self) -> String {
let octets = self.octets();
format!("{}.{}.{}.xxx", octets[0], octets[1], octets[2])
}
}
const DEFAULT_REDACTED_VALUE: &str = "***";
#[cfg(test)]
mod tests {
use std::net::Ipv6Addr;
use super::*;
#[test]
fn can_redact_string() {
let value = String::from("qax qex qqx");
let redacted = value.redacted();
assert_ne!(value, redacted);
assert_eq!(DEFAULT_REDACTED_VALUE, redacted);
}
#[test]
fn can_redact_integer() {
let value = 1234;
let redacted = value.redacted();
assert_ne!(value.to_string(), redacted);
assert_eq!(DEFAULT_REDACTED_VALUE, redacted);
}
#[test]
fn can_redact_float() {
let value = 12.34;
let redacted = value.redacted();
assert_ne!(value.to_string(), redacted);
assert_eq!(DEFAULT_REDACTED_VALUE, redacted);
}
#[test]
fn can_redact_ipv4() {
let value = Ipv4Addr::new(1, 2, 3, 4);
let redacted = value.redacted();
assert_ne!(value.to_string(), redacted);
assert_eq!("1.2.3.xxx", redacted);
}
#[test]
fn can_redact_ipv6() {
let value = Ipv6Addr::new(1, 2, 3, 4, 5, 6, 7, 8);
let redacted = value.redacted();
assert_ne!(value.to_string(), redacted);
assert_eq!(DEFAULT_REDACTED_VALUE, redacted);
}
#[test]
fn can_redact_ip() {
let value = IpAddr::V4(Ipv4Addr::new(1, 2, 3, 4));
let redacted = value.redacted();
assert_ne!(value.to_string(), redacted);
assert_eq!("1.2.3.xxx", redacted);
}
struct CustomSecretStructViaDisplay {
tell_noone: String,
}
impl Display for CustomSecretStructViaDisplay {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(&self.tell_noone)
}
}
#[test]
fn can_redact_custom_struct_via_display() {
let value = CustomSecretStructViaDisplay {
tell_noone: "the secret value".to_owned(),
};
let redacted = value.redacted();
assert_ne!(value.to_string(), redacted);
assert_eq!(DEFAULT_REDACTED_VALUE, redacted);
}
struct CustomSecretStructViaCustomLogic {
first_name: String,
last_name: String,
}
impl Display for CustomSecretStructViaCustomLogic {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_fmt(format_args!("{} {}", &self.first_name, &self.last_name))
}
}
impl Redact for CustomSecretStructViaCustomLogic {
fn redacted(&self) -> String {
format!("{}. {}.", &self.first_name[0..1], &self.last_name[0..1])
}
}
#[test]
fn can_redact_custom_struct_via_custom_logic() {
let value = CustomSecretStructViaCustomLogic {
first_name: "Firstname".to_owned(),
last_name: "Lastname".to_owned(),
};
let redacted = value.redacted();
assert_ne!(value.to_string(), redacted);
assert_eq!("F. L.", redacted);
}
}