apollo_redaction/strategy/
url.rs1use crate::RedactionStrategy;
2use std::fmt;
3use url::Url;
4
5#[derive(Default, Clone)]
22pub struct UrlHostRedactor;
23
24impl<T: AsRef<str>> RedactionStrategy<T> for UrlHostRedactor {
25 fn display(&self, value: &T, f: &mut fmt::Formatter<'_>) -> fmt::Result {
26 redact_url(value.as_ref(), f)
27 }
28}
29
30fn redact_url(input: &str, f: &mut fmt::Formatter<'_>) -> fmt::Result {
31 let Ok(parsed) = Url::parse(input) else {
32 return write!(f, "[REDACTED: invalid URL]");
33 };
34
35 write!(f, "****")?;
36
37 let path = parsed.path();
39 if path != "/" {
40 write!(f, "{}", path)?;
41 }
42
43 if parsed.query().is_some() {
45 write!(f, "?")?;
46 let mut first = true;
47 for (key, _value) in parsed.query_pairs() {
48 if !first {
49 write!(f, "&")?;
50 }
51 first = false;
52 write!(f, "{}=****", key)?;
53 }
54 }
55
56 if parsed.fragment().is_some() {
58 write!(f, "#****")?;
59 }
60
61 Ok(())
62}
63
64#[cfg(test)]
65mod tests {
66 use super::*;
67 use crate::Redacted;
68
69 fn url(input: &str) -> String {
70 Redacted::<_, UrlHostRedactor>::new(input).to_string()
71 }
72
73 #[test]
74 fn test_https_with_path() {
75 assert_eq!(url("https://example.com/api/secret"), "****/api/secret");
76 }
77
78 #[test]
79 fn test_no_path() {
80 assert_eq!(url("https://example.com"), "****");
81 }
82
83 #[test]
84 fn test_with_credentials() {
85 assert_eq!(url("postgres://user:pass@host/db"), "****/db");
86 }
87
88 #[test]
89 fn test_invalid_url() {
90 assert_eq!(url("not a url"), "[REDACTED: invalid URL]");
91 }
92
93 #[test]
94 fn test_with_query_string() {
95 assert_eq!(
96 url("https://example.com/api?key=secret"),
97 "****/api?key=****"
98 );
99 }
100
101 #[test]
102 fn test_with_multiple_query_params() {
103 assert_eq!(
104 url("https://example.com/api?key=secret&token=abc123"),
105 "****/api?key=****&token=****"
106 );
107 }
108
109 #[test]
110 fn test_with_query_flag_no_value() {
111 assert_eq!(
112 url("https://example.com/api?debug&key=secret"),
113 "****/api?debug=****&key=****"
114 );
115 }
116
117 #[test]
118 fn test_with_fragment() {
119 assert_eq!(url("https://example.com/page#section"), "****/page#****");
120 }
121
122 #[test]
123 fn test_with_query_and_fragment() {
124 assert_eq!(
125 url("https://example.com/api?key=secret#section"),
126 "****/api?key=****#****"
127 );
128 }
129
130 #[test]
131 fn test_query_without_path() {
132 assert_eq!(url("https://example.com?key=secret"), "****?key=****");
133 }
134
135 #[test]
136 fn test_fragment_without_path() {
137 assert_eq!(url("https://example.com#section"), "****#****");
138 }
139}