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(
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(url: Url) -> Self {
25 Self(Arc::new(url))
26 }
27}
28
29#[stack_error(derive, add_meta)]
31#[error("Failed to parse relay URL")]
32pub struct RelayUrlParseError(#[error(std_err)] url::ParseError);
33
34impl FromStr for RelayUrl {
39 type Err = RelayUrlParseError;
40
41 fn from_str(s: &str) -> Result<Self, Self::Err> {
42 let inner = Url::from_str(s).map_err(RelayUrlParseError::new)?;
43 Ok(RelayUrl::from(inner))
44 }
45}
46
47impl From<RelayUrl> for Url {
48 fn from(value: RelayUrl) -> Self {
49 Arc::unwrap_or_clone(value.0)
50 }
51}
52
53impl Deref for RelayUrl {
60 type Target = Url;
61
62 fn deref(&self) -> &Self::Target {
63 &self.0
64 }
65}
66
67impl fmt::Debug for RelayUrl {
68 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
69 f.debug_tuple("RelayUrl")
70 .field(&DbgStr(self.0.as_str()))
71 .finish()
72 }
73}
74
75struct DbgStr<'a>(&'a str);
81
82impl fmt::Debug for DbgStr<'_> {
83 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
84 write!(f, r#""{}""#, self.0)
85 }
86}
87
88#[cfg(test)]
89mod tests {
90 use super::*;
91
92 #[test]
93 fn test_relay_url_debug_display() {
94 let url = RelayUrl::from(Url::parse("https://example.com").unwrap());
95
96 assert_eq!(format!("{url:?}"), r#"RelayUrl("https://example.com/")"#);
97
98 assert_eq!(format!("{url}"), "https://example.com/");
99 }
100
101 #[test]
102 fn test_relay_url_absolute() {
103 let url = RelayUrl::from(Url::parse("https://example.com").unwrap());
104
105 assert_eq!(url.domain(), Some("example.com"));
106
107 let url1 = RelayUrl::from(Url::parse("https://example.com.").unwrap());
108 assert_eq!(url1.domain(), Some("example.com."));
109
110 let url2 = RelayUrl::from(Url::parse("https://example.com./").unwrap());
111 assert_eq!(url2.domain(), Some("example.com."));
112
113 let url3 = RelayUrl::from(Url::parse("https://example.com/").unwrap());
114 assert_eq!(url3.domain(), Some("example.com"));
115 }
116}