use anyhow::Result; use http::{Request, Response, StatusCode, HeaderValue}; use http_body_util::Full; use hyper::body::Bytes; use std::collections::HashMap; use tracing::debug; /// Handler for static responses (redirects, custom responses, etc.) #[derive(Debug, Clone)] pub struct StaticResponseHandler { pub status_code: u16, pub body: Option, pub headers: HashMap, } impl StaticResponseHandler { /// Create a redirect response pub fn redirect(location: String, status_code: Option) -> Self { let mut headers = HashMap::new(); headers.insert("location".to_string(), location); Self { status_code: status_code.unwrap_or(301), body: None, headers, } } /// Create a custom response with body pub fn with_body(status_code: u16, body: String) -> Self { Self { status_code, body: Some(body), headers: HashMap::new(), } } /// Add a header to the response pub fn with_header(mut self, name: String, value: String) -> Self { self.headers.insert(name, value); self } /// Add multiple headers pub fn with_headers(mut self, headers: HashMap) -> Self { self.headers.extend(headers); self } /// Generate the HTTP response pub fn generate_response(&self) -> Result>> { let status = StatusCode::from_u16(self.status_code) .map_err(|e| anyhow::anyhow!("Invalid status code {}: {}", self.status_code, e))?; let mut response_builder = Response::builder().status(status); // Add headers for (name, value) in &self.headers { let header_name = name.to_lowercase(); let header_value = HeaderValue::from_str(value) .map_err(|e| anyhow::anyhow!("Invalid header value for '{}': {}", name, e))?; response_builder = response_builder.header(header_name, header_value); } // Set body let body = self.body.as_deref().unwrap_or(""); response_builder .body(Full::new(Bytes::from(body.to_string()))) .map_err(|e| anyhow::anyhow!("Failed to build response: {}", e)) } /// Handle a request with this static response pub async fn handle_request(&self, _req: Request) -> Result>> { debug!("Serving static response: {} {}", self.status_code, self.body.as_deref().unwrap_or("")); self.generate_response() } } #[cfg(test)] mod tests { use super::*; use http::Method; fn create_test_request(method: Method, path: &str, body: T) -> Request { Request::builder() .method(method) .uri(path) .body(body) .unwrap() } #[test] fn test_redirect_handler() { let handler = StaticResponseHandler::redirect( "https://example.com".to_string(), Some(302) ); assert_eq!(handler.status_code, 302); assert_eq!(handler.headers.get("location").unwrap(), "https://example.com"); assert!(handler.body.is_none()); } #[test] fn test_custom_response_handler() { let handler = StaticResponseHandler::with_body( 410, "Service has been migrated".to_string() ); assert_eq!(handler.status_code, 410); assert_eq!(handler.body.as_ref().unwrap(), "Service has been migrated"); } #[test] fn test_response_with_headers() { let handler = StaticResponseHandler::with_body(200, "OK".to_string()) .with_header("content-type".to_string(), "application/json".to_string()) .with_header("cache-control".to_string(), "no-cache".to_string()); assert_eq!(handler.headers.len(), 2); assert_eq!(handler.headers.get("content-type").unwrap(), "application/json"); assert_eq!(handler.headers.get("cache-control").unwrap(), "no-cache"); } #[tokio::test] async fn test_generate_response() { let handler = StaticResponseHandler::redirect( "https://example.com".to_string(), Some(301) ); let response = handler.generate_response().unwrap(); assert_eq!(response.status(), StatusCode::MOVED_PERMANENTLY); assert_eq!( response.headers().get("location").unwrap(), "https://example.com" ); } #[tokio::test] async fn test_handle_request() { let handler = StaticResponseHandler::with_body(200, "Hello World".to_string()); let req = create_test_request(Method::GET, "/test", "body"); let response = handler.handle_request(req).await.unwrap(); assert_eq!(response.status(), StatusCode::OK); } }