fake/impls/http/
mod.rs

1use crate::{Dummy, Fake, Faker};
2use http::uri;
3use rand::seq::IndexedRandom;
4use rand::Rng;
5use std::mem;
6use std::net::Ipv4Addr;
7use std::str::FromStr;
8
9const RFC_STATUS_CODES: &[u16] = &[
10    100, 101, 102, 200, 201, 202, 203, 204, 205, 206, 207, 208, 226, 300, 301, 302, 303, 304, 305,
11    307, 308, 400, 401, 402, 403, 404, 405, 406, 407, 408, 409, 410, 411, 412, 413, 414, 415, 416,
12    417, 418, 421, 422, 423, 424, 426, 428, 429, 431, 451, 500, 501, 502, 503, 504, 505, 506, 507,
13    508, 510, 511,
14];
15
16const VALID_SCHEME_CHARACTERS: &[char] = &[
17    'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's',
18    't', 'u', 'v', 'w', 'x', 'y', 'z', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L',
19    'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '0', '1', '2', '3', '4',
20    '5', '6', '7', '8', '9', '.', '-', '+',
21];
22
23impl Dummy<Faker> for http::Method {
24    fn dummy_with_rng<R: Rng + ?Sized>(_: &Faker, rng: &mut R) -> Self {
25        let i: u8 = (0..9).fake_with_rng(rng);
26        match i {
27            0 => http::Method::GET,
28            1 => http::Method::POST,
29            2 => http::Method::PUT,
30            3 => http::Method::DELETE,
31            4 => http::Method::HEAD,
32            5 => http::Method::OPTIONS,
33            6 => http::Method::CONNECT,
34            7 => http::Method::PATCH,
35            _ => http::Method::TRACE,
36        }
37    }
38}
39
40impl Dummy<Faker> for http::HeaderValue {
41    fn dummy_with_rng<R: Rng + ?Sized>(config: &Faker, rng: &mut R) -> Self {
42        let val = config.fake_with_rng::<String, _>(rng);
43        http::HeaderValue::try_from(val).unwrap()
44    }
45}
46
47impl Dummy<Faker> for http::HeaderName {
48    fn dummy_with_rng<R: Rng + ?Sized>(config: &Faker, rng: &mut R) -> Self {
49        let val = config.fake_with_rng::<String, _>(rng);
50        http::HeaderName::try_from(val).unwrap()
51    }
52}
53
54impl<T: Dummy<Faker>> Dummy<Faker> for http::HeaderMap<T> {
55    fn dummy_with_rng<R: Rng + ?Sized>(config: &Faker, rng: &mut R) -> Self {
56        let len = rng.random_range(1..10);
57        let mut map = http::HeaderMap::with_capacity(len);
58        for _ in 0..len {
59            let name: http::HeaderName = config.fake_with_rng(rng);
60            if rng.random_bool(0.7) {
61                map.insert(name, config.fake_with_rng(rng));
62            } else {
63                for _ in 0..rng.random_range(1..5) {
64                    map.append(&name, config.fake_with_rng(rng));
65                }
66            }
67        }
68        map
69    }
70}
71
72impl Dummy<Faker> for http::StatusCode {
73    fn dummy_with_rng<R: Rng + ?Sized>(_: &Faker, rng: &mut R) -> Self {
74        let code = RFC_STATUS_CODES.choose(rng).unwrap();
75        http::StatusCode::from_u16(*code).unwrap()
76    }
77}
78
79impl Dummy<&[u16]> for Result<http::StatusCode, http::status::InvalidStatusCode> {
80    fn dummy_with_rng<R: Rng + ?Sized>(codes: &&[u16], rng: &mut R) -> Self {
81        let code = codes.choose(rng).expect("no codes provided");
82        http::StatusCode::from_u16(*code)
83    }
84}
85
86impl Dummy<&[u16]> for http::StatusCode {
87    fn dummy_with_rng<R: Rng + ?Sized>(codes: &&[u16], rng: &mut R) -> Self {
88        let code = codes.choose(rng).expect("no codes provided");
89        http::StatusCode::from_u16(*code).expect("invalid status code")
90    }
91}
92
93impl Dummy<Faker> for http::Version {
94    fn dummy_with_rng<R: Rng + ?Sized>(_: &Faker, rng: &mut R) -> Self {
95        let i: u8 = (0..4).fake_with_rng(rng);
96        match i {
97            0 => http::Version::HTTP_2,
98            1 => http::Version::HTTP_10,
99            2 => http::Version::HTTP_09,
100            _ => http::Version::HTTP_11,
101        }
102    }
103}
104
105impl Dummy<Faker> for http::uri::Authority {
106    fn dummy_with_rng<R: Rng + ?Sized>(config: &Faker, rng: &mut R) -> Self {
107        let mut authority = String::new();
108
109        if rng.random_bool(0.5) {
110            // Include password
111            let user = config.fake_with_rng::<String, _>(rng);
112            let username = url_escape::encode_userinfo(&user);
113            authority.push_str(&username);
114            if rng.random_bool(0.5) {
115                authority.push(':');
116                let pass = config.fake_with_rng::<String, _>(rng);
117                let password = url_escape::encode_userinfo(&pass);
118                authority.push_str(&password);
119            }
120            authority.push('@');
121        }
122
123        let host_type = (0..3).fake_with_rng(rng);
124        match host_type {
125            0 => {
126                authority.push_str("localhost");
127                // Include port number
128                if rng.random_bool(0.5) {
129                    let port_num = config.fake_with_rng::<u16, _>(rng);
130                    authority.push(':');
131                    authority.push_str(&port_num.to_string());
132                }
133            }
134            1 => {
135                // ip
136                let ip: Ipv4Addr = config.fake_with_rng(rng);
137                authority.push_str(&ip.to_string());
138            }
139            _ => {
140                if rng.random_bool(0.5) {
141                    authority.push_str("www.");
142                }
143                let host_len = (1..100).fake_with_rng(rng);
144                authority
145                    .extend((0..host_len).map(|_| *VALID_SCHEME_CHARACTERS.choose(rng).unwrap()));
146
147                // Include port number
148                if rng.random_bool(0.5) {
149                    let port_num = config.fake_with_rng::<u16, _>(rng);
150                    authority.push(':');
151                    authority.push_str(&port_num.to_string());
152                }
153            }
154        }
155        uri::Authority::try_from(authority).unwrap()
156    }
157}
158
159impl Dummy<Faker> for http::uri::Scheme {
160    fn dummy_with_rng<R: Rng + ?Sized>(_: &Faker, rng: &mut R) -> Self {
161        // Some common schemes or a random valid scheme
162        let scheme = match (0..7).fake_with_rng(rng) {
163            0 => "http".to_string(),
164            1 => "https".to_string(),
165            2 => "ws".to_string(),
166            3 => "wss".to_string(),
167            4 => "ftp".to_string(),
168            5 => "git".to_string(),
169            _ => {
170                // A valid scheme is any letter followed by any combination of letters, digits, '+',
171                // '.', '-'. Looking at a list of know schemes 28 seems to be the longest so I'll
172                // generate one a max of that long.
173
174                let len = rng.random_range(1..29);
175                let mut scheme = String::with_capacity(len);
176                scheme.push(rng.random_range(b'a'..=b'z') as char);
177                if rng.random_bool(0.5) {
178                    scheme.make_ascii_uppercase();
179                }
180                scheme.extend((1..len).map(|_| *VALID_SCHEME_CHARACTERS.choose(rng).unwrap()));
181
182                scheme
183            }
184        };
185        uri::Scheme::from_str(&scheme).unwrap()
186    }
187}
188
189impl Dummy<Faker> for http::uri::PathAndQuery {
190    fn dummy_with_rng<R: Rng + ?Sized>(config: &Faker, rng: &mut R) -> Self {
191        let mut path = format!(
192            "/{}",
193            url_escape::encode_path(&config.fake_with_rng::<String, _>(rng))
194        );
195
196        if rng.random_bool(0.5) {
197            path.push('?');
198            let mut query_parts = vec![];
199            let query_len = (2..5).fake_with_rng(rng);
200            for _ in 1..query_len {
201                let key = url_escape::encode_component(&config.fake_with_rng::<String, _>(rng))
202                    .to_string();
203                let value = url_escape::encode_component(&config.fake_with_rng::<String, _>(rng))
204                    .to_string();
205                query_parts.push(format!("{}={}", key, value));
206            }
207            path.push_str(&query_parts.join("&"));
208        }
209        uri::PathAndQuery::try_from(path).unwrap()
210    }
211}
212
213impl Dummy<Faker> for http::Uri {
214    fn dummy_with_rng<R: Rng + ?Sized>(config: &Faker, rng: &mut R) -> Self {
215        let scheme = config.fake_with_rng::<uri::Scheme, _>(rng);
216        let authority = config.fake_with_rng::<uri::Authority, _>(rng);
217        let path = config.fake_with_rng::<uri::PathAndQuery, _>(rng);
218
219        uri::Builder::new()
220            .scheme(scheme)
221            .authority(authority)
222            .path_and_query(path)
223            .build()
224            .unwrap()
225    }
226}
227
228impl<T: Dummy<Faker>> Dummy<Faker> for http::Request<T> {
229    fn dummy_with_rng<R: Rng + ?Sized>(config: &Faker, rng: &mut R) -> Self {
230        let method: http::Method = config.fake_with_rng(rng);
231        let uri: http::Uri = config.fake_with_rng(rng);
232        let mut req = http::Request::builder()
233            .method(method)
234            .uri(uri)
235            .version(config.fake_with_rng(rng));
236        let mut headers: http::HeaderMap = config.fake_with_rng(rng);
237        mem::swap(req.headers_mut().unwrap(), &mut headers);
238        req.body(config.fake_with_rng(rng)).unwrap()
239    }
240}
241
242impl<T: Dummy<Faker>> Dummy<Faker> for http::Response<T> {
243    fn dummy_with_rng<R: Rng + ?Sized>(config: &Faker, rng: &mut R) -> Self {
244        let status: http::StatusCode = config.fake_with_rng(rng);
245        let mut res = http::Response::builder()
246            .status(status)
247            .version(config.fake_with_rng(rng));
248        let mut headers: http::HeaderMap = config.fake_with_rng(rng);
249        mem::swap(res.headers_mut().unwrap(), &mut headers);
250        res.body(config.fake_with_rng(rng)).unwrap()
251    }
252}
253
254#[cfg(test)]
255mod tests {
256    use super::*;
257
258    #[test]
259    fn valid_urls_generated() {
260        for _ in 0..1000 {
261            let _url = Faker.fake::<http::Uri>();
262            println!("{}", _url);
263        }
264    }
265}