uri_parsing_rs/parser/parsers/
host_parsers.rs

1use nom::{AsChar, InputTakeAtPosition, IResult};
2use nom::branch::alt;
3use nom::character::complete;
4use nom::combinator::map;
5use nom::error::{context, ErrorKind, ParseError};
6use nom::multi::many0;
7use nom::sequence::{delimited, preceded, terminated, tuple};
8
9use crate::ast::host_name::HostName;
10use crate::parser::parsers::{Elms, ipv4_address_parsers, ipv6_address_parsers, UResult};
11use crate::parser::parsers::basic_parsers::*;
12
13#[inline]
14fn hd_code_point<T, E: ParseError<T>>(input: T) -> IResult<T, T, E>
15where
16  T: InputTakeAtPosition,
17  <T as InputTakeAtPosition>::Item: AsChar,
18{
19  input.split_at_position1_complete(
20    |item| {
21      let c = item.as_char();
22      !is_hex_digit(c)
23    },
24    ErrorKind::HexDigit,
25  )
26}
27
28#[inline]
29fn p_code_point<T, E: ParseError<T>>(input: T) -> IResult<T, T, E>
30where
31  T: InputTakeAtPosition,
32  <T as InputTakeAtPosition>::Item: AsChar,
33{
34  input.split_at_position1_complete(
35    |item| {
36      let c = item.as_char();
37      !(is_unreserved(c) || is_sub_delims(c) || c == ':')
38    },
39    ErrorKind::Char,
40  )
41}
42
43// "v" 1*HEXDIG "." 1*( unreserved / sub-delims / ":" )
44#[inline]
45pub(crate) fn ipv_future(i: Elms) -> UResult<Elms, String> {
46  map(
47    tuple((
48      preceded(
49        complete::char('v'),
50        terminated(hd_code_point, complete::char('.')),
51      ),
52      p_code_point,
53    )),
54    |(k, m): (Elms, Elms)| {
55      let ks = k.as_str().unwrap();
56      let ms = m.as_str().unwrap();
57      format!("v{}.{}", ks, ms)
58    },
59  )(i)
60}
61
62#[inline]
63pub(crate) fn reg_name(i: Elms) -> UResult<Elms, String> {
64  context(
65    "reg_name",
66    map(
67      many0(alt((
68        map(unreserved, |c| c.into()),
69        pct_encoded,
70        map(sub_delims, |c| c.into()),
71      ))),
72      |sl| sl.into_iter().collect(),
73    ),
74  )(i)
75}
76
77#[inline]
78pub(crate) fn ip_literal(i: Elms) -> UResult<Elms, String> {
79  context(
80    "ip_literal",
81    map(
82      delimited(
83        complete::char('['),
84        alt((ipv_future, ipv6_address_parsers::ipv6_address)),
85        complete::char(']'),
86      ),
87      |s| format!("[{}]", s),
88    ),
89  )(i)
90}
91
92#[inline]
93pub fn host_name(i: Elms) -> UResult<Elms, HostName> {
94  map(
95    context(
96      "host",
97      alt((ip_literal, ipv4_address_parsers::ipv4_address, reg_name)),
98    ),
99    |s| HostName::new(s),
100  )(i)
101}
102
103#[cfg(test)]
104pub mod gens {
105  use prop_check_rs::gen::{Gen, Gens};
106
107  use crate::parser::parsers::basic_parsers::gens::*;
108  use crate::parser::parsers::ipv4_address_parsers::gens::*;
109  use crate::parser::parsers::ipv6_address_parsers::gens::*;
110
111  pub fn reg_name_str_gen() -> Gen<String> {
112    rep_str_gen(1, 10, || {
113      Gens::choose_u8(1, 3).bind(|n| match n {
114        1 => unreserved_char_gen().fmap(|c| c.into()),
115        2 => sub_delims_char_gen().fmap(|c| c.into()),
116        3 => pct_encoded_str_gen(),
117        x => panic!("x = {}", x),
118      })
119    })
120  }
121
122  pub fn ipv_future_str_gen() -> Gen<String> {
123    let a = || rep_char_gen(5, || hex_digit_char_gen());
124    let b = || {
125      rep_char_gen(5, || {
126        Gens::choose_u8(1, 3).bind(|n| match n {
127          1 => unreserved_char_gen(),
128          2 => sub_delims_char_gen(),
129          3 => Gen::<char>::unit(|| ':'),
130          x => panic!("x = {}", x),
131        })
132      })
133    };
134    a().bind(move |s1| b().fmap(move |s2| format!("v{}.{}", s1, s2)))
135  }
136
137  pub fn ip_literal_str_gen() -> Gen<String> {
138    Gens::choose_u8(1, 2)
139      .bind(|n| match n {
140        1 => ipv6_address_str_gen(),
141        2 => ipv_future_str_gen(),
142        x => panic!("x = {}", x),
143      })
144      .fmap(|s| format!("[{}]", s))
145  }
146
147  pub fn host_gen() -> Gen<String> {
148    Gens::choose_u8(1, 3).bind(|n| match n {
149      1 => ip_literal_str_gen(),
150      2 => ipv4_address_str_gen(),
151      3 => reg_name_str_gen(),
152      x => panic!("x = {}", x),
153    })
154  }
155}
156
157#[cfg(test)]
158mod tests {
159  use std::env;
160
161  use anyhow::Result;
162  use prop_check_rs::prop;
163  use prop_check_rs::prop::TestCases;
164  use prop_check_rs::rng::RNG;
165
166  use super::*;
167  use super::gens::*;
168
169  const TEST_COUNT: TestCases = 100;
170
171  fn init() {
172    env::set_var("RUST_LOG", "debug");
173    let _ = env_logger::builder().is_test(true).try_init();
174  }
175
176  #[test]
177  fn test_ip_literal() -> Result<()> {
178    init();
179    let mut counter = 0;
180    let prop = prop::for_all(
181      || ip_literal_str_gen(),
182      move |s| {
183        counter += 1;
184        log::debug!("{}, ip_literal = {}", counter, s);
185        let (_, r) = ip_literal(Elms::new(s.as_bytes())).ok().unwrap();
186        assert_eq!(r, s);
187        true
188      },
189    );
190    prop::test_with_prop(prop, 5, TEST_COUNT, RNG::new())
191  }
192
193  #[test]
194  fn test_ipv_future() -> Result<()> {
195    init();
196    let mut counter = 0;
197    let prop = prop::for_all(
198      || ipv_future_str_gen(),
199      move |s| {
200        counter += 1;
201        log::debug!("{}, ipv_future = {}", counter, s);
202        let (_, r) = ipv_future(Elms::new(s.as_bytes())).ok().unwrap();
203        assert_eq!(r, s);
204        true
205      },
206    );
207    prop::test_with_prop(prop, 5, TEST_COUNT, RNG::new())
208  }
209
210  #[test]
211  fn test_reg_name() -> Result<()> {
212    init();
213    let mut counter = 0;
214    let prop = prop::for_all(
215      || reg_name_str_gen(),
216      move |s| {
217        counter += 1;
218        log::debug!("{}, reg_name = {}", counter, s);
219        let (_, reg_name) = reg_name(Elms::new(s.as_bytes())).ok().unwrap();
220        assert_eq!(reg_name.to_string(), s);
221        true
222      },
223    );
224    prop::test_with_prop(prop, 5, TEST_COUNT, RNG::new())
225  }
226
227  #[test]
228  fn test_host() -> Result<()> {
229    init();
230    let mut counter = 0;
231    let prop = prop::for_all(
232      || host_gen(),
233      move |s| {
234        counter += 1;
235        log::debug!("{}, host_name = {}", counter, s);
236        let (_, host_name) = host_name(Elms::new(s.as_bytes())).ok().unwrap();
237        assert_eq!(host_name.to_string(), s);
238        true
239      },
240    );
241    prop::test_with_prop(prop, 5, TEST_COUNT, RNG::new())
242  }
243}