better_url/
better_host.rs

1//! Like [`url::Host`] but better.
2
3use std::str::FromStr;
4
5#[cfg(feature = "serde")]
6use serde::{Serialize, Deserialize, ser::Serializer, de::{Visitor, Deserializer, Error}};
7
8use crate::*;
9
10/// A URL host and its details.
11#[derive(Debug, Clone)]
12pub struct BetterHost {
13    /// The host string.
14    string: String,
15    /// The [`HostDetails`].
16    details: HostDetails
17}
18
19impl BetterHost {
20    /// The host string.
21    pub fn host_str(&self) -> &str {
22        &self.string
23    }
24
25    /// The host's [`HostDetails`].
26    pub fn host_details(&self) -> &HostDetails {
27        &self.details
28    }
29
30    /// The [`Self::host_details`]'s [`HostDetails::domain_details`].
31    pub fn domain_details(&self) -> Option<&DomainDetails> {
32        self.host_details().domain_details()
33    }
34
35    /// The [`Self::host_details`]'s [`HostDetails::ipv4_details`].
36    pub fn ipv4_details(&self) -> Option<&Ipv4Details> {
37        self.host_details().ipv4_details()
38    }
39
40    /// The [`Self::host_details`]'s [`HostDetails::ipv6_details`].
41    pub fn ipv6_details(&self) -> Option<&Ipv6Details> {
42        self.host_details().ipv6_details()
43    }
44
45    /// The [`Self::host_str`] with any `www,` prefix and `.` suffix removed.
46    pub fn normalized_host(&self) -> &str {
47        let mut ret = self.host_str();
48        ret = ret.strip_prefix("www.").unwrap_or(ret);
49        ret = ret.strip_suffix(".").unwrap_or(ret);
50        ret
51    }
52
53    /// The [`BetterUrl::domain`].
54    pub fn domain(&self) -> Option<&str> {
55        self.host_str().get(self.domain_details()?.domain_bounds())
56    }
57
58    /// The [`BetterUrl::subdomain`].
59    pub fn subdomain(&self) -> Option<&str> {
60        self.host_str().get(self.domain_details()?.subdomain_bounds()?)
61    }
62
63    /// The [`BetterUrl::not_domain_suffix`].
64    pub fn not_domain_suffix(&self) -> Option<&str> {
65        self.host_str().get(self.domain_details()?.not_domain_suffix_bounds()?)
66    }
67
68    /// The [`BetterUrl::domain_middle`].
69    pub fn domain_middle(&self) -> Option<&str> {
70        self.host_str().get(self.domain_details()?.domain_middle_bounds()?)
71    }
72
73    /// The [`BetterUrl::reg_domain`].
74    pub fn reg_domain(&self) -> Option<&str> {
75        self.host_str().get(self.domain_details()?.reg_domain_bounds()?)
76    }
77
78    /// The [`BetterUrl::domain_suffix`].
79    pub fn domain_suffix(&self) -> Option<&str> {
80        self.host_str().get(self.domain_details()?.domain_suffix_bounds()?)
81    }
82}
83
84impl PartialEq for BetterHost {
85    fn eq(&self, other: &Self) -> bool {
86        self.string == other.string
87    }
88}
89impl Eq for BetterHost {}
90
91impl FromStr for BetterHost {
92    type Err = <HostDetails as FromStr>::Err;
93
94    fn from_str(s: &str) -> Result<Self, Self::Err> {
95        Ok(Self {
96            details: s.parse()?,
97            string: s.into()
98        })
99    }
100}
101
102impl TryFrom<&str> for BetterHost {
103    type Error = <Self as FromStr>::Err;
104
105    fn try_from(value: &str) -> Result<Self, Self::Error> {
106        value.parse()
107    }
108}
109
110impl TryFrom<String> for BetterHost {
111    type Error = <Self as FromStr>::Err;
112
113    fn try_from(value: String) -> Result<Self, Self::Error> {
114        Ok(Self {
115            details: value.parse()?,
116            string: value
117        })
118    }
119}
120
121impl From<BetterHost> for String {
122    fn from(value: BetterHost) -> String {
123        value.string
124    }
125}
126
127impl std::fmt::Display for BetterHost {
128    fn fmt(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
129        write!(formatter, "{}", self.string)
130    }
131}
132
133/// Serde helper for deserializing [`BetterHost`].
134#[cfg(feature = "serde")]
135struct BetterHostVisitor;
136
137#[cfg(feature = "serde")]
138impl<'de> Visitor<'de> for BetterHostVisitor {
139    type Value = BetterHost;
140
141    fn visit_str<E: Error>(self, s: &str) -> Result<Self::Value, E> {
142        s.try_into().map_err(E::custom)
143    }
144
145    fn visit_string<E: Error>(self, s: String) -> Result<Self::Value, E> {
146        s.try_into().map_err(E::custom)
147    }
148
149    fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
150        write!(formatter, "Expected a string")
151    }
152}
153
154#[cfg(feature = "serde")]
155impl<'de> Deserialize<'de> for BetterHost {
156    fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
157        deserializer.deserialize_any(BetterHostVisitor)
158    }
159}
160
161#[cfg(feature = "serde")]
162impl Serialize for BetterHost {
163    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
164        serializer.serialize_str(&self.string)
165    }
166}