Skip to main content

reinhardt_dispatch/
lib.rs

1#![warn(missing_docs)]
2
3//! # Reinhardt Dispatch
4//!
5//! HTTP request dispatching and handler system for Reinhardt framework.
6//!
7//! This module provides the core request handling functionality,
8//! equivalent to Django's `django.core.handlers` and `django.dispatch`.
9//!
10//! ## Overview
11//!
12//! The dispatch system handles:
13//! - HTTP request handling and routing
14//! - Middleware chain execution
15//! - View dispatching
16//! - Exception handling
17//! - Signal emission for request lifecycle events
18//!
19//! ## Architecture
20//!
21//! ```text
22//! Request → BaseHandler → Middleware Chain → URL Resolver → View → Response
23//!                ↓                                            ↓
24//!           Signals                                      Signals
25//!       (request_started)                          (request_finished)
26//! ```
27//!
28//! ## Examples
29//!
30//! ### Basic Request Handling with URL Routing
31//!
32//! ```rust
33//! use reinhardt_dispatch::BaseHandler;
34//! use reinhardt_urls::routers::{DefaultRouter, Router, path};
35//! use reinhardt_http::{Request, Response};
36//! use reinhardt_http::Handler;
37//! use std::sync::Arc;
38//! use hyper::{Method, Version, HeaderMap, StatusCode};
39//! use bytes::Bytes;
40//! use async_trait::async_trait;
41//!
42//! // Define a simple handler
43//! struct HelloHandler;
44//!
45//! #[async_trait]
46//! impl Handler for HelloHandler {
47//!     async fn handle(&self, _req: Request) -> reinhardt_core::exception::Result<Response> {
48//!         Ok(Response::ok().with_body("Hello, World!"))
49//!     }
50//! }
51//!
52//! # tokio_test::block_on(async {
53//! // Create a router and register routes
54//! let mut router = DefaultRouter::new();
55//! let route = path("/", Arc::new(HelloHandler)).with_name("index");
56//! router.add_route(route);
57//!
58//! // Create handler with router
59//! let handler = BaseHandler::with_router(Arc::new(router));
60//!
61//! // Create a request
62//! let request = Request::builder()
63//!     .method(Method::GET)
64//!     .uri("/")
65//!     .version(Version::HTTP_11)
66//!     .headers(HeaderMap::new())
67//!     .body(Bytes::new())
68//!     .build()
69//!     .unwrap();
70//!
71//! // Handle request
72//! let response = handler.handle_request(request).await.unwrap();
73//! assert_eq!(response.status, StatusCode::OK);
74//! # });
75//! ```
76//!
77//! ### With Middleware
78//!
79//! ```rust
80//! use reinhardt_dispatch::{BaseHandler, MiddlewareChain};
81//! use reinhardt_urls::routers::{DefaultRouter, Router, path};
82//! use reinhardt_http::{Handler, Middleware};
83//! use reinhardt_http::{Request, Response};
84//! use std::sync::Arc;
85//! use async_trait::async_trait;
86//!
87//! // Example middleware
88//! struct LoggingMiddleware;
89//!
90//! #[async_trait]
91//! impl Middleware for LoggingMiddleware {
92//!     async fn process(&self, request: Request, next: Arc<dyn Handler>) -> reinhardt_core::exception::Result<Response> {
93//!         println!("Request: {} {}", request.method, request.path());
94//!         let response = next.handle(request).await?;
95//!         println!("Response: {}", response.status);
96//!         Ok(response)
97//!     }
98//! }
99//!
100//! // Example handler
101//! struct ApiHandler;
102//!
103//! #[async_trait]
104//! impl Handler for ApiHandler {
105//!     async fn handle(&self, _req: Request) -> reinhardt_core::exception::Result<Response> {
106//!         Ok(Response::ok().with_json(&serde_json::json!({"status": "ok"})).unwrap())
107//!     }
108//! }
109//!
110//! # tokio_test::block_on(async {
111//! // Setup router
112//! let mut router = DefaultRouter::new();
113//! let api_route = path("/api", Arc::new(ApiHandler)).with_name("api");
114//! router.add_route(api_route);
115//!
116//! // Create base handler with router
117//! let base_handler: Arc<dyn Handler> = Arc::new(BaseHandler::with_router(Arc::new(router)));
118//!
119//! // Wrap with middleware
120//! let handler = MiddlewareChain::new(base_handler)
121//!     .add_middleware(Arc::new(LoggingMiddleware))
122//!     .expect("Failed to add middleware")
123//!     .build();
124//!
125//! // Use the handler
126//! let request = Request::builder()
127//!     .method(hyper::Method::GET)
128//!     .uri("/api")
129//!     .version(hyper::Version::HTTP_11)
130//!     .headers(hyper::HeaderMap::new())
131//!     .body(bytes::Bytes::new())
132//!     .build()
133//!     .unwrap();
134//!
135//! let response = handler.handle(request).await.unwrap();
136//! assert_eq!(response.status, hyper::StatusCode::OK);
137//! # });
138//! ```
139
140pub mod dispatcher;
141pub mod exception;
142pub mod handler;
143pub mod middleware;
144
145// Re-exports
146pub use dispatcher::Dispatcher;
147pub use exception::{ExceptionHandler, convert_exception_to_response};
148pub use handler::BaseHandler;
149pub use middleware::MiddlewareChain;
150
151use thiserror::Error;
152
153/// Errors that can occur during request dispatching
154#[derive(Debug, Error)]
155pub enum DispatchError {
156	/// Middleware configuration error
157	#[error("Middleware error: {0}")]
158	Middleware(String),
159
160	/// View execution error
161	#[error("View error: {0}")]
162	View(String),
163
164	/// URL resolution error
165	#[error("URL resolution error: {0}")]
166	UrlResolution(String),
167
168	/// HTTP error
169	#[error("HTTP error: {0}")]
170	Http(String),
171
172	/// Internal error
173	#[error("Internal error: {0}")]
174	Internal(String),
175}
176
177/// Build a plain-text error response with security headers.
178///
179/// Sets `Content-Type: text/plain; charset=utf-8` and
180/// `X-Content-Type-Options: nosniff` to prevent browsers from MIME-sniffing
181/// the error body into an executable content type.
182pub(crate) fn build_error_response(
183	status: hyper::StatusCode,
184	message: &str,
185) -> reinhardt_http::Response {
186	let mut response = reinhardt_http::Response::new(status);
187	response.body = bytes::Bytes::from(message.to_owned());
188	response.headers.insert(
189		hyper::header::CONTENT_TYPE,
190		hyper::header::HeaderValue::from_static("text/plain; charset=utf-8"),
191	);
192	response.headers.insert(
193		hyper::header::HeaderName::from_static("x-content-type-options"),
194		hyper::header::HeaderValue::from_static("nosniff"),
195	);
196	response
197}