1use std::{fmt, net, str};
17
18use super::parser::{ConnectionExprLanguage, Pair, Parser, Rule};
19
20#[derive(Clone, Debug, PartialEq)]
21pub enum Addr {
22 Domain(String),
23 IP(net::IpAddr),
24}
25
26impl fmt::Display for Addr {
27 fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
28 match self {
29 Addr::Domain(domain) => domain.fmt(fmt),
30 Addr::IP(ip) => ip.fmt(fmt),
31 }
32 }
33}
34
35impl<'a> TryFrom<Pair<'a, Rule>> for Addr {
36 type Error = ConversionError;
37
38 fn try_from(pair: Pair<'a, Rule>) -> Result<Self, Self::Error> {
39 if matches!(pair.as_rule(), Rule::addr) {
40 let addr = pair
41 .into_inner()
42 .next()
43 .expect("grammar guarantees specific inner address");
44 Ok(match addr.as_rule() {
45 Rule::ipv4_addr => Addr::IP(
46 addr
47 .as_str()
48 .parse::<net::Ipv4Addr>()
49 .expect("grammar guarantees legal IPv4 address")
50 .into(),
51 ),
52 Rule::ipv6_addr => Addr::IP(
53 addr
54 .as_str()
55 .parse::<net::Ipv6Addr>()
56 .expect("grammar guarantees legal IPv6 address")
57 .into(),
58 ),
59 Rule::domain_addr => {
60 let domain = addr.as_str().to_owned();
61 if addr.as_str().len() > 253
62 || addr.into_inner().any(|l| l.as_str().len() > 63)
63 {
64 return Err(ConversionError);
65 }
66 Addr::Domain(domain)
67 }
68 _ => unreachable!("grammar guarantees IPv6, IPv4, or domain address"),
69 })
70 } else {
71 Err(ConversionError)
72 }
73 }
74}
75
76#[derive(Clone, Copy, Debug, PartialEq, thiserror::Error)]
77#[error("failed to convert into address")]
78pub struct ConversionError;
79
80impl str::FromStr for Addr {
81 type Err = ParseError;
82
83 fn from_str(s: &str) -> Result<Self, Self::Err> {
84 ConnectionExprLanguage::parse(Rule::addr_expr, s)
85 .map_err(|_| ParseError)?
86 .next()
87 .expect("grammar guaranteed addr_expr")
88 .into_inner()
89 .next()
90 .expect("grammar guarantees addr")
91 .try_into()
92 .map_err(|_| ParseError)
93 }
94}
95
96#[derive(Clone, Copy, Debug, PartialEq, thiserror::Error)]
97#[error("failed to parse address")]
98pub struct ParseError;
99
100#[cfg(test)]
101mod test {
102
103 use super::*;
104
105 #[test]
106 fn parse_domain_name() {
107 let d = |s: &str| Ok(Addr::Domain(s.to_string()));
108 assert_eq!("localhost".parse(), d("localhost"));
109 assert_eq!("localhost.".parse(), d("localhost."));
110 assert_eq!("a.b.c.d".parse(), d("a.b.c.d"));
111 assert_eq!("a1.b-2.c--3.d---4".parse(), d("a1.b-2.c--3.d---4"));
112 }
113
114 #[test]
115 fn parse_ipv4_address() {
116 let ip4 = |a, b, c, d| Ok(Addr::IP(net::Ipv4Addr::new(a, b, c, d).into()));
117 assert_eq!("0.0.0.0".parse(), ip4(0, 0, 0, 0));
118 assert_eq!("1.2.3.4".parse(), ip4(1, 2, 3, 4));
119 assert_eq!("255.255.255.255".parse(), ip4(255, 255, 255, 255));
120 }
121
122 #[test]
123 fn parse_ipv6_address() {
124 let ip6 = |a, b, c, d, e, f, g, h| {
125 Ok(Addr::IP(net::Ipv6Addr::new(a, b, c, d, e, f, g, h).into()))
126 };
127 assert_eq!("[::]".parse(), ip6(0, 0, 0, 0, 0, 0, 0, 0));
128 assert_eq!("[::1]".parse(), ip6(0, 0, 0, 0, 0, 0, 0, 1));
129 assert_eq!("[1::]".parse(), ip6(1, 0, 0, 0, 0, 0, 0, 0));
130 assert_eq!("[::0.0.0.0]".parse(), ip6(0, 0, 0, 0, 0, 0, 0, 0));
131 assert_eq!(
132 "[DEAD::BEEF]".parse(),
133 ip6(0xDEAD, 0, 0, 0, 0, 0, 0, 0xBEEF)
134 );
135 assert_eq!(
136 "[dead::beef]".parse(),
137 ip6(0xDEAD, 0, 0, 0, 0, 0, 0, 0xBEEF)
138 );
139 assert_eq!(
140 "[1:23:456:789a::127.0.0.1]".parse(),
141 ip6(0x0001, 0x0023, 0x0456, 0x789A, 0, 0, 0x7F00, 1),
142 );
143 }
144
145 #[test]
146 fn parse_bad_address() {
147 let err = Err(ParseError);
148 assert_eq!(" 1.2.3.4".parse::<Addr>(), err);
149 assert_eq!("".parse::<Addr>(), err);
150 assert_eq!(".".parse::<Addr>(), err);
151 assert_eq!("01.2.3.4".parse::<Addr>(), err);
152 assert_eq!("1. 2.3.4".parse::<Addr>(), err);
153 assert_eq!("1.2.3.04".parse::<Addr>(), err);
154 assert_eq!("1.2.3.256".parse::<Addr>(), err);
155 assert_eq!("1.2.3.4 ".parse::<Addr>(), err);
156 assert_eq!("1.2.3.4.com".parse::<Addr>(), err);
157 assert_eq!("1.com".parse::<Addr>(), err);
158 assert_eq!("[ ::]".parse::<Addr>(), err);
159 assert_eq!("[:: ]".parse::<Addr>(), err);
160 assert_eq!("[]".parse::<Addr>(), err);
161 assert_eq!("dash-.com".parse::<Addr>(), err);
162 assert_eq!("tyre.8bar.com".parse::<Addr>(), err);
163 assert_eq!("1".parse(), err);
165 assert_eq!("1.2".parse(), err);
166 assert_eq!("1.2.3".parse(), err);
167 }
168}