Skip to main content

apollo_redaction/strategy/
last4.rs

1use crate::RedactionStrategy;
2use std::fmt;
3
4/// Redact all but the last 4 characters.
5///
6/// Useful for tokens, card numbers, or other identifiers where seeing
7/// the last few characters helps with identification.
8///
9/// Always shows at least 4 asterisks to avoid leaking information about
10/// short values or value length.
11///
12/// ```rust
13/// use apollo_redaction::Redacted;
14/// use apollo_redaction::strategy::Last4Redactor;
15///
16/// fn last4(input: &str) -> String {
17///     Redacted::<_, Last4Redactor>::new(input).to_string()
18/// }
19///
20/// assert_eq!(last4("1234567890"), "******7890");
21/// assert_eq!(last4("abc"), "****");
22/// assert_eq!(last4("4242424242424242"), "************4242");
23/// ```
24#[derive(Default, Clone)]
25pub struct Last4Redactor;
26
27impl<T: AsRef<str>> RedactionStrategy<T> for Last4Redactor {
28    fn display(&self, value: &T, f: &mut fmt::Formatter<'_>) -> fmt::Result {
29        let s = value.as_ref();
30        let len = s.chars().count();
31
32        if len <= 4 {
33            write!(f, "****")
34        } else {
35            // Always show at least 4 asterisks
36            let redacted_count = (len - 4).max(4);
37            for _ in 0..redacted_count {
38                write!(f, "*")?;
39            }
40            for c in s.chars().skip(len - 4) {
41                write!(f, "{c}")?;
42            }
43            Ok(())
44        }
45    }
46}
47
48#[cfg(test)]
49mod tests {
50    use super::*;
51    use crate::Redacted;
52
53    fn last4(input: &str) -> String {
54        Redacted::<_, Last4Redactor>::new(input).to_string()
55    }
56
57    #[test]
58    fn test_last4() {
59        assert_eq!(last4("1234567890"), "******7890");
60        assert_eq!(last4("4242424242424242"), "************4242");
61    }
62
63    #[test]
64    fn test_short_input() {
65        assert_eq!(last4("abc"), "****");
66        assert_eq!(last4("abcd"), "****");
67        assert_eq!(last4("a"), "****");
68        assert_eq!(last4(""), "****");
69    }
70
71    #[test]
72    fn test_exactly_5_chars() {
73        assert_eq!(last4("12345"), "****2345");
74    }
75
76    #[test]
77    fn test_unicode() {
78        assert_eq!(last4("안녕하세요한글"), "****세요한글");
79    }
80}