apollo-redaction 0.2.0

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

/// Redact all but the last 4 characters.
///
/// Useful for tokens, card numbers, or other identifiers where seeing
/// the last few characters helps with identification.
///
/// Always shows at least 4 asterisks to avoid leaking information about
/// short values or value length.
///
/// ```rust
/// use apollo_redaction::Redacted;
/// use apollo_redaction::strategy::Last4Redactor;
///
/// fn last4(input: &str) -> String {
///     Redacted::<_, Last4Redactor>::new(input).to_string()
/// }
///
/// assert_eq!(last4("1234567890"), "******7890");
/// assert_eq!(last4("abc"), "****");
/// assert_eq!(last4("4242424242424242"), "************4242");
/// ```
#[derive(Default, Clone)]
pub struct Last4Redactor;

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

        if len <= 4 {
            write!(f, "****")
        } else {
            // Always show at least 4 asterisks
            let redacted_count = (len - 4).max(4);
            for _ in 0..redacted_count {
                write!(f, "*")?;
            }
            for c in s.chars().skip(len - 4) {
                write!(f, "{c}")?;
            }
            Ok(())
        }
    }
}

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

    fn last4(input: &str) -> String {
        Redacted::<_, Last4Redactor>::new(input).to_string()
    }

    #[test]
    fn test_last4() {
        assert_eq!(last4("1234567890"), "******7890");
        assert_eq!(last4("4242424242424242"), "************4242");
    }

    #[test]
    fn test_short_input() {
        assert_eq!(last4("abc"), "****");
        assert_eq!(last4("abcd"), "****");
        assert_eq!(last4("a"), "****");
        assert_eq!(last4(""), "****");
    }

    #[test]
    fn test_exactly_5_chars() {
        assert_eq!(last4("12345"), "****2345");
    }

    #[test]
    fn test_unicode() {
        assert_eq!(last4("안녕하세요한글"), "****세요한글");
    }
}