use crate::RedactionStrategy;
use std::fmt;
use url::Url;
#[derive(Default, Clone)]
pub struct UrlHostRedactor;
impl<T: AsRef<str>> RedactionStrategy<T> for UrlHostRedactor {
fn display(&self, value: &T, f: &mut fmt::Formatter<'_>) -> fmt::Result {
redact_url(value.as_ref(), f)
}
}
fn redact_url(input: &str, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let Ok(parsed) = Url::parse(input) else {
return write!(f, "[REDACTED: invalid URL]");
};
write!(f, "****")?;
let path = parsed.path();
if path != "/" {
write!(f, "{}", path)?;
}
if parsed.query().is_some() {
write!(f, "?")?;
let mut first = true;
for (key, _value) in parsed.query_pairs() {
if !first {
write!(f, "&")?;
}
first = false;
write!(f, "{}=****", key)?;
}
}
if parsed.fragment().is_some() {
write!(f, "#****")?;
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
use crate::Redacted;
fn url(input: &str) -> String {
Redacted::<_, UrlHostRedactor>::new(input).to_string()
}
#[test]
fn test_https_with_path() {
assert_eq!(url("https://example.com/api/secret"), "****/api/secret");
}
#[test]
fn test_no_path() {
assert_eq!(url("https://example.com"), "****");
}
#[test]
fn test_with_credentials() {
assert_eq!(url("postgres://user:pass@host/db"), "****/db");
}
#[test]
fn test_invalid_url() {
assert_eq!(url("not a url"), "[REDACTED: invalid URL]");
}
#[test]
fn test_with_query_string() {
assert_eq!(
url("https://example.com/api?key=secret"),
"****/api?key=****"
);
}
#[test]
fn test_with_multiple_query_params() {
assert_eq!(
url("https://example.com/api?key=secret&token=abc123"),
"****/api?key=****&token=****"
);
}
#[test]
fn test_with_query_flag_no_value() {
assert_eq!(
url("https://example.com/api?debug&key=secret"),
"****/api?debug=****&key=****"
);
}
#[test]
fn test_with_fragment() {
assert_eq!(url("https://example.com/page#section"), "****/page#****");
}
#[test]
fn test_with_query_and_fragment() {
assert_eq!(
url("https://example.com/api?key=secret#section"),
"****/api?key=****#****"
);
}
#[test]
fn test_query_without_path() {
assert_eq!(url("https://example.com?key=secret"), "****?key=****");
}
#[test]
fn test_fragment_without_path() {
assert_eq!(url("https://example.com#section"), "****#****");
}
}