Skip to main content

apollo_redaction/strategy/
fixed.rs

1use crate::RedactionStrategy;
2use std::fmt;
3
4/// Redact all but the first N characters.
5///
6/// Shows the first N characters, then always shows exactly 4 asterisks
7/// to prevent leaking the length of the redacted portion.
8///
9/// If the value has N or fewer characters, outputs just 4 asterisks.
10///
11/// This is a configurable version - for common cases, prefer [`First4Redactor`](super::First4Redactor).
12///
13/// ```rust
14/// use apollo_redaction::Redacted;
15/// use apollo_redaction::strategy::FixedRedactor;
16///
17/// fn fixed<const N: usize>(input: &str) -> String {
18///     Redacted::<_, FixedRedactor<N>>::new(input).to_string()
19/// }
20///
21/// assert_eq!(fixed::<4>("password123"), "pass****");
22/// assert_eq!(fixed::<4>("test1"), "test****");
23/// assert_eq!(fixed::<4>("abc"), "****"); // short input = 4 asterisks
24/// ```
25#[derive(Clone)]
26pub struct FixedRedactor<const N: usize>;
27
28impl<const N: usize> Default for FixedRedactor<N> {
29    fn default() -> Self {
30        Self
31    }
32}
33
34impl<T: AsRef<str>, const N: usize> RedactionStrategy<T> for FixedRedactor<N> {
35    fn display(&self, value: &T, f: &mut fmt::Formatter<'_>) -> fmt::Result {
36        let s = value.as_ref();
37        let len = s.chars().count();
38
39        // Short inputs are completely replaced with 4 asterisks
40        if len <= N {
41            return write!(f, "****");
42        }
43
44        for c in s.chars().take(N) {
45            write!(f, "{c}")?;
46        }
47        write!(f, "****")
48    }
49}
50
51#[cfg(test)]
52mod tests {
53    use super::*;
54    use crate::Redacted;
55
56    fn fixed<const N: usize>(input: &str) -> String {
57        Redacted::<_, FixedRedactor<N>>::new(input).to_string()
58    }
59
60    #[test]
61    fn test_fixed_4() {
62        assert_eq!(fixed::<4>("password123"), "pass****");
63        assert_eq!(fixed::<4>("test1"), "test****");
64        assert_eq!(fixed::<4>("test1234"), "test****");
65        assert_eq!(fixed::<4>("test12345"), "test****");
66    }
67
68    #[test]
69    fn test_fixed_2() {
70        assert_eq!(fixed::<2>("test1"), "te****");
71        assert_eq!(fixed::<2>("testing"), "te****");
72    }
73
74    #[test]
75    fn test_short_input() {
76        // N or fewer chars = just 4 asterisks
77        assert_eq!(fixed::<4>("abc"), "****");
78        assert_eq!(fixed::<4>("ab"), "****");
79        assert_eq!(fixed::<4>("test"), "****");
80        assert_eq!(fixed::<4>(""), "****");
81    }
82
83    #[test]
84    fn test_fixed_0() {
85        // N=0 means show nothing, so any input gets just asterisks
86        assert_eq!(fixed::<0>("testing"), "****");
87        assert_eq!(fixed::<0>("test"), "****");
88        assert_eq!(fixed::<0>(""), "****");
89    }
90
91    #[test]
92    fn test_unicode() {
93        assert_eq!(fixed::<4>("안녕하세요한글"), "안녕하세****");
94    }
95}