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 mut route = path("/", Arc::new(HelloHandler));
56//! route.name = Some("index".to_string());
57//! router.add_route(route);
58//!
59//! // Create handler with router
60//! let handler = BaseHandler::with_router(Arc::new(router));
61//!
62//! // Create a request
63//! let request = Request::builder()
64//!     .method(Method::GET)
65//!     .uri("/")
66//!     .version(Version::HTTP_11)
67//!     .headers(HeaderMap::new())
68//!     .body(Bytes::new())
69//!     .build()
70//!     .unwrap();
71//!
72//! // Handle request
73//! let response = handler.handle_request(request).await.unwrap();
74//! assert_eq!(response.status, StatusCode::OK);
75//! # });
76//! ```
77//!
78//! ### With Middleware
79//!
80//! ```rust
81//! use reinhardt_dispatch::{BaseHandler, MiddlewareChain};
82//! use reinhardt_urls::routers::{DefaultRouter, Router, path};
83//! use reinhardt_http::{Handler, Middleware};
84//! use reinhardt_http::{Request, Response};
85//! use std::sync::Arc;
86//! use async_trait::async_trait;
87//!
88//! // Example middleware
89//! struct LoggingMiddleware;
90//!
91//! #[async_trait]
92//! impl Middleware for LoggingMiddleware {
93//!     async fn process(&self, request: Request, next: Arc<dyn Handler>) -> reinhardt_core::exception::Result<Response> {
94//!         println!("Request: {} {}", request.method, request.path());
95//!         let response = next.handle(request).await?;
96//!         println!("Response: {}", response.status);
97//!         Ok(response)
98//!     }
99//! }
100//!
101//! // Example handler
102//! struct ApiHandler;
103//!
104//! #[async_trait]
105//! impl Handler for ApiHandler {
106//!     async fn handle(&self, _req: Request) -> reinhardt_core::exception::Result<Response> {
107//!         Ok(Response::ok().with_json(&serde_json::json!({"status": "ok"})).unwrap())
108//!     }
109//! }
110//!
111//! # tokio_test::block_on(async {
112//! // Setup router
113//! let mut router = DefaultRouter::new();
114//! let mut api_route = path("/api", Arc::new(ApiHandler));
115//! api_route.name = Some("api".to_string());
116//! router.add_route(api_route);
117//!
118//! // Create base handler with router
119//! let base_handler: Arc<dyn Handler> = Arc::new(BaseHandler::with_router(Arc::new(router)));
120//!
121//! // Wrap with middleware
122//! let handler = MiddlewareChain::new(base_handler)
123//!     .add_middleware(Arc::new(LoggingMiddleware))
124//!     .expect("Failed to add middleware")
125//!     .build();
126//!
127//! // Use the handler
128//! let request = Request::builder()
129//!     .method(hyper::Method::GET)
130//!     .uri("/api")
131//!     .version(hyper::Version::HTTP_11)
132//!     .headers(hyper::HeaderMap::new())
133//!     .body(bytes::Bytes::new())
134//!     .build()
135//!     .unwrap();
136//!
137//! let response = handler.handle(request).await.unwrap();
138//! assert_eq!(response.status, hyper::StatusCode::OK);
139//! # });
140//! ```
141
142pub mod dispatcher;
143pub mod exception;
144pub mod handler;
145pub mod middleware;
146
147// Re-exports
148pub use dispatcher::Dispatcher;
149pub use exception::{ExceptionHandler, convert_exception_to_response};
150pub use handler::BaseHandler;
151pub use middleware::MiddlewareChain;
152
153use thiserror::Error;
154
155/// Errors that can occur during request dispatching
156#[derive(Debug, Error)]
157pub enum DispatchError {
158	/// Middleware configuration error
159	#[error("Middleware error: {0}")]
160	Middleware(String),
161
162	/// View execution error
163	#[error("View error: {0}")]
164	View(String),
165
166	/// URL resolution error
167	#[error("URL resolution error: {0}")]
168	UrlResolution(String),
169
170	/// HTTP error
171	#[error("HTTP error: {0}")]
172	Http(String),
173
174	/// Internal error
175	#[error("Internal error: {0}")]
176	Internal(String),
177}
178
179/// Build a plain-text error response with security headers.
180///
181/// Sets `Content-Type: text/plain; charset=utf-8` and
182/// `X-Content-Type-Options: nosniff` to prevent browsers from MIME-sniffing
183/// the error body into an executable content type.
184pub(crate) fn build_error_response(
185	status: hyper::StatusCode,
186	message: &str,
187) -> reinhardt_http::Response {
188	let mut response = reinhardt_http::Response::new(status);
189	response.body = bytes::Bytes::from(message.to_owned());
190	response.headers.insert(
191		hyper::header::CONTENT_TYPE,
192		hyper::header::HeaderValue::from_static("text/plain; charset=utf-8"),
193	);
194	response.headers.insert(
195		hyper::header::HeaderName::from_static("x-content-type-options"),
196		hyper::header::HeaderValue::from_static("nosniff"),
197	);
198	response
199}