1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
use crate::models::query::Query;
use crate::parsers::basic_parsers::pchar_without_eq_amp;
use oni_comb_parser_rs::prelude::*;

//  query         = *( pchar / "/" / "?" )
pub fn query<'a>() -> Parser<'a, u8, Query> {
  let code_point = || {
    (pchar_without_eq_amp() | elm_of(b"/?").collect())
      .of_many0()
      .collect()
      .map(|e| e.to_vec())
      .map_res(String::from_utf8)
  };
  let key_values = || (code_point() + (elm(b'=') * code_point()).opt());
  (key_values() + (elm(b'&') * key_values()).of_many0()).map(|(a, b)| {
    let mut m = vec![a];
    m.extend(b);
    Query::new(m)
  })
}

#[cfg(test)]
pub mod gens {
  use prop_check_rs::gen::{Gen, Gens};

  use crate::parsers::basic_parsers::gens::*;

  fn sub_delims_without_gen_of_char() -> Gen<char> {
    Gens::one_of_vec(vec!['!', '$', '\'', '(', ')', '*', '+', ',', ';'])
  }

  fn sub_delims_without_gen(len: u8) -> Gen<String> {
    repeat_gen_of_char(len, sub_delims_without_gen_of_char())
  }

  pub fn pchar_without_eq_amp_gen(min: u8, max: u8) -> Gen<String> {
    repeat_gen_of_string(min, max, {
      Gens::choose_u8(1, 4).flat_map(|n| match n {
        1 => unreserved_gen_of_char().map(|c| c.into()),
        2 => pct_encoded_gen(),
        3 => sub_delims_without_gen_of_char().map(|c| c.into()),
        4 => Gens::one_of_vec(vec![':', '@']).map(|c| c.into()),
        x => panic!("x = {}", x),
      })
    })
  }

  pub fn query_gen() -> Gen<String> {
    Gens::list_of_n(3, {
      pchar_without_eq_amp_gen(1, 10).flat_map(|key| {
        Gens::list_of_n(2, pchar_without_eq_amp_gen(1, 10)).map(move |vl| {
          let kvl = vl.into_iter().map(|v| format!("{}={}", key, v)).collect::<Vec<_>>();
          kvl.join("&")
        })
      })
    })
    .map(|v| v.join("&"))
  }
}

#[cfg(test)]
mod tests {
  use std::env;

  use anyhow::Result;

  use prop_check_rs::prop;
  use prop_check_rs::prop::TestCases;
  use prop_check_rs::rng::RNG;

  use super::gens::*;
  use super::*;

  const TEST_COUNT: TestCases = 100;

  fn init() {
    env::set_var("RUST_LOG", "debug");
    let _ = env_logger::builder().is_test(true).try_init();
  }

  #[test]
  fn test_pchar_without_eq_amp() -> Result<()> {
    init();
    let mut counter = 0;
    let prop = prop::for_all(pchar_without_eq_amp_gen(1, u8::MAX - 1), move |s| {
      counter += 1;
      log::debug!("{:>03}, query:string = {}", counter, s);
      let input = s.as_bytes();
      let result = (query() - end()).parse(input).to_result();
      let query = result.unwrap();
      log::debug!("{:>03}, query:object = {:?}", counter, query);
      assert_eq!(query.to_string(), s);
      true
    });
    prop::test_with_prop(prop, 5, TEST_COUNT, RNG::new())
  }

  #[test]
  fn test_query() -> Result<()> {
    init();
    let mut counter = 0;
    let prop = prop::for_all(query_gen(), move |s| {
      counter += 1;
      log::debug!("{:>03}, query:string = {}", counter, s);
      let input = s.as_bytes();
      let result = (query() - end()).parse(input).to_result();
      let query = result.unwrap();
      log::debug!("{:>03}, query:object = {:?}", counter, query);
      assert_eq!(query.to_string(), s);
      true
    });
    prop::test_with_prop(prop, 5, TEST_COUNT, RNG::new())
  }
}