mirror of
https://github.com/actix/actix-web.git
synced 2024-11-26 19:41:12 +00:00
expose Handler trait
This commit is contained in:
parent
adf9935841
commit
5860fe5381
5 changed files with 91 additions and 27 deletions
|
@ -8,26 +8,88 @@ use crate::{
|
||||||
BoxError, FromRequest, HttpResponse, Responder,
|
BoxError, FromRequest, HttpResponse, Responder,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// A request handler is an async function that accepts zero or more parameters that can be
|
/// The interface for request handlers.
|
||||||
/// extracted from a request (i.e., [`impl FromRequest`]) and returns a type that can be converted
|
|
||||||
/// into an [`HttpResponse`] (that is, it impls the [`Responder`] trait).
|
|
||||||
///
|
///
|
||||||
/// If you got the error `the trait Handler<_, _, _> is not implemented`, then your function is not
|
/// # What Is A Request Handler
|
||||||
/// a valid handler. See <https://actix.rs/docs/handlers> for more information.
|
/// A request handler has three requirements:
|
||||||
|
/// 1. It is an async function (or a function/closure that returns an appropriate future);
|
||||||
|
/// 1. The function accepts zero or more parameters that implement [`FromRequest`];
|
||||||
|
/// 1. The async function (or future) resolves to a type that can be converted into an
|
||||||
|
/// [`HttpResponse`] (i.e., it implements the [`Responder`] trait).
|
||||||
///
|
///
|
||||||
/// [`impl FromRequest`]: crate::FromRequest
|
/// # Compiler Errors
|
||||||
|
/// If you get the error `the trait Handler<_, _, _> is not implemented`, then your handler does not
|
||||||
|
/// fulfill one or more of the above requirements.
|
||||||
|
///
|
||||||
|
/// Unfortunately we cannot provide a better compile error message (while keeping the trait's
|
||||||
|
/// flexibility) unless a stable alternative to [`#[rustc_on_unimplemented]`][on_unimpl] is added
|
||||||
|
/// to Rust.
|
||||||
|
///
|
||||||
|
/// # How Do Handlers Receive Variable Numbers Of Arguments
|
||||||
|
/// Rest assured there is no macro magic here; it's just traits.
|
||||||
|
///
|
||||||
|
/// The first thing to note is that [`FromRequest`] is implemented for tuples (up to 12 in length).
|
||||||
|
///
|
||||||
|
/// Secondly, the `Handler` trait is implemented for functions (up to an [arity] of 12) in a way
|
||||||
|
/// that aligns their parameter positions with a corresponding tuple of types (becoming the `T` type
|
||||||
|
/// parameter in this trait's implementation).
|
||||||
|
///
|
||||||
|
/// Thanks to Rust's type system, Actix Web can infer the function parameter types. During the
|
||||||
|
/// extraction step, the parameter types are described as a tuple type, [`from_request`] is run on
|
||||||
|
/// that tuple, and the `Handler::call` implementation for that particular function arity
|
||||||
|
/// destructures the tuple into it's component types and calls your handler function with them.
|
||||||
|
///
|
||||||
|
/// In pseudo-code the process looks something like this:
|
||||||
|
/// ```ignore
|
||||||
|
/// async fn my_handler(body: String, state: web::Data<MyState>) -> impl Responder {
|
||||||
|
/// ...
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// // the function params above described as a tuple, names do not matter, only position
|
||||||
|
/// type InferredMyHandlerArgs = (String, web::Data<MyState>);
|
||||||
|
///
|
||||||
|
/// // create tuple of arguments to be passed to handler
|
||||||
|
/// let args = InferredMyHandlerArgs::from_request(&request, &payload).await;
|
||||||
|
///
|
||||||
|
/// // call handler with argument tuple
|
||||||
|
/// let response = Handler::call(&my_handler, args).await;
|
||||||
|
///
|
||||||
|
/// // which is effectively...
|
||||||
|
///
|
||||||
|
/// let (body, state) = args;
|
||||||
|
/// let response = my_handler(body, state).await;
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// This is the source code for the 2-parameter implementation of `Handler` to help illustrate the
|
||||||
|
/// bounds of the handler call after argument extraction:
|
||||||
|
/// ```ignore
|
||||||
|
/// impl<Func, Arg1, Arg2, R> Handler<(Arg1, Arg2), R> for Func
|
||||||
|
/// where
|
||||||
|
/// Func: Fn(Arg1, Arg2) -> R + Clone + 'static,
|
||||||
|
/// R: Future,
|
||||||
|
/// R::Output: Responder,
|
||||||
|
/// {
|
||||||
|
/// fn call(&self, (arg1, arg2): (Arg1, Arg2)) -> R {
|
||||||
|
/// (self)(arg1, arg2)
|
||||||
|
/// }
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// [arity]: https://en.wikipedia.org/wiki/Arity
|
||||||
|
/// [`from_request`]: FromRequest::from_request
|
||||||
|
/// [on_unimpl]: https://github.com/rust-lang/rust/issues/29628
|
||||||
pub trait Handler<T, R>: Clone + 'static
|
pub trait Handler<T, R>: Clone + 'static
|
||||||
where
|
where
|
||||||
R: Future,
|
R: Future,
|
||||||
R::Output: Responder,
|
R::Output: Responder,
|
||||||
{
|
{
|
||||||
fn call(&self, param: T) -> R;
|
fn call(&self, args: T) -> R;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn handler_service<F, T, R>(handler: F) -> BoxedHttpServiceFactory
|
pub(crate) fn handler_service<F, Args, R>(handler: F) -> BoxedHttpServiceFactory
|
||||||
where
|
where
|
||||||
F: Handler<T, R>,
|
F: Handler<Args, R>,
|
||||||
T: FromRequest,
|
Args: FromRequest,
|
||||||
R: Future,
|
R: Future,
|
||||||
R::Output: Responder,
|
R::Output: Responder,
|
||||||
<R::Output as Responder>::Body: MessageBody,
|
<R::Output as Responder>::Body: MessageBody,
|
||||||
|
@ -39,7 +101,7 @@ where
|
||||||
async move {
|
async move {
|
||||||
let (req, mut payload) = req.into_parts();
|
let (req, mut payload) = req.into_parts();
|
||||||
|
|
||||||
let res = match T::from_request(&req, &mut payload).await {
|
let res = match Args::from_request(&req, &mut payload).await {
|
||||||
Err(err) => HttpResponse::from_error(err),
|
Err(err) => HttpResponse::from_error(err),
|
||||||
|
|
||||||
Ok(data) => handler
|
Ok(data) => handler
|
||||||
|
@ -59,17 +121,18 @@ where
|
||||||
///
|
///
|
||||||
/// # Examples
|
/// # Examples
|
||||||
/// ```ignore
|
/// ```ignore
|
||||||
/// factory_tuple! {} // implements Handler for types: fn() -> Res
|
/// factory_tuple! {} // implements Handler for types: fn() -> R
|
||||||
/// factory_tuple! { A B C } // implements Handler for types: fn(A, B, C) -> Res
|
/// factory_tuple! { A B C } // implements Handler for types: fn(A, B, C) -> R
|
||||||
/// ```
|
/// ```
|
||||||
macro_rules! factory_tuple ({ $($param:ident)* } => {
|
macro_rules! factory_tuple ({ $($param:ident)* } => {
|
||||||
impl<Func, $($param,)* Res> Handler<($($param,)*), Res> for Func
|
impl<Func, $($param,)* R> Handler<($($param,)*), R> for Func
|
||||||
where Func: Fn($($param),*) -> Res + Clone + 'static,
|
where Func: Fn($($param),*) -> R + Clone + 'static,
|
||||||
Res: Future,
|
R: Future,
|
||||||
Res::Output: Responder,
|
R::Output: Responder,
|
||||||
{
|
{
|
||||||
|
#[inline]
|
||||||
#[allow(non_snake_case)]
|
#[allow(non_snake_case)]
|
||||||
fn call(&self, ($($param,)*): ($($param,)*)) -> Res {
|
fn call(&self, ($($param,)*): ($($param,)*)) -> R {
|
||||||
(self)($($param,)*)
|
(self)($($param,)*)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -106,6 +106,7 @@ pub use cookie;
|
||||||
pub use crate::app::App;
|
pub use crate::app::App;
|
||||||
pub use crate::error::{Error, ResponseError, Result};
|
pub use crate::error::{Error, ResponseError, Result};
|
||||||
pub use crate::extract::FromRequest;
|
pub use crate::extract::FromRequest;
|
||||||
|
pub use crate::handler::Handler;
|
||||||
pub use crate::request::HttpRequest;
|
pub use crate::request::HttpRequest;
|
||||||
pub use crate::resource::Resource;
|
pub use crate::resource::Resource;
|
||||||
pub use crate::response::{CustomizeResponder, HttpResponse, HttpResponseBuilder, Responder};
|
pub use crate::response::{CustomizeResponder, HttpResponse, HttpResponseBuilder, Responder};
|
||||||
|
|
|
@ -232,10 +232,10 @@ where
|
||||||
/// # fn index(req: HttpRequest) -> HttpResponse { unimplemented!() }
|
/// # fn index(req: HttpRequest) -> HttpResponse { unimplemented!() }
|
||||||
/// App::new().service(web::resource("/").route(web::route().to(index)));
|
/// App::new().service(web::resource("/").route(web::route().to(index)));
|
||||||
/// ```
|
/// ```
|
||||||
pub fn to<F, I, R>(mut self, handler: F) -> Self
|
pub fn to<F, Args, R>(mut self, handler: F) -> Self
|
||||||
where
|
where
|
||||||
F: Handler<I, R>,
|
F: Handler<Args, R>,
|
||||||
I: FromRequest + 'static,
|
Args: FromRequest + 'static,
|
||||||
R: Future + 'static,
|
R: Future + 'static,
|
||||||
R::Output: Responder + 'static,
|
R::Output: Responder + 'static,
|
||||||
<R::Output as Responder>::Body: MessageBody,
|
<R::Output as Responder>::Body: MessageBody,
|
||||||
|
|
|
@ -176,10 +176,10 @@ impl Route {
|
||||||
/// );
|
/// );
|
||||||
/// }
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
pub fn to<F, T, R>(mut self, handler: F) -> Self
|
pub fn to<F, Args, R>(mut self, handler: F) -> Self
|
||||||
where
|
where
|
||||||
F: Handler<T, R>,
|
F: Handler<Args, R>,
|
||||||
T: FromRequest + 'static,
|
Args: FromRequest + 'static,
|
||||||
R: Future + 'static,
|
R: Future + 'static,
|
||||||
R::Output: Responder + 'static,
|
R::Output: Responder + 'static,
|
||||||
<R::Output as Responder>::Body: MessageBody,
|
<R::Output as Responder>::Body: MessageBody,
|
||||||
|
|
|
@ -146,10 +146,10 @@ pub fn method(method: Method) -> Route {
|
||||||
/// web::to(index))
|
/// web::to(index))
|
||||||
/// );
|
/// );
|
||||||
/// ```
|
/// ```
|
||||||
pub fn to<F, I, R>(handler: F) -> Route
|
pub fn to<F, Args, R>(handler: F) -> Route
|
||||||
where
|
where
|
||||||
F: Handler<I, R>,
|
F: Handler<Args, R>,
|
||||||
I: FromRequest + 'static,
|
Args: FromRequest + 'static,
|
||||||
R: Future + 'static,
|
R: Future + 'static,
|
||||||
R::Output: Responder + 'static,
|
R::Output: Responder + 'static,
|
||||||
<R::Output as Responder>::Body: MessageBody + 'static,
|
<R::Output as Responder>::Body: MessageBody + 'static,
|
||||||
|
|
Loading…
Reference in a new issue