uri_parsing_rs/parser/parsers/
path_parsers.rs

1use nom::branch::alt;
2use nom::character::complete;
3use nom::combinator::{eof, map, not, opt, value};
4use nom::error::context;
5use nom::multi::{many0, many1};
6use nom::sequence::{preceded, tuple};
7
8use crate::ast::path::Path;
9use crate::parser::parsers::{Elms, UResult};
10use crate::parser::parsers::basic_parsers::*;
11
12#[inline]
13pub(crate) fn segment(i: Elms) -> UResult<Elms, String> {
14  map(many0(pchar), |sl| sl.into_iter().collect())(i)
15}
16
17#[inline]
18fn segment_without_colon(i: Elms) -> UResult<Elms, String> {
19  map(many0(pchar_without_colon), |sl| sl.into_iter().collect())(i)
20}
21
22#[inline]
23pub(crate) fn segment_nz(i: Elms) -> UResult<Elms, String> {
24  map(many1(pchar), |sl| sl.into_iter().collect())(i)
25}
26
27#[inline]
28pub(crate) fn segment_nz_nc(i: Elms) -> UResult<Elms, String> {
29  map(
30    many1(alt((
31      // ALPHA / DIGIT / "-" / "." / "_" / "~"
32      map(unreserved, |c| c.into()),
33      pct_encoded,
34      // "!" / "$" / "&" / "'" / "(" / ")" / "*" / "+" / "," / ";" / "
35      map(sub_delims, |c| c.into()),
36      map(complete::char('@'), |c| c.into()),
37    ))),
38    |sl| sl.into_iter().collect(),
39  )(i)
40}
41
42#[inline]
43pub(crate) fn path_abempty(i: Elms) -> UResult<Elms, Path> {
44  map(many0(preceded(complete::char('/'), segment)), |sl| {
45    Path::of_abempty_from_strings(&sl)
46  })(i)
47}
48
49#[inline]
50pub(crate) fn path_absolute(i: Elms) -> UResult<Elms, Path> {
51  context(
52    "path_absolute",
53    map(
54      preceded(
55        complete::char('/'),
56        opt(map(
57          tuple((segment_nz, many0(preceded(complete::char('/'), segment)))),
58          |(s, sl)| {
59            let mut r = vec![s];
60            r.extend(sl);
61            r
62          },
63        )),
64      ),
65      |sl_opt| Path::of_absolute_from_strings(&sl_opt.unwrap_or(Vec::new())),
66    ),
67  )(i)
68}
69
70pub(crate) fn path_no_scheme(i: Elms) -> UResult<Elms, Path> {
71  log::debug!("path_no_scheme = {}", i.as_str().unwrap());
72  let result = context(
73    "path_no_scheme",
74    map(
75      tuple((segment_nz_nc, many0(preceded(complete::char('/'), segment)))),
76      |(s, sl)| {
77        let mut parts = vec![s];
78        parts.extend(sl);
79        Path::of_no_scheme_from_strings(&parts)
80      },
81    ),
82  )(i);
83  match result {
84    Ok((p1, p2)) => {
85      log::debug!("p1 = {}", p1);
86      log::debug!("p2 = {}", p2);
87      Ok((p1, p2))
88    }
89    Err(e) => {
90      log::debug!("e = {}", e);
91      Err(e)
92    }
93  }
94}
95
96pub(crate) fn path_rootless(i: Elms) -> UResult<Elms, Path> {
97  context(
98    "path_rootless",
99    map(
100      tuple((segment_nz, many0(preceded(complete::char('/'), segment)))),
101      |(s, sl)| {
102        let mut parts = vec![s];
103        parts.extend(sl);
104        Path::of_rootless_from_strings(&parts)
105      },
106    ),
107  )(i)
108}
109
110#[inline]
111pub(crate) fn path_empty(i: Elms) -> UResult<Elms, Path> {
112  context("path_empty", value(Path::of_empty(), eof))(i)
113}
114
115#[inline]
116pub(crate) fn path_without_abempty(i: Elms) -> UResult<Elms, Path> {
117  let is_absolute = opt(preceded(complete::char('/'), not(complete::char('/'))))(i.clone())
118    .map(|(_, v)| v.is_some())?;
119  let is_no_scheme =
120    opt(segment_nz_nc)(i.clone()).map(|(_, v)| v.iter().any(|s| !s.contains(':')))?;
121  let is_empty = opt(eof)(i.clone()).map(|(_, v)| v.is_some())?;
122
123  log::debug!("is_absolute = {}", is_absolute);
124  log::debug!("is_no_scheme = {}", is_no_scheme);
125  log::debug!("is_empty = {}", is_empty);
126
127  if is_empty {
128    path_empty(i.clone())
129  } else {
130    if is_absolute {
131      path_absolute(i)
132    // } else if is_no_scheme {
133    //   path_no_scheme(i)
134    } else {
135      path_rootless(i)
136    }
137  }
138}
139
140#[cfg(test)]
141pub mod gens {
142  use std::fmt::Formatter;
143
144  use prop_check_rs::gen::{Gen, Gens};
145
146  use crate::parser::parsers::basic_parsers::gens::*;
147
148  pub fn segment_str_gen() -> Gen<String> {
149    pchar_str_gen(0, u8::MAX - 1)
150  }
151
152  pub fn segment_nz_str_gen() -> Gen<String> {
153    pchar_str_gen(1, u8::MAX - 1)
154  }
155
156  pub fn segment_nz_nc_str_gen() -> Gen<String> {
157    rep_str_gen(1, u8::MAX - 1, || {
158      Gens::choose_u8(1, 2).bind(|n| match n {
159        1 => unreserved_char_gen().fmap(|c| c.into()),
160        2 => pct_encoded_str_gen(),
161        3 => sub_delims_char_gen().fmap(|c| c.into()),
162        4 => Gens::one_of_vec(vec!['@']).fmap(|c| c.into()),
163        x => panic!("x = {}", x),
164      })
165    })
166  }
167
168  pub fn path_abempty_str_gen() -> Gen<String> {
169    rep_str_gen(1, 10, || segment_str_gen().fmap(|s| format!("/{}", s)))
170  }
171
172  pub fn path_absolute_str_gen() -> Gen<String> {
173    rep_str_gen(1, 10, || segment_nz_str_gen().fmap(|s| format!("/{}", s))).bind(|s1| {
174      path_abempty_str_gen().fmap(move |s2| {
175        let prefix = if !s1.starts_with("/") { "/" } else { "" };
176        format!("{}{}{}", prefix, s1, s2)
177      })
178    })
179  }
180
181  pub fn path_no_scheme_str_gen() -> Gen<String> {
182    segment_nz_nc_str_gen().bind(|s1| {
183      rep_str_gen(1, 10, || segment_str_gen().fmap(|s2| format!("/{}", s2)))
184        .fmap(move |s2| format!("{}{}", s1, s2))
185    })
186  }
187
188  pub fn path_rootless_str_gen() -> Gen<String> {
189    segment_nz_str_gen().bind(|s1| {
190      rep_str_gen(1, 10, || segment_str_gen().fmap(|s2| format!("/{}", s2)))
191        .fmap(move |s2| format!("{}{}", s1, s2))
192    })
193  }
194
195  #[derive(Clone, Debug)]
196  pub struct Pair<A, B>(pub(crate) A, pub(crate) B);
197
198  impl<A, B> std::fmt::Display for Pair<A, B>
199  where
200    A: std::fmt::Display,
201    B: std::fmt::Display,
202  {
203    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
204      write!(f, "({},{})", self.0, self.1)
205    }
206  }
207
208  pub fn path_str_with_abempty_gen() -> Gen<Pair<String, String>> {
209    Gens::choose_u8(1, 5).bind(|n| match n {
210      1 => path_abempty_str_gen().fmap(|s| Pair("abempty_path".to_string(), s)),
211      2 => path_absolute_str_gen().fmap(|s| Pair("absolute_path".to_string(), s)),
212      3 => path_no_scheme_str_gen().fmap(|s| Pair("no_scheme_path".to_string(), s)),
213      4 => path_rootless_str_gen().fmap(|s| Pair("rootless_path".to_string(), s)),
214      5 => Gen::<String>::unit(|| Pair("empty_path".to_string(), "".to_string())),
215      x => panic!("x = {}", x),
216    })
217  }
218
219  pub fn path_str_without_abempty_gen() -> Gen<Pair<String, String>> {
220    Gens::choose_u8(1, 3).bind(|n| match n {
221      1 => path_absolute_str_gen().fmap(|s| Pair("absolute_path".to_string(), s)),
222      2 => path_rootless_str_gen().fmap(|s| Pair("rootless_path".to_string(), s)),
223      3 => Gen::<String>::unit(|| Pair("empty_path".to_string(), "".to_string())),
224      x => panic!("x = {}", x),
225    })
226  }
227}
228
229#[cfg(test)]
230mod tests {
231  use std::env;
232
233  use anyhow::Result;
234
235  use prop_check_rs::prop;
236  use prop_check_rs::prop::TestCases;
237  use prop_check_rs::rng::RNG;
238
239  use super::*;
240  use super::gens::*;
241
242  const TEST_COUNT: TestCases = 100;
243
244  fn init() {
245    env::set_var("RUST_LOG", "debug");
246    let _ = env_logger::builder().is_test(true).try_init();
247  }
248
249  #[test]
250  fn test_segment() -> Result<()> {
251    init();
252    let mut counter = 0;
253    let prop = prop::for_all(
254      || segment_str_gen(),
255      move |s| {
256        counter += 1;
257        log::debug!("{}, segment = {}", counter, s);
258        let (_, r) = segment(Elms::new(s.as_bytes())).ok().unwrap();
259        assert_eq!(r, s);
260        true
261      },
262    );
263    prop::test_with_prop(prop, 5, 1000, RNG::new())
264  }
265
266  #[test]
267  fn test_segment_nz() -> Result<()> {
268    init();
269    let mut counter = 0;
270    let prop = prop::for_all(
271      || segment_nz_str_gen(),
272      move |s| {
273        counter += 1;
274        log::debug!("{}, value = {}", counter, s);
275        let (_, r) = segment_nz(Elms::new(s.as_bytes())).ok().unwrap();
276        assert_eq!(r, s);
277        true
278      },
279    );
280    prop::test_with_prop(prop, 5, TEST_COUNT, RNG::new())
281  }
282
283  #[test]
284  fn test_segment_nz_nc() -> Result<()> {
285    init();
286    let mut counter = 0;
287    let prop = prop::for_all(
288      || segment_nz_nc_str_gen(),
289      move |s| {
290        counter += 1;
291        log::debug!("{}, segment_nz_nc = {}", counter, s);
292        let (_, r) = segment_nz_nc(Elms::new(s.as_bytes())).ok().unwrap();
293        assert_eq!(r, s);
294        true
295      },
296    );
297    prop::test_with_prop(prop, 5, TEST_COUNT, RNG::new())
298  }
299
300  #[test]
301  fn test_path_abempty() -> Result<()> {
302    init();
303    let mut counter = 0;
304    let prop = prop::for_all(
305      || path_abempty_str_gen(),
306      move |s| {
307        counter += 1;
308        log::debug!("{:>03}, path_abempty = {}", counter, s);
309        let (_, r) = path_abempty(Elms::new(s.as_bytes())).ok().unwrap();
310        assert_eq!(r.to_string(), s);
311        true
312      },
313    );
314    prop::test_with_prop(prop, 5, TEST_COUNT, RNG::new())
315  }
316
317  #[test]
318  fn test_path_absolute() -> Result<()> {
319    init();
320    let mut counter = 0;
321    let prop = prop::for_all(
322      || path_absolute_str_gen(),
323      move |s| {
324        counter += 1;
325        log::debug!("{:>03}, path_absolute = {}", counter, s);
326        let (_, r) = path_absolute(Elms::new(s.as_bytes())).ok().unwrap();
327        assert_eq!(r.to_string(), s);
328        true
329      },
330    );
331    prop::test_with_prop(prop, 5, TEST_COUNT, RNG::new())
332  }
333
334  #[test]
335  fn test_path_no_scheme() -> Result<()> {
336    init();
337    let mut counter = 0;
338    let prop = prop::for_all(
339      || path_no_scheme_str_gen(),
340      move |s| {
341        counter += 1;
342        log::debug!("{:>03}, path_no_scheme = {}", counter, s);
343        let (_, r) = path_no_scheme(Elms::new(s.as_bytes())).ok().unwrap();
344        assert_eq!(r.to_string(), s);
345        true
346      },
347    );
348    prop::test_with_prop(prop, 5, TEST_COUNT, RNG::new())
349  }
350
351  #[test]
352  fn test_path_rootless() -> Result<()> {
353    init();
354    let mut counter = 0;
355    let prop = prop::for_all(
356      || path_rootless_str_gen(),
357      move |s| {
358        counter += 1;
359        log::debug!("{:>03}, path_rootless = {}", counter, s);
360        let (_, r) = path_rootless(Elms::new(s.as_bytes())).ok().unwrap();
361        assert_eq!(r.to_string(), s);
362        true
363      },
364    );
365    prop::test_with_prop(prop, 5, TEST_COUNT, RNG::new())
366  }
367
368  #[test]
369  fn test_path_without_abempty() -> Result<()> {
370    init();
371    let mut counter = 0;
372    let prop = prop::for_all(
373      || path_str_without_abempty_gen(),
374      move |s| {
375        counter += 1;
376        log::debug!(
377          "{:>03}, path_type = {:?}, path_without_abempty = {:?}",
378          counter,
379          s.0,
380          s.1
381        );
382        let (_, r) = path_without_abempty(Elms::new(s.1.as_bytes()))
383          .ok()
384          .unwrap();
385        assert_eq!(r.type_name(), s.0);
386        assert_eq!(r.to_string(), s.1);
387        true
388      },
389    );
390    prop::test_with_prop(prop, 5, TEST_COUNT, RNG::new())
391  }
392}