torch_web/extractors/
headers.rs

1//! Header extraction
2//!
3//! Extract headers from the HTTP request.
4
5use std::pin::Pin;
6use std::future::Future;
7use crate::{Request, extractors::{FromRequestParts, ExtractionError}};
8use http::{HeaderMap, HeaderName, HeaderValue};
9
10/// Extract headers from the request
11///
12/// # Example
13///
14/// ```rust,no_run
15/// use torch_web::extractors::Headers;
16/// use http::HeaderMap;
17///
18/// async fn handler(Headers(headers): Headers) {
19///     if let Some(auth) = headers.get("authorization") {
20///         // Handle authorization header
21///     }
22/// }
23/// ```
24pub struct Headers(pub HeaderMap);
25
26impl FromRequestParts for Headers {
27    type Error = ExtractionError;
28
29    fn from_request_parts(
30        req: &mut Request,
31    ) -> Pin<Box<dyn Future<Output = Result<Self, Self::Error>> + Send + 'static>> {
32        let headers = req.headers().clone();
33        
34        Box::pin(async move {
35            Ok(Headers(headers))
36        })
37    }
38}
39
40/// Extract a specific header by name (simplified version without const generics)
41///
42/// # Example
43///
44/// ```rust,no_run
45/// use torch_web::extractors::HeaderExtractor;
46///
47/// async fn handler(header: HeaderExtractor) {
48///     // Use header.get() to access the header value
49/// }
50/// ```
51pub struct HeaderExtractor {
52    name: String,
53    value: Option<String>,
54}
55
56impl HeaderExtractor {
57    pub fn new(name: &str) -> Self {
58        Self {
59            name: name.to_string(),
60            value: None,
61        }
62    }
63
64    pub fn get(&self) -> Option<&str> {
65        self.value.as_deref()
66    }
67}
68
69// Note: Const generic header extractors removed due to current Rust limitations
70// In a production implementation, you'd use macros or a different approach
71
72/// Extract the User-Agent header
73///
74/// # Example
75///
76/// ```rust,no_run
77/// use torch_web::extractors::UserAgent;
78///
79/// async fn handler(UserAgent(user_agent): UserAgent) {
80///     // user_agent contains the User-Agent header value
81/// }
82/// ```
83pub struct UserAgent(pub Option<String>);
84
85impl FromRequestParts for UserAgent {
86    type Error = ExtractionError;
87
88    fn from_request_parts(
89        req: &mut Request,
90    ) -> Pin<Box<dyn Future<Output = Result<Self, Self::Error>> + Send + 'static>> {
91        let user_agent = req.headers()
92            .get("user-agent")
93            .and_then(|v| v.to_str().ok())
94            .map(|s| s.to_string());
95        
96        Box::pin(async move {
97            Ok(UserAgent(user_agent))
98        })
99    }
100}
101
102/// Extract the Authorization header
103///
104/// # Example
105///
106/// ```rust,no_run
107/// use torch_web::extractors::Authorization;
108///
109/// async fn handler(Authorization(auth): Authorization) {
110///     match auth {
111///         Some(token) => {
112///             // Handle authorization
113///         }
114///         None => {
115///             // No authorization provided
116///         }
117///     }
118/// }
119/// ```
120pub struct Authorization(pub Option<String>);
121
122impl FromRequestParts for Authorization {
123    type Error = ExtractionError;
124
125    fn from_request_parts(
126        req: &mut Request,
127    ) -> Pin<Box<dyn Future<Output = Result<Self, Self::Error>> + Send + 'static>> {
128        let auth = req.headers()
129            .get("authorization")
130            .and_then(|v| v.to_str().ok())
131            .map(|s| s.to_string());
132        
133        Box::pin(async move {
134            Ok(Authorization(auth))
135        })
136    }
137}
138
139/// Extract the Content-Type header
140///
141/// # Example
142///
143/// ```rust,no_run
144/// use torch_web::extractors::ContentType;
145///
146/// async fn handler(ContentType(content_type): ContentType) {
147///     match content_type.as_deref() {
148///         Some("application/json") => {
149///             // Handle JSON content
150///         }
151///         Some("application/xml") => {
152///             // Handle XML content
153///         }
154///         _ => {
155///             // Handle other or missing content type
156///         }
157///     }
158/// }
159/// ```
160pub struct ContentType(pub Option<String>);
161
162impl FromRequestParts for ContentType {
163    type Error = ExtractionError;
164
165    fn from_request_parts(
166        req: &mut Request,
167    ) -> Pin<Box<dyn Future<Output = Result<Self, Self::Error>> + Send + 'static>> {
168        let content_type = req.headers()
169            .get("content-type")
170            .and_then(|v| v.to_str().ok())
171            .map(|s| s.to_string());
172        
173        Box::pin(async move {
174            Ok(ContentType(content_type))
175        })
176    }
177}
178
179#[cfg(test)]
180mod tests {
181    use super::*;
182    use crate::Request;
183
184    #[tokio::test]
185    async fn test_headers_extraction() {
186        let mut req = Request::new();
187        req.headers_mut().insert("x-custom", "test-value".parse().unwrap());
188        
189        let result = Headers::from_request_parts(&mut req).await;
190        assert!(result.is_ok());
191        
192        let Headers(headers) = result.unwrap();
193        assert_eq!(headers.get("x-custom").unwrap(), "test-value");
194    }
195
196    #[tokio::test]
197    async fn test_user_agent_extraction() {
198        let mut req = Request::new();
199        req.headers_mut().insert("user-agent", "Mozilla/5.0".parse().unwrap());
200        
201        let result = UserAgent::from_request_parts(&mut req).await;
202        assert!(result.is_ok());
203        
204        let UserAgent(user_agent) = result.unwrap();
205        assert_eq!(user_agent, Some("Mozilla/5.0".to_string()));
206    }
207
208    #[tokio::test]
209    async fn test_missing_user_agent() {
210        let mut req = Request::new();
211        
212        let result = UserAgent::from_request_parts(&mut req).await;
213        assert!(result.is_ok());
214        
215        let UserAgent(user_agent) = result.unwrap();
216        assert_eq!(user_agent, None);
217    }
218
219    // Note: RequiredHeader tests removed due to const generic limitations
220    // In a production implementation, you'd use a different approach for typed headers
221}