distant_net/common/
destination.rs1use std::fmt;
2use std::hash::Hash;
3use std::str::FromStr;
4
5use serde::de::Deserializer;
6use serde::ser::Serializer;
7use serde::{Deserialize, Serialize};
8
9use super::utils::{deserialize_from_str, serialize_to_str};
10
11mod host;
12mod parser;
13
14pub use host::{Host, HostParseError};
15
16#[derive(Clone, Debug, Hash, PartialEq, Eq)]
25pub struct Destination {
26 pub scheme: Option<String>,
30
31 pub username: Option<String>,
33
34 pub password: Option<String>,
36
37 pub host: Host,
41
42 pub port: Option<u16>,
44}
45
46impl Destination {
47 pub fn scheme_eq(&self, s: &str) -> bool {
49 match self.scheme.as_ref() {
50 Some(scheme) => scheme.eq_ignore_ascii_case(s),
51 None => false,
52 }
53 }
54}
55
56impl AsRef<Destination> for &Destination {
57 fn as_ref(&self) -> &Destination {
58 self
59 }
60}
61
62impl AsMut<Destination> for &mut Destination {
63 fn as_mut(&mut self) -> &mut Destination {
64 self
65 }
66}
67
68impl fmt::Display for Destination {
69 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
70 if let Some(scheme) = self.scheme.as_ref() {
71 write!(f, "{scheme}://")?;
72 }
73
74 if let Some(username) = self.username.as_ref() {
75 write!(f, "{username}")?;
76 }
77
78 if let Some(password) = self.password.as_ref() {
79 write!(f, ":{password}")?;
80 }
81
82 if self.username.is_some() || self.password.is_some() {
83 write!(f, "@")?;
84 }
85
86 match &self.host {
88 Host::Ipv6(x) if self.port.is_some() => write!(f, "[{x}]")?,
89 x => write!(f, "{x}")?,
90 }
91
92 if let Some(port) = self.port {
93 write!(f, ":{port}")?;
94 }
95
96 Ok(())
97 }
98}
99
100impl FromStr for Destination {
101 type Err = &'static str;
102
103 fn from_str(s: &str) -> Result<Self, Self::Err> {
105 parser::parse(s)
106 }
107}
108
109impl FromStr for Box<Destination> {
110 type Err = &'static str;
111
112 fn from_str(s: &str) -> Result<Self, Self::Err> {
113 let destination = s.parse::<Destination>()?;
114 Ok(Box::new(destination))
115 }
116}
117
118impl<'a> PartialEq<&'a str> for Destination {
119 #[allow(clippy::cmp_owned)]
120 fn eq(&self, other: &&'a str) -> bool {
121 self.to_string() == *other
122 }
123}
124
125impl Serialize for Destination {
126 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
127 where
128 S: Serializer,
129 {
130 serialize_to_str(self, serializer)
131 }
132}
133
134impl<'de> Deserialize<'de> for Destination {
135 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
136 where
137 D: Deserializer<'de>,
138 {
139 deserialize_from_str(deserializer)
140 }
141}
142
143#[cfg(test)]
144mod tests {
145 use super::*;
146
147 #[test]
148 fn display_should_output_using_available_components() {
149 let destination = Destination {
150 scheme: None,
151 username: None,
152 password: None,
153 host: Host::Name("example.com".to_string()),
154 port: None,
155 };
156 assert_eq!(destination, "example.com");
157 }
158
159 #[test]
160 fn display_should_not_wrap_ipv6_in_square_brackets_if_has_no_port() {
161 let destination = Destination {
162 scheme: None,
163 username: None,
164 password: None,
165 host: Host::Ipv6("::1".parse().unwrap()),
166 port: None,
167 };
168 assert_eq!(destination, "::1");
169 }
170
171 #[test]
172 fn display_should_wrap_ipv6_in_square_brackets_if_has_port() {
173 let destination = Destination {
174 scheme: None,
175 username: None,
176 password: None,
177 host: Host::Ipv6("::1".parse().unwrap()),
178 port: Some(12345),
179 };
180 assert_eq!(destination, "[::1]:12345");
181 }
182}