armature_core/
http.rs

1// HTTP request and response types
2
3use crate::body::RequestBody;
4use crate::extensions::Extensions;
5use bytes::Bytes;
6use serde::{Deserialize, Serialize};
7use std::collections::HashMap;
8use std::sync::Arc;
9
10/// HTTP request wrapper
11///
12/// The body is stored as `Vec<u8>` for backwards compatibility.
13/// For zero-copy body handling, use `body_bytes()` or `set_body_bytes()`.
14#[derive(Debug, Clone)]
15pub struct HttpRequest {
16    pub method: String,
17    pub path: String,
18    pub headers: HashMap<String, String>,
19    /// Request body as raw bytes.
20    ///
21    /// For zero-copy access, use `body_bytes()` to get a `Bytes` view,
22    /// or use `RequestBody` for efficient body handling.
23    pub body: Vec<u8>,
24    pub path_params: HashMap<String, String>,
25    pub query_params: HashMap<String, String>,
26    /// Type-safe extensions for storing application state.
27    ///
28    /// Use this to pass typed data to handlers without DI container lookups.
29    /// Access via the `State<T>` extractor for zero-cost state retrieval.
30    pub extensions: Extensions,
31    /// Optional zero-copy body storage using Bytes.
32    /// When set, this takes precedence over `body` for read operations.
33    body_bytes: Option<Bytes>,
34}
35
36impl HttpRequest {
37    pub fn new(method: String, path: String) -> Self {
38        Self {
39            method,
40            path,
41            headers: HashMap::new(),
42            body: Vec::new(),
43            path_params: HashMap::new(),
44            query_params: HashMap::new(),
45            extensions: Extensions::new(),
46            body_bytes: None,
47        }
48    }
49
50    /// Create a new request with pre-allocated extensions capacity.
51    #[inline]
52    pub fn with_extensions_capacity(method: String, path: String, capacity: usize) -> Self {
53        Self {
54            method,
55            path,
56            headers: HashMap::new(),
57            body: Vec::new(),
58            path_params: HashMap::new(),
59            query_params: HashMap::new(),
60            extensions: Extensions::with_capacity(capacity),
61            body_bytes: None,
62        }
63    }
64
65    /// Create a new request with a Bytes body (zero-copy).
66    ///
67    /// This is the most efficient way to create a request from Hyper's body,
68    /// as it avoids copying the body data.
69    #[inline]
70    pub fn with_bytes_body(method: String, path: String, body: Bytes) -> Self {
71        Self {
72            method,
73            path,
74            headers: HashMap::new(),
75            body: Vec::new(), // Not used when body_bytes is set
76            path_params: HashMap::new(),
77            query_params: HashMap::new(),
78            extensions: Extensions::new(),
79            body_bytes: Some(body),
80        }
81    }
82
83    /// Set the body using Bytes (zero-copy).
84    ///
85    /// This avoids copying the body data from Hyper.
86    #[inline]
87    pub fn set_body_bytes(&mut self, bytes: Bytes) {
88        self.body_bytes = Some(bytes);
89        self.body.clear(); // Clear legacy body to save memory
90    }
91
92    /// Get the body as Bytes (zero-copy if stored as Bytes).
93    ///
94    /// If the body was set via `set_body_bytes()` or `with_bytes_body()`,
95    /// this returns a clone of the Bytes (O(1) reference count increment).
96    /// Otherwise, it creates Bytes from the Vec<u8>.
97    #[inline]
98    pub fn body_bytes(&self) -> Bytes {
99        if let Some(ref bytes) = self.body_bytes {
100            bytes.clone() // O(1) - just increments ref count
101        } else {
102            Bytes::copy_from_slice(&self.body)
103        }
104    }
105
106    /// Get a reference to the body bytes.
107    ///
108    /// Returns a reference to the body data without copying.
109    #[inline]
110    pub fn body_ref(&self) -> &[u8] {
111        if let Some(ref bytes) = self.body_bytes {
112            bytes.as_ref()
113        } else {
114            &self.body
115        }
116    }
117
118    /// Get the body as a RequestBody (zero-copy wrapper).
119    #[inline]
120    pub fn request_body(&self) -> RequestBody {
121        RequestBody::from_bytes(self.body_bytes())
122    }
123
124    /// Check if the body is using zero-copy Bytes storage.
125    #[inline]
126    pub fn has_bytes_body(&self) -> bool {
127        self.body_bytes.is_some()
128    }
129
130    /// Set the body from a Vec<u8>.
131    #[inline]
132    pub fn set_body(&mut self, body: Vec<u8>) {
133        self.body = body;
134        self.body_bytes = None;
135    }
136
137    /// Create a request from all parts (for compatibility in tests).
138    #[inline]
139    pub fn from_parts(
140        method: String,
141        path: String,
142        headers: HashMap<String, String>,
143        body: Vec<u8>,
144        path_params: HashMap<String, String>,
145        query_params: HashMap<String, String>,
146    ) -> Self {
147        Self {
148            method,
149            path,
150            headers,
151            body,
152            path_params,
153            query_params,
154            extensions: Extensions::new(),
155            body_bytes: None,
156        }
157    }
158
159    /// Insert a typed value into request extensions.
160    ///
161    /// Use this to pass application state to handlers.
162    ///
163    /// # Example
164    ///
165    /// ```rust,ignore
166    /// let mut request = HttpRequest::new("GET".into(), "/".into());
167    /// request.insert_extension(app_state);
168    /// ```
169    #[inline]
170    pub fn insert_extension<T: Send + Sync + 'static>(&mut self, value: T) {
171        self.extensions.insert(value);
172    }
173
174    /// Insert an Arc-wrapped value into request extensions.
175    ///
176    /// This is more efficient when you already have an Arc.
177    #[inline]
178    pub fn insert_extension_arc<T: Send + Sync + 'static>(&mut self, value: Arc<T>) {
179        self.extensions.insert_arc(value);
180    }
181
182    /// Get a reference to a typed extension.
183    ///
184    /// Returns `None` if no value of this type exists.
185    #[inline]
186    pub fn extension<T: Send + Sync + 'static>(&self) -> Option<&T> {
187        self.extensions.get::<T>()
188    }
189
190    /// Get an Arc reference to a typed extension.
191    #[inline]
192    pub fn extension_arc<T: Send + Sync + 'static>(&self) -> Option<Arc<T>> {
193        self.extensions.get_arc::<T>()
194    }
195
196    /// Parse the request body as JSON.
197    ///
198    /// With the `simd-json` feature enabled, this uses SIMD-accelerated parsing
199    /// which can be 2-3x faster on modern x86_64 CPUs.
200    ///
201    /// # Example
202    ///
203    /// ```rust,ignore
204    /// let user: CreateUser = request.json()?;
205    /// ```
206    #[inline]
207    pub fn json<T: for<'de> Deserialize<'de>>(&self) -> Result<T, crate::Error> {
208        crate::json::from_slice(self.body_ref())
209            .map_err(|e| crate::Error::Deserialization(e.to_string()))
210    }
211
212    /// Parse URL-encoded form data
213    pub fn form<T: for<'de> Deserialize<'de>>(&self) -> Result<T, crate::Error> {
214        crate::form::parse_form(self.body_ref())
215    }
216
217    /// Parse URL-encoded form data into a HashMap
218    pub fn form_map(&self) -> Result<HashMap<String, String>, crate::Error> {
219        crate::form::parse_form_map(self.body_ref())
220    }
221
222    /// Parse multipart form data
223    pub fn multipart(&self) -> Result<Vec<crate::form::FormField>, crate::Error> {
224        let content_type = self
225            .headers
226            .get("Content-Type")
227            .or_else(|| self.headers.get("content-type"))
228            .ok_or_else(|| crate::Error::BadRequest("Missing Content-Type header".to_string()))?;
229
230        let parser = crate::form::MultipartParser::from_content_type(content_type)?;
231        parser.parse(self.body_ref())
232    }
233
234    /// Get a path parameter by name
235    pub fn param(&self, name: &str) -> Option<&String> {
236        self.path_params.get(name)
237    }
238
239    /// Get a query parameter by name
240    pub fn query(&self, name: &str) -> Option<&String> {
241        self.query_params.get(name)
242    }
243}
244
245/// Lazy-initialized HashMap that doesn't allocate until first insert.
246///
247/// This provides the same API as HashMap but with zero allocation cost
248/// for empty maps.
249#[derive(Debug, Clone, Default)]
250pub struct LazyHeaders {
251    inner: Option<HashMap<String, String>>,
252}
253
254impl LazyHeaders {
255    /// Create a new empty LazyHeaders (no allocation).
256    #[inline(always)]
257    pub const fn new() -> Self {
258        Self { inner: None }
259    }
260
261    /// Create with pre-allocated capacity.
262    #[inline]
263    pub fn with_capacity(cap: usize) -> Self {
264        Self {
265            inner: Some(HashMap::with_capacity(cap)),
266        }
267    }
268
269    /// Insert a key-value pair.
270    #[inline]
271    pub fn insert(&mut self, key: String, value: String) -> Option<String> {
272        self.inner
273            .get_or_insert_with(HashMap::new)
274            .insert(key, value)
275    }
276
277    /// Get a value by key.
278    #[inline]
279    pub fn get(&self, key: &str) -> Option<&String> {
280        self.inner.as_ref()?.get(key)
281    }
282
283    /// Check if key exists.
284    #[inline]
285    pub fn contains_key(&self, key: &str) -> bool {
286        self.inner.as_ref().is_some_and(|m| m.contains_key(key))
287    }
288
289    /// Get number of headers.
290    #[inline]
291    pub fn len(&self) -> usize {
292        self.inner.as_ref().map_or(0, |m| m.len())
293    }
294
295    /// Check if empty.
296    #[inline]
297    pub fn is_empty(&self) -> bool {
298        self.inner.as_ref().is_none_or(|m| m.is_empty())
299    }
300
301    /// Iterate over headers.
302    #[inline]
303    pub fn iter(&self) -> impl Iterator<Item = (&String, &String)> {
304        self.inner.iter().flat_map(|m| m.iter())
305    }
306
307    /// Convert to HashMap (for compatibility).
308    #[inline]
309    pub fn to_hashmap(&self) -> HashMap<String, String> {
310        self.inner.clone().unwrap_or_default()
311    }
312
313    /// Remove a header by key.
314    #[inline]
315    pub fn remove(&mut self, key: &str) -> Option<String> {
316        self.inner.as_mut()?.remove(key)
317    }
318
319    /// Get an entry for in-place manipulation.
320    #[inline]
321    pub fn entry(&mut self, key: String) -> std::collections::hash_map::Entry<'_, String, String> {
322        self.inner.get_or_insert_with(HashMap::new).entry(key)
323    }
324
325    /// Extend with headers from an iterator.
326    #[inline]
327    pub fn extend<I: IntoIterator<Item = (String, String)>>(&mut self, iter: I) {
328        let map = self.inner.get_or_insert_with(HashMap::new);
329        map.extend(iter);
330    }
331
332    /// Clear all headers.
333    #[inline]
334    pub fn clear(&mut self) {
335        if let Some(ref mut map) = self.inner {
336            map.clear();
337        }
338    }
339
340    /// Clone the inner HashMap if present.
341    #[inline]
342    pub fn clone_inner(&self) -> Option<HashMap<String, String>> {
343        self.inner.clone()
344    }
345}
346
347impl From<HashMap<String, String>> for LazyHeaders {
348    #[inline]
349    fn from(map: HashMap<String, String>) -> Self {
350        Self { inner: Some(map) }
351    }
352}
353
354impl From<LazyHeaders> for HashMap<String, String> {
355    #[inline]
356    fn from(lazy: LazyHeaders) -> Self {
357        lazy.inner.unwrap_or_default()
358    }
359}
360
361// Allow iteration
362impl<'a> IntoIterator for &'a LazyHeaders {
363    type Item = (&'a String, &'a String);
364    type IntoIter = std::iter::Flatten<std::option::Iter<'a, HashMap<String, String>>>;
365
366    fn into_iter(self) -> Self::IntoIter {
367        self.inner.iter().flatten()
368    }
369}
370
371/// HTTP response wrapper
372///
373/// The body is stored as `Vec<u8>` for backwards compatibility.
374/// For zero-copy body handling, use `with_bytes_body()` or `body_bytes()`.
375///
376/// ## Performance Note
377///
378/// Response creation is optimized for minimal allocation:
379/// - `headers` uses `LazyHeaders` which doesn't allocate until first insert
380/// - `body` uses `Option<Vec<u8>>` which doesn't allocate for empty responses
381/// - Use `FastResponse` from `armature_core::fast_response` for even faster creation
382#[derive(Debug)]
383pub struct HttpResponse {
384    pub status: u16,
385    /// Response headers with lazy allocation.
386    pub headers: LazyHeaders,
387    /// Response body as raw bytes (legacy field for compatibility).
388    pub body: Vec<u8>,
389    /// Optional zero-copy body storage using Bytes.
390    /// When set, this takes precedence over `body`.
391    body_bytes: Option<Bytes>,
392}
393
394/// Default pre-allocated response buffer size (512 bytes).
395pub const DEFAULT_RESPONSE_CAPACITY: usize = 512;
396
397impl HttpResponse {
398    /// Create a new response with the given status code.
399    ///
400    /// This is optimized for minimal allocation - headers use `LazyHeaders`
401    /// which doesn't allocate until first insert, and body uses `Vec::new()`
402    /// which is zero-cost.
403    #[inline(always)]
404    pub fn new(status: u16) -> Self {
405        Self {
406            status,
407            headers: LazyHeaders::new(),
408            body: Vec::new(),
409            body_bytes: None,
410        }
411    }
412
413    /// Create a new response with pre-allocated body buffer.
414    ///
415    /// This is more efficient than `new()` when you know you'll be
416    /// writing to the body, as it avoids reallocation.
417    ///
418    /// # Example
419    ///
420    /// ```rust,ignore
421    /// // Pre-allocate for typical JSON responses
422    /// let response = HttpResponse::with_capacity(200, 512);
423    /// ```
424    #[inline]
425    pub fn with_capacity(status: u16, capacity: usize) -> Self {
426        Self {
427            status,
428            headers: LazyHeaders::with_capacity(8),
429            body: Vec::with_capacity(capacity),
430            body_bytes: None,
431        }
432    }
433
434    /// Create a 200 OK response.
435    #[inline(always)]
436    pub fn ok() -> Self {
437        Self::new(200)
438    }
439
440    /// Create a 200 OK response with pre-allocated buffer (512 bytes default).
441    #[inline]
442    pub fn ok_preallocated() -> Self {
443        Self::with_capacity(200, DEFAULT_RESPONSE_CAPACITY)
444    }
445
446    /// Create a 201 Created response.
447    #[inline(always)]
448    pub fn created() -> Self {
449        Self::new(201)
450    }
451
452    /// Create a 204 No Content response.
453    #[inline(always)]
454    pub fn no_content() -> Self {
455        Self::new(204)
456    }
457
458    /// Create a 400 Bad Request response.
459    #[inline(always)]
460    pub fn bad_request() -> Self {
461        Self::new(400)
462    }
463
464    /// Create a 404 Not Found response.
465    #[inline(always)]
466    pub fn not_found() -> Self {
467        Self::new(404)
468    }
469
470    /// Create a 500 Internal Server Error response.
471    #[inline(always)]
472    pub fn internal_server_error() -> Self {
473        Self::new(500)
474    }
475
476    pub fn with_body(mut self, body: Vec<u8>) -> Self {
477        self.body = body;
478        self.body_bytes = None;
479        self
480    }
481
482    /// Set the body using Bytes (zero-copy).
483    ///
484    /// This is the most efficient way to set response body data,
485    /// as it can be passed directly to Hyper without copying.
486    #[inline]
487    pub fn with_bytes_body(mut self, bytes: Bytes) -> Self {
488        self.body_bytes = Some(bytes);
489        self.body.clear();
490        self
491    }
492
493    /// Set the body from a static byte slice (zero-copy).
494    #[inline]
495    pub fn with_static_body(mut self, body: &'static [u8]) -> Self {
496        self.body_bytes = Some(Bytes::from_static(body));
497        self.body.clear();
498        self
499    }
500
501    /// Get the body as Bytes (zero-copy if stored as Bytes).
502    ///
503    /// This is the key method for zero-copy Hyper body passthrough.
504    /// If body was set via `with_bytes_body()`, returns the Bytes directly (O(1)).
505    /// Otherwise, converts from Vec<u8>.
506    #[inline]
507    pub fn body_bytes(&self) -> Bytes {
508        if let Some(ref bytes) = self.body_bytes {
509            bytes.clone() // O(1) - just increments ref count
510        } else {
511            Bytes::copy_from_slice(&self.body)
512        }
513    }
514
515    /// Consume the response and return body as Bytes (zero-copy).
516    ///
517    /// This is the most efficient way to get the body for Hyper,
518    /// as it avoids cloning when body_bytes is set.
519    #[inline]
520    pub fn into_body_bytes(self) -> Bytes {
521        if let Some(bytes) = self.body_bytes {
522            bytes
523        } else {
524            Bytes::from(self.body)
525        }
526    }
527
528    /// Get a reference to the body bytes.
529    #[inline]
530    pub fn body_ref(&self) -> &[u8] {
531        if let Some(ref bytes) = self.body_bytes {
532            bytes.as_ref()
533        } else {
534            &self.body
535        }
536    }
537
538    /// Get the body length.
539    #[inline]
540    pub fn body_len(&self) -> usize {
541        if let Some(ref bytes) = self.body_bytes {
542            bytes.len()
543        } else {
544            self.body.len()
545        }
546    }
547
548    /// Check if using zero-copy Bytes storage.
549    #[inline]
550    pub fn has_bytes_body(&self) -> bool {
551        self.body_bytes.is_some()
552    }
553
554    /// Serialize a value as JSON and set it as the response body.
555    ///
556    /// With the `simd-json` feature enabled, this uses SIMD-accelerated serialization
557    /// which can be 1.5-2x faster on modern x86_64 CPUs.
558    ///
559    /// The body is stored as `Bytes` for zero-copy passthrough to Hyper.
560    ///
561    /// # Example
562    ///
563    /// ```rust,ignore
564    /// HttpResponse::ok().with_json(&user)?
565    /// ```
566    #[inline]
567    pub fn with_json<T: Serialize>(mut self, value: &T) -> Result<Self, crate::Error> {
568        let vec =
569            crate::json::to_vec(value).map_err(|e| crate::Error::Serialization(e.to_string()))?;
570        self.body_bytes = Some(Bytes::from(vec));
571        self.body.clear();
572        self.headers
573            .insert("Content-Type".to_string(), "application/json".to_string());
574        Ok(self)
575    }
576
577    pub fn with_header(mut self, key: String, value: String) -> Self {
578        self.headers.insert(key, value);
579        self
580    }
581
582    /// Set multiple headers from a HashMap.
583    #[inline]
584    pub fn with_headers(mut self, headers: HashMap<String, String>) -> Self {
585        self.headers = LazyHeaders::from(headers);
586        self
587    }
588
589    /// Create a response with status and headers (for CORS preflight, etc.).
590    #[inline]
591    pub fn with_status_and_headers(status: u16, headers: HashMap<String, String>) -> Self {
592        Self {
593            status,
594            headers: LazyHeaders::from(headers),
595            body: Vec::new(),
596            body_bytes: None,
597        }
598    }
599
600    /// Create a response with all components (for compatibility).
601    ///
602    /// This is useful when you need to construct a response with all parts at once.
603    #[inline]
604    pub fn from_parts(status: u16, headers: HashMap<String, String>, body: Vec<u8>) -> Self {
605        Self {
606            status,
607            headers: LazyHeaders::from(headers),
608            body,
609            body_bytes: None,
610        }
611    }
612
613    // ============================================================================
614    // Convenience Methods for Common Response Types
615    // ============================================================================
616
617    /// Create an accepted response (202).
618    ///
619    /// # Example
620    /// ```
621    /// use armature_core::HttpResponse;
622    /// let response = HttpResponse::accepted();
623    /// assert_eq!(response.status, 202);
624    /// ```
625    pub fn accepted() -> Self {
626        Self::new(202)
627    }
628
629    /// Create an unauthorized response (401).
630    ///
631    /// # Example
632    /// ```
633    /// use armature_core::HttpResponse;
634    /// let response = HttpResponse::unauthorized();
635    /// assert_eq!(response.status, 401);
636    /// ```
637    pub fn unauthorized() -> Self {
638        Self::new(401)
639    }
640
641    /// Create a forbidden response (403).
642    ///
643    /// # Example
644    /// ```
645    /// use armature_core::HttpResponse;
646    /// let response = HttpResponse::forbidden();
647    /// assert_eq!(response.status, 403);
648    /// ```
649    pub fn forbidden() -> Self {
650        Self::new(403)
651    }
652
653    /// Create a conflict response (409).
654    ///
655    /// # Example
656    /// ```
657    /// use armature_core::HttpResponse;
658    /// let response = HttpResponse::conflict();
659    /// assert_eq!(response.status, 409);
660    /// ```
661    pub fn conflict() -> Self {
662        Self::new(409)
663    }
664
665    /// Create a service unavailable response (503).
666    ///
667    /// # Example
668    /// ```
669    /// use armature_core::HttpResponse;
670    /// let response = HttpResponse::service_unavailable();
671    /// assert_eq!(response.status, 503);
672    /// ```
673    pub fn service_unavailable() -> Self {
674        Self::new(503)
675    }
676
677    /// Shorthand for creating a JSON response with 200 OK status.
678    ///
679    /// # Example
680    /// ```
681    /// use armature_core::HttpResponse;
682    /// use serde_json::json;
683    ///
684    /// let response = HttpResponse::json(&json!({"message": "Hello"})).unwrap();
685    /// assert_eq!(response.status, 200);
686    /// ```
687    pub fn json<T: Serialize>(value: &T) -> Result<Self, crate::Error> {
688        Self::ok().with_json(value)
689    }
690
691    /// Create an HTML response with 200 OK status.
692    ///
693    /// # Example
694    /// ```
695    /// use armature_core::HttpResponse;
696    /// let response = HttpResponse::html("<h1>Hello</h1>");
697    /// assert_eq!(response.status, 200);
698    /// assert_eq!(response.headers.get("Content-Type"), Some(&"text/html; charset=utf-8".to_string()));
699    /// ```
700    pub fn html(content: impl Into<String>) -> Self {
701        Self::ok()
702            .with_header(
703                "Content-Type".to_string(),
704                "text/html; charset=utf-8".to_string(),
705            )
706            .with_body(content.into().into_bytes())
707    }
708
709    /// Create a plain text response with 200 OK status.
710    ///
711    /// # Example
712    /// ```
713    /// use armature_core::HttpResponse;
714    /// let response = HttpResponse::text("Hello, World!");
715    /// assert_eq!(response.status, 200);
716    /// assert_eq!(response.headers.get("Content-Type"), Some(&"text/plain; charset=utf-8".to_string()));
717    /// ```
718    pub fn text(content: impl Into<String>) -> Self {
719        Self::ok()
720            .with_header(
721                "Content-Type".to_string(),
722                "text/plain; charset=utf-8".to_string(),
723            )
724            .with_body(content.into().into_bytes())
725    }
726
727    /// Create a redirect response (302 Found).
728    ///
729    /// # Example
730    /// ```
731    /// use armature_core::HttpResponse;
732    /// let response = HttpResponse::redirect("https://example.com");
733    /// assert_eq!(response.status, 302);
734    /// assert_eq!(response.headers.get("Location"), Some(&"https://example.com".to_string()));
735    /// ```
736    pub fn redirect(url: impl Into<String>) -> Self {
737        Self::new(302).with_header("Location".to_string(), url.into())
738    }
739
740    /// Create a permanent redirect response (301 Moved Permanently).
741    ///
742    /// # Example
743    /// ```
744    /// use armature_core::HttpResponse;
745    /// let response = HttpResponse::redirect_permanent("https://example.com");
746    /// assert_eq!(response.status, 301);
747    /// ```
748    pub fn redirect_permanent(url: impl Into<String>) -> Self {
749        Self::new(301).with_header("Location".to_string(), url.into())
750    }
751
752    /// Create a see other redirect response (303 See Other).
753    /// Useful after a POST request to redirect to a GET.
754    ///
755    /// # Example
756    /// ```
757    /// use armature_core::HttpResponse;
758    /// let response = HttpResponse::see_other("/success");
759    /// assert_eq!(response.status, 303);
760    /// ```
761    pub fn see_other(url: impl Into<String>) -> Self {
762        Self::new(303).with_header("Location".to_string(), url.into())
763    }
764
765    /// Alias for no_content() - returns 204 with empty body.
766    ///
767    /// # Example
768    /// ```
769    /// use armature_core::HttpResponse;
770    /// let response = HttpResponse::empty();
771    /// assert_eq!(response.status, 204);
772    /// ```
773    pub fn empty() -> Self {
774        Self::no_content()
775    }
776
777    /// Set the Content-Type header.
778    ///
779    /// # Example
780    /// ```
781    /// use armature_core::HttpResponse;
782    /// let response = HttpResponse::ok().content_type("application/xml");
783    /// assert_eq!(response.headers.get("Content-Type"), Some(&"application/xml".to_string()));
784    /// ```
785    pub fn content_type(self, content_type: impl Into<String>) -> Self {
786        self.with_header("Content-Type".to_string(), content_type.into())
787    }
788
789    /// Set the Cache-Control header.
790    ///
791    /// # Example
792    /// ```
793    /// use armature_core::HttpResponse;
794    /// let response = HttpResponse::ok().cache_control("max-age=3600");
795    /// ```
796    pub fn cache_control(self, directive: impl Into<String>) -> Self {
797        self.with_header("Cache-Control".to_string(), directive.into())
798    }
799
800    /// Mark the response as not cacheable.
801    ///
802    /// # Example
803    /// ```
804    /// use armature_core::HttpResponse;
805    /// let response = HttpResponse::ok().no_cache();
806    /// ```
807    pub fn no_cache(self) -> Self {
808        self.cache_control("no-store, no-cache, must-revalidate")
809    }
810
811    /// Set a cookie on the response.
812    ///
813    /// # Example
814    /// ```
815    /// use armature_core::HttpResponse;
816    /// let response = HttpResponse::ok().cookie("session", "abc123; HttpOnly; Secure");
817    /// ```
818    pub fn cookie(self, name: impl Into<String>, value: impl Into<String>) -> Self {
819        self.with_header(
820            "Set-Cookie".to_string(),
821            format!("{}={}", name.into(), value.into()),
822        )
823    }
824
825    /// Get the response body as a string (lossy UTF-8 conversion).
826    pub fn body_string(&self) -> String {
827        String::from_utf8_lossy(self.body_ref()).to_string()
828    }
829
830    /// Check if the response is successful (2xx status code).
831    pub fn is_success(&self) -> bool {
832        (200..300).contains(&self.status)
833    }
834
835    /// Check if the response is a redirect (3xx status code).
836    pub fn is_redirect(&self) -> bool {
837        (300..400).contains(&self.status)
838    }
839
840    /// Check if the response is a client error (4xx status code).
841    pub fn is_client_error(&self) -> bool {
842        (400..500).contains(&self.status)
843    }
844
845    /// Check if the response is a server error (5xx status code).
846    pub fn is_server_error(&self) -> bool {
847        (500..600).contains(&self.status)
848    }
849}
850
851/// JSON response helper
852#[derive(Debug)]
853pub struct Json<T: Serialize>(pub T);
854
855impl<T: Serialize> Json<T> {
856    pub fn into_response(self) -> Result<HttpResponse, crate::Error> {
857        HttpResponse::ok().with_json(&self.0)
858    }
859}
860
861#[cfg(test)]
862mod tests {
863    use super::*;
864
865    #[test]
866    fn test_http_request_new() {
867        let req = HttpRequest::new("GET".to_string(), "/test".to_string());
868        assert_eq!(req.method, "GET");
869        assert_eq!(req.path, "/test");
870        assert!(req.headers.is_empty());
871        assert!(req.body.is_empty());
872    }
873
874    #[test]
875    fn test_http_request_with_body() {
876        let mut req = HttpRequest::new("POST".to_string(), "/api".to_string());
877        req.body = vec![1, 2, 3, 4];
878        assert_eq!(req.body.len(), 4);
879    }
880
881    #[test]
882    fn test_http_request_json_deserialization() {
883        #[derive(Deserialize, Debug, PartialEq)]
884        struct TestData {
885            name: String,
886            age: u32,
887        }
888
889        let mut req = HttpRequest::new("POST".to_string(), "/api".to_string());
890        req.body = serde_json::to_vec(&serde_json::json!({
891            "name": "John",
892            "age": 30
893        }))
894        .unwrap();
895
896        let data: TestData = req.json().unwrap();
897        assert_eq!(data.name, "John");
898        assert_eq!(data.age, 30);
899    }
900
901    #[test]
902    fn test_http_request_param() {
903        let mut req = HttpRequest::new("GET".to_string(), "/users/123".to_string());
904        req.path_params.insert("id".to_string(), "123".to_string());
905
906        assert_eq!(req.param("id"), Some(&"123".to_string()));
907        assert_eq!(req.param("name"), None);
908    }
909
910    #[test]
911    fn test_http_request_query() {
912        let mut req = HttpRequest::new("GET".to_string(), "/users".to_string());
913        req.query_params
914            .insert("sort".to_string(), "asc".to_string());
915
916        assert_eq!(req.query("sort"), Some(&"asc".to_string()));
917        assert_eq!(req.query("limit"), None);
918    }
919
920    #[test]
921    fn test_http_request_clone() {
922        let req1 = HttpRequest::new("GET".to_string(), "/test".to_string());
923        let req2 = req1.clone();
924
925        assert_eq!(req1.method, req2.method);
926        assert_eq!(req1.path, req2.path);
927    }
928
929    #[test]
930    fn test_http_response_ok() {
931        let res = HttpResponse::ok();
932        assert_eq!(res.status, 200);
933    }
934
935    #[test]
936    fn test_http_response_created() {
937        let res = HttpResponse::created();
938        assert_eq!(res.status, 201);
939    }
940
941    #[test]
942    fn test_http_response_no_content() {
943        let res = HttpResponse::no_content();
944        assert_eq!(res.status, 204);
945    }
946
947    #[test]
948    fn test_http_response_bad_request() {
949        let res = HttpResponse::bad_request();
950        assert_eq!(res.status, 400);
951    }
952
953    #[test]
954    fn test_http_response_not_found() {
955        let res = HttpResponse::not_found();
956        assert_eq!(res.status, 404);
957    }
958
959    #[test]
960    fn test_http_response_internal_server_error() {
961        let res = HttpResponse::internal_server_error();
962        assert_eq!(res.status, 500);
963    }
964
965    #[test]
966    fn test_http_response_with_body() {
967        let body = b"Hello, World!".to_vec();
968        let res = HttpResponse::ok().with_body(body.clone());
969        assert_eq!(res.body, body);
970    }
971
972    #[test]
973    fn test_http_response_with_json() {
974        #[derive(Serialize)]
975        struct TestData {
976            message: String,
977        }
978
979        let data = TestData {
980            message: "test".to_string(),
981        };
982
983        let res = HttpResponse::ok().with_json(&data).unwrap();
984        assert!(!res.body_ref().is_empty());
985        assert_eq!(
986            res.headers.get("Content-Type"),
987            Some(&"application/json".to_string())
988        );
989    }
990
991    #[test]
992    fn test_http_response_with_header() {
993        let res = HttpResponse::ok().with_header("X-Custom".to_string(), "value".to_string());
994
995        assert_eq!(res.headers.get("X-Custom"), Some(&"value".to_string()));
996    }
997
998    #[test]
999    fn test_http_response_multiple_headers() {
1000        let res = HttpResponse::ok()
1001            .with_header("X-Header-1".to_string(), "value1".to_string())
1002            .with_header("X-Header-2".to_string(), "value2".to_string());
1003
1004        assert_eq!(res.headers.len(), 2);
1005    }
1006
1007    #[test]
1008    fn test_json_helper() {
1009        #[derive(Serialize)]
1010        struct Data {
1011            value: i32,
1012        }
1013
1014        let json = Json(Data { value: 42 });
1015        let response = json.into_response().unwrap();
1016
1017        assert_eq!(response.status, 200);
1018        assert!(!response.body_ref().is_empty());
1019    }
1020
1021    #[test]
1022    fn test_http_request_with_headers() {
1023        let mut req = HttpRequest::new("GET".to_string(), "/api".to_string());
1024        req.headers
1025            .insert("Authorization".to_string(), "Bearer token".to_string());
1026        req.headers
1027            .insert("Content-Type".to_string(), "application/json".to_string());
1028
1029        assert_eq!(req.headers.len(), 2);
1030    }
1031
1032    #[test]
1033    fn test_http_request_json_invalid() {
1034        #[derive(Deserialize)]
1035        #[allow(dead_code)]
1036        struct TestData {
1037            name: String,
1038        }
1039
1040        let mut req = HttpRequest::new("POST".to_string(), "/api".to_string());
1041        req.body = b"invalid json".to_vec();
1042
1043        let result: Result<TestData, crate::Error> = req.json();
1044        assert!(result.is_err());
1045    }
1046
1047    #[test]
1048    fn test_http_response_new_custom_status() {
1049        let res = HttpResponse::new(418); // I'm a teapot
1050        assert_eq!(res.status, 418);
1051    }
1052
1053    #[test]
1054    fn test_http_response_with_json_complex() {
1055        #[derive(Serialize)]
1056        struct ComplexData {
1057            nested: Vec<HashMap<String, i32>>,
1058        }
1059
1060        let mut map = HashMap::new();
1061        map.insert("key".to_string(), 123);
1062
1063        let data = ComplexData { nested: vec![map] };
1064
1065        let res = HttpResponse::ok().with_json(&data);
1066        assert!(res.is_ok());
1067    }
1068}