picky/http/
http_request.rs1use std::borrow::Cow;
2use thiserror::Error;
3
4#[derive(Debug, Error, Clone)]
5#[non_exhaustive]
6pub enum HttpRequestError {
7 #[error("couldn't convert http header value to string for header key {key}")]
9 HeaderValueToStr { key: String },
10
11 #[error("unexpected error: {reason}")]
13 Unexpected { reason: String },
14}
15
16pub trait HttpRequest {
17 fn get_header_concatenated_values<'a>(&'a self, header_name: &str) -> Result<Cow<'a, str>, HttpRequestError>;
18 fn get_lowercased_method(&self) -> Result<Cow<'_, str>, HttpRequestError>;
19 fn get_target(&self) -> Result<Cow<'_, str>, HttpRequestError>;
20}
21
22#[cfg(feature = "http_trait_impl")]
23mod http_trait_impl {
24 use super::*;
25
26 impl HttpRequest for http::request::Parts {
27 fn get_header_concatenated_values<'a>(&'a self, header_name: &str) -> Result<Cow<'a, str>, HttpRequestError> {
28 let mut values = Vec::new();
29 let all_values = self.headers.get_all(header_name);
30 for value in all_values {
31 let value_str = value.to_str().map_err(|_| HttpRequestError::HeaderValueToStr {
32 key: header_name.to_owned(),
33 })?;
34 values.push(value_str.trim());
35 }
36 Ok(Cow::Owned(values.join(", ")))
37 }
38
39 fn get_lowercased_method(&self) -> Result<Cow<'_, str>, HttpRequestError> {
40 Ok(Cow::Owned(self.method.as_str().to_lowercase()))
41 }
42
43 fn get_target(&self) -> Result<Cow<'_, str>, HttpRequestError> {
44 Ok(Cow::Borrowed(self.uri.path()))
45 }
46 }
47 impl<T> HttpRequest for http::request::Request<T> {
48 fn get_header_concatenated_values<'a>(&'a self, header_name: &str) -> Result<Cow<'a, str>, HttpRequestError> {
49 let mut values = Vec::new();
50 let all_values = self.headers().get_all(header_name);
51 for value in all_values {
52 let value_str = value.to_str().map_err(|_| HttpRequestError::HeaderValueToStr {
53 key: header_name.to_owned(),
54 })?;
55 values.push(value_str.trim());
56 }
57 Ok(Cow::Owned(values.join(", ")))
58 }
59
60 fn get_lowercased_method(&self) -> Result<Cow<'_, str>, HttpRequestError> {
61 Ok(Cow::Owned(self.method().as_str().to_lowercase()))
62 }
63
64 fn get_target(&self) -> Result<Cow<'_, str>, HttpRequestError> {
65 Ok(Cow::Borrowed(self.uri().path()))
66 }
67 }
68
69 #[cfg(test)]
70 mod tests {
71 use super::*;
72 use http::method::Method;
73 use http::{header, request};
74
75 #[test]
76 fn http_request_parts() {
77 let req = request::Builder::new()
78 .method(Method::GET)
79 .uri("/foo")
80 .header("Host", "example.org")
81 .header(header::DATE, "Tue, 07 Jun 2014 20:51:35 GMT")
82 .header("X-Example", " Example header with some whitespace. ")
83 .header("X-EmptyHeader", "")
84 .header(header::CACHE_CONTROL, "max-age=60")
85 .header(header::CACHE_CONTROL, "must-revalidate")
86 .body(())
87 .expect("couldn't build request");
88
89 let (parts, _) = req.into_parts();
90
91 assert_eq!(parts.get_target().expect("target"), "/foo");
92 assert_eq!(parts.get_lowercased_method().expect("method"), "get");
93 assert_eq!(
94 parts.get_header_concatenated_values("host").expect("host"),
95 "example.org"
96 );
97 assert_eq!(
98 parts.get_header_concatenated_values("date").expect("date"),
99 "Tue, 07 Jun 2014 20:51:35 GMT"
100 );
101 assert_eq!(
102 parts.get_header_concatenated_values("x-example").expect("example"),
103 "Example header with some whitespace."
104 );
105 assert_eq!(
106 parts.get_header_concatenated_values("X-EmptyHeader").expect("empty"),
107 ""
108 );
109 assert_eq!(
110 parts
111 .get_header_concatenated_values(header::CACHE_CONTROL.as_str())
112 .expect("cache control"),
113 "max-age=60, must-revalidate"
114 );
115 }
116
117 #[test]
118 fn http_request_request() {
119 let req = request::Builder::new()
120 .method(Method::GET)
121 .uri("/foo")
122 .header("Host", "example.org")
123 .header(header::DATE, "Tue, 07 Jun 2014 20:51:35 GMT")
124 .header("X-Example", " Example header with some whitespace. ")
125 .header("X-EmptyHeader", "")
126 .header(header::CACHE_CONTROL, "max-age=60")
127 .header(header::CACHE_CONTROL, "must-revalidate")
128 .body(())
129 .expect("couldn't build request");
130
131 assert_eq!(req.get_target().expect("target"), "/foo");
132 assert_eq!(req.get_lowercased_method().expect("method"), "get");
133 assert_eq!(req.get_header_concatenated_values("host").expect("host"), "example.org");
134 assert_eq!(
135 req.get_header_concatenated_values("date").expect("date"),
136 "Tue, 07 Jun 2014 20:51:35 GMT"
137 );
138 assert_eq!(
139 req.get_header_concatenated_values("x-example").expect("example"),
140 "Example header with some whitespace."
141 );
142 assert_eq!(req.get_header_concatenated_values("X-EmptyHeader").expect("empty"), "");
143 assert_eq!(
144 req.get_header_concatenated_values(header::CACHE_CONTROL.as_str())
145 .expect("cache control"),
146 "max-age=60, must-revalidate"
147 );
148 }
149 }
150}