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