rws-server 18.29.0

The server layer of rust-web-server (internal: use the rust-web-server crate).
Documentation
//! Typed handlers (18.28.0, #86): extractors as function arguments, any
//! [`IntoResponse`] as the return value, the route's method and pattern
//! taken from the `#[get]`/`#[post]`/… attribute.
//!
//! ```rust,ignore
//! use rust_web_server::app::App;
//! use rust_web_server::error::AppError;
//! use rust_web_server::get;
//! use rust_web_server::handler::{Json, Path, State};
//!
//! struct Db;
//!
//! #[get("/posts/{id}")]
//! fn show_post(Path(id): Path<u64>, State(db): State<Db>) -> Result<Json<String>, AppError> {
//!     let _ = db;
//!     Ok(Json(format!("post {id}")))
//! }
//!
//! let app = App::with_state(Db).route(show_post);
//! ```
//!
//! The attribute turns `show_post` into a unit struct implementing
//! [`Handler`] (and [`AsyncHandler`]); the function as written stays callable
//! as `show_post::call(…)`. Registration —
//! [`AppWithState::route`](crate::state::AppWithState::route),
//! [`Router::route`](crate::router::Router::route),
//! `AsyncAppWithState::route` — reads the method and pattern from
//! [`RouteSpec`], so the pattern the macro checked is the one that's served.
//!
//! The macro checks at compile time that the pattern's parameters and the
//! [`Path`] argument agree: a route with parameters needs a `Path` (or
//! `&PathParams`) argument, a `Path` needs parameters, a tuple has one
//! element per parameter, names bound as `Path(id)` / `Path((id, slug))`
//! are parameters of the route, and a `#[derive(FromPath)]` struct's
//! fields are exactly its parameters.
//!
//! Each argument is extracted in order; the first that fails answers the
//! request with its rejection (problem details, like rws's own errors):
//!
//! | Argument | Rejection |
//! |---|---|
//! | [`Path<T>`] | `400` when a parameter doesn't parse |
//! | `Query<T>` (`serde`) | `400` when the query string doesn't fit `T` |
//! | `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` |
//! | `Json<T>` (`serde`) | `400` (as `json::Json`'s `FromRequest`) |
//! | [`BodyText`] | `400` for a body that isn't UTF-8 |
//! | [`State<S>`], `&S`, [`Body`], [`RequestHeaders`], [`RequestId`], `&Request`, `&ConnectionInfo`, `&PathParams` | never |
//! | any other [`FromRequest`] type (`Validated<T>`, your own) | its own |
//!
//! Classic handlers — `fn(&Request, &PathParams, &ConnectionInfo[, &S])` —
//! keep the old behaviour: the attribute only adds a doc comment and they
//! are registered with `.get(pattern, handler)` as before.

#[cfg(test)]
mod tests;
mod path;
#[cfg(feature = "serde")]
mod urlencoded;

use std::future::Future;
use std::pin::Pin;
use std::sync::Arc;

use crate::extract::FromRequest;
use crate::header::Header;
use crate::request::Request;
use crate::response::{Response, StatusCodeReasonPhrase};
use crate::router::PathParams;
use crate::server::ConnectionInfo;

pub use crate::error::IntoResponse;
pub use crate::extract::{Body, BodyText, RequestHeaders, RequestId};
#[cfg(feature = "serde")]
pub use crate::json::Json;
pub use path::{FromPath, Path};

/// What an extractor sees: the request, the route's parameters, the
/// connection and the app's state.
pub struct RequestParts<'a, S> {
    pub request: &'a Request,
    pub params: &'a PathParams,
    pub connection: &'a ConnectionInfo,
    pub state: &'a Arc<S>,
    /// The route parameters the handler's [`Path`] argument takes, in the
    /// order it takes them (set by the route macro).
    pub path_names: &'static [&'static str],
}

/// A typed handler argument: built from the [`RequestParts`], or a
/// ready-to-send rejection.
///
/// Every [`FromRequest`] type is one, so `#[derive(FromRequest)]` structs
/// and `Validated<T>` work as arguments too.
pub trait FromRequestParts<S>: Sized {
    fn from_request_parts(parts: &RequestParts<'_, S>) -> Result<Self, Response>;
}

impl<S, T: FromRequest> FromRequestParts<S> for T {
    fn from_request_parts(parts: &RequestParts<'_, S>) -> Result<Self, Response> {
        T::from_request(parts.request)
    }
}

/// The app's shared state (`App::with_state(state)`): a clone of its `Arc`.
///
/// `State(db): State<Db>` in a handler's arguments; `&Db` works too.
pub struct State<S>(pub Arc<S>);

impl<S> Clone for State<S> {
    fn clone(&self) -> Self {
        State(Arc::clone(&self.0))
    }
}

impl<S> std::ops::Deref for State<S> {
    type Target = S;
    fn deref(&self) -> &S {
        &self.0
    }
}

impl<S> FromRequestParts<S> for State<S> {
    fn from_request_parts(parts: &RequestParts<'_, S>) -> Result<Self, Response> {
        Ok(State(Arc::clone(parts.state)))
    }
}

#[cfg(feature = "serde")]
pub use urlencoded::{Form, Query};

/// A route's method and pattern, generated by `#[get("/path")]` & co.
pub trait RouteSpec {
    const METHOD: &'static str;
    const PATTERN: &'static str;
}

/// A typed handler for apps with state `S`, made by the route macros from a
/// function whose arguments are extractors. Register it with `.route(h)`.
pub trait Handler<S>: RouteSpec + Send + Sync + 'static {
    /// Extract the arguments, call the function and turn its value into a
    /// [`Response`] (or answer with the first argument's rejection).
    fn handle(&self, request: &Request, params: &PathParams, connection: &ConnectionInfo, state: &Arc<S>) -> Response;
}

/// The future an [`AsyncHandler`] returns.
pub type HandlerFuture = Pin<Box<dyn Future<Output = Response> + Send + 'static>>;

/// A typed handler for `AsyncAppWithState` (`http2`): the route macros make
/// one from an `async fn`, and from every sync typed handler too.
pub trait AsyncHandler<S>: RouteSpec + Send + Sync + 'static {
    fn handle_async(&self, request: Request, params: PathParams, connection: ConnectionInfo, state: Arc<S>) -> HandlerFuture;
}

/// An extractor's rejection: problem details (or text, with
/// `error_format = "text"`) carrying the usual response headers.
pub(crate) fn rejection(request: &Request, status: &StatusCodeReasonPhrase, detail: &str) -> Response {
    crate::error::problem::with_headers(status, detail, detail, Some(Header::get_header_list(request)))
}

/// What the route macros' output calls. Not an API.
#[doc(hidden)]
pub mod __private {
    pub use super::path::{parse_param, path_fits};
    /// `Query`/`Form`'s deserializer on `input` (fuzz target `form`).
    #[cfg(feature = "serde")]
    pub use super::urlencoded::fuzz as fuzz_urlencoded;

    /// The handler's return value as a response.
    #[inline]
    pub fn respond<R: crate::error::IntoResponse>(value: R) -> crate::response::Response {
        value.into_response()
    }
}