1use super::Res;
6use anyhow::anyhow;
7use nom::{
8 branch::alt,
9 bytes::complete::tag,
10 character::complete::{alphanumeric1, digit1, hex_digit1, one_of, u16},
11 combinator::{map, map_res, opt, recognize},
12 multi::{count, many0, many1, separated_list0},
13 sequence::{delimited, pair, preceded, terminated, tuple},
14 Finish,
15};
16use std::{
17 fmt::Display,
18 net::{IpAddr, Ipv4Addr, Ipv6Addr},
19 path::PathBuf,
20 str::FromStr,
21};
22
23#[derive(Clone, Debug, Default, PartialEq, Eq)]
24pub(crate) struct HostSpec {
25 pub host: Option<Host>,
26 pub port: Option<u16>,
27}
28
29#[derive(Clone, Debug, PartialEq, Eq)]
30pub enum Host {
31 Path(PathBuf),
32 Name(String),
33 Ip(IpAddr),
34}
35
36impl Display for Host {
37 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
38 match self {
39 Host::Path(path) => {
40 write!(f, "{}", path.to_str().unwrap_or("<invalid>"))
41 }
42 Host::Name(name) => write!(f, "{name}"),
43 Host::Ip(IpAddr::V4(ip)) => write!(f, "{ip}"),
44 Host::Ip(IpAddr::V6(ip)) => write!(f, "[{ip}]"),
45 }
46 }
47}
48
49impl FromStr for Host {
50 type Err = anyhow::Error;
51
52 fn from_str(s: &str) -> Result<Self, Self::Err> {
53 host(s)
54 .finish()
55 .map(|(_, host)| host)
56 .map_err(|e| anyhow!(e.to_string()))
57 }
58}
59
60pub(crate) fn hostspecs(i: &str) -> Res<&str, Vec<HostSpec>> {
62 many0(hostspec)(i)
64}
65
66pub(crate) fn hostspec(i: &str) -> Res<&str, HostSpec> {
68 terminated(
70 alt((
71 map(pair(host, port), |(host, port)| HostSpec {
72 host: Some(host),
73 port: Some(port),
74 }),
75 map(pair(host, opt(port)), |(host, port)| HostSpec {
76 host: Some(host),
77 port,
78 }),
79 map(pair(opt(host), port), |(host, port)| HostSpec {
80 host: host.map(std::convert::Into::into),
81 port: Some(port),
82 }),
83 )),
84 opt(tag(",")),
85 )(i)
86}
87
88fn port(i: &str) -> Res<&str, u16> {
90 preceded(tag(":"), u16)(i)
91}
92
93fn host(i: &str) -> Res<&str, Host> {
95 alt((
96 map(ipv4, |ip| Host::Ip(IpAddr::V4(ip))),
97 map(ipv6, |ip| Host::Ip(IpAddr::V6(ip))),
98 map(
99 recognize(many1(alt((alphanumeric1, recognize(one_of("._-")))))),
100 |s: &str| Host::Name(s.to_string()),
101 ),
102 ))(i)
103}
104
105fn ipv6(i: &str) -> Res<&str, Ipv6Addr> {
107 delimited(
108 tag("["),
109 map_res(
110 recognize(tuple((
111 separated_list0(tag(":"), hex_digit1),
112 tag("::"),
113 hex_digit1,
114 ))),
115 Ipv6Addr::from_str,
116 ),
117 tag("]"),
118 )(i)
119}
120
121fn ipv4(i: &str) -> Res<&str, Ipv4Addr> {
123 map_res(
124 recognize(pair(count(pair(digit1, tag(".")), 3), digit1)),
125 Ipv4Addr::from_str,
126 )(i)
127}
128
129#[cfg(test)]
130mod tests {
131 use super::*;
132
133 #[test]
134 fn test_port() {
135 for (input, rem, output) in vec![(":123", "", 123)] {
137 assert_eq!(
138 port(input).unwrap(),
139 (rem, output),
140 "input: {:?}; rem: {:?}",
141 input,
142 rem
143 );
144 }
145
146 for input in vec![",123", ",:123", ":port"] {
147 assert!(port(input).is_err());
148 }
149 }
150
151 #[test]
152 fn test_host() {
153 for (input, rem, output) in vec![
155 ("myhost", "", Host::Name("myhost".into())),
156 ("myhost.mydomain", "", Host::Name("myhost.mydomain".into())),
157 (
158 "subdomain.myhost.com",
159 "",
160 Host::Name("subdomain.myhost.com".into()),
161 ),
162 (
163 "sub-domain.my_host.com",
164 "",
165 Host::Name("sub-domain.my_host.com".into()),
166 ),
167 (
168 "sub-domain.my_host.com/db",
169 "/db",
170 Host::Name("sub-domain.my_host.com".into()),
171 ),
172 (
173 "[2001:db8::1234]/database",
174 "/database",
175 Host::Ip(IpAddr::from_str("2001:db8::1234").unwrap()),
176 ),
177 ] {
178 assert_eq!(
179 host(input),
180 Ok((rem, output)),
181 "input: {input:?}; rem: {rem:?}",
182 );
183 }
184
185 for input in [",host", "/db"] {
187 assert!(host(input).is_err());
188 }
189 }
190
191 #[test]
192 fn test_ipv6() {
193 for (input, rem, output) in [(
195 "[2001:db8::1234]",
196 "",
197 Ipv6Addr::from_str("2001:db8::1234").unwrap(),
198 )] {
199 assert_eq!(
200 ipv6(input),
201 Ok((rem, output)),
202 "input: {input:?}; rem: {rem:?}",
203 );
204 }
205 }
206
207 #[test]
208 fn test_ipv4() {
209 for (input, rem, output) in vec![("192.168.0.1", "", Ipv4Addr::new(192, 168, 0, 1))] {
211 assert_eq!(
212 ipv4(input).unwrap(),
213 (rem, output),
214 "input: {:?}; rem: {:?}",
215 input,
216 rem
217 );
218 }
219 }
220
221 macro_rules! host {
222 ($s:expr) => {
223 Some(host($s).unwrap().1)
224 };
225 }
226
227 #[test]
228 fn test_hostspec() {
229 for (input, rem, output) in vec![(
231 "myhost:123",
232 "",
233 HostSpec {
234 host: host!("myhost"),
235 port: Some(123),
236 },
237 )] {
238 assert_eq!(
239 hostspec(input).unwrap(),
240 (rem, output),
241 "input: {input:?}; rem: {rem:?}",
242 );
243 }
244
245 for input in &["", ",", ",host:123"] {
246 assert!(hostspec(input).is_err());
247 }
248 }
249 #[test]
250 fn test_hostspecs() {
251 for (input, rem, output) in vec![
253 ("", "", vec![]),
254 (", ", ", ", vec![]),
255 (",host:123", ",host:123", vec![]),
256 (
257 "myhost:123",
258 "",
259 vec![HostSpec {
260 host: host!("myhost"),
261 port: Some(123),
262 }],
263 ),
264 (
265 "myhost:123,secondhost:65535",
266 "",
267 vec![
268 HostSpec {
269 host: host!("myhost"),
270 port: Some(123),
271 },
272 HostSpec {
273 host: host!("secondhost"),
274 port: Some(65535),
275 },
276 ],
277 ),
278 ] {
279 assert_eq!(
280 hostspecs(input).unwrap(),
281 (rem, output),
282 "input: {input:?}; rem: {rem:?}",
283 );
284 }
285
286 for _input in &[",", ",host:123"] {}
287 }
288}