postgres_conn_str/parser/authority/
host.rs

1/*!
2If you'd like to go insane, go read [https://paquier.xyz/postgresql-2/postgres-10-multi-host-connstr](https://paquier.xyz/postgresql-2/postgres-10-multi-host-connstr).
3*/
4
5use 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
60/// Get a list of host:port pairs.
61pub(crate) fn hostspecs(i: &str) -> Res<&str, Vec<HostSpec>> {
62    // We deconstruct the result a bit to remove any pairs that are both None.
63    many0(hostspec)(i)
64}
65
66/// Get a list of host:port pairs.
67pub(crate) fn hostspec(i: &str) -> Res<&str, HostSpec> {
68    // We deconstruct the result a bit to remove any pairs that are both None.
69    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
88/// Parse the `port` component of a URI.
89fn port(i: &str) -> Res<&str, u16> {
90    preceded(tag(":"), u16)(i)
91}
92
93/// Parse the `host` component of a URI.
94fn 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
105/// Recognize an ipv6 address.
106fn 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
121/// Recognize an ipv4 address.
122fn 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        // Good cases.
136        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        // Good cases.
154        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        // Bad cases.
186        for input in [",host", "/db"] {
187            assert!(host(input).is_err());
188        }
189    }
190
191    #[test]
192    fn test_ipv6() {
193        // Good cases.
194        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        // Good cases.
210        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        // Good cases.
230        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        // Good cases.
252        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}