proptest_http_message/request_line/target/
origin_form.rs

1//! HTTP request target in origin form strategies.
2
3use std::{num::NonZero, ops::RangeInclusive};
4
5use proptest::prelude::Strategy;
6
7use super::components::{
8  path::{Path, path_absolute},
9  query::{QueryParam, query},
10};
11
12/// URL origin form components
13#[derive(Debug)]
14pub struct OriginForm {
15  pub path: Path,
16  pub query: Option<Vec<QueryParam>>,
17}
18
19/// strategy for generating target origin form.
20/// # Returns
21/// [`OriginForm`] and its representation.
22pub fn origin(
23  max_segments: NonZero<usize>,
24  query_count_range: RangeInclusive<usize>,
25) -> impl Strategy<Value = (OriginForm, String)> {
26  (path_absolute(max_segments), query(*query_count_range.start(), *query_count_range.end()))
27    .prop_map(|((path, path_repr), (query, query_repr))| {
28      if query.is_empty() {
29        (OriginForm { path, query: None }, path_repr)
30      } else {
31        let repr = format!("{path_repr}?{query_repr}");
32        (OriginForm { path, query: Some(query) }, repr)
33      }
34    })
35}
36
37#[cfg(test)]
38pub(super) mod tests {
39  use std::sync::LazyLock;
40
41  use claims::assert_ok;
42  use proptest::proptest;
43  use url::Url;
44
45  use super::*;
46
47  const DUMMY_BASE_URL: &str = "https://example.com";
48  static BASE_URL: LazyLock<Url> = LazyLock::new(|| {
49    Url::parse(DUMMY_BASE_URL).unwrap_or_else(|_| panic!("{DUMMY_BASE_URL:?} is a valid base URL"))
50  });
51
52  pub(in super::super) fn origin_asserts(origin: &OriginForm, repr: &str) {
53    let url = assert_ok!(BASE_URL.join(repr));
54
55    assert_eq!(
56      origin.path.normalized,
57      url.path(),
58      "expected path {:?} but parsed {}",
59      origin.path.normalized,
60      url.path()
61    );
62
63    match (origin.query.as_deref(), url.query()) {
64      (None, None) => {}
65      (Some(query), Some(query2)) => {
66        let query = query
67          .iter()
68          .map(|query| format!("{}={}", query.key, query.value.as_deref().unwrap_or_default()))
69          .collect::<Vec<_>>()
70          .join("&");
71        assert_eq!(query, query2, "expected query {query:?} but parsed {query2:?}");
72      }
73      (query, query2) => panic!("expected query {query:?} but got {query2:?}"),
74    }
75  }
76
77  proptest! {
78    #[test]
79    fn origin_works((origin, repr) in origin(50.try_into().unwrap(), 0..=20)) {
80      origin_asserts(&origin, &repr);
81    }
82  }
83}