eggserve_core/primitives/
request_head.rs1use crate::primitives::header_block::HeaderBlock;
8use crate::primitives::method::Method;
9use crate::primitives::request_target::{RequestTarget, RequestTargetError};
10use crate::primitives::version::HttpVersion;
11
12#[derive(Debug)]
14pub enum RequestHeadError {
15 Target(RequestTargetError),
17 Version(crate::primitives::version::HttpVersionError),
19 HeaderName(crate::primitives::header_block::HeaderError),
21 HeaderValue(crate::primitives::header_block::HeaderError),
23 AbsoluteForm,
25}
26
27impl std::fmt::Display for RequestHeadError {
28 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
29 match self {
30 Self::Target(e) => write!(f, "invalid request target: {e}"),
31 Self::Version(e) => write!(f, "unsupported HTTP version: {e}"),
32 Self::HeaderName(e) => write!(f, "invalid header name: {e}"),
33 Self::HeaderValue(e) => write!(f, "invalid header value: {e}"),
34 Self::AbsoluteForm => write!(f, "absolute-form URI not supported"),
35 }
36 }
37}
38
39impl std::error::Error for RequestHeadError {
40 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
41 match self {
42 Self::Target(e) => Some(e),
43 Self::Version(e) => Some(e),
44 Self::HeaderName(e) => Some(e),
45 Self::HeaderValue(e) => Some(e),
46 Self::AbsoluteForm => None,
47 }
48 }
49}
50
51impl From<RequestTargetError> for RequestHeadError {
52 fn from(e: RequestTargetError) -> Self {
53 Self::Target(e)
54 }
55}
56
57impl From<crate::primitives::version::HttpVersionError> for RequestHeadError {
58 fn from(e: crate::primitives::version::HttpVersionError) -> Self {
59 Self::Version(e)
60 }
61}
62
63#[derive(Debug, Clone)]
78pub struct RequestHead {
79 method: Method,
80 target: RequestTarget,
81 version: HttpVersion,
82 headers: HeaderBlock,
83}
84
85impl RequestHead {
86 pub fn new(
92 method: Method,
93 target: RequestTarget,
94 version: HttpVersion,
95 headers: HeaderBlock,
96 ) -> Self {
97 Self {
98 method,
99 target,
100 version,
101 headers,
102 }
103 }
104
105 pub fn method(&self) -> &Method {
107 &self.method
108 }
109
110 pub fn target(&self) -> &RequestTarget {
112 &self.target
113 }
114
115 pub fn version(&self) -> HttpVersion {
117 self.version
118 }
119
120 pub fn headers(&self) -> &HeaderBlock {
122 &self.headers
123 }
124
125 pub fn is_head(&self) -> bool {
127 self.method.is_head()
128 }
129
130 pub fn is_get(&self) -> bool {
132 self.method.is_get()
133 }
134
135 pub fn permits_static_resolution(&self) -> bool {
137 self.method.permits_static_resolution()
138 }
139
140 pub fn try_from_hyper<B>(req: &hyper::Request<B>) -> Result<Self, RequestHeadError> {
151 let method =
152 Method::new(req.method().as_str()).map_err(|_| RequestHeadError::AbsoluteForm)?;
153
154 let uri = req.uri();
155 if uri.authority().is_some() {
156 return Err(RequestHeadError::AbsoluteForm);
157 }
158 let target_str = uri.to_string();
159 let target = RequestTarget::parse(target_str)?;
160
161 let version = HttpVersion::from(&req.version());
162
163 let mut headers = HeaderBlock::with_capacity(req.headers().len());
164 for (name, value) in req.headers().iter() {
165 let header_name = crate::primitives::header_block::HeaderName::new(name.as_str())
166 .map_err(RequestHeadError::HeaderName)?;
167 let value_str = value.to_str().unwrap_or("").to_string();
168 let header_value = crate::primitives::header_block::HeaderValue::new(value_str)
169 .map_err(RequestHeadError::HeaderValue)?;
170 headers.push(header_name, header_value);
171 }
172
173 Ok(Self::new(method, target, version, headers))
174 }
175}
176
177#[cfg(test)]
178mod tests {
179 use super::*;
180 use crate::primitives::header_block::HeaderBlock;
181 use crate::primitives::method::Method;
182 use crate::primitives::request_target::RequestTarget;
183 use crate::primitives::version::HttpVersion;
184
185 fn make_head(method: &str, target: &str) -> RequestHead {
186 RequestHead::new(
187 Method::new(method).unwrap(),
188 RequestTarget::parse(target).unwrap(),
189 HttpVersion::Http11,
190 HeaderBlock::new(),
191 )
192 }
193
194 #[test]
195 fn basic_construction() {
196 let head = make_head("GET", "/");
197 assert_eq!(head.method().as_str(), "GET");
198 assert_eq!(head.target().path(), "/");
199 assert_eq!(head.version(), HttpVersion::Http11);
200 assert!(head.headers().is_empty());
201 }
202
203 #[test]
204 fn is_head() {
205 let head = make_head("HEAD", "/foo");
206 assert!(head.is_head());
207 assert!(!head.is_get());
208 }
209
210 #[test]
211 fn is_get() {
212 let head = make_head("GET", "/foo");
213 assert!(head.is_get());
214 assert!(!head.is_head());
215 }
216
217 #[test]
218 fn permits_static_resolution() {
219 let head = make_head("GET", "/");
220 assert!(head.permits_static_resolution());
221
222 let head = make_head("HEAD", "/");
223 assert!(head.permits_static_resolution());
224
225 let head = make_head("POST", "/");
226 assert!(!head.permits_static_resolution());
227 }
228
229 #[test]
230 fn with_headers() {
231 let mut headers = HeaderBlock::new();
232 headers.push_str("content-type", "text/html").unwrap();
233 let head = RequestHead::new(
234 Method::get(),
235 RequestTarget::parse("/").unwrap(),
236 HttpVersion::Http11,
237 headers,
238 );
239 assert!(head.headers().contains("content-type"));
240 }
241
242 #[test]
243 fn clone_preserves_values() {
244 let head = make_head("GET", "/foo?bar");
245 let cloned = head.clone();
246 assert_eq!(cloned.method().as_str(), "GET");
247 assert_eq!(cloned.target().path(), "/foo");
248 assert_eq!(cloned.target().query(), Some("bar"));
249 }
250}