rust_web_server/state/mod.rs
1//! Shared application state and state-aware routing.
2//!
3//! [`AppWithState<S>`] combines a typed state value (database pools, config,
4//! caches) with route registration. Routes are tried first; requests that do
5//! not match fall through to the built-in [`App`] controller chain (static
6//! files, healthz, metrics, …).
7//!
8//! State is stored as an [`Arc<S>`] and shared across all handlers. Handlers
9//! receive an immutable `&S` reference alongside the request context.
10//!
11//! # Example
12//!
13//! ```rust,no_run
14//! use rust_web_server::state::AppWithState;
15//! use rust_web_server::response::{Response, STATUS_CODE_REASON_PHRASE};
16//! use rust_web_server::range::Range;
17//! use rust_web_server::mime_type::MimeType;
18//! use rust_web_server::core::New;
19//!
20//! struct AppState {
21//! greeting: String,
22//! }
23//!
24//! let app = AppWithState::new(AppState { greeting: "Hello".to_string() })
25//! .get("/greet", |_req, _params, _conn, state| {
26//! let mut r = Response::new();
27//! r.status_code = *STATUS_CODE_REASON_PHRASE.n200_ok.status_code;
28//! r.reason_phrase = STATUS_CODE_REASON_PHRASE.n200_ok.reason_phrase.to_string();
29//! r.content_range_list = vec![
30//! Range::get_content_range(
31//! state.greeting.as_bytes().to_vec(),
32//! MimeType::TEXT_PLAIN.to_string(),
33//! )
34//! ];
35//! r
36//! })
37//! .get("/users/:id", |_req, params, _conn, state| {
38//! let id = params.get("id").unwrap_or("?");
39//! let body = format!("{}, user {}!", state.greeting, id);
40//! let mut r = Response::new();
41//! r.status_code = *STATUS_CODE_REASON_PHRASE.n200_ok.status_code;
42//! r.reason_phrase = STATUS_CODE_REASON_PHRASE.n200_ok.reason_phrase.to_string();
43//! r.content_range_list = vec![
44//! Range::get_content_range(body.into_bytes(), MimeType::TEXT_PLAIN.to_string())
45//! ];
46//! r
47//! });
48//! ```
49
50#[cfg(test)]
51mod tests;
52
53use std::sync::Arc;
54
55use crate::app::App;
56use crate::application::Application;
57use crate::core::New;
58use crate::middleware::{Middleware, WithMiddleware};
59use crate::request::Request;
60use crate::response::Response;
61use crate::router::{PathParams, Router};
62use crate::server::ConnectionInfo;
63
64/// An [`Application`] that combines user-defined state-aware routes with the
65/// built-in [`App`] controller chain as a fallback.
66///
67/// Routes are matched in registration order. The first match wins; unmatched
68/// requests are forwarded to [`App`] (static files, health probes, etc.).
69pub struct AppWithState<S> {
70 state: Arc<S>,
71 router: Router,
72}
73
74impl<S: Send + Sync + 'static> AppWithState<S> {
75 /// Create a new `AppWithState` wrapping `state`.
76 ///
77 /// `state` is stored behind an `Arc` so it can be shared across threads
78 /// without cloning. Register routes with the builder methods.
79 pub fn new(state: S) -> Self {
80 AppWithState {
81 state: Arc::new(state),
82 router: Router::new(),
83 }
84 }
85
86 /// Return a reference to the shared state.
87 pub fn state(&self) -> &S {
88 &self.state
89 }
90
91 /// Register a `GET` handler for `pattern`.
92 pub fn get<F>(mut self, pattern: &str, handler: F) -> Self
93 where
94 F: Fn(&Request, &PathParams, &ConnectionInfo, &S) -> Response + Send + Sync + 'static,
95 {
96 let state = Arc::clone(&self.state);
97 self.router = self.router.get(pattern, move |req, params, conn| {
98 handler(req, params, conn, &state)
99 });
100 self
101 }
102
103 /// Register a `POST` handler for `pattern`.
104 pub fn post<F>(mut self, pattern: &str, handler: F) -> Self
105 where
106 F: Fn(&Request, &PathParams, &ConnectionInfo, &S) -> Response + Send + Sync + 'static,
107 {
108 let state = Arc::clone(&self.state);
109 self.router = self.router.post(pattern, move |req, params, conn| {
110 handler(req, params, conn, &state)
111 });
112 self
113 }
114
115 /// Register a `PUT` handler for `pattern`.
116 pub fn put<F>(mut self, pattern: &str, handler: F) -> Self
117 where
118 F: Fn(&Request, &PathParams, &ConnectionInfo, &S) -> Response + Send + Sync + 'static,
119 {
120 let state = Arc::clone(&self.state);
121 self.router = self.router.put(pattern, move |req, params, conn| {
122 handler(req, params, conn, &state)
123 });
124 self
125 }
126
127 /// Register a `PATCH` handler for `pattern`.
128 pub fn patch<F>(mut self, pattern: &str, handler: F) -> Self
129 where
130 F: Fn(&Request, &PathParams, &ConnectionInfo, &S) -> Response + Send + Sync + 'static,
131 {
132 let state = Arc::clone(&self.state);
133 self.router = self.router.patch(pattern, move |req, params, conn| {
134 handler(req, params, conn, &state)
135 });
136 self
137 }
138
139 /// Register a `DELETE` handler for `pattern`.
140 pub fn delete<F>(mut self, pattern: &str, handler: F) -> Self
141 where
142 F: Fn(&Request, &PathParams, &ConnectionInfo, &S) -> Response + Send + Sync + 'static,
143 {
144 let state = Arc::clone(&self.state);
145 self.router = self.router.delete(pattern, move |req, params, conn| {
146 handler(req, params, conn, &state)
147 });
148 self
149 }
150
151 /// Wrap this application in a middleware layer.
152 ///
153 /// Enables fluent composition:
154 ///
155 /// ```rust,no_run
156 /// use rust_web_server::app::App;
157 /// use rust_web_server::core::New;
158 /// use rust_web_server::middleware::RateLimitLayer;
159 /// use rust_web_server::response::Response;
160 ///
161 /// let app = App::with_state(())
162 /// .get("/ping", |_, _, _, _| Response::new())
163 /// .wrap(RateLimitLayer);
164 /// ```
165 pub fn wrap<M: Middleware + 'static>(self, layer: M) -> WithMiddleware<AppWithState<S>> {
166 WithMiddleware::new(self).wrap(layer)
167 }
168}
169
170impl<S: Send + Sync + 'static> Application for AppWithState<S> {
171 fn execute(&self, request: &Request, connection: &ConnectionInfo) -> Result<Response, String> {
172 if let Some(response) = self.router.handle(request, connection) {
173 return Ok(response);
174 }
175 App::new().execute(request, connection)
176 }
177}