1use crate::models::path::Path;
2use crate::parsers::basic_parsers::*;
3use oni_comb_parser_rs::prelude::*;
4
5pub 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
16pub 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
27pub 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
55pub 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
73pub 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
91fn segment<'a>() -> Parser<'a, u8, &'a [u8]> {
93 pchar().of_many0().collect().name("segment")
94}
95
96fn seqment_nz<'a>() -> Parser<'a, u8, &'a [u8]> {
98 pchar().of_many1().collect().name("segment-nz")
99}
100
101fn 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}