oni_comb_uri_rs/parsers/
path_parsers.rs

1use crate::models::path::Path;
2use crate::parsers::basic_parsers::*;
3use oni_comb_parser_rs::prelude::*;
4
5//  path          = path-abempty    ; begins with "/" or is empty
6//                / path-absolute   ; begins with "/" but not "//"
7//                / path-noscheme   ; begins with a non-colon segment
8//                / path-rootless   ; begins with a segment
9//                / path-empty      ; zero characters
10pub fn path<'a>() -> Parser<'a, u8, Option<Path>> {
11  (path_rootless().attempt() | path_abempty(true).attempt() | path_absolute().attempt() | path_noscheme())
12    .opt()
13    .name("path")
14}
15
16//  path-abempty  = *( "/" segment )
17pub fn path_abempty<'a>(required: bool) -> Parser<'a, u8, Path> {
18  let n = if required { 1 } else { 0 };
19  ((elm(b'/') + segment()).collect())
20    .map(|e| e.to_vec())
21    .map_res(String::from_utf8)
22    .repeat(n..)
23    .map(|e| Path::of_abempty_from_strings(&e))
24    .name("path-abempty")
25}
26
27//  path-absolute = "/" [ segment-nz *( "/" segment ) ]
28pub fn path_absolute<'a>() -> Parser<'a, u8, Path> {
29  let p = (seqment_nz() + ((elm(b'/') + segment()).collect()).of_many0())
30    .map(|(a, b)| {
31      let mut l = vec![a];
32      l.extend_from_slice(&b);
33      l
34    })
35    .opt();
36  (elm(b'/').collect() + p)
37    .map(|(a, b_opt)| match b_opt {
38      None => vec![a],
39      Some(b) => {
40        let mut l = vec![a];
41        l.extend_from_slice(&b);
42        l
43      }
44    })
45    .map(|e| {
46      e.into_iter()
47        .map(|e| e.to_vec())
48        .map(|v| String::from_utf8(v).unwrap())
49        .collect::<Vec<_>>()
50    })
51    .map(|e| Path::of_absolute_from_strings(&e))
52    .name("path-absolute")
53}
54
55//  path-rootless = segment-nz *( "/" segment )
56pub fn path_rootless<'a>() -> Parser<'a, u8, Path> {
57  (seqment_nz() + ((elm(b'/') + segment()).collect()).of_many0())
58    .map(|(a, b)| {
59      let mut l = vec![a];
60      l.extend_from_slice(&b);
61      l
62    })
63    .map(|e| {
64      e.into_iter()
65        .map(|e| e.to_vec())
66        .map(|v| String::from_utf8(v).unwrap())
67        .collect::<Vec<_>>()
68    })
69    .map(|e| Path::of_rootless_from_strings(&e))
70    .name("path-rootless")
71}
72
73//  path-noscheme = segment-nz-nc *( "/" segment )
74pub fn path_noscheme<'a>() -> Parser<'a, u8, Path> {
75  (seqment_nz_nc() + ((elm(b'/') + segment()).collect()).of_many0())
76    .map(|(a, b)| {
77      let mut l = vec![a];
78      l.extend_from_slice(&b);
79      l
80    })
81    .map(|e| {
82      e.into_iter()
83        .map(|e| e.to_vec())
84        .map(|v| String::from_utf8(v).unwrap())
85        .collect::<Vec<_>>()
86    })
87    .map(|e| Path::of_rootless_from_strings(&e))
88    .name("path-noscheme")
89}
90
91// segment       = *pchar
92fn segment<'a>() -> Parser<'a, u8, &'a [u8]> {
93  pchar().of_many0().collect().name("segment")
94}
95
96// segment-nz    = 1*pchar
97fn seqment_nz<'a>() -> Parser<'a, u8, &'a [u8]> {
98  pchar().of_many1().collect().name("segment-nz")
99}
100
101// segment-nz-nc = 1*( unreserved / pct-encoded / sub-delims / "@" )
102// ; non-zero-length segment without any colon ":"
103fn seqment_nz_nc<'a>() -> Parser<'a, u8, &'a [u8]> {
104  (unreserved() | pct_encoded() | sub_delims() | elm(b'@').collect())
105    .of_many1()
106    .collect()
107    .name("segment-nz-nc")
108}
109
110#[cfg(test)]
111pub mod gens {
112  use std::fmt::Formatter;
113
114  use prop_check_rs::gen::{Gen, Gens};
115
116  use crate::parsers::basic_parsers::gens::*;
117
118  pub fn segment_gen() -> Gen<String> {
119    pchar_gen(0, u8::MAX - 1)
120  }
121
122  pub fn segment_nz_gen() -> Gen<String> {
123    pchar_gen(1, u8::MAX - 1)
124  }
125
126  pub fn segment_nz_nc_gen() -> Gen<String> {
127    repeat_gen_of_string(1, u8::MAX - 1, {
128      Gens::frequency([
129        (1, unreserved_gen_of_char().map(|c| c.into())),
130        (1, pct_encoded_gen()),
131        (1, sub_delims_gen_of_char().map(|c| c.into())),
132        (1, Gens::pure('@').map(|c| c.into())),
133      ])
134    })
135  }
136
137  pub fn path_abempty_gen() -> Gen<String> {
138    repeat_gen_of_string(1, 10, segment_gen().map(|s| format!("/{}", s)))
139  }
140
141  pub fn path_absolute_gen() -> Gen<String> {
142    repeat_gen_of_string(1, 10, segment_nz_gen().map(|s| format!("/{}", s))).flat_map(|s1| {
143      path_abempty_gen().map(move |s2| {
144        let prefix = if !s1.starts_with("/") { "/" } else { "" };
145        format!("{}{}{}", prefix, s1, s2)
146      })
147    })
148  }
149
150  pub fn path_no_scheme_gen() -> Gen<String> {
151    segment_nz_nc_gen().flat_map(|s1| {
152      repeat_gen_of_string(1, 10, segment_gen().map(|s2| format!("/{}", s2))).map(move |s2| format!("{}{}", s1, s2))
153    })
154  }
155
156  pub fn path_rootless_gen() -> Gen<String> {
157    segment_nz_gen().flat_map(|s1| {
158      repeat_gen_of_string(1, 10, segment_gen().map(|s2| format!("/{}", s2))).map(move |s2| format!("{}{}", s1, s2))
159    })
160  }
161
162  #[derive(Clone, Debug)]
163  pub struct Pair<A, B>(pub(crate) A, pub(crate) B);
164
165  impl<A, B> std::fmt::Display for Pair<A, B>
166  where
167    A: std::fmt::Display,
168    B: std::fmt::Display,
169  {
170    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
171      write!(f, "({},{})", self.0, self.1)
172    }
173  }
174
175  pub fn path_with_abempty_gen() -> Gen<Pair<String, String>> {
176    Gens::frequency([
177      (1, path_abempty_gen().map(|s| Pair("abempty_path".to_string(), s))),
178      (1, path_absolute_gen().map(|s| Pair("absolute_path".to_string(), s))),
179      (1, path_no_scheme_gen().map(|s| Pair("no_scheme_path".to_string(), s))),
180      (1, path_rootless_gen().map(|s| Pair("rootless_path".to_string(), s))),
181      (1, Gens::pure(Pair("empty_path".to_string(), "".to_string()))),
182    ])
183  }
184
185  pub fn path_str_without_abempty_gen() -> Gen<Pair<String, String>> {
186    Gens::frequency([
187      (1, path_absolute_gen().map(|s| Pair("absolute_path".to_string(), s))),
188      (1, path_rootless_gen().map(|s| Pair("rootless_path".to_string(), s))),
189      (1, Gens::pure(Pair("empty_path".to_string(), "".to_string()))),
190    ])
191  }
192}
193
194#[cfg(test)]
195mod tests {
196  use super::*;
197  use anyhow::Result;
198  use prop_check_rs::prop;
199  use prop_check_rs::prop::TestCases;
200  use prop_check_rs::rng::RNG;
201  use std::env;
202
203  const TEST_COUNT: TestCases = 100;
204
205  #[ctor::ctor]
206  fn init_logger() {
207    env::set_var("RUST_LOG", "debug");
208    let _ = env_logger::builder().is_test(true).try_init();
209  }
210
211  #[test]
212  fn test_path() -> Result<()> {
213    let mut counter = 0;
214    let prop = prop::for_all_gen(gens::path_with_abempty_gen(), move |s| {
215      counter += 1;
216      log::debug!("{:>03}, path_str_with_abempty:string = {}", counter, s);
217      let input = s.1.as_bytes();
218      let result = (path() - end()).parse(input).to_result();
219      let path = result.unwrap();
220      log::debug!("{:>03}, path_str_with_abempty:object = {:?}", counter, path);
221      assert_eq!(path.map(|e| e.to_string()).unwrap_or("".to_string()), s.1);
222      true
223    });
224    prop::test_with_prop(prop, 5, TEST_COUNT, RNG::new())
225  }
226
227  #[test]
228  fn test_path_abempty() -> Result<()> {
229    let mut counter = 0;
230    let prop = prop::for_all_gen(gens::path_abempty_gen(), move |s| {
231      counter += 1;
232      log::debug!("{:>03}, path_abempty:string = {}", counter, s);
233      let input = s.as_bytes();
234      let result = (path_abempty(true) - end()).parse(input).to_result();
235      let path = result.unwrap();
236      log::debug!("{:>03}, path_abempty:object = {:?}", counter, path);
237      assert_eq!(path.to_string(), s);
238      true
239    });
240    prop::test_with_prop(prop, 5, TEST_COUNT, RNG::new())
241  }
242
243  #[test]
244  fn test_path_absolute() -> Result<()> {
245    let mut counter = 0;
246    let prop = prop::for_all_gen(gens::path_absolute_gen(), move |s| {
247      counter += 1;
248      log::debug!("{:>03}, path_absolute:string = {}", counter, s);
249      let input = s.as_bytes();
250      let result = (path_absolute() - end()).parse(input).to_result();
251      let path = result.unwrap();
252      log::debug!("{:>03}, path_absolute:object = {:?}", counter, path);
253      assert_eq!(path.to_string(), s);
254      true
255    });
256    prop::test_with_prop(prop, 5, TEST_COUNT, RNG::new())
257  }
258
259  #[test]
260  fn test_path_noscheme() -> Result<()> {
261    let mut counter = 0;
262    let prop = prop::for_all_gen(gens::path_no_scheme_gen(), move |s| {
263      counter += 1;
264      log::debug!("{:>03}, path_noscheme:string = {}", counter, s);
265      let input = s.as_bytes();
266      let result = (path_noscheme() - end()).parse(input).to_result();
267      let path = result.unwrap();
268      log::debug!("{:>03}, path_noscheme:object = {:?}", counter, path);
269      assert_eq!(path.to_string(), s);
270      true
271    });
272    prop::test_with_prop(prop, 5, TEST_COUNT, RNG::new())
273  }
274
275  #[test]
276  fn test_path_rootless() -> Result<()> {
277    let mut counter = 0;
278    let prop = prop::for_all_gen(gens::path_rootless_gen(), move |s| {
279      counter += 1;
280      log::debug!("{:>03}, path_rootless:string = {}", counter, s);
281      let input = s.as_bytes();
282      let result = (path_rootless() - end()).parse(input).to_result();
283      let path = result.unwrap();
284      log::debug!("{:>03}, path_rootless:object = {:?}", counter, path);
285      assert_eq!(path.to_string(), s);
286      true
287    });
288    prop::test_with_prop(prop, 5, TEST_COUNT, RNG::new())
289  }
290}