#[path = "support.rs"]
mod support;
use noyalib::{from_str, to_string, Value};
use serde::{Deserialize, Serialize};
use std::fmt;
#[derive(Clone, Serialize, Deserialize)]
#[serde(transparent)]
struct Secret<T>(T);
impl<T> fmt::Debug for Secret<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "***")
}
}
impl<T> fmt::Display for Secret<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "***")
}
}
#[derive(Debug, Serialize, Deserialize)]
struct DatabaseConfig {
host: String,
port: u16,
username: String,
password: Secret<String>,
api_key: Secret<String>,
}
fn redact(value: &mut Value, sensitive_keys: &[&str]) {
if let Some(map) = value.as_mapping_mut() {
for (key, val) in map.iter_mut() {
if sensitive_keys.iter().any(|&s| key.contains(s)) {
*val = Value::String("***REDACTED***".to_string());
} else {
redact(val, sensitive_keys);
}
}
}
if let Some(seq) = value.as_sequence_mut() {
for item in seq.iter_mut() {
redact(item, sensitive_keys);
}
}
}
fn main() {
support::header("noyalib -- mask");
let config = DatabaseConfig {
host: "db.production.internal".to_string(),
port: 5432,
username: "admin".to_string(),
password: Secret("super-secret-password-123".to_string()),
api_key: Secret("sk_live_abc123def456".to_string()),
};
support::task_with_output("Secret<T> redacts in Debug/Display", || {
vec![
format!("Debug: {:?}", config.password),
format!("Display: {}", config.api_key),
"Secrets never appear in logs or error messages".to_string(),
]
});
support::task_with_output("Secret<T> serializes real value to YAML", || {
let yaml = to_string(&config).unwrap();
let has_password = yaml.contains("super-secret");
let has_api_key = yaml.contains("sk_live");
vec![
format!("password in YAML = {} (real value preserved)", has_password),
format!("api_key in YAML = {} (real value preserved)", has_api_key),
]
});
support::task_with_output("Roundtrip: YAML -> struct -> safe log", || {
let yaml = to_string(&config).unwrap();
let parsed: DatabaseConfig = from_str(&yaml).unwrap();
vec![
format!("host = {}", parsed.host),
format!("username = {}", parsed.username),
format!("password = {} (redacted in display)", parsed.password),
format!("api_key = {} (redacted in display)", parsed.api_key),
]
});
support::task_with_output("Value-level redaction by key pattern", || {
let yaml = "database:\n host: db.local\n password: secret123\n connection_string: postgres://user:pass@host\napi:\n key: sk_live_xyz\n secret_token: tok_abc\n";
let mut v: Value = from_str(yaml).unwrap();
redact(&mut v, &["password", "secret", "key"]);
let output = to_string(&v).unwrap();
output.lines().map(|l| l.to_string()).collect()
});
support::summary(4);
}