elif_http/response/
headers.rs

1//! HTTP header utilities and wrappers
2
3use crate::errors::ParseError;
4use std::collections::HashMap;
5use std::fmt;
6use std::str::FromStr;
7
8/// Framework-native header name wrapper that hides Axum internals
9#[repr(transparent)]
10#[derive(Debug, Clone, PartialEq, Eq, Hash)]
11pub struct ElifHeaderName(axum::http::HeaderName);
12
13impl ElifHeaderName {
14    /// Create a new header name from a string
15    pub fn from_str(name: &str) -> Result<Self, ParseError> {
16        axum::http::HeaderName::from_str(name)
17            .map(Self)
18            .map_err(ParseError::from)
19    }
20
21    /// Get header name as string
22    pub fn as_str(&self) -> &str {
23        self.0.as_str()
24    }
25
26    /// Internal method to convert to axum HeaderName (for framework internals only)
27    pub(crate) fn to_axum(&self) -> &axum::http::HeaderName {
28        &self.0
29    }
30
31    // ///Internal method to create from axum HeaderName (for framework internals only)
32    // pub(crate) fn from_axum(name: axum::http::HeaderName) -> Self {
33    //     Self(name)
34    // }
35}
36
37impl FromStr for ElifHeaderName {
38    type Err = ParseError;
39
40    fn from_str(s: &str) -> Result<Self, Self::Err> {
41        // Call our inherent method using qualified syntax to avoid recursion
42        <ElifHeaderName>::from_str(s)
43    }
44}
45
46impl fmt::Display for ElifHeaderName {
47    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
48        write!(f, "{}", self.0)
49    }
50}
51
52/// Framework-native header value wrapper that hides Axum internals
53#[repr(transparent)]
54#[derive(Debug, Clone, PartialEq, Eq)]
55pub struct ElifHeaderValue(axum::http::HeaderValue);
56
57impl ElifHeaderValue {
58    /// Create a new header value from a string
59    pub fn from_str(value: &str) -> Result<Self, ParseError> {
60        axum::http::HeaderValue::from_str(value)
61            .map(Self)
62            .map_err(ParseError::from)
63    }
64
65    /// Create a new header value from bytes
66    pub fn from_bytes(bytes: &[u8]) -> Result<Self, ParseError> {
67        axum::http::HeaderValue::from_bytes(bytes)
68            .map(Self)
69            .map_err(ParseError::from)
70    }
71
72    /// Create a new header value from a static string
73    pub fn from_static(value: &'static str) -> Self {
74        Self(axum::http::HeaderValue::from_static(value))
75    }
76
77    /// Get header value as string
78    pub fn to_str(&self) -> Result<&str, ParseError> {
79        self.0.to_str().map_err(ParseError::from)
80    }
81
82    /// Get header value as bytes
83    pub fn as_bytes(&self) -> &[u8] {
84        self.0.as_bytes()
85    }
86
87    /// Internal method to convert to axum HeaderValue (for framework internals only)
88    pub(crate) fn to_axum(&self) -> &axum::http::HeaderValue {
89        &self.0
90    }
91
92    // /// Internal method to create from axum HeaderValue (for framework internals only)
93    // pub(crate) fn from_axum(value: axum::http::HeaderValue) -> Self {
94    //     Self(value)
95    // }
96}
97
98impl FromStr for ElifHeaderValue {
99    type Err = ParseError;
100
101    fn from_str(s: &str) -> Result<Self, Self::Err> {
102        // Call our inherent method using qualified syntax to avoid recursion
103        <ElifHeaderValue>::from_str(s)
104    }
105}
106
107impl fmt::Display for ElifHeaderValue {
108    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
109        match self.to_str() {
110            Ok(s) => write!(f, "{}", s),
111            Err(_) => write!(f, "<invalid UTF-8>"),
112        }
113    }
114}
115
116/// Framework-native header map wrapper that hides Axum internals
117#[derive(Debug, Clone)]
118pub struct ElifHeaderMap(axum::http::HeaderMap);
119
120impl ElifHeaderMap {
121    /// Create a new empty header map
122    pub fn new() -> Self {
123        Self(axum::http::HeaderMap::new())
124    }
125
126    /// Insert a header into the map
127    pub fn insert(
128        &mut self,
129        name: ElifHeaderName,
130        value: ElifHeaderValue,
131    ) -> Option<ElifHeaderValue> {
132        self.0.insert(name.0, value.0).map(ElifHeaderValue)
133    }
134
135    /// Append a header to the map (supports multi-value headers like Set-Cookie)
136    pub fn append(&mut self, name: ElifHeaderName, value: ElifHeaderValue) -> bool {
137        self.0.append(name.0, value.0)
138    }
139
140    /// Get a header value by name
141    pub fn get(&self, name: &ElifHeaderName) -> Option<&ElifHeaderValue> {
142        // SAFETY: This is safe because ElifHeaderValue is a transparent wrapper around HeaderValue
143        unsafe { std::mem::transmute(self.0.get(&name.0)) }
144    }
145
146    /// Get a header value by string name
147    pub fn get_str(&self, name: &str) -> Option<&ElifHeaderValue> {
148        if let Ok(header_name) = ElifHeaderName::from_str(name) {
149            self.get(&header_name)
150        } else {
151            None
152        }
153    }
154
155    /// Remove a header from the map
156    pub fn remove(&mut self, name: &ElifHeaderName) -> Option<ElifHeaderValue> {
157        self.0.remove(&name.0).map(ElifHeaderValue)
158    }
159
160    /// Check if the map contains a header
161    pub fn contains_key(&self, name: &ElifHeaderName) -> bool {
162        self.0.contains_key(&name.0)
163    }
164
165    /// Check if the map contains a header by string name
166    pub fn contains_key_str(&self, name: &str) -> bool {
167        if let Ok(header_name) = ElifHeaderName::from_str(name) {
168            self.contains_key(&header_name)
169        } else {
170            false
171        }
172    }
173
174    /// Get the number of headers
175    pub fn len(&self) -> usize {
176        self.0.len()
177    }
178
179    /// Check if the header map is empty
180    pub fn is_empty(&self) -> bool {
181        self.0.is_empty()
182    }
183
184    /// Clear all headers
185    pub fn clear(&mut self) {
186        self.0.clear();
187    }
188
189    /// Iterate over all headers
190    pub fn iter(&self) -> impl Iterator<Item = (&ElifHeaderName, &ElifHeaderValue)> {
191        self.0.iter().map(|(k, v)| {
192            // SAFETY: This is safe because our wrapper types are transparent
193            unsafe { (std::mem::transmute(k), std::mem::transmute(v)) }
194        })
195    }
196
197    /// Iterate over all header keys
198    pub fn keys(&self) -> impl Iterator<Item = &ElifHeaderName> {
199        self.0.keys().map(|k| {
200            // SAFETY: This is safe because our wrapper type is transparent
201            unsafe { std::mem::transmute(k) }
202        })
203    }
204
205    /// Iterate over all header values
206    pub fn values(&self) -> impl Iterator<Item = &ElifHeaderValue> {
207        self.0.values().map(|v| {
208            // SAFETY: This is safe because our wrapper type is transparent
209            unsafe { std::mem::transmute(v) }
210        })
211    }
212
213    /// Add a header to the map (used by middleware for response headers)
214    pub fn add_header(&mut self, name: &str, value: &str) -> Result<(), String> {
215        if let Ok(header_name) = ElifHeaderName::from_str(name) {
216            if let Ok(header_value) = ElifHeaderValue::from_str(value) {
217                self.insert(header_name, header_value);
218                Ok(())
219            } else {
220                Err("Invalid header value".to_string())
221            }
222        } else {
223            Err("Invalid header name".to_string())
224        }
225    }
226
227    /// Remove a header from the map
228    pub fn remove_header(&mut self, name: &str) -> Option<ElifHeaderValue> {
229        if let Ok(header_name) = ElifHeaderName::from_str(name) {
230            self.remove(&header_name)
231        } else {
232            None
233        }
234    }
235
236    /// Convert to a HashMap for easier manipulation
237    pub fn to_hash_map(&self) -> HashMap<String, String> {
238        self.0
239            .iter()
240            .filter_map(|(k, v)| {
241                v.to_str()
242                    .ok()
243                    .map(|v| (k.as_str().to_string(), v.to_string()))
244            })
245            .collect()
246    }
247
248    // /// Internal method to convert to axum HeaderMap (for framework internals only)
249    // pub(crate) fn to_axum(&self) -> &axum::http::HeaderMap {
250    //     &self.0
251    // }
252
253    /// Internal method to create from axum HeaderMap (for framework internals only)
254    pub(crate) fn from_axum(headers: axum::http::HeaderMap) -> Self {
255        Self(headers)
256    }
257}
258
259impl Default for ElifHeaderMap {
260    fn default() -> Self {
261        Self::new()
262    }
263}
264
265impl From<axum::http::HeaderMap> for ElifHeaderMap {
266    fn from(headers: axum::http::HeaderMap) -> Self {
267        Self::from_axum(headers)
268    }
269}
270
271// Common header name constants
272pub mod header_names {
273    use super::ElifHeaderName;
274    use axum::http::header;
275
276    // Define headers using string constants rather than axum header constants
277    // to avoid type mismatch issues
278    pub const AUTHORIZATION: ElifHeaderName = ElifHeaderName(header::AUTHORIZATION);
279    pub const CONTENT_TYPE: ElifHeaderName = ElifHeaderName(header::CONTENT_TYPE);
280    pub const CONTENT_LENGTH: ElifHeaderName = ElifHeaderName(header::CONTENT_LENGTH);
281    pub const ACCEPT: ElifHeaderName = ElifHeaderName(header::ACCEPT);
282    pub const CACHE_CONTROL: ElifHeaderName = ElifHeaderName(header::CACHE_CONTROL);
283    pub const ETAG: ElifHeaderName = ElifHeaderName(header::ETAG);
284    pub const IF_NONE_MATCH: ElifHeaderName = ElifHeaderName(header::IF_NONE_MATCH);
285    pub const LOCATION: ElifHeaderName = ElifHeaderName(header::LOCATION);
286    pub const SET_COOKIE: ElifHeaderName = ElifHeaderName(header::SET_COOKIE);
287    pub const COOKIE: ElifHeaderName = ElifHeaderName(header::COOKIE);
288    pub const USER_AGENT: ElifHeaderName = ElifHeaderName(header::USER_AGENT);
289    pub const REFERER: ElifHeaderName = ElifHeaderName(header::REFERER);
290    pub const ORIGIN: ElifHeaderName = ElifHeaderName(header::ORIGIN);
291    pub const ACCESS_CONTROL_ALLOW_ORIGIN: ElifHeaderName =
292        ElifHeaderName(header::ACCESS_CONTROL_ALLOW_ORIGIN);
293    pub const ACCESS_CONTROL_ALLOW_METHODS: ElifHeaderName =
294        ElifHeaderName(header::ACCESS_CONTROL_ALLOW_METHODS);
295    pub const ACCESS_CONTROL_ALLOW_HEADERS: ElifHeaderName =
296        ElifHeaderName(header::ACCESS_CONTROL_ALLOW_HEADERS);
297    pub const ACCESS_CONTROL_EXPOSE_HEADERS: ElifHeaderName =
298        ElifHeaderName(header::ACCESS_CONTROL_EXPOSE_HEADERS);
299    pub const ACCESS_CONTROL_ALLOW_CREDENTIALS: ElifHeaderName =
300        ElifHeaderName(header::ACCESS_CONTROL_ALLOW_CREDENTIALS);
301    pub const ACCESS_CONTROL_MAX_AGE: ElifHeaderName =
302        ElifHeaderName(header::ACCESS_CONTROL_MAX_AGE);
303    pub const CONTENT_SECURITY_POLICY: ElifHeaderName =
304        ElifHeaderName(header::CONTENT_SECURITY_POLICY);
305    pub const STRICT_TRANSPORT_SECURITY: ElifHeaderName =
306        ElifHeaderName(header::STRICT_TRANSPORT_SECURITY);
307    pub const X_FRAME_OPTIONS: ElifHeaderName = ElifHeaderName(header::X_FRAME_OPTIONS);
308    pub const X_CONTENT_TYPE_OPTIONS: ElifHeaderName =
309        ElifHeaderName(header::X_CONTENT_TYPE_OPTIONS);
310    pub const X_XSS_PROTECTION: ElifHeaderName = ElifHeaderName(header::X_XSS_PROTECTION);
311    pub const REFERRER_POLICY: ElifHeaderName = ElifHeaderName(header::REFERRER_POLICY);
312}