proptest_http_message/request_line/target/
absolute_form.rs

1//! HTTP request target in absolute form strategies.
2
3use std::{num::NonZero, ops::RangeInclusive};
4
5use proptest::{option::of, prelude::Strategy};
6
7use crate::request_line::target::components::{
8  authority::{Authority, authority},
9  fragment::fragment,
10  path::{Path, path_absolute},
11  query::{QueryParam, query},
12  scheme::http_scheme,
13};
14
15/// URL absolute form components
16#[derive(Debug)]
17pub struct AbsoluteForm {
18  pub scheme: String,
19  pub authority: Authority,
20  pub path: Option<Path>,
21  pub query: Option<Vec<QueryParam>>,
22  pub fragment: Option<String>,
23}
24
25/// strategy for generating target absolute form.
26pub fn absolute(
27  max_label_count: usize,
28  max_segments: NonZero<usize>,
29  query_count_range: RangeInclusive<usize>,
30) -> impl Strategy<Value = (AbsoluteForm, String)> {
31  (
32    http_scheme(),
33    authority(max_label_count),
34    of(path_absolute(max_segments)),
35    of(query(*query_count_range.start(), *query_count_range.end())),
36    of(fragment()),
37  )
38    .prop_map(|(scheme, (authority, authority_repr), path, query, fragment)| {
39      let repr = format!(
40        "{scheme}://{authority}{path}{query}{fragment}",
41        scheme = scheme,
42        authority = authority_repr,
43        path = if let Some(path) = path.as_ref() { path.1.as_str() } else { "" },
44        query = query.as_ref().map(|(_, query)| format!("?{query}")).as_deref().unwrap_or_default(),
45        fragment =
46          fragment.as_ref().map(|fragment| format!("#{fragment}")).as_deref().unwrap_or_default()
47      );
48
49      (
50        AbsoluteForm {
51          scheme,
52          authority,
53          path: path.map(|p| p.0),
54          query: query.map(|q| q.0),
55          fragment,
56        },
57        repr,
58      )
59    })
60}
61
62#[cfg(test)]
63pub(super) mod tests {
64  use claims::{assert_none, assert_ok};
65  use proptest::proptest;
66  use url::{Host, Url};
67
68  use super::*;
69
70  pub(in super::super) fn absolute_asserts(absolute_form: &AbsoluteForm, repr: &str) {
71    let url = assert_ok!(Url::parse(repr), "should be good URL but got {repr}");
72    assert_eq!(absolute_form.scheme.to_ascii_lowercase(), url.scheme().to_ascii_lowercase());
73    if let Some(user_info) = absolute_form.authority.user_info.as_ref() {
74      assert_eq!(
75        user_info.username,
76        url.username(),
77        "expected to get username {:?} but got {:?}",
78        user_info.username,
79        url.username()
80      );
81      assert_eq!(
82        user_info.password.as_deref(),
83        url.password(),
84        "expected to get password {:?} but got {:?}",
85        user_info.password,
86        url.password()
87      );
88    } else {
89      assert!(url.username().is_empty(), "username should be empty if user info is absent");
90      assert_none!(url.password(), "password should not exist if user info is absent");
91    }
92
93    match (&absolute_form.authority.host, url.host()) {
94      (
95        crate::request_line::target::components::host::Host::Domain(domain),
96        Some(Host::Domain(domain2)),
97      ) => assert_eq!(
98        domain.to_lowercase(),
99        domain2.to_lowercase(),
100        "expected domain {:?} but parsed domain {:?}",
101        domain.to_lowercase(),
102        domain2.to_lowercase()
103      ),
104      (
105        crate::request_line::target::components::host::Host::Ipv6(ipv6_addr, _),
106        Some(Host::Ipv6(ipv6_addr2)),
107      ) => assert_eq!(
108        *ipv6_addr, ipv6_addr2,
109        "expected IP v6 {ipv6_addr:?} but parsed IP v6 {ipv6_addr:?}"
110      ),
111      (
112        crate::request_line::target::components::host::Host::Ipv4(ipv4_addr, _),
113        Some(Host::Ipv4(ipv4_addr2)),
114      ) => assert_eq!(
115        *ipv4_addr, ipv4_addr2,
116        "expected IP v6 {ipv4_addr:?} but parsed IP v6 {ipv4_addr:?}"
117      ),
118      _ => panic!("expected host {:?} but parsed {:?}", absolute_form.authority.host, url.host()),
119    }
120
121    match (absolute_form.authority.port, url.port()) {
122      (None | Some(80), None) => {}
123      (Some(port), Some(port2)) => {
124        assert_eq!(port, port2, "expected port but {port} parsed {port2}");
125      }
126      (port, port2) => panic!("expected port but {port:?} parsed {port2:?}"),
127    }
128
129    match (&absolute_form.path, url.path()) {
130      (Some(path), path2) => {
131        assert_eq!(path.normalized, path2, "expected path {path:?} but parsed {path2}");
132      }
133      (None, path) => {
134        assert!(path.is_empty() || path == "/", "expected path to be empty but parsed {path}");
135      }
136    }
137
138    match (absolute_form.query.as_deref(), url.query()) {
139      (None, None) => {}
140      (Some(query), Some(query2)) => {
141        let query = query
142          .iter()
143          .map(|query| format!("{}={}", query.key, query.value.as_deref().unwrap_or_default()))
144          .collect::<Vec<_>>()
145          .join("&");
146        assert_eq!(query, query2, "expected query {query:?} but parsed {query2:?}");
147      }
148      (query, query2) => panic!("expected query {query:?} but got {query2:?}"),
149    }
150
151    match (absolute_form.fragment.as_deref(), url.fragment()) {
152      (None, None) => {}
153      (Some(fragment), Some(fragment2)) => {
154        assert_eq!(fragment, fragment2, "expected fragment {fragment:?} but parsed {fragment2:?}");
155      }
156      (fragment, fragment2) => panic!("expected fragment {fragment:?} but parsed {fragment2:?}"),
157    }
158  }
159  proptest! {
160    #[test]
161    fn absolute_works((absolute_form, repr) in absolute(20, 50.try_into().unwrap(), 0..=20)) {
162      absolute_asserts(&absolute_form, &repr);
163    }
164  }
165}