ignitia/response/
mod.rs

1//! # HTTP Response Generation Module
2//!
3//! This module provides comprehensive HTTP response generation capabilities for the Ignitia web framework.
4//! It includes response building, content type handling, status code management, and error response generation
5//! with efficient serialization and flexible customization options.
6//!
7//! ## Features
8//!
9//! - **Multiple Response Formats**: JSON, HTML, text, and binary response generation
10//! - **Status Code Management**: Easy status code setting with validation
11//! - **Header Management**: Flexible header manipulation and content type handling
12//! - **Error Response Generation**: Automatic error-to-response conversion with structured formats
13//! - **Builder Pattern Support**: Fluent response building with method chaining
14//! - **Performance Optimized**: Efficient serialization and memory usage
15//!
16//! ## Response Types
17//!
18//! ### JSON Responses
19//! - Automatic serialization with proper content-type headers
20//! - Support for any type implementing `Serialize`
21//! - Comprehensive error handling for serialization failures
22//!
23//! ### HTML Responses
24//! - Proper content-type headers with charset specification
25//! - Template integration support
26//! - XSS protection considerations
27//!
28//! ### Text Responses
29//! - UTF-8 encoded plain text with proper headers
30//! - Support for various text formats
31//!
32//! ### Binary Responses
33//! - Efficient handling of binary data
34//! - Flexible content-type specification
35//! - Support for file downloads and streaming
36//!
37//! ## Usage Examples
38//!
39//! ### Basic Response Creation
40//! ```
41//! use ignitia::{Response, Result};
42//! use http::StatusCode;
43//!
44//! // Simple text response
45//! async fn hello_handler() -> Result<Response> {
46//!     Ok(Response::text("Hello, World!"))
47//! }
48//!
49//! // JSON response
50//! async fn json_handler() -> Result<Response> {
51//!     let data = serde_json::json!({
52//!         "message": "Hello, World!",
53//!         "timestamp": chrono::Utc::now()
54//!     });
55//!     Response::json(data)
56//! }
57//!
58//! // HTML response
59//! async fn html_handler() -> Result<Response> {
60//!     let html = r#"
61//!         <!DOCTYPE html>
62//!         <html>
63//!         <head><title>Hello</title></head>
64//!         <body><h1>Hello, World!</h1></body>
65//!         </html>
66//!     "#;
67//!     Ok(Response::html(html))
68//! }
69//! ```
70//!
71//! ### Custom Status Codes
72//! ```
73//! use ignitia::Response;
74//! use http::StatusCode;
75//!
76//! async fn custom_status_handler() -> ignitia::Result<Response> {
77//!     Ok(Response::text("Created successfully")
78//!         .with_status(StatusCode::CREATED))
79//! }
80//!
81//! async fn not_found_handler() -> ignitia::Result<Response> {
82//!     Ok(Response::text("Resource not found")
83//!         .with_status_code(404))
84//! }
85//! ```
86//!
87//! ### Working with Headers
88//! ```
89//! use ignitia::Response;
90//! use http::{HeaderMap, HeaderName, HeaderValue};
91//!
92//! async fn custom_headers_handler() -> ignitia::Result<Response> {
93//!     let mut response = Response::text("Custom headers example");
94//!
95//!     // Add custom headers
96//!     response.headers.insert(
97//!         HeaderName::from_static("x-custom-header"),
98//!         HeaderValue::from_static("custom-value")
99//!     );
100//!
101//!     response.headers.insert(
102//!         HeaderName::from_static("cache-control"),
103//!         HeaderValue::from_static("no-cache, no-store, must-revalidate")
104//!     );
105//!
106//!     Ok(response)
107//! }
108//! ```
109//!
110//! ## Advanced Usage
111//!
112//! ### API Response Patterns
113//! ```
114//! use ignitia::{Response, Result};
115//! use serde::{Deserialize, Serialize};
116//! use http::StatusCode;
117//!
118//! #[derive(Serialize)]
119//! struct ApiResponse<T> {
120//!     success: bool,
121//!     data: Option<T>,
122//!     message: String,
123//!     timestamp: String,
124//! }
125//!
126//! #[derive(Serialize)]
127//! struct User {
128//!     id: u32,
129//!     name: String,
130//!     email: String,
131//! }
132//!
133//! async fn get_user_handler() -> Result<Response> {
134//!     let user = User {
135//!         id: 1,
136//!         name: "Alice".to_string(),
137//!         email: "alice@example.com".to_string(),
138//!     };
139//!
140//!     let api_response = ApiResponse {
141//!         success: true,
142//!         data: Some(user),
143//!         message: "User retrieved successfully".to_string(),
144//!         timestamp: chrono::Utc::now().to_rfc3339(),
145//!     };
146//!
147//!     Response::json(api_response)
148//! }
149//!
150//! async fn user_not_found_handler() -> Result<Response> {
151//!     let api_response = ApiResponse::<()> {
152//!         success: false,
153//!         data: None,
154//!         message: "User not found".to_string(),
155//!         timestamp: chrono::Utc::now().to_rfc3339(),
156//!     };
157//!
158//!     let mut response = Response::json(api_response)?;
159//!     response.status = StatusCode::NOT_FOUND;
160//!     Ok(response)
161//! }
162//! ```
163//!
164//! ### File Download Responses
165//! ```
166//! use ignitia::Response;
167//! use bytes::Bytes;
168//! use http::{HeaderName, HeaderValue};
169//!
170//! async fn download_handler() -> ignitia::Result<Response> {
171//!     // Simulate file content
172//!     let file_content = b"Hello, this is a downloadable file!";
173//!     let filename = "example.txt";
174//!
175//!     let mut response = Response::new(http::StatusCode::OK);
176//!     response.body = Bytes::from(&file_content[..]);
177//!
178//!     // Set appropriate headers for file download
179//!     response.headers.insert(
180//!         HeaderName::from_static("content-type"),
181//!         HeaderValue::from_static("application/octet-stream")
182//!     );
183//!
184//!     response.headers.insert(
185//!         HeaderName::from_static("content-disposition"),
186//!         HeaderValue::from_str(&format!("attachment; filename=\"{}\"", filename))
187//!             .map_err(|_| ignitia::Error::Internal("Invalid filename".into()))?
188//!     );
189//!
190//!     response.headers.insert(
191//!         HeaderName::from_static("content-length"),
192//!         HeaderValue::from_str(&file_content.len().to_string())
193//!             .map_err(|_| ignitia::Error::Internal("Invalid content length".into()))?
194//!     );
195//!
196//!     Ok(response)
197//! }
198//! ```
199//!
200//! ### Streaming Responses
201//! ```
202//! use ignitia::Response;
203//! use bytes::Bytes;
204//! use http::{HeaderName, HeaderValue};
205//!
206//! async fn streaming_handler() -> ignitia::Result<Response> {
207//!     // Server-Sent Events example
208//!     let event_data = "data: Hello from server!\n\n";
209//!
210//!     let mut response = Response::new(http::StatusCode::OK);
211//!     response.body = Bytes::from(event_data);
212//!
213//!     response.headers.insert(
214//!         HeaderName::from_static("content-type"),
215//!         HeaderValue::from_static("text/event-stream")
216//!     );
217//!
218//!     response.headers.insert(
219//!         HeaderName::from_static("cache-control"),
220//!         HeaderValue::from_static("no-cache")
221//!     );
222//!
223//!     response.headers.insert(
224//!         HeaderName::from_static("connection"),
225//!         HeaderValue::from_static("keep-alive")
226//!     );
227//!
228//!     Ok(response)
229//! }
230//! ```
231//!
232//! ## Error Response Handling
233//!
234//! ### Automatic Error Conversion
235//! ```
236//! use ignitia::{Response, Error, Result};
237//! use http::StatusCode;
238//!
239//! async fn error_example_handler() -> Result<Response> {
240//!     // This will automatically be converted to an error response
241//!     Err(Error::NotFound("User not found".to_string()))
242//! }
243//!
244//! // The framework automatically converts errors to responses:
245//! // {
246//! //   "error": "Not Found",
247//! //   "message": "User not found",
248//! //   "status": 404,
249//! //   "error_type": "not_found",
250//! //   "timestamp": "2023-01-01T12:00:00Z"
251//! // }
252//! ```
253//!
254//! ### Custom Error Responses
255//! ```
256//! use ignitia::{Response, Error, Result};
257//! use serde_json::json;
258//!
259//! async fn custom_error_handler() -> Result<Response> {
260//!     let error_messages = vec![
261//!         "Name is required".to_string(),
262//!         "Email format is invalid".to_string(),
263//!     ];
264//!
265//!     Response::validation_error(error_messages)
266//! }
267//!
268//! async fn api_error_handler() -> Result<Response> {
269//!     let error_response = json!({
270//!         "error": {
271//!             "code": "RATE_LIMITED",
272//!             "message": "Too many requests",
273//!             "retry_after": 60
274//!         }
275//!     });
276//!
277//!     let mut response = Response::json(error_response)?;
278//!     response.status = http::StatusCode::TOO_MANY_REQUESTS;
279//!     Ok(response)
280//! }
281//! ```
282//!
283//! ## Performance Considerations
284//!
285//! ### Memory Efficiency
286//! - Uses `bytes::Bytes` for zero-copy operations
287//! - Efficient serialization with pre-allocated buffers
288//! - Minimal header allocation overhead
289//!
290//! ### Serialization Performance
291//! ```
292//! use ignitia::Response;
293//! use serde::Serialize;
294//!
295//! #[derive(Serialize)]
296//! struct LargeData {
297//!     items: Vec<String>,
298//! }
299//!
300//! async fn optimized_json_handler() -> ignitia::Result<Response> {
301//!     let data = LargeData {
302//!         items: (0..1000).map(|i| format!("Item {}", i)).collect(),
303//!     };
304//!
305//!     // Efficient JSON serialization with error handling
306//!     match Response::json(data) {
307//!         Ok(response) => Ok(response),
308//!         Err(e) => {
309//!             tracing::error!("JSON serialization failed: {}", e);
310//!             Ok(Response::text("Internal server error")
311//!                 .with_status(http::StatusCode::INTERNAL_SERVER_ERROR))
312//!         }
313//!     }
314//! }
315//! ```
316//!
317//! ## Security Considerations
318//!
319//! ### Content Type Security
320//! - Always set appropriate content-type headers
321//! - Validate content before setting as HTML
322//! - Use proper encoding for text responses
323//!
324//! ### XSS Prevention
325//! ```
326//! use ignitia::Response;
327//!
328//! async fn safe_html_handler(user_input: &str) -> ignitia::Result<Response> {
329//!     // Escape user input to prevent XSS
330//!     let escaped_input = html_escape::encode_text(user_input);
331//!
332//!     let safe_html = format!(
333//!         r#"<!DOCTYPE html>
334//!         <html>
335//!         <head><title>Safe Output</title></head>
336//!         <body><h1>Hello, {}</h1></body>
337//!         </html>"#,
338//!         escaped_input
339//!     );
340//!
341//!     Ok(Response::html(safe_html))
342//! }
343//! ```
344//!
345//! ### Security Headers
346//! ```
347//! use ignitia::Response;
348//! use http::{HeaderName, HeaderValue};
349//!
350//! async fn secure_response_handler() -> ignitia::Result<Response> {
351//!     let mut response = Response::html("<h1>Secure Page</h1>");
352//!
353//!     // Add security headers
354//!     response.headers.insert(
355//!         HeaderName::from_static("x-content-type-options"),
356//!         HeaderValue::from_static("nosniff")
357//!     );
358//!
359//!     response.headers.insert(
360//!         HeaderName::from_static("x-frame-options"),
361//!         HeaderValue::from_static("DENY")
362//!     );
363//!
364//!     response.headers.insert(
365//!         HeaderName::from_static("x-xss-protection"),
366//!         HeaderValue::from_static("1; mode=block")
367//!     );
368//!
369//!     response.headers.insert(
370//!         HeaderName::from_static("content-security-policy"),
371//!         HeaderValue::from_static("default-src 'self'")
372//!     );
373//!
374//!     Ok(response)
375//! }
376//! ```
377//!
378//! ## Testing Response Generation
379//!
380//! ### Unit Testing
381//! ```
382//! #[cfg(test)]
383//! mod tests {
384//!     use super::*;
385//!     use http::StatusCode;
386//!
387//!     #[tokio::test]
388//!     async fn test_text_response() {
389//!         let response = Response::text("Hello, World!");
390//!
391//!         assert_eq!(response.status, StatusCode::OK);
392//!         assert_eq!(
393//!             response.headers.get("content-type").unwrap(),
394//!             "text/plain; charset=utf-8"
395//!         );
396//!         assert_eq!(response.body, "Hello, World!");
397//!     }
398//!
399//!     #[tokio::test]
400//!     async fn test_json_response() {
401//!         let data = serde_json::json!({"message": "test"});
402//!         let response = Response::json(data).unwrap();
403//!
404//!         assert_eq!(response.status, StatusCode::OK);
405//!         assert_eq!(
406//!             response.headers.get("content-type").unwrap(),
407//!             "application/json"
408//!         );
409//!     }
410//!
411//!     #[tokio::test]
412//!     async fn test_error_conversion() {
413//!         let error = ignitia::Error::NotFound("test".to_string());
414//!         let response = Response::from(error);
415//!
416//!         assert_eq!(response.status, StatusCode::NOT_FOUND);
417//!     }
418//! }
419//! ```
420
421pub mod builder;
422pub mod status;
423
424pub mod into_response;
425
426// Re-export IntoResponse
427pub use into_response::{Html, IntoResponse};
428
429use bytes::Bytes;
430use http::{header, HeaderMap, HeaderName, HeaderValue, StatusCode};
431use serde::Serialize;
432
433/// HTTP response representation containing status, headers, and body.
434///
435/// The `Response` struct encapsulates all data needed to send an HTTP response,
436/// including the status code, headers, and body content. It provides convenient
437/// methods for creating responses with different content types and formats.
438///
439/// # Structure
440/// - **status**: HTTP status code (200, 404, 500, etc.)
441/// - **headers**: HTTP headers as a HeaderMap
442/// - **body**: Response body as bytes
443///
444/// # Examples
445///
446/// ## Basic Response Creation
447/// ```
448/// use ignitia::Response;
449/// use http::StatusCode;
450///
451/// // Create a simple text response
452/// let response = Response::text("Hello, World!");
453/// assert_eq!(response.status, StatusCode::OK);
454///
455/// // Create a response with custom status
456/// let response = Response::new(StatusCode::CREATED);
457/// ```
458///
459/// ## JSON Responses
460/// ```
461/// use ignitia::Response;
462/// use serde_json::json;
463///
464/// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
465/// let data = json!({
466///     "message": "Success",
467///     "data": {
468///         "id": 123,
469///         "name": "Example"
470///     }
471/// });
472///
473/// let response = Response::json(data)?;
474/// # Ok(())
475/// # }
476/// ```
477///
478/// ## HTML Responses
479/// ```
480/// use ignitia::Response;
481///
482/// let html = r#"
483/// <!DOCTYPE html>
484/// <html>
485/// <head><title>Hello</title></head>
486/// <body><h1>Hello, World!</h1></body>
487/// </html>
488/// "#;
489///
490/// let response = Response::html(html);
491/// ```
492#[derive(Debug, Clone)]
493pub struct Response {
494    /// HTTP status code
495    pub status: StatusCode,
496    /// HTTP response headers
497    pub headers: HeaderMap,
498    /// Response body as bytes
499    pub body: Bytes,
500    /// Cache control information
501    pub cache_control: Option<CacheControl>, // Add this field
502}
503
504/// HTTP cache control configuration for optimizing response caching.
505///
506/// The `CacheControl` struct encapsulates caching metadata used to optimize
507/// HTTP response delivery through strategic cache management. It provides
508/// fine-grained control over cache behavior including cache duration and
509/// cache key generation for efficient content delivery.
510///
511/// # Purpose
512///
513/// This struct enables:
514/// - **Cache Duration Control**: Setting appropriate cache lifetimes via `max_age`
515/// - **Cache Key Management**: Generating unique identifiers for cached content
516/// - **Performance Optimization**: Reducing server load through intelligent caching
517/// - **CDN Integration**: Supporting Content Delivery Network caching strategies
518///
519/// # Cache Strategy
520///
521/// The cache control system implements a dual-approach strategy:
522/// 1. **Time-based Expiration**: Uses `max_age` for cache lifetime management
523/// 2. **Content-based Invalidation**: Uses `key` for cache versioning and invalidation
524///
525/// # Integration with Response
526///
527/// When attached to a `Response`, the `CacheControl` struct automatically:
528/// - Sets appropriate HTTP cache headers (`Cache-Control`, `ETag`, etc.)
529/// - Generates cache keys for storage systems
530/// - Enables conditional requests (304 Not Modified responses)
531/// - Supports cache invalidation strategies
532///
533/// # Examples
534///
535/// ## Basic Cache Control
536/// ```
537/// use ignitia::{Response, CacheControl};
538///
539/// let cache_control = CacheControl {
540///     max_age: 3600, // 1 hour
541///     key: "user_profile_123".to_string(),
542/// };
543///
544/// let response = Response::json(user_data)?
545///     .with_cache_control(cache_control);
546/// ```
547///
548/// ## Static Asset Caching
549/// ```
550/// // Long-term caching for static assets
551/// let static_cache = CacheControl {
552///     max_age: 31536000, // 1 year
553///     key: format!("static_{}_{}", filename, version_hash),
554/// };
555/// ```
556///
557/// ## API Response Caching
558/// ```
559/// // Short-term caching for API responses
560/// let api_cache = CacheControl {
561///     max_age: 300, // 5 minutes
562///     key: format!("api_{}_{}_{}", endpoint, user_id, timestamp),
563/// };
564/// ```
565///
566/// ## Dynamic Content Caching
567/// ```
568/// // User-specific content with medium cache duration
569/// let user_cache = CacheControl {
570///     max_age: 1800, // 30 minutes
571///     key: format!("content_{}_{}", content_id, last_modified),
572/// };
573/// ```
574#[derive(Debug, Clone)]
575pub struct CacheControl {
576    /// Maximum age for cached content in seconds.
577    ///
578    /// This field determines how long the content should be considered fresh
579    /// by browsers, CDNs, and intermediate caches. The value directly maps
580    /// to the HTTP `Cache-Control: max-age=` directive.
581    ///
582    /// # Common Values
583    /// - **0**: No caching (always revalidate)
584    /// - **300**: 5 minutes (dynamic API responses)
585    /// - **3600**: 1 hour (semi-static content)
586    /// - **86400**: 24 hours (daily updated content)
587    /// - **31536000**: 1 year (static assets with versioning)
588    ///
589    /// # Performance Considerations
590    /// - Longer cache times reduce server load but may serve stale content
591    /// - Shorter cache times ensure freshness but increase server requests
592    /// - Consider content update frequency when setting values
593    ///
594    /// # Examples
595    /// ```
596    /// // No caching for sensitive data
597    /// let sensitive = CacheControl { max_age: 0, key: "...".to_string() };
598    ///
599    /// // Medium caching for API responses
600    /// let api = CacheControl { max_age: 600, key: "...".to_string() };
601    ///
602    /// // Long caching for static assets
603    /// let static_content = CacheControl { max_age: 2592000, key: "...".to_string() };
604    /// ```
605    pub max_age: u64,
606
607    /// Unique identifier for cache entry management and invalidation.
608    ///
609    /// The cache key serves multiple purposes in the caching infrastructure:
610    /// - **Uniqueness**: Ensures different content versions are cached separately
611    /// - **Invalidation**: Enables targeted cache clearing when content changes
612    /// - **Versioning**: Supports content versioning through key changes
613    /// - **Debugging**: Provides identifiable cache entries for troubleshooting
614    ///
615    /// # Key Generation Strategies
616    ///
617    /// ## Content-Based Keys
618    /// Include content identifiers that change when content changes:
619    /// ```
620    /// let key = format!("article_{}_{}", article_id, last_modified_timestamp);
621    /// ```
622    ///
623    /// ## User-Specific Keys
624    /// Include user context for personalized content:
625    /// ```
626    /// let key = format!("dashboard_{}_{}_{}", user_id, role, preferences_hash);
627    /// ```
628    ///
629    /// ## Version-Based Keys
630    /// Include application or content version for cache busting:
631    /// ```
632    /// let key = format!("api_response_{}_v{}", endpoint, api_version);
633    /// ```
634    ///
635    /// ## Hierarchical Keys
636    /// Use hierarchical structure for organized cache management:
637    /// ```
638    /// let key = format!("app:{}:user:{}:page:{}", app_version, user_id, page_id);
639    /// ```
640    ///
641    /// # Best Practices
642    /// - Include all relevant context that affects content
643    /// - Use consistent naming conventions across the application
644    /// - Include version or timestamp information for automatic invalidation
645    /// - Keep keys reasonably short while maintaining uniqueness
646    /// - Avoid sensitive information in cache keys
647    ///
648    /// # Performance Notes
649    /// - Shorter keys reduce memory overhead in cache systems
650    /// - Consistent key patterns improve cache hit rates
651    /// - Include enough context to avoid cache collisions
652    /// - Consider key distribution for cache sharding strategies
653    pub key: String,
654}
655
656impl Response {
657    /// Creates a new Response with the specified status code.
658    ///
659    /// This is the basic constructor for Response instances. It creates a response
660    /// with the given status code, empty headers, and an empty body.
661    ///
662    /// # Parameters
663    /// - `status`: The HTTP status code for the response
664    ///
665    /// # Returns
666    /// A new Response instance
667    ///
668    /// # Examples
669    /// ```
670    /// use ignitia::Response;
671    /// use http::StatusCode;
672    ///
673    /// let response = Response::new(StatusCode::OK);
674    /// assert_eq!(response.status, StatusCode::OK);
675    /// assert!(response.body.is_empty());
676    ///
677    /// let error_response = Response::new(StatusCode::INTERNAL_SERVER_ERROR);
678    /// assert_eq!(error_response.status, StatusCode::INTERNAL_SERVER_ERROR);
679    /// ```
680    #[inline]
681    pub fn new(status: StatusCode) -> Self {
682        Self {
683            status,
684            headers: HeaderMap::new(),
685            body: Bytes::new(),
686            cache_control: None,
687        }
688    }
689
690    /// Sets the status code of the response (builder pattern).
691    ///
692    /// This method consumes the response and returns it with the new status code,
693    /// enabling fluent method chaining.
694    ///
695    /// # Parameters
696    /// - `status`: The new status code to set
697    ///
698    /// # Returns
699    /// The response with the updated status code
700    ///
701    /// # Examples
702    /// ```
703    /// use ignitia::Response;
704    /// use http::StatusCode;
705    ///
706    /// let response = Response::text("Created successfully")
707    ///     .with_status(StatusCode::CREATED);
708    /// assert_eq!(response.status, StatusCode::CREATED);
709    /// ```
710    #[inline]
711    pub fn with_status(mut self, status: StatusCode) -> Self {
712        self.status = status;
713        self
714    }
715
716    /// Sets the status code using a numeric value (builder pattern).
717    ///
718    /// This is a convenience method that accepts a u16 status code and converts
719    /// it to a StatusCode. Invalid status codes are ignored.
720    ///
721    /// # Parameters
722    /// - `status_code`: The numeric status code (e.g., 200, 404, 500)
723    ///
724    /// # Returns
725    /// The response with the updated status code (if valid)
726    ///
727    /// # Examples
728    /// ```
729    /// use ignitia::Response;
730    ///
731    /// let response = Response::text("Not Found")
732    ///     .with_status_code(404);
733    /// assert_eq!(response.status.as_u16(), 404);
734    ///
735    /// // Invalid status codes are ignored
736    /// let response = Response::text("Test")
737    ///     .with_status_code(9999); // Invalid, ignored
738    /// ```
739    #[inline]
740    pub fn with_status_code(mut self, status_code: u16) -> Self {
741        if let Ok(status) = StatusCode::from_u16(status_code) {
742            self.status = status;
743        }
744        self
745    }
746
747    /// Sets the response body (builder pattern).
748    ///
749    /// This method accepts any type that can be converted to `Bytes` and sets
750    /// it as the response body.
751    ///
752    /// # Parameters
753    /// - `body`: The body content (String, &str, Vec<u8>, Bytes, etc.)
754    ///
755    /// # Returns
756    /// The response with the updated body
757    ///
758    /// # Examples
759    /// ```
760    /// use ignitia::Response;
761    /// use bytes::Bytes;
762    ///
763    /// // From string
764    /// let response = Response::new(http::StatusCode::OK)
765    ///     .with_body("Hello, World!");
766    ///
767    /// // From bytes
768    /// let data = Bytes::from("Binary data");
769    /// let response = Response::new(http::StatusCode::OK)
770    ///     .with_body(data);
771    /// ```
772    #[inline]
773    pub fn with_body(mut self, body: impl Into<Bytes>) -> Self {
774        self.body = body.into();
775        self
776    }
777
778    /// Returns a shared reference to the response body.
779    ///
780    /// This method returns a shared reference to the response body, allowing
781    /// multiple parts of the application to access the body without cloning it.
782    ///
783    /// # Returns
784    /// A shared reference to the response body
785    ///
786    /// # Examples
787    /// ```
788    /// use ignitia::Response;
789    /// use bytes::Bytes;
790    ///
791    /// let response = Response::new(http::StatusCode::OK)
792    ///     .with_body("Hello, World!");
793    ///
794    /// let body = response.body_shared();
795    /// assert_eq!(body.as_ref(), b"Hello, World!");
796    /// ```
797    #[inline]
798    pub fn body_shared(&self) -> &Bytes {
799        &self.body
800    }
801
802    /// Creates a successful response (200 OK).
803    ///
804    /// This is a convenience method for creating responses with a 200 OK status.
805    ///
806    /// # Returns
807    /// A new response with status 200 OK
808    ///
809    /// # Examples
810    /// ```
811    /// use ignitia::Response;
812    /// use http::StatusCode;
813    ///
814    /// let response = Response::ok();
815    /// assert_eq!(response.status, StatusCode::OK);
816    /// ```
817    #[inline]
818    pub fn ok() -> Self {
819        Self::new(StatusCode::OK)
820    }
821
822    /// Creates a not found response (404 Not Found).
823    ///
824    /// This is a convenience method for creating 404 responses.
825    ///
826    /// # Returns
827    /// A new response with status 404 Not Found
828    ///
829    /// # Examples
830    /// ```
831    /// use ignitia::Response;
832    /// use http::StatusCode;
833    ///
834    /// let response = Response::not_found();
835    /// assert_eq!(response.status, StatusCode::NOT_FOUND);
836    /// ```
837    #[inline]
838    pub fn not_found() -> Self {
839        Self::new(StatusCode::NOT_FOUND)
840    }
841
842    /// Creates an internal server error response (500 Internal Server Error).
843    ///
844    /// This is a convenience method for creating 500 error responses.
845    ///
846    /// # Returns
847    /// A new response with status 500 Internal Server Error
848    ///
849    /// # Examples
850    /// ```
851    /// use ignitia::Response;
852    /// use http::StatusCode;
853    ///
854    /// let response = Response::internal_error();
855    /// assert_eq!(response.status, StatusCode::INTERNAL_SERVER_ERROR);
856    /// ```
857    #[inline]
858    pub fn internal_error() -> Self {
859        Self::new(StatusCode::INTERNAL_SERVER_ERROR)
860    }
861
862    /// Creates a JSON response with automatic serialization.
863    ///
864    /// This method serializes the provided data to JSON and creates a response
865    /// with the appropriate content-type header. It returns a Result because
866    /// serialization can fail.
867    ///
868    /// # Type Parameters
869    /// - `T`: The type to serialize (must implement `Serialize`)
870    ///
871    /// # Parameters
872    /// - `data`: The data to serialize as JSON
873    ///
874    /// # Returns
875    /// - `Ok(Response)`: Successfully created JSON response
876    /// - `Err(Error)`: JSON serialization error
877    ///
878    /// # Examples
879    /// ```
880    /// use ignitia::Response;
881    /// use serde::Serialize;
882    /// use serde_json::json;
883    ///
884    /// #[derive(Serialize)]
885    /// struct ApiResponse {
886    ///     success: bool,
887    ///     message: String,
888    /// }
889    ///
890    /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
891    /// // With custom struct
892    /// let data = ApiResponse {
893    ///     success: true,
894    ///     message: "Operation completed".to_string(),
895    /// };
896    /// let response = Response::json(data)?;
897    ///
898    /// // With serde_json::Value
899    /// let data = json!({
900    ///     "users": [
901    ///         {"id": 1, "name": "Alice"},
902    ///         {"id": 2, "name": "Bob"}
903    ///     ],
904    ///     "total": 2
905    /// });
906    /// let response = Response::json(data)?;
907    /// # Ok(())
908    /// # }
909    /// ```
910    ///
911    /// ## Error Handling
912    /// ```
913    /// use ignitia::Response;
914    /// use serde_json::json;
915    ///
916    /// async fn safe_json_handler() -> ignitia::Result<Response> {
917    ///     let data = json!({"key": "value"});
918    ///
919    ///     match Response::json(data) {
920    ///         Ok(response) => Ok(response),
921    ///         Err(e) => {
922    ///             tracing::error!("JSON serialization failed: {}", e);
923    ///             Ok(Response::text("Internal server error")
924    ///                 .with_status_code(500))
925    ///         }
926    ///     }
927    /// }
928    /// ```
929    pub fn json<T: Serialize>(data: T) -> Self {
930        let body = match serde_json::to_vec(&data) {
931            Ok(body) => body,
932            Err(e) => {
933                tracing::error!("JSON serialization failed: {}", e);
934                return Response::text("JSON serialization failed").with_status_code(500);
935            }
936        };
937        let mut response = Self::new(StatusCode::OK);
938        response.headers.insert(
939            HeaderName::from_static("content-type"),
940            HeaderValue::from_static("application/json"),
941        );
942        response.body = Bytes::from(body);
943        response
944    }
945
946    /// Creates a plain text response.
947    ///
948    /// This method creates a response with UTF-8 encoded text and sets the
949    /// appropriate content-type header.
950    ///
951    /// # Parameters
952    /// - `text`: The text content (String, &str, or anything that converts to String)
953    ///
954    /// # Returns
955    /// A new response with the text content
956    ///
957    /// # Examples
958    /// ```
959    /// use ignitia::Response;
960    ///
961    /// let response = Response::text("Hello, World!");
962    /// assert_eq!(
963    ///     response.headers.get("content-type").unwrap(),
964    ///     "text/plain; charset=utf-8"
965    /// );
966    ///
967    /// let response = Response::text(format!("User ID: {}", 123));
968    /// ```
969    ///
970    /// ## Multi-line Text
971    /// ```
972    /// use ignitia::Response;
973    ///
974    /// let text = r#"
975    /// Line 1
976    /// Line 2
977    /// Line 3
978    /// "#;
979    /// let response = Response::text(text);
980    /// ```
981    #[inline]
982    pub fn text(text: impl Into<String>) -> Self {
983        let mut response = Self::new(StatusCode::OK);
984        response.headers.insert(
985            HeaderName::from_static("content-type"),
986            HeaderValue::from_static("text/plain; charset=utf-8"),
987        );
988        response.body = Bytes::from(text.into());
989        response
990    }
991
992    /// Creates an HTML response.
993    ///
994    /// This method creates a response with HTML content and sets the appropriate
995    /// content-type header with UTF-8 charset.
996    ///
997    /// # Parameters
998    /// - `html`: The HTML content (String, &str, or anything that converts to String)
999    ///
1000    /// # Returns
1001    /// A new response with the HTML content
1002    ///
1003    /// # Examples
1004    /// ```
1005    /// use ignitia::Response;
1006    ///
1007    /// let html = r#"
1008    /// <!DOCTYPE html>
1009    /// <html>
1010    /// <head>
1011    ///     <title>Hello Page</title>
1012    ///     <meta charset="UTF-8">
1013    /// </head>
1014    /// <body>
1015    ///     <h1>Hello, World!</h1>
1016    ///     <p>Welcome to our website!</p>
1017    /// </body>
1018    /// </html>
1019    /// "#;
1020    ///
1021    /// let response = Response::html(html);
1022    /// assert_eq!(
1023    ///     response.headers.get("content-type").unwrap(),
1024    ///     "text/html; charset=utf-8"
1025    /// );
1026    /// ```
1027    ///
1028    /// ## Dynamic HTML Generation
1029    /// ```
1030    /// use ignitia::Response;
1031    ///
1032    /// fn generate_user_page(username: &str, email: &str) -> Response {
1033    ///     let html = format!(r#"
1034    ///     <!DOCTYPE html>
1035    ///     <html>
1036    ///     <head><title>User Profile</title></head>
1037    ///     <body>
1038    ///         <h1>User Profile</h1>
1039    ///         <p><strong>Username:</strong> {}</p>
1040    ///         <p><strong>Email:</strong> {}</p>
1041    ///     </body>
1042    ///     </html>
1043    ///     "#, username, email);
1044    ///
1045    ///     Response::html(html)
1046    /// }
1047    /// ```
1048    ///
1049    /// ## Security Note
1050    /// When generating HTML with user input, always escape the input to prevent XSS:
1051    /// ```
1052    /// use ignitia::Response;
1053    ///
1054    /// fn safe_html_response(user_input: &str) -> Response {
1055    ///     // Escape user input (you would use a proper HTML escape function)
1056    ///     let escaped_input = user_input
1057    ///         .replace('&', "&amp;")
1058    ///         .replace('<', "&lt;")
1059    ///         .replace('>', "&gt;")
1060    ///         .replace('"', "&quot;")
1061    ///         .replace('\'', "&#x27;");
1062    ///
1063    ///     let html = format!("<p>Hello, {}</p>", escaped_input);
1064    ///     Response::html(html)
1065    /// }
1066    /// ```
1067    #[inline]
1068    pub fn html(html: impl Into<String>) -> Self {
1069        let mut response = Self::new(StatusCode::OK);
1070        response.headers.insert(
1071            HeaderName::from_static("content-type"),
1072            HeaderValue::from_static("text/html; charset=utf-8"),
1073        );
1074        response.body = Bytes::from(html.into());
1075        response
1076    }
1077
1078    /// Creates a temporary redirect response (HTTP 302 Found).
1079    ///
1080    /// This is the most commonly used redirect method. The client will make a new request
1081    /// to the provided location, but the original URL should be used for future requests.
1082    /// The HTTP method may change to GET for the redirected request.
1083    ///
1084    /// # Arguments
1085    ///
1086    /// * `location` - The URL to redirect to
1087    ///
1088    /// # Examples
1089    ///
1090    /// ## Basic Usage
1091    /// ```
1092    /// use ignitia::Response;
1093    ///
1094    /// let response = Response::redirect("/dashboard");
1095    /// assert_eq!(response.status, ignitia::StatusCode::FOUND);
1096    /// assert_eq!(
1097    ///     response.headers.get("location").unwrap(),
1098    ///     "/dashboard"
1099    /// );
1100    /// ```
1101    ///
1102    /// ## Redirect After Login
1103    /// ```
1104    /// use ignitia::Response;
1105    ///
1106    /// async fn login_handler() -> ignitia::Result<Response> {
1107    ///     // Authenticate user...
1108    ///     Ok(Response::redirect("/dashboard"))
1109    /// }
1110    /// ```
1111    ///
1112    /// ## Conditional Redirect
1113    /// ```
1114    /// use ignitia::Response;
1115    ///
1116    /// fn redirect_based_on_role(user_role: &str) -> Response {
1117    ///     match user_role {
1118    ///         "admin" => Response::redirect("/admin/dashboard"),
1119    ///         "user" => Response::redirect("/user/profile"),
1120    ///         _ => Response::redirect("/login"),
1121    ///     }
1122    /// }
1123    /// ```
1124    #[inline]
1125    pub fn redirect(location: impl Into<String>) -> Self {
1126        Self::redirect_with_status(StatusCode::FOUND, location)
1127    }
1128
1129    /// Creates a permanent redirect response (HTTP 301 Moved Permanently).
1130    ///
1131    /// Use this when a resource has permanently moved to a new location. Search engines
1132    /// and browsers will update their records to use the new URL. The HTTP method may
1133    /// change to GET for the redirected request.
1134    ///
1135    /// # Arguments
1136    ///
1137    /// * `location` - The new permanent URL location
1138    ///
1139    /// # Examples
1140    ///
1141    /// ## Basic Permanent Redirect
1142    /// ```
1143    /// use ignitia::Response;
1144    ///
1145    /// let response = Response::permanent_redirect("/new-location");
1146    /// assert_eq!(response.status, ignitia::StatusCode::MOVED_PERMANENTLY);
1147    /// ```
1148    ///
1149    /// ## Redirecting Old URLs
1150    /// ```
1151    /// use ignitia::Response;
1152    ///
1153    /// async fn handle_old_blog_url() -> ignitia::Result<Response> {
1154    ///     // Old blog structure: /blog/2023/article-title
1155    ///     // New blog structure: /articles/article-title
1156    ///     Ok(Response::permanent_redirect("/articles/migrating-to-new-blog"))
1157    /// }
1158    /// ```
1159    ///
1160    /// ## SEO-Friendly Redirects
1161    /// ```
1162    /// use ignitia::Response;
1163    ///
1164    /// fn redirect_old_product_page(old_id: u32, new_slug: &str) -> Response {
1165    ///     // Permanently redirect old product IDs to new slug-based URLs
1166    ///     Response::permanent_redirect(&format!("/products/{}", new_slug))
1167    /// }
1168    /// ```
1169    #[inline]
1170    pub fn permanent_redirect(location: impl Into<String>) -> Self {
1171        Self::redirect_with_status(StatusCode::MOVED_PERMANENTLY, location)
1172    }
1173
1174    /// Creates a redirect response with a custom HTTP status code.
1175    ///
1176    /// This method allows you to specify any redirect status code (3xx series).
1177    /// The response includes an HTML fallback page for browsers that don't
1178    /// automatically follow redirects.
1179    ///
1180    /// # Arguments
1181    ///
1182    /// * `status` - The HTTP status code for the redirect (typically 3xx)
1183    /// * `location` - The URL to redirect to
1184    ///
1185    /// # Examples
1186    ///
1187    /// ## Custom Status Redirect
1188    /// ```
1189    /// use ignitia::{Response, StatusCode};
1190    ///
1191    /// let response = Response::redirect_with_status(
1192    ///     StatusCode::FOUND,
1193    ///     "/custom-redirect"
1194    /// );
1195    /// ```
1196    ///
1197    /// ## Multiple Choice Redirect
1198    /// ```
1199    /// use ignitia::{Response, StatusCode};
1200    ///
1201    /// fn handle_ambiguous_request() -> Response {
1202    ///     Response::redirect_with_status(
1203    ///         StatusCode::MULTIPLE_CHOICES,
1204    ///         "/choose-option"
1205    ///     )
1206    /// }
1207    /// ```
1208    ///
1209    /// ## Temporary Maintenance Redirect
1210    /// ```
1211    /// use ignitia::{Response, StatusCode};
1212    ///
1213    /// fn maintenance_redirect() -> Response {
1214    ///     Response::redirect_with_status(
1215    ///         StatusCode::TEMPORARY_REDIRECT,
1216    ///         "/maintenance"
1217    ///     )
1218    /// }
1219    /// ```
1220    pub fn redirect_with_status(status: StatusCode, location: impl Into<String>) -> Self {
1221        let location_str = location.into();
1222
1223        let mut response = Self::new(status);
1224        response.headers.insert(
1225            header::LOCATION,
1226            HeaderValue::from_str(&location_str).unwrap_or_else(|_| HeaderValue::from_static("/")),
1227        );
1228
1229        // Add a simple HTML body for browsers that don't handle redirects automatically
1230        let html_body = format!(
1231            r#"<!DOCTYPE html>
1232    <html>
1233    <head>
1234        <title>Redirect</title>
1235        <meta http-equiv="refresh" content="0; url={}">
1236    </head>
1237    <body>
1238        <p>Redirecting to <a href="{}">{}</a></p>
1239    </body>
1240    </html>"#,
1241            html_escape(&location_str),
1242            html_escape(&location_str),
1243            html_escape(&location_str)
1244        );
1245
1246        response.headers.insert(
1247            header::CONTENT_TYPE,
1248            HeaderValue::from_static("text/html; charset=utf-8"),
1249        );
1250        response.body = bytes::Bytes::from(html_body);
1251
1252        response
1253    }
1254
1255    /// Creates a "See Other" redirect response (HTTP 303 See Other).
1256    ///
1257    /// This redirect is ideal for the POST-redirect-GET pattern, where after
1258    /// processing a POST request, you redirect the client to a GET endpoint.
1259    /// This prevents duplicate form submissions if the user refreshes the page.
1260    ///
1261    /// # Arguments
1262    ///
1263    /// * `location` - The URL to redirect to (typically a GET endpoint)
1264    ///
1265    /// # Examples
1266    ///
1267    /// ## POST-Redirect-GET Pattern
1268    /// ```
1269    /// use ignitia::Response;
1270    ///
1271    /// async fn process_form() -> ignitia::Result<Response> {
1272    ///     // Process form data...
1273    ///     // Save to database...
1274    ///
1275    ///     // Redirect to success page to prevent duplicate submissions
1276    ///     Ok(Response::see_other("/form-success"))
1277    /// }
1278    /// ```
1279    ///
1280    /// ## After User Registration
1281    /// ```
1282    /// use ignitia::Response;
1283    ///
1284    /// async fn register_user() -> ignitia::Result<Response> {
1285    ///     // Create new user account...
1286    ///
1287    ///     // Redirect to welcome page
1288    ///     Ok(Response::see_other("/welcome"))
1289    /// }
1290    /// ```
1291    ///
1292    /// ## Shopping Cart Checkout
1293    /// ```
1294    /// use ignitia::Response;
1295    ///
1296    /// async fn checkout_handler() -> ignitia::Result<Response> {
1297    ///     // Process payment...
1298    ///     // Update inventory...
1299    ///
1300    ///     // Redirect to order confirmation
1301    ///     Ok(Response::see_other("/order-confirmation"))
1302    /// }
1303    /// ```
1304    #[inline]
1305    pub fn see_other(location: impl Into<String>) -> Self {
1306        Self::redirect_with_status(StatusCode::SEE_OTHER, location)
1307    }
1308
1309    /// Creates a temporary redirect that preserves the HTTP method (HTTP 307 Temporary Redirect).
1310    ///
1311    /// Unlike 302 redirects, this guarantees that the client will use the same HTTP method
1312    /// when making the redirected request. Use this when the method preservation is important.
1313    ///
1314    /// # Arguments
1315    ///
1316    /// * `location` - The temporary URL to redirect to
1317    ///
1318    /// # Examples
1319    ///
1320    /// ## Method-Preserving Redirect
1321    /// ```
1322    /// use ignitia::Response;
1323    ///
1324    /// async fn api_endpoint() -> ignitia::Result<Response> {
1325    ///     // Temporarily redirect POST to another server
1326    ///     Ok(Response::temporary_redirect("/api/v2/endpoint"))
1327    /// }
1328    /// ```
1329    ///
1330    /// ## Load Balancing Redirect
1331    /// ```
1332    /// use ignitia::Response;
1333    ///
1334    /// fn balance_load() -> Response {
1335    ///     // Redirect to less busy server while preserving method
1336    ///     Response::temporary_redirect("https://server2.example.com/api")
1337    /// }
1338    /// ```
1339    #[inline]
1340    pub fn temporary_redirect(location: impl Into<String>) -> Self {
1341        Self::redirect_with_status(StatusCode::TEMPORARY_REDIRECT, location)
1342    }
1343
1344    /// Creates a permanent redirect that preserves the HTTP method (HTTP 308 Permanent Redirect).
1345    ///
1346    /// This is like 301 but guarantees the client will use the same HTTP method.
1347    /// Use this for permanent moves where method preservation is crucial.
1348    ///
1349    /// # Arguments
1350    ///
1351    /// * `location` - The new permanent URL
1352    ///
1353    /// # Examples
1354    ///
1355    /// ## API Version Migration
1356    /// ```
1357    /// use ignitia::Response;
1358    ///
1359    /// async fn old_api_endpoint() -> ignitia::Result<Response> {
1360    ///     // Permanently moved to new API version, preserve HTTP method
1361    ///     Ok(Response::permanent_redirect_308("/api/v2/users"))
1362    /// }
1363    /// ```
1364    #[inline]
1365    pub fn permanent_redirect_308(location: impl Into<String>) -> Self {
1366        Self::redirect_with_status(StatusCode::PERMANENT_REDIRECT, location)
1367    }
1368
1369    /// Creates a redirect response without an HTML body (useful for APIs).
1370    ///
1371    /// This method creates a minimal redirect response with only the necessary headers,
1372    /// making it ideal for REST APIs or situations where you don't want the HTML fallback.
1373    ///
1374    /// # Arguments
1375    ///
1376    /// * `status` - The HTTP status code for the redirect
1377    /// * `location` - The URL to redirect to
1378    ///
1379    /// # Examples
1380    ///
1381    /// ## API Redirect
1382    /// ```
1383    /// use ignitia::{Response, StatusCode};
1384    ///
1385    /// async fn api_redirect() -> ignitia::Result<Response> {
1386    ///     Ok(Response::redirect_empty(
1387    ///         StatusCode::FOUND,
1388    ///         "https://api.example.com/v2/endpoint"
1389    ///     ))
1390    /// }
1391    /// ```
1392    ///
1393    /// ## Minimal Redirect
1394    /// ```
1395    /// use ignitia::{Response, StatusCode};
1396    ///
1397    /// fn lightweight_redirect() -> Response {
1398    ///     Response::redirect_empty(
1399    ///         StatusCode::MOVED_PERMANENTLY,
1400    ///         "/new-location"
1401    ///     )
1402    /// }
1403    /// ```
1404    pub fn redirect_empty(status: StatusCode, location: impl Into<String>) -> Self {
1405        let location_str = location.into();
1406
1407        let mut response = Self::new(status);
1408        response.headers.insert(
1409            header::LOCATION,
1410            HeaderValue::from_str(&location_str).unwrap_or_else(|_| HeaderValue::from_static("/")),
1411        );
1412
1413        response
1414    }
1415
1416    /// Convenience method for redirecting to a login page.
1417    ///
1418    /// Creates a temporary redirect (302) to the specified login path.
1419    /// This is a commonly used pattern in web applications for authentication flows.
1420    ///
1421    /// # Arguments
1422    ///
1423    /// * `login_path` - The path to the login page
1424    ///
1425    /// # Examples
1426    ///
1427    /// ## Basic Login Redirect
1428    /// ```
1429    /// use ignitia::Response;
1430    ///
1431    /// async fn protected_handler() -> ignitia::Result<Response> {
1432    ///     // Check authentication...
1433    ///     if !user_authenticated {
1434    ///         return Ok(Response::redirect_to_login("/auth/login"));
1435    ///     }
1436    ///
1437    ///     // Continue with protected logic...
1438    ///     Ok(Response::text("Welcome to protected area"))
1439    /// }
1440    /// ```
1441    ///
1442    /// ## With Return URL
1443    /// ```
1444    /// use ignitia::Response;
1445    ///
1446    /// fn redirect_with_return_url(current_path: &str) -> Response {
1447    ///     let login_url = format!("/login?return_to={}",
1448    ///         urlencoding::encode(current_path));
1449    ///     Response::redirect_to_login(login_url)
1450    /// }
1451    /// ```
1452    #[inline]
1453    pub fn redirect_to_login(login_path: impl Into<String>) -> Self {
1454        Self::redirect(login_path)
1455    }
1456
1457    /// Convenience method for redirecting after a successful POST request.
1458    ///
1459    /// Uses HTTP 303 (See Other) to implement the POST-redirect-GET pattern,
1460    /// preventing duplicate form submissions when users refresh the page.
1461    ///
1462    /// # Arguments
1463    ///
1464    /// * `location` - The success page URL to redirect to
1465    ///
1466    /// # Examples
1467    ///
1468    /// ## Form Submission Success
1469    /// ```
1470    /// use ignitia::Response;
1471    ///
1472    /// async fn contact_form_handler() -> ignitia::Result<Response> {
1473    ///     // Process contact form...
1474    ///     // Send email...
1475    ///     // Save to database...
1476    ///
1477    ///     Ok(Response::redirect_after_post("/contact/thank-you"))
1478    /// }
1479    /// ```
1480    ///
1481    /// ## E-commerce Order
1482    /// ```
1483    /// use ignitia::Response;
1484    ///
1485    /// async fn place_order() -> ignitia::Result<Response> {
1486    ///     // Process order...
1487    ///     // Charge payment...
1488    ///     // Update inventory...
1489    ///
1490    ///     Ok(Response::redirect_after_post("/orders/success"))
1491    /// }
1492    /// ```
1493    #[inline]
1494    pub fn redirect_after_post(location: impl Into<String>) -> Self {
1495        Self::see_other(location)
1496    }
1497
1498    /// Convenience method for redirecting moved content.
1499    ///
1500    /// Creates a permanent redirect (301) for content that has been permanently moved.
1501    /// This is ideal for SEO as search engines will update their indexes.
1502    ///
1503    /// # Arguments
1504    ///
1505    /// * `new_location` - The new permanent location of the content
1506    ///
1507    /// # Examples
1508    ///
1509    /// ## Content Migration
1510    /// ```
1511    /// use ignitia::Response;
1512    ///
1513    /// async fn old_article_handler() -> ignitia::Result<Response> {
1514    ///     // Article has moved to new URL structure
1515    ///     Ok(Response::redirect_moved("/articles/2023/new-article-slug"))
1516    /// }
1517    /// ```
1518    ///
1519    /// ## Domain Migration
1520    /// ```
1521    /// use ignitia::Response;
1522    ///
1523    /// fn redirect_to_new_domain() -> Response {
1524    ///     Response::redirect_moved("https://newdomain.com/same-path")
1525    /// }
1526    /// ```
1527    #[inline]
1528    pub fn redirect_moved(new_location: impl Into<String>) -> Self {
1529        Self::permanent_redirect(new_location)
1530    }
1531
1532    /// Sets cache control header with specified max-age value.
1533    ///
1534    /// This method adds a `Cache-Control` header to the response with the specified
1535    /// maximum age in seconds. The cache control header instructs browsers, CDNs,
1536    /// and other caching systems how long to cache this response before considering
1537    /// it stale and requiring revalidation.
1538    ///
1539    /// # Parameters
1540    /// - `max_age`: Cache lifetime in seconds (0 = no caching)
1541    ///
1542    /// # HTTP Header Generated
1543    /// Creates: `Cache-Control: max-age={max_age}`
1544    ///
1545    /// # Common Cache Durations
1546    /// - **0**: No caching (immediate expiration)
1547    /// - **300**: 5 minutes (dynamic API responses)
1548    /// - **3600**: 1 hour (semi-static content)
1549    /// - **86400**: 24 hours (daily updated content)
1550    /// - **2592000**: 30 days (monthly content)
1551    /// - **31536000**: 1 year (static assets with versioning)
1552    ///
1553    /// # Examples
1554    ///
1555    /// ## API Response Caching
1556    /// ```
1557    /// use ignitia::Response;
1558    /// use serde_json::json;
1559    ///
1560    /// async fn user_profile() -> ignitia::Result<Response> {
1561    ///     let profile = get_user_profile().await?;
1562    ///
1563    ///     Response::json(profile)?
1564    ///         .with_cache_control(1800) // Cache for 30 minutes
1565    /// }
1566    /// ```
1567    ///
1568    /// ## Static Asset Caching
1569    /// ```
1570    /// async fn serve_css() -> Response {
1571    ///     Response::text_static(include_str!("styles.css"))
1572    ///         .with_cache_control(31536000) // Cache for 1 year
1573    /// }
1574    /// ```
1575    ///
1576    /// ## No-Cache for Sensitive Data
1577    /// ```
1578    /// async fn user_balance() -> ignitia::Result<Response> {
1579    ///     let balance = get_current_balance().await?;
1580    ///
1581    ///     Response::json(balance)?
1582    ///         .with_cache_control(0) // Never cache sensitive financial data
1583    /// }
1584    /// ```
1585    #[inline]
1586    pub fn with_cache_control(mut self, max_age: u64) -> Self {
1587        self.headers.insert(
1588            http::header::CACHE_CONTROL,
1589            HeaderValue::from_str(&format!("max-age={}", max_age)).unwrap(),
1590        );
1591        self
1592    }
1593
1594    /// Generates a unique cache key for this response based on request URI and ETag.
1595    ///
1596    /// Creates a cache key that uniquely identifies this response for storage in
1597    /// cache systems like Redis, Memcached, or CDN edge caches. The key combines
1598    /// the request URI with the response's ETag header (if present) to ensure
1599    /// cache invalidation when content changes.
1600    ///
1601    /// # Key Format
1602    /// `cache_{request_uri}_{etag_or_default}`
1603    ///
1604    /// # Parameters
1605    /// - `request_uri`: The URI path of the original request
1606    ///
1607    /// # Cache Key Strategy
1608    /// - Uses request URI as the primary identifier
1609    /// - Includes ETag header value for content versioning
1610    /// - Falls back to "default" if no ETag is present
1611    /// - Ensures unique keys for different content versions
1612    ///
1613    /// # Use Cases
1614    /// - **CDN Cache Keys**: Identifying cached responses in CDN systems
1615    /// - **Application Cache**: Storing responses in Redis/Memcached
1616    /// - **Cache Invalidation**: Targeting specific cached entries for removal
1617    /// - **Analytics**: Tracking cache hit/miss ratios per endpoint
1618    ///
1619    /// # Examples
1620    ///
1621    /// ## Basic Cache Key Generation
1622    /// ```
1623    /// use ignitia::Response;
1624    ///
1625    /// let response = Response::json(user_data)?
1626    ///     .with_header("etag", "\"abc123\"");
1627    ///
1628    /// let cache_key = response.cache_key("/api/users/123");
1629    /// // Returns: "cache_/api/users/123_abc123"
1630    /// ```
1631    ///
1632    /// ## Cache Storage Integration
1633    /// ```
1634    /// async fn cached_response(uri: &str) -> ignitia::Result<Response> {
1635    ///     let response = Response::json(expensive_computation().await)?;
1636    ///     let cache_key = response.cache_key(uri);
1637    ///
1638    ///     // Store in Redis for future requests
1639    ///     redis_client.set(&cache_key, &response.body).await?;
1640    ///
1641    ///     Ok(response)
1642    /// }
1643    /// ```
1644    ///
1645    /// ## Cache Invalidation
1646    /// ```
1647    /// async fn invalidate_user_cache(user_id: u64) {
1648    ///     let pattern = format!("cache_/api/users/{}_*", user_id);
1649    ///     redis_client.delete_pattern(&pattern).await;
1650    /// }
1651    /// ```
1652    pub fn cache_key(&self, request_uri: &str) -> String {
1653        format!(
1654            "cache_{}_{}",
1655            request_uri,
1656            self.headers
1657                .get("etag")
1658                .and_then(|v| v.to_str().ok())
1659                .unwrap_or("default")
1660        )
1661    }
1662
1663    /// Extracts the max-age value from the Cache-Control header.
1664    ///
1665    /// Parses the `Cache-Control` header to extract the `max-age` directive value,
1666    /// which indicates how many seconds the response should be cached. Returns 0
1667    /// if no valid max-age directive is found or if the header is malformed.
1668    ///
1669    /// # Parsing Logic
1670    /// 1. Retrieves `Cache-Control` header value
1671    /// 2. Searches for `max-age=` directive
1672    /// 3. Extracts numeric value after the equals sign
1673    /// 4. Handles comma-separated directives correctly
1674    /// 5. Returns 0 for invalid or missing values
1675    ///
1676    /// # Return Value
1677    /// - **> 0**: Cache lifetime in seconds
1678    /// - **0**: No caching or invalid header
1679    ///
1680    /// # Examples
1681    ///
1682    /// ## Reading Cache Duration
1683    /// ```
1684    /// use ignitia::Response;
1685    ///
1686    /// let response = Response::json(data)?
1687    ///     .with_cache_control(3600);
1688    ///
1689    /// assert_eq!(response.cache_max_age(), 3600);
1690    /// ```
1691    ///
1692    /// ## Conditional Processing Based on Cache Duration
1693    /// ```
1694    /// async fn process_response(response: &Response) {
1695    ///     let cache_duration = response.cache_max_age();
1696    ///
1697    ///     match cache_duration {
1698    ///         0 => {
1699    ///             // No caching - always fetch fresh
1700    ///             log::info!("Response not cacheable");
1701    ///         }
1702    ///         1..=300 => {
1703    ///             // Short-term cache - good for dynamic content
1704    ///             log::info!("Short-term cache: {} seconds", cache_duration);
1705    ///         }
1706    ///         301..=3600 => {
1707    ///             // Medium-term cache - semi-static content
1708    ///             log::info!("Medium-term cache: {} seconds", cache_duration);
1709    ///         }
1710    ///         _ => {
1711    ///             // Long-term cache - static assets
1712    ///             log::info!("Long-term cache: {} seconds", cache_duration);
1713    ///         }
1714    ///     }
1715    /// }
1716    /// ```
1717    ///
1718    /// ## Cache Validation Logic
1719    /// ```
1720    /// fn should_serve_from_cache(response: &Response, cached_at: SystemTime) -> bool {
1721    ///     let max_age = response.cache_max_age();
1722    ///     if max_age == 0 {
1723    ///         return false; // Never cache
1724    ///     }
1725    ///
1726    ///     let elapsed = cached_at.elapsed().unwrap_or_default();
1727    ///     elapsed.as_secs() < max_age
1728    /// }
1729    /// ```
1730    pub fn cache_max_age(&self) -> u64 {
1731        self.headers
1732            .get("cache-control")
1733            .and_then(|v| v.to_str().ok())
1734            .and_then(|v| v.split("max-age=").nth(1))
1735            .and_then(|v| v.split(',').next())
1736            .and_then(|v| v.trim().parse().ok())
1737            .unwrap_or(0)
1738    }
1739
1740    /// Determines if this response is cacheable based on status and headers.
1741    ///
1742    /// Analyzes the response to determine if it should be cached by browsers,
1743    /// CDNs, and other caching systems. A response is considered cacheable if:
1744    /// 1. The HTTP status indicates success (2xx range)
1745    /// 2. The Cache-Control header contains a valid `max-age` directive
1746    /// 3. The max-age value is greater than 0
1747    ///
1748    /// # Cacheability Rules
1749    /// - **Success Status Required**: Only 2xx status codes are cacheable
1750    /// - **Valid Cache-Control**: Must have `max-age=` directive
1751    /// - **Non-Zero Duration**: `max-age=0` indicates no caching
1752    /// - **Header Presence**: Missing Cache-Control header = not cacheable
1753    ///
1754    /// # Returns
1755    /// - `true`: Response should be cached by clients and intermediaries
1756    /// - `false`: Response should not be cached (fetch fresh each time)
1757    ///
1758    /// # Examples
1759    ///
1760    /// ## Conditional Cache Storage
1761    /// ```
1762    /// use ignitia::Response;
1763    ///
1764    /// async fn handle_api_request() -> ignitia::Result<Response> {
1765    ///     let response = Response::json(get_data().await)?
1766    ///         .with_cache_control(1800);
1767    ///
1768    ///     if response.is_cacheable() {
1769    ///         // Store in application cache
1770    ///         cache_service.store(&response).await?;
1771    ///         log::info!("Response cached for {} seconds", response.cache_max_age());
1772    ///     } else {
1773    ///         log::info!("Response not cacheable - serving fresh");
1774    ///     }
1775    ///
1776    ///     Ok(response)
1777    /// }
1778    /// ```
1779    ///
1780    /// ## CDN Integration
1781    /// ```
1782    /// fn configure_cdn_headers(mut response: Response) -> Response {
1783    ///     if response.is_cacheable() {
1784    ///         // Add CDN-specific headers for cacheable responses
1785    ///         response.headers.insert("x-cdn-cache", HeaderValue::from_static("HIT"));
1786    ///         response.headers.insert("x-cache-duration",
1787    ///             HeaderValue::from_str(&response.cache_max_age().to_string()).unwrap());
1788    ///     } else {
1789    ///         // Ensure CDN doesn't cache non-cacheable responses
1790    ///         response.headers.insert("x-cdn-cache", HeaderValue::from_static("BYPASS"));
1791    ///     }
1792    ///     response
1793    /// }
1794    /// ```
1795    ///
1796    /// ## Performance Monitoring
1797    /// ```
1798    /// fn log_cache_metrics(response: &Response, endpoint: &str) {
1799    ///     if response.is_cacheable() {
1800    ///         metrics::increment_counter("cacheable_responses", &[("endpoint", endpoint)]);
1801    ///         metrics::histogram("cache_duration_seconds", response.cache_max_age() as f64);
1802    ///     } else {
1803    ///         metrics::increment_counter("non_cacheable_responses", &[("endpoint", endpoint)]);
1804    ///     }
1805    /// }
1806    /// ```
1807    ///
1808    /// ## Error Response Handling
1809    /// ```
1810    /// fn create_error_response(error: &AppError) -> Response {
1811    ///     let response = Response::json(error.to_json())
1812    ///         .unwrap_or_else(|_| Response::server_error());
1813    ///
1814    ///     // Error responses are typically not cacheable
1815    ///     assert!(!response.is_cacheable());
1816    ///
1817    ///     response
1818    /// }
1819    /// ```
1820    pub fn is_cacheable(&self) -> bool {
1821        self.status.is_success()
1822            && self
1823                .headers
1824                .get("cache-control")
1825                .and_then(|v| v.to_str().ok())
1826                .map(|v| v.contains("max-age=") && !v.contains("max-age=0"))
1827                .unwrap_or(false)
1828    }
1829}
1830
1831pub use builder::ResponseBuilder;
1832
1833use crate::error::{Error, ErrorResponse};
1834
1835impl From<Error> for Response {
1836    /// Converts an Error into an HTTP Response.
1837    ///
1838    /// This implementation automatically converts framework errors into proper
1839    /// HTTP responses with JSON formatting and appropriate status codes.
1840    ///
1841    /// # Parameters
1842    /// - `err`: The error to convert
1843    ///
1844    /// # Returns
1845    /// An HTTP response representing the error
1846    ///
1847    /// # Error Response Format
1848    /// The generated response contains:
1849    /// - Appropriate HTTP status code
1850    /// - JSON body with error details
1851    /// - Proper content-type headers
1852    /// - Timestamp information
1853    ///
1854    /// # Examples
1855    /// ```
1856    /// use ignitia::{Error, Response};
1857    /// use http::StatusCode;
1858    ///
1859    /// // Convert a NotFound error
1860    /// let error = Error::NotFound("User not found".to_string());
1861    /// let response = Response::from(error);
1862    /// assert_eq!(response.status, StatusCode::NOT_FOUND);
1863    ///
1864    /// // The response body will be JSON:
1865    /// // {
1866    /// //   "error": "Not Found",
1867    /// //   "message": "User not found",
1868    /// //   "status": 404,
1869    /// //   "error_type": "not_found",
1870    /// //   "timestamp": "2023-01-01T12:00:00Z"
1871    /// // }
1872    /// ```
1873    fn from(err: Error) -> Self {
1874        let status = err.status_code();
1875        let error_response = err.to_response(true);
1876
1877        // Try JSON first, fallback to plain text
1878        match serde_json::to_vec(&error_response) {
1879            Ok(json_body) => {
1880                let mut response = Response::new(status);
1881                response.headers.insert(
1882                    http::header::CONTENT_TYPE,
1883                    http::HeaderValue::from_static("application/json"),
1884                );
1885                response.body = bytes::Bytes::from(json_body);
1886                response
1887            }
1888            Err(_) => {
1889                let mut response = Response::new(status);
1890                response.headers.insert(
1891                    http::header::CONTENT_TYPE,
1892                    http::HeaderValue::from_static("text/plain; charset=utf-8"),
1893                );
1894                response.body = bytes::Bytes::from(err.to_string());
1895                response
1896            }
1897        }
1898    }
1899}
1900
1901// Enhanced response methods
1902impl Response {
1903    /// Creates a JSON error response from an error.
1904    ///
1905    /// This method provides more control over error response generation than
1906    /// the automatic From implementation. It always returns JSON and provides
1907    /// better error handling for serialization failures.
1908    ///
1909    /// # Type Parameters
1910    /// - `E`: Error type that can be converted to framework Error
1911    ///
1912    /// # Parameters
1913    /// - `error`: The error to convert to a response
1914    ///
1915    /// # Returns
1916    /// - `Ok(Response)`: Successfully created error response
1917    /// - `Err(Error)`: JSON serialization error
1918    ///
1919    /// # Examples
1920    /// ```
1921    /// use ignitia::{Response, Error};
1922    ///
1923    /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
1924    /// let error = Error::Validation("Email format is invalid".to_string());
1925    /// let response = Response::error_json(error)?;
1926    /// # Ok(())
1927    /// # }
1928    /// ```
1929    pub fn error_json<E: Into<Error>>(error: E) -> crate::Result<Self> {
1930        let err = error.into();
1931        let status = err.status_code();
1932        let error_response = err.to_response(true);
1933
1934        let mut response = Self::new(status);
1935        response.headers.insert(
1936            http::header::CONTENT_TYPE,
1937            http::HeaderValue::from_static("application/json"),
1938        );
1939        response.body = bytes::Bytes::from(serde_json::to_vec(&error_response)?);
1940        Ok(response)
1941    }
1942
1943    /// Creates a validation error response with multiple error messages.
1944    ///
1945    /// This method creates a structured validation error response that can
1946    /// contain multiple validation failure messages.
1947    ///
1948    /// # Parameters
1949    /// - `messages`: Vector of validation error messages
1950    ///
1951    /// # Returns
1952    /// - `Ok(Response)`: Successfully created validation error response
1953    /// - `Err(Error)`: JSON serialization error
1954    ///
1955    /// # Examples
1956    /// ```
1957    /// use ignitia::Response;
1958    ///
1959    /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
1960    /// let validation_errors = vec![
1961    ///     "Name is required".to_string(),
1962    ///     "Email format is invalid".to_string(),
1963    ///     "Password must be at least 8 characters".to_string(),
1964    /// ];
1965    ///
1966    /// let response = Response::validation_error(validation_errors)?;
1967    /// # Ok(())
1968    /// # }
1969    /// ```
1970    ///
1971    /// ## Response Format
1972    /// The generated response has this structure:
1973    /// ```
1974    /// {
1975    ///   "error": "Validation Failed",
1976    ///   "message": "Name is required, Email format is invalid, Password must be at least 8 characters",
1977    ///   "status": 400,
1978    ///   "error_type": "validation_error",
1979    ///   "error_code": "VALIDATION_FAILED",
1980    ///   "metadata": {
1981    ///     "validation_errors": ["Name is required", "Email format is invalid", "Password must be at least 8 characters"]
1982    ///   },
1983    ///   "timestamp": "2023-01-01T12:00:00Z"
1984    /// }
1985    /// ```
1986    ///
1987    /// ## Usage in Form Validation
1988    /// ```
1989    /// use ignitia::{Response, Request, Result};
1990    /// use serde::Deserialize;
1991    ///
1992    /// #[derive(Deserialize)]
1993    /// struct UserForm {
1994    ///     name: String,
1995    ///     email: String,
1996    ///     password: String,
1997    /// }
1998    ///
1999    /// async fn validate_user_form(req: Request) -> Result<Response> {
2000    ///     let form: UserForm = req.json()?;
2001    ///     let mut errors = Vec::new();
2002    ///
2003    ///     if form.name.trim().is_empty() {
2004    ///         errors.push("Name is required".to_string());
2005    ///     }
2006    ///
2007    ///     if !form.email.contains('@') {
2008    ///         errors.push("Invalid email format".to_string());
2009    ///     }
2010    ///
2011    ///     if form.password.len() < 8 {
2012    ///         errors.push("Password must be at least 8 characters".to_string());
2013    ///     }
2014    ///
2015    ///     if !errors.is_empty() {
2016    ///         return Response::validation_error(errors);
2017    ///     }
2018    ///
2019    ///     Ok(Response::json(serde_json::json!({
2020    ///         "message": "User created successfully"
2021    ///     }))?)
2022    /// }
2023    /// ```
2024    pub fn validation_error(messages: Vec<String>) -> crate::Result<Self> {
2025        let error_response = ErrorResponse {
2026            error: "Validation Failed".to_string(),
2027            message: messages.join(", "),
2028            status: 400,
2029            error_type: Some("validation_error".to_string()),
2030            error_code: Some("VALIDATION_FAILED".to_string()),
2031            metadata: Some(serde_json::json!({
2032                "validation_errors": messages
2033            })),
2034            timestamp: Some(chrono::Utc::now().to_rfc3339()),
2035        };
2036
2037        let mut response = Self::new(StatusCode::BAD_REQUEST);
2038        response.headers.insert(
2039            http::header::CONTENT_TYPE,
2040            http::HeaderValue::from_static("application/json"),
2041        );
2042        response.body = bytes::Bytes::from(serde_json::to_vec(&error_response)?);
2043        Ok(response)
2044    }
2045}
2046
2047// Helper function to escape HTML entities
2048fn html_escape(input: &str) -> String {
2049    input
2050        .replace('&', "&amp;")
2051        .replace('<', "&lt;")
2052        .replace('>', "&gt;")
2053        .replace('"', "&quot;")
2054        .replace('\'', "&#x27;")
2055}