iroh_base/
relay_url.rs

1use std::{fmt, ops::Deref, str::FromStr, sync::Arc};
2
3use n0_error::stack_error;
4use serde::{Deserialize, Serialize};
5use url::Url;
6
7/// A URL identifying a relay server.
8///
9/// It is cheaply clonable, as the underlying type is wrapped into an `Arc`.  The main type
10/// under the hood though is [`Url`].
11///
12/// To create a [`RelayUrl`] use the `From<Url>` implementation.
13///
14/// It is encouraged to use a fully-qualified DNS domain name in the URL.  Meaning a DNS
15/// name which ends in a `.`, e.g, in `relay.example.com.`.  Otherwise the DNS resolution of
16/// your local host or network could interpret the DNS name as relative and in some
17/// configurations might cause additional delays or even connection problems.
18#[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/// Can occur when parsing a string into a [`RelayUrl`].
30#[stack_error(derive, add_meta)]
31#[error("Failed to parse relay URL")]
32pub struct RelayUrlParseError(#[error(std_err)] url::ParseError);
33
34/// Support for parsing strings directly.
35///
36/// If you need more control over the error first create a [`Url`] and use [`RelayUrl::from`]
37/// instead.
38impl 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
53/// Dereferences to the wrapped [`Url`].
54///
55/// Note that [`DerefMut`] is not implemented on purpose, so this type has more flexibility
56/// to change the inner later.
57///
58/// [`DerefMut`]: std::ops::DerefMut
59impl 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
75/// Helper struct to format a &str without allocating a String.
76///
77/// Maybe this is entirely unneeded and the compiler would be smart enough to never allocate
78/// the String anyway.  Who knows.  Writing this was faster than checking the assembler
79/// output.
80struct 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}