mirror of
https://github.com/actix/actix-web.git
synced 2024-11-22 17:41:11 +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,
|
||||
};
|
||||
|
||||
/// A request handler is an async function that accepts zero or more parameters that can be
|
||||
/// 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).
|
||||
/// The interface for request handlers.
|
||||
///
|
||||
/// If you got the error `the trait Handler<_, _, _> is not implemented`, then your function is not
|
||||
/// a valid handler. See <https://actix.rs/docs/handlers> for more information.
|
||||
/// # What Is A Request Handler
|
||||
/// 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
|
||||
where
|
||||
R: Future,
|
||||
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
|
||||
F: Handler<T, R>,
|
||||
T: FromRequest,
|
||||
F: Handler<Args, R>,
|
||||
Args: FromRequest,
|
||||
R: Future,
|
||||
R::Output: Responder,
|
||||
<R::Output as Responder>::Body: MessageBody,
|
||||
|
@ -39,7 +101,7 @@ where
|
|||
async move {
|
||||
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),
|
||||
|
||||
Ok(data) => handler
|
||||
|
@ -59,17 +121,18 @@ where
|
|||
///
|
||||
/// # Examples
|
||||
/// ```ignore
|
||||
/// factory_tuple! {} // implements Handler for types: fn() -> Res
|
||||
/// factory_tuple! { A B C } // implements Handler for types: fn(A, B, C) -> Res
|
||||
/// factory_tuple! {} // implements Handler for types: fn() -> R
|
||||
/// factory_tuple! { A B C } // implements Handler for types: fn(A, B, C) -> R
|
||||
/// ```
|
||||
macro_rules! factory_tuple ({ $($param:ident)* } => {
|
||||
impl<Func, $($param,)* Res> Handler<($($param,)*), Res> for Func
|
||||
where Func: Fn($($param),*) -> Res + Clone + 'static,
|
||||
Res: Future,
|
||||
Res::Output: Responder,
|
||||
impl<Func, $($param,)* R> Handler<($($param,)*), R> for Func
|
||||
where Func: Fn($($param),*) -> R + Clone + 'static,
|
||||
R: Future,
|
||||
R::Output: Responder,
|
||||
{
|
||||
#[inline]
|
||||
#[allow(non_snake_case)]
|
||||
fn call(&self, ($($param,)*): ($($param,)*)) -> Res {
|
||||
fn call(&self, ($($param,)*): ($($param,)*)) -> R {
|
||||
(self)($($param,)*)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -106,6 +106,7 @@ pub use cookie;
|
|||
pub use crate::app::App;
|
||||
pub use crate::error::{Error, ResponseError, Result};
|
||||
pub use crate::extract::FromRequest;
|
||||
pub use crate::handler::Handler;
|
||||
pub use crate::request::HttpRequest;
|
||||
pub use crate::resource::Resource;
|
||||
pub use crate::response::{CustomizeResponder, HttpResponse, HttpResponseBuilder, Responder};
|
||||
|
|
|
@ -232,10 +232,10 @@ where
|
|||
/// # fn index(req: HttpRequest) -> HttpResponse { unimplemented!() }
|
||||
/// 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
|
||||
F: Handler<I, R>,
|
||||
I: FromRequest + 'static,
|
||||
F: Handler<Args, R>,
|
||||
Args: FromRequest + 'static,
|
||||
R: Future + 'static,
|
||||
R::Output: Responder + 'static,
|
||||
<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
|
||||
F: Handler<T, R>,
|
||||
T: FromRequest + 'static,
|
||||
F: Handler<Args, R>,
|
||||
Args: FromRequest + 'static,
|
||||
R: Future + 'static,
|
||||
R::Output: Responder + 'static,
|
||||
<R::Output as Responder>::Body: MessageBody,
|
||||
|
|
|
@ -146,10 +146,10 @@ pub fn method(method: Method) -> Route {
|
|||
/// web::to(index))
|
||||
/// );
|
||||
/// ```
|
||||
pub fn to<F, I, R>(handler: F) -> Route
|
||||
pub fn to<F, Args, R>(handler: F) -> Route
|
||||
where
|
||||
F: Handler<I, R>,
|
||||
I: FromRequest + 'static,
|
||||
F: Handler<Args, R>,
|
||||
Args: FromRequest + 'static,
|
||||
R: Future + 'static,
|
||||
R::Output: Responder + 'static,
|
||||
<R::Output as Responder>::Body: MessageBody + 'static,
|
||||
|
|
Loading…
Reference in a new issue