#[cfg(feature = "pii-redaction")]
use rust_expect::pii::{
PiiDetector, PiiRedactor, PiiType, RedactionStyle, StreamingRedactor, contains_pii, redact,
redact_asterisks,
};
fn main() {
println!("rust-expect PII Redaction Example");
println!("==================================\n");
#[cfg(not(feature = "pii-redaction"))]
{
println!("This example requires the 'pii-redaction' feature.");
println!("Run with: cargo run --example pii_redaction --features pii-redaction");
}
#[cfg(feature = "pii-redaction")]
run_examples();
}
#[cfg(feature = "pii-redaction")]
fn run_examples() {
println!("1. Quick PII detection...");
let texts = [
"Hello, world!",
"SSN: 123-45-6789",
"Email: user@example.com",
"Card: 4111-1111-1111-1111",
"Phone: (555) 123-4567",
];
for text in texts {
let has_pii = contains_pii(text);
println!(" '{text}' -> PII: {has_pii}");
}
println!("\n2. Quick redaction...");
let sensitive = "Contact: john@example.com, SSN: 123-45-6789";
let redacted = redact(sensitive);
println!(" Original: {sensitive}");
println!(" Redacted: {redacted}");
println!("\n3. Asterisk style redaction...");
let card_info = "Credit card: 4111-1111-1111-1111";
let masked = redact_asterisks(card_info);
println!(" Original: {card_info}");
println!(" Masked: {masked}");
println!("\n4. PII Detector with match details...");
let detector = PiiDetector::new();
let text = "User data: SSN 123-45-6789, email test@test.com";
if detector.contains_pii(text) {
println!(" Found PII in: '{text}'");
println!(" Types detected: SSN, Email");
}
println!("\n5. Configurable PII Redactor...");
let redactor = PiiRedactor::new();
let sample = "Email: admin@company.com, Phone: 555-1234";
println!(" Original: {sample}");
println!(" Default: {}", redactor.redact(sample));
let asterisk_redactor = PiiRedactor::new().style(RedactionStyle::Asterisks);
println!(" Asterisks: {}", asterisk_redactor.redact(sample));
println!("\n6. PII types supported...");
let pii_types = [
(PiiType::Ssn, "Social Security Numbers (XXX-XX-XXXX)"),
(PiiType::CreditCard, "Credit card numbers"),
(PiiType::Email, "Email addresses"),
(PiiType::Phone, "Phone numbers"),
(PiiType::ApiKey, "API keys and tokens"),
];
for (pii_type, description) in pii_types {
println!(" {pii_type:?}: {description}");
}
println!("\n7. Log sanitization example...");
let log_entries = [
"[INFO] User login: user@example.com",
"[DEBUG] Processing payment for card 4111111111111111",
"[WARN] Failed auth for SSN 987-65-4321",
"[ERROR] API key exposed: sk_live_abc123xyz",
];
let redactor = PiiRedactor::new();
println!(" Sanitized logs:");
for entry in log_entries {
let safe = redactor.redact(entry);
println!(" {safe}");
}
println!("\n8. Session output sanitization...");
let terminal_output = r"
Database query results:
- User: john.doe@company.com
- SSN: 555-12-3456
- Card ending: ****1234
- Balance: $1,234.56
";
let safe_output = redactor.redact(terminal_output);
println!(" Sanitized output would hide SSN and email");
println!(" Original has {} characters", terminal_output.len());
println!(" Redacted has {} characters", safe_output.len());
println!("\n9. Streaming PII redaction...");
demonstrate_streaming_redaction();
println!("\n10. Real-time session integration pattern...");
demonstrate_session_integration();
println!("\nPII redaction examples completed successfully!");
}
#[cfg(feature = "pii-redaction")]
fn demonstrate_streaming_redaction() {
println!(" Streaming redaction processes data chunk by chunk,");
println!(" buffering until safe boundaries are found.");
println!();
let redactor = PiiRedactor::new();
let mut streaming = StreamingRedactor::new(redactor).max_buffer(256);
let chunks = [
"Connecting to database...\n",
"User authenticated: admin@", "company.com\n",
"Loading profile for SSN: 123", "-45-6789\n",
"Session complete.\n",
];
println!(" Processing {} chunks:", chunks.len());
let mut combined_output = String::new();
for (i, chunk) in chunks.iter().enumerate() {
let output = streaming.process(chunk);
if output.is_empty() {
println!(" Chunk {}: Buffering (waiting for boundary)", i + 1);
} else {
println!(" Chunk {}: Emitted {} chars", i + 1, output.len());
combined_output.push_str(&output);
}
}
let final_output = streaming.flush();
if !final_output.is_empty() {
println!(" Flush: Emitted {} chars", final_output.len());
combined_output.push_str(&final_output);
}
println!();
println!(" Combined redacted output:");
for line in combined_output.lines() {
println!(" {line}");
}
assert!(!combined_output.contains("admin@company.com"));
assert!(!combined_output.contains("123-45-6789"));
println!();
println!(" Verified: PII redacted even when split across chunks");
}
#[cfg(feature = "pii-redaction")]
fn demonstrate_session_integration() {
println!(" Pattern for integrating with rust-expect sessions:");
println!();
println!(" ```rust");
println!(" let redactor = PiiRedactor::new();");
println!(" let mut streaming = StreamingRedactor::new(redactor);");
println!();
println!(" // In your session read loop:");
println!(" loop {{");
println!(" let data = session.read().await?;");
println!(" let safe = streaming.process(&data);");
println!(" if !safe.is_empty() {{");
println!(" log::info!(\"{{safe}}\"); // Safe to log");
println!(" }}");
println!(" }}");
println!();
println!(" // On session end:");
println!(" let remaining = streaming.flush();");
println!(" log::info!(\"{{remaining}}\");");
println!(" ```");
}