proptest_http_message/request_line/target/components/
query.rs1use std::{ops::RangeInclusive, sync::LazyLock};
4
5use array_concat::{concat_arrays, concat_arrays_size};
6use proptest::prelude::Strategy;
7
8use crate::request_line::target::components::{
9 UNRESERVED, UrlChar, char_diff_intervals, safe_and_percent_encoded_char, url_chars_to_string,
10};
11
12static QUERY_UNSAFE_CHARS: LazyLock<Vec<RangeInclusive<char>>> =
13 LazyLock::new(|| char_diff_intervals(&QUERY_SAFE_CHARS));
14
15const QUERY_SAFE_CHARS: [char; concat_arrays_size!(UNRESERVED) + 5] =
21 concat_arrays!(UNRESERVED, [':', '@', '/', '?', ' ']);
22
23fn chars() -> impl Strategy<Value = UrlChar> {
24 safe_and_percent_encoded_char(&QUERY_SAFE_CHARS, &QUERY_UNSAFE_CHARS).prop_map(|c| {
25 if let UrlChar::Normal(c) = c
26 && c == ' '
27 {
28 UrlChar::Normal('+')
30 } else {
31 c
32 }
33 })
34}
35
36fn query_subcomponent(min_chars: usize, max_chars: usize) -> impl Strategy<Value = String> {
37 proptest::collection::vec(chars(), min_chars..=max_chars).prop_map(url_chars_to_string)
38}
39
40#[derive(Debug)]
42pub struct QueryParam {
43 pub key: String,
45 pub value: Option<String>,
47}
48
49pub fn query_param() -> impl Strategy<Value = (QueryParam, String)> {
53 (query_subcomponent(0, 50), query_subcomponent(0, 50)).prop_map(|(key, value)| {
54 let repr = format!("{key}={value}");
55 (QueryParam { key, value: if value.is_empty() { None } else { Some(value) } }, repr)
56 })
57}
58
59pub fn query(
64 min_queries: usize,
65 max_queries: usize,
66) -> impl Strategy<Value = (Vec<QueryParam>, String)> {
67 proptest::collection::vec(query_param(), min_queries..=max_queries).prop_map(|params| {
68 let (params, reprs): (Vec<_>, Vec<_>) = params.into_iter().unzip();
69 (params, reprs.join("&"))
70 })
71}
72
73#[cfg(test)]
74mod tests {
75 use proptest::proptest;
76
77 use super::*;
78
79 proptest! {
80 #[test]
81 fn query_param_works((param, repr) in query_param()) {
82 println!("{repr:?}");
83 assert!(repr.starts_with(param.key.as_str()), "param should start with key but got {param:?} {repr:?}");
84 assert!(repr.ends_with(param.value.as_deref().unwrap_or_default()), "param should end with value but got {param:?} {repr:?}");
85 }
86 }
87}