apollo-redaction 0.1.0

Redaction utility for sensitive data in error messages, debug logs, etc.
Documentation
use crate::RedactionStrategy;
use std::fmt;

/// Redact all but the first N characters.
///
/// Shows the first N characters, then always shows exactly 4 asterisks
/// to prevent leaking the length of the redacted portion.
///
/// If the value has N or fewer characters, outputs just 4 asterisks.
///
/// This is a configurable version - for common cases, prefer [`First4Redactor`](super::First4Redactor).
///
/// ```rust
/// use apollo_redaction::Redacted;
/// use apollo_redaction::strategy::FixedRedactor;
///
/// fn fixed<const N: usize>(input: &str) -> String {
///     Redacted::<_, FixedRedactor<N>>::new(input).to_string()
/// }
///
/// assert_eq!(fixed::<4>("password123"), "pass****");
/// assert_eq!(fixed::<4>("test1"), "test****");
/// assert_eq!(fixed::<4>("abc"), "****"); // short input = 4 asterisks
/// ```
#[derive(Clone)]
pub struct FixedRedactor<const N: usize>;

impl<const N: usize> Default for FixedRedactor<N> {
    fn default() -> Self {
        Self
    }
}

impl<T: AsRef<str>, const N: usize> RedactionStrategy<T> for FixedRedactor<N> {
    fn display(&self, value: &T, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        let s = value.as_ref();
        let len = s.chars().count();

        // Short inputs are completely replaced with 4 asterisks
        if len <= N {
            return write!(f, "****");
        }

        for c in s.chars().take(N) {
            write!(f, "{c}")?;
        }
        write!(f, "****")
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::Redacted;

    fn fixed<const N: usize>(input: &str) -> String {
        Redacted::<_, FixedRedactor<N>>::new(input).to_string()
    }

    #[test]
    fn test_fixed_4() {
        assert_eq!(fixed::<4>("password123"), "pass****");
        assert_eq!(fixed::<4>("test1"), "test****");
        assert_eq!(fixed::<4>("test1234"), "test****");
        assert_eq!(fixed::<4>("test12345"), "test****");
    }

    #[test]
    fn test_fixed_2() {
        assert_eq!(fixed::<2>("test1"), "te****");
        assert_eq!(fixed::<2>("testing"), "te****");
    }

    #[test]
    fn test_short_input() {
        // N or fewer chars = just 4 asterisks
        assert_eq!(fixed::<4>("abc"), "****");
        assert_eq!(fixed::<4>("ab"), "****");
        assert_eq!(fixed::<4>("test"), "****");
        assert_eq!(fixed::<4>(""), "****");
    }

    #[test]
    fn test_fixed_0() {
        // N=0 means show nothing, so any input gets just asterisks
        assert_eq!(fixed::<0>("testing"), "****");
        assert_eq!(fixed::<0>("test"), "****");
        assert_eq!(fixed::<0>(""), "****");
    }

    #[test]
    fn test_unicode() {
        assert_eq!(fixed::<4>("안녕하세요한글"), "안녕하세****");
    }
}