1use std::{fmt, ops::Deref, str::FromStr, sync::Arc};
2
3use n0_error::stack_error;
4use serde::{Deserialize, Serialize};
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#[stack_error(derive, add_meta)]
43#[error("Failed to parse relay URL")]
44pub struct RelayUrlParseError(#[error(std_err)] url::ParseError);
45
46impl FromStr for RelayUrl {
51 type Err = RelayUrlParseError;
52
53 fn from_str(s: &str) -> Result<Self, Self::Err> {
54 let inner = Url::from_str(s).map_err(RelayUrlParseError::new)?;
55 Ok(RelayUrl::from(inner))
56 }
57}
58
59impl From<RelayUrl> for Url {
60 fn from(value: RelayUrl) -> Self {
61 Arc::unwrap_or_clone(value.0)
62 }
63}
64
65impl Deref for RelayUrl {
72 type Target = Url;
73
74 fn deref(&self) -> &Self::Target {
75 &self.0
76 }
77}
78
79impl fmt::Debug for RelayUrl {
80 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
81 f.debug_tuple("RelayUrl")
82 .field(&DbgStr(self.0.as_str()))
83 .finish()
84 }
85}
86
87struct DbgStr<'a>(&'a str);
93
94impl fmt::Debug for DbgStr<'_> {
95 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
96 write!(f, r#""{}""#, self.0)
97 }
98}
99
100#[cfg(test)]
101mod tests {
102 use super::*;
103
104 #[test]
105 fn test_relay_url_debug_display() {
106 let url = RelayUrl::from(Url::parse("https://example.com").unwrap());
107
108 assert_eq!(format!("{url:?}"), r#"RelayUrl("https://example.com./")"#);
109
110 assert_eq!(format!("{url}"), "https://example.com./");
111 }
112
113 #[test]
114 fn test_relay_url_absolute() {
115 let url = RelayUrl::from(Url::parse("https://example.com").unwrap());
116
117 assert_eq!(url.domain(), Some("example.com."));
118
119 let url1 = RelayUrl::from(Url::parse("https://example.com.").unwrap());
120 assert_eq!(url, url1);
121
122 let url2 = RelayUrl::from(Url::parse("https://example.com./").unwrap());
123 assert_eq!(url, url2);
124
125 let url3 = RelayUrl::from(Url::parse("https://example.com/").unwrap());
126 assert_eq!(url, url3);
127 }
128}