1use std::{fmt, ops::Deref, str::FromStr, sync::Arc};
2
3use serde::{Deserialize, Serialize};
4use snafu::{Backtrace, ResultExt, Snafu};
5use url::Url;
6
7#[derive(
20 Clone, derive_more::Display, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize,
21)]
22pub struct RelayUrl(Arc<Url>);
23
24impl From<Url> for RelayUrl {
25 fn from(mut url: Url) -> Self {
26 if let Some(domain) = url.domain() {
27 if !domain.ends_with('.') {
28 let domain = String::from(domain) + ".";
29
30 url.set_host(Some(&domain)).ok();
35 }
36 }
37 Self(Arc::new(url))
38 }
39}
40
41#[derive(Debug, Snafu)]
43#[snafu(display("Failed to parse"))]
44pub struct RelayUrlParseError {
45 source: url::ParseError,
46 backtrace: Option<Backtrace>,
47}
48
49impl FromStr for RelayUrl {
54 type Err = RelayUrlParseError;
55
56 fn from_str(s: &str) -> Result<Self, Self::Err> {
57 let inner = Url::from_str(s).context(RelayUrlParseSnafu)?;
58 Ok(RelayUrl::from(inner))
59 }
60}
61
62impl From<RelayUrl> for Url {
63 fn from(value: RelayUrl) -> Self {
64 Arc::unwrap_or_clone(value.0)
65 }
66}
67
68impl Deref for RelayUrl {
75 type Target = Url;
76
77 fn deref(&self) -> &Self::Target {
78 &self.0
79 }
80}
81
82impl fmt::Debug for RelayUrl {
83 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
84 f.debug_tuple("RelayUrl")
85 .field(&DbgStr(self.0.as_str()))
86 .finish()
87 }
88}
89
90struct DbgStr<'a>(&'a str);
96
97impl fmt::Debug for DbgStr<'_> {
98 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
99 write!(f, r#""{}""#, self.0)
100 }
101}
102
103#[cfg(test)]
104mod tests {
105 use super::*;
106
107 #[test]
108 fn test_relay_url_debug_display() {
109 let url = RelayUrl::from(Url::parse("https://example.com").unwrap());
110
111 assert_eq!(format!("{url:?}"), r#"RelayUrl("https://example.com./")"#);
112
113 assert_eq!(format!("{url}"), "https://example.com./");
114 }
115
116 #[test]
117 fn test_relay_url_absolute() {
118 let url = RelayUrl::from(Url::parse("https://example.com").unwrap());
119
120 assert_eq!(url.domain(), Some("example.com."));
121
122 let url1 = RelayUrl::from(Url::parse("https://example.com.").unwrap());
123 assert_eq!(url, url1);
124
125 let url2 = RelayUrl::from(Url::parse("https://example.com./").unwrap());
126 assert_eq!(url, url2);
127
128 let url3 = RelayUrl::from(Url::parse("https://example.com/").unwrap());
129 assert_eq!(url, url3);
130 }
131}