1use std::fmt;
19use std::ops::Deref;
20use std::str::FromStr;
21
22#[cfg(feature = "proptest")]
23use proptest::prelude::{Arbitrary, BoxedStrategy, Strategy};
24use serde::{Deserialize, Serialize};
25use url::Url;
26
27#[derive(Clone, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord, Hash)]
29pub struct SensitiveUrl(pub Url);
30
31impl SensitiveUrl {
32 pub fn into_redacted(mut self) -> Url {
35 if self.0.password().is_some() {
36 self.0.set_password(Some("<redacted>")).unwrap();
37 }
38 self.0
39 }
40
41 pub fn to_string_unredacted(&self) -> String {
43 self.0.to_string()
44 }
45}
46
47impl fmt::Display for SensitiveUrl {
48 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
49 self.clone().into_redacted().fmt(f)
50 }
51}
52
53impl fmt::Debug for SensitiveUrl {
54 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
55 self.clone().into_redacted().fmt(f)
56 }
57}
58
59impl FromStr for SensitiveUrl {
60 type Err = url::ParseError;
61
62 fn from_str(s: &str) -> Result<Self, Self::Err> {
63 Ok(Self(Url::from_str(s)?))
64 }
65}
66
67impl Deref for SensitiveUrl {
68 type Target = Url;
69
70 fn deref(&self) -> &Self::Target {
71 &self.0
72 }
73}
74
75#[cfg(feature = "cli")]
76#[derive(Clone, Debug)]
77pub struct SensitiveUrlParser;
79
80#[cfg(feature = "cli")]
81impl clap::builder::TypedValueParser for SensitiveUrlParser {
82 type Value = SensitiveUrl;
83
84 fn parse_ref(
85 &self,
86 cmd: &clap::Command,
87 arg: Option<&clap::Arg>,
88 value: &std::ffi::OsStr,
89 ) -> Result<Self::Value, clap::Error> {
90 let s = value
91 .to_str()
92 .ok_or_else(|| clap::Error::new(clap::error::ErrorKind::InvalidUtf8).with_cmd(cmd))?;
93 let url = s.parse().map_err(|_e| {
94 let mut err = clap::Error::new(clap::error::ErrorKind::ValueValidation);
95 if let Some(arg) = arg {
96 err.insert(
97 clap::error::ContextKind::InvalidArg,
98 clap::error::ContextValue::String(arg.to_string()),
99 );
100 }
101 err.insert(
102 clap::error::ContextKind::InvalidValue,
103 clap::error::ContextValue::String("<redacted>".to_string()),
104 );
105 err
109 })?;
110 Ok(SensitiveUrl(url))
111 }
112}
113
114#[cfg(feature = "cli")]
115impl clap::builder::ValueParserFactory for SensitiveUrl {
116 type Parser = SensitiveUrlParser;
117
118 fn value_parser() -> Self::Parser {
119 SensitiveUrlParser
120 }
121}
122
123#[cfg(feature = "proptest")]
124impl Arbitrary for SensitiveUrl {
125 type Parameters = ();
126 type Strategy = BoxedStrategy<Self>;
127
128 fn arbitrary_with(_: Self::Parameters) -> Self::Strategy {
129 proptest::sample::select(vec![
130 SensitiveUrl::from_str("http://user:pass@example.com").unwrap(),
131 SensitiveUrl::from_str("http://user@example.com").unwrap(),
132 SensitiveUrl::from_str("http://example.com").unwrap(),
133 ])
134 .boxed()
135 }
136}