Skip to main content

rws_server/handler/
mod.rs

1//! Typed handlers (18.28.0, #86): extractors as function arguments, any
2//! [`IntoResponse`] as the return value, the route's method and pattern
3//! taken from the `#[get]`/`#[post]`/… attribute.
4//!
5//! ```rust,ignore
6//! use rust_web_server::app::App;
7//! use rust_web_server::error::AppError;
8//! use rust_web_server::get;
9//! use rust_web_server::handler::{Json, Path, State};
10//!
11//! struct Db;
12//!
13//! #[get("/posts/{id}")]
14//! fn show_post(Path(id): Path<u64>, State(db): State<Db>) -> Result<Json<String>, AppError> {
15//!     let _ = db;
16//!     Ok(Json(format!("post {id}")))
17//! }
18//!
19//! let app = App::with_state(Db).route(show_post);
20//! ```
21//!
22//! The attribute turns `show_post` into a unit struct implementing
23//! [`Handler`] (and [`AsyncHandler`]); the function as written stays callable
24//! as `show_post::call(…)`. Registration —
25//! [`AppWithState::route`](crate::state::AppWithState::route),
26//! [`Router::route`](crate::router::Router::route),
27//! `AsyncAppWithState::route` — reads the method and pattern from
28//! [`RouteSpec`], so the pattern the macro checked is the one that's served.
29//!
30//! The macro checks at compile time that the pattern's parameters and the
31//! [`Path`] argument agree: a route with parameters needs a `Path` (or
32//! `&PathParams`) argument, a `Path` needs parameters, a tuple has one
33//! element per parameter, names bound as `Path(id)` / `Path((id, slug))`
34//! are parameters of the route, and a `#[derive(FromPath)]` struct's
35//! fields are exactly its parameters.
36//!
37//! Each argument is extracted in order; the first that fails answers the
38//! request with its rejection (problem details, like rws's own errors):
39//!
40//! | Argument | Rejection |
41//! |---|---|
42//! | [`Path<T>`] | `400` when a parameter doesn't parse |
43//! | `Query<T>` (`serde`) | `400` when the query string doesn't fit `T` |
44//! | `Form<T>` (`serde`) | `415` without an urlencoded `Content-Type`, `400` for a body that isn't UTF-8, `422` when the fields don't fit `T` |
45//! | `Json<T>` (`serde`) | `400` (as `json::Json`'s `FromRequest`) |
46//! | [`BodyText`] | `400` for a body that isn't UTF-8 |
47//! | [`State<S>`], `&S`, [`Body`], [`RequestHeaders`], [`RequestId`], `&Request`, `&ConnectionInfo`, `&PathParams` | never |
48//! | any other [`FromRequest`] type (`Validated<T>`, your own) | its own |
49//!
50//! Classic handlers — `fn(&Request, &PathParams, &ConnectionInfo[, &S])` —
51//! keep the old behaviour: the attribute only adds a doc comment and they
52//! are registered with `.get(pattern, handler)` as before.
53
54#[cfg(test)]
55mod tests;
56mod path;
57#[cfg(feature = "serde")]
58mod urlencoded;
59
60use std::future::Future;
61use std::pin::Pin;
62use std::sync::Arc;
63
64use crate::extract::FromRequest;
65use crate::header::Header;
66use crate::request::Request;
67use crate::response::{Response, StatusCodeReasonPhrase};
68use crate::router::PathParams;
69use crate::server::ConnectionInfo;
70
71pub use crate::error::IntoResponse;
72pub use crate::extract::{Body, BodyText, RequestHeaders, RequestId};
73#[cfg(feature = "serde")]
74pub use crate::json::Json;
75pub use path::{FromPath, Path};
76
77/// What an extractor sees: the request, the route's parameters, the
78/// connection and the app's state.
79pub struct RequestParts<'a, S> {
80    pub request: &'a Request,
81    pub params: &'a PathParams,
82    pub connection: &'a ConnectionInfo,
83    pub state: &'a Arc<S>,
84    /// The route parameters the handler's [`Path`] argument takes, in the
85    /// order it takes them (set by the route macro).
86    pub path_names: &'static [&'static str],
87}
88
89/// A typed handler argument: built from the [`RequestParts`], or a
90/// ready-to-send rejection.
91///
92/// Every [`FromRequest`] type is one, so `#[derive(FromRequest)]` structs
93/// and `Validated<T>` work as arguments too.
94pub trait FromRequestParts<S>: Sized {
95    fn from_request_parts(parts: &RequestParts<'_, S>) -> Result<Self, Response>;
96}
97
98impl<S, T: FromRequest> FromRequestParts<S> for T {
99    fn from_request_parts(parts: &RequestParts<'_, S>) -> Result<Self, Response> {
100        T::from_request(parts.request)
101    }
102}
103
104/// The app's shared state (`App::with_state(state)`): a clone of its `Arc`.
105///
106/// `State(db): State<Db>` in a handler's arguments; `&Db` works too.
107pub struct State<S>(pub Arc<S>);
108
109impl<S> Clone for State<S> {
110    fn clone(&self) -> Self {
111        State(Arc::clone(&self.0))
112    }
113}
114
115impl<S> std::ops::Deref for State<S> {
116    type Target = S;
117    fn deref(&self) -> &S {
118        &self.0
119    }
120}
121
122impl<S> FromRequestParts<S> for State<S> {
123    fn from_request_parts(parts: &RequestParts<'_, S>) -> Result<Self, Response> {
124        Ok(State(Arc::clone(parts.state)))
125    }
126}
127
128#[cfg(feature = "serde")]
129pub use urlencoded::{Form, Query};
130
131/// A route's method and pattern, generated by `#[get("/path")]` & co.
132pub trait RouteSpec {
133    const METHOD: &'static str;
134    const PATTERN: &'static str;
135}
136
137/// A typed handler for apps with state `S`, made by the route macros from a
138/// function whose arguments are extractors. Register it with `.route(h)`.
139pub trait Handler<S>: RouteSpec + Send + Sync + 'static {
140    /// Extract the arguments, call the function and turn its value into a
141    /// [`Response`] (or answer with the first argument's rejection).
142    fn handle(&self, request: &Request, params: &PathParams, connection: &ConnectionInfo, state: &Arc<S>) -> Response;
143}
144
145/// The future an [`AsyncHandler`] returns.
146pub type HandlerFuture = Pin<Box<dyn Future<Output = Response> + Send + 'static>>;
147
148/// A typed handler for `AsyncAppWithState` (`http2`): the route macros make
149/// one from an `async fn`, and from every sync typed handler too.
150pub trait AsyncHandler<S>: RouteSpec + Send + Sync + 'static {
151    fn handle_async(&self, request: Request, params: PathParams, connection: ConnectionInfo, state: Arc<S>) -> HandlerFuture;
152}
153
154/// An extractor's rejection: problem details (or text, with
155/// `error_format = "text"`) carrying the usual response headers.
156pub(crate) fn rejection(request: &Request, status: &StatusCodeReasonPhrase, detail: &str) -> Response {
157    crate::error::problem::with_headers(status, detail, detail, Some(Header::get_header_list(request)))
158}
159
160/// What the route macros' output calls. Not an API.
161#[doc(hidden)]
162pub mod __private {
163    pub use super::path::{parse_param, path_fits};
164    /// `Query`/`Form`'s deserializer on `input` (fuzz target `form`).
165    #[cfg(feature = "serde")]
166    pub use super::urlencoded::fuzz as fuzz_urlencoded;
167
168    /// The handler's return value as a response.
169    #[inline]
170    pub fn respond<R: crate::error::IntoResponse>(value: R) -> crate::response::Response {
171        value.into_response()
172    }
173}