uri_parsing_rs/parser/parsers/
query_parsers.rs1use nom::branch::alt;
2use nom::character::complete;
3use nom::combinator::{map, opt};
4use nom::error::context;
5use nom::multi::many0;
6use nom::sequence::{preceded, tuple};
7
8use crate::ast::query::Query;
9use crate::parser::parsers::{Elms, UResult};
10use crate::parser::parsers::basic_parsers::pchar_without_eq_and;
11
12#[inline]
13fn code_point(i: Elms) -> UResult<Elms, String> {
14 map(
15 many0(alt((
16 pchar_without_eq_and,
17 map(complete::char('/'), |c| c.into()),
18 map(complete::char('?'), |c| c.into()),
19 ))),
20 |s| s.into_iter().collect(),
21 )(i)
22}
23
24#[inline]
26pub fn query(i: Elms) -> UResult<Elms, Query> {
27 let key_values = || tuple((code_point, opt(preceded(complete::char('='), code_point))));
28 context(
29 "query",
30 map(
31 tuple((
32 key_values(),
33 many0(preceded(complete::char('&'), key_values())),
34 )),
35 |(head, tail)| {
36 let mut m = vec![head];
37 m.extend(tail);
38 Query::new(m)
39 },
40 ),
41 )(i)
42}
43
44#[cfg(test)]
45pub mod gens {
46 use itertools::Itertools;
47 use prop_check_rs::gen::{Gen, Gens};
48
49 use crate::parser::parsers::basic_parsers::gens::*;
50
51 fn sub_delims_without_char_gen() -> Gen<char> {
52 Gens::one_of_vec(vec!['!', '$', '\'', '(', ')', '*', '+', ',', ';'])
53 }
54
55 fn sub_delims_without_str_gen(len: u8) -> Gen<String> {
56 rep_char_gen(len, || sub_delims_without_char_gen())
57 }
58
59 pub fn pchar_without_eq_and_str_gen(min: u8, max: u8) -> Gen<String> {
60 rep_str_gen(min, max, || {
61 Gens::choose_u8(1, 4).bind(|n| match n {
62 1 => unreserved_char_gen().fmap(|c| c.into()),
63 2 => pct_encoded_str_gen(),
64 3 => sub_delims_without_char_gen().fmap(|c| c.into()),
65 4 => Gens::one_of_vec(vec![':', '@']).fmap(|c| c.into()),
66 x => panic!("x = {}", x),
67 })
68 })
69 }
70
71 pub fn query_gen() -> Gen<String> {
72 Gens::list_of_n(3, move || {
73 pchar_without_eq_and_str_gen(1, 10).bind(|key| {
74 Gens::list_of_n(2, || pchar_without_eq_and_str_gen(1, 10)).fmap(move |vl| {
75 let kvl = vl
76 .into_iter()
77 .map(|v| format!("{}={}", key, v))
78 .collect_vec();
79 kvl.join("&")
80 })
81 })
82 })
83 .fmap(|v| v.join("&"))
84 }
85}
86
87#[cfg(test)]
88mod tests {
89 use std::env;
90
91 use anyhow::Result;
92
93 use nom::multi::many1;
94
95 use prop_check_rs::prop;
96 use prop_check_rs::prop::TestCases;
97 use prop_check_rs::rng::RNG;
98
99 use crate::parser::parsers::Elms;
100
101 use super::*;
102 use super::gens::*;
103
104 const TEST_COUNT: TestCases = 100;
105
106 fn init() {
107 env::set_var("RUST_LOG", "debug");
108 let _ = env_logger::builder().is_test(true).try_init();
109 }
110
111 #[test]
112 fn test_pchar_without_eq_and() -> Result<()> {
113 init();
114 let mut counter = 0;
115 let prop = prop::for_all(
116 || pchar_without_eq_and_str_gen(1, u8::MAX - 1),
117 move |s| {
118 counter += 1;
119 log::debug!("{:>03}, value = {}", counter, s);
120 let (_, r) = many1(pchar_without_eq_and)(Elms::new(s.as_bytes()))
121 .ok()
122 .unwrap();
123 r.into_iter().collect::<String>() == s
124 },
125 );
126 prop::test_with_prop(prop, 5, TEST_COUNT, RNG::new())
127 }
128
129 #[test]
130 fn test_query() -> Result<()> {
131 init();
132 let mut counter = 0;
133 let prop = prop::for_all(
134 || query_gen(),
135 move |s| {
136 counter += 1;
137 log::debug!("{:>03}, query = {}", counter, s);
138 let (_, query) = query(Elms::new(s.as_bytes())).ok().unwrap();
139 log::debug!("as_string = {:?}", query.as_string());
140 let params = query.params();
141 log::debug!("params = {:?}", params);
142 true
143 },
144 );
145 prop::test_with_prop(prop, 5, TEST_COUNT, RNG::new())
146 }
147}