pub fn mask_sensitive_value(key: &str, value: &str) -> String {
let key_lower = key.to_lowercase();
let is_sensitive = key_lower.contains("api_key")
|| key_lower.contains("token")
|| key_lower.contains("secret");
if !is_sensitive {
return value.to_string();
}
if value.is_empty() {
return String::new();
}
if value.chars().count() <= 4 {
"****".to_string()
} else {
let tail: String = value
.chars()
.rev()
.take(4)
.collect::<Vec<_>>()
.into_iter()
.rev()
.collect();
format!("****{tail}")
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn non_sensitive_key_returns_value_unchanged() {
assert_eq!(mask_sensitive_value("ai.model", "gpt-4"), "gpt-4");
assert_eq!(mask_sensitive_value("formats.default_output", "srt"), "srt");
}
#[test]
fn short_sensitive_value_is_fully_masked() {
assert_eq!(mask_sensitive_value("ai.api_key", "abcd"), "****");
assert_eq!(mask_sensitive_value("ai.api_key", "a"), "****");
}
#[test]
fn long_sensitive_value_preserves_last_four() {
assert_eq!(
mask_sensitive_value("ai.api_key", "sk-1234567890abcdef"),
"****cdef"
);
}
#[test]
fn empty_sensitive_value_stays_empty() {
assert_eq!(mask_sensitive_value("ai.api_key", ""), "");
}
#[test]
fn matching_is_case_insensitive_and_substring() {
assert_eq!(mask_sensitive_value("AI.API_KEY", "abcdefgh"), "****efgh");
assert_eq!(
mask_sensitive_value("auth.access_token", "tokenvalue12345"),
"****2345"
);
assert_eq!(
mask_sensitive_value("client.secret", "supersecretvalue"),
"****alue"
);
}
}