From 5860fe53814ede54d3f6939af1a457a9b9549613 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Sat, 25 Dec 2021 04:43:59 +0000 Subject: [PATCH] expose Handler trait --- src/handler.rs | 99 ++++++++++++++++++++++++++++++++++++++++--------- src/lib.rs | 1 + src/resource.rs | 6 +-- src/route.rs | 6 +-- src/web.rs | 6 +-- 5 files changed, 91 insertions(+), 27 deletions(-) diff --git a/src/handler.rs b/src/handler.rs index e543ecc7f..647606890 100644 --- a/src/handler.rs +++ b/src/handler.rs @@ -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 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) -> impl Responder { +/// ... +/// } +/// +/// // the function params above described as a tuple, names do not matter, only position +/// type InferredMyHandlerArgs = (String, web::Data); +/// +/// // 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 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: 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(handler: F) -> BoxedHttpServiceFactory +pub(crate) fn handler_service(handler: F) -> BoxedHttpServiceFactory where - F: Handler, - T: FromRequest, + F: Handler, + Args: FromRequest, R: Future, R::Output: 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 Handler<($($param,)*), Res> for Func - where Func: Fn($($param),*) -> Res + Clone + 'static, - Res: Future, - Res::Output: Responder, + impl 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,)*) } } diff --git a/src/lib.rs b/src/lib.rs index 171a2d101..3462ae90b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -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}; diff --git a/src/resource.rs b/src/resource.rs index 0d82bb004..c6c8a5b89 100644 --- a/src/resource.rs +++ b/src/resource.rs @@ -232,10 +232,10 @@ where /// # fn index(req: HttpRequest) -> HttpResponse { unimplemented!() } /// App::new().service(web::resource("/").route(web::route().to(index))); /// ``` - pub fn to(mut self, handler: F) -> Self + pub fn to(mut self, handler: F) -> Self where - F: Handler, - I: FromRequest + 'static, + F: Handler, + Args: FromRequest + 'static, R: Future + 'static, R::Output: Responder + 'static, ::Body: MessageBody, diff --git a/src/route.rs b/src/route.rs index 16c01275b..6396c4286 100644 --- a/src/route.rs +++ b/src/route.rs @@ -176,10 +176,10 @@ impl Route { /// ); /// } /// ``` - pub fn to(mut self, handler: F) -> Self + pub fn to(mut self, handler: F) -> Self where - F: Handler, - T: FromRequest + 'static, + F: Handler, + Args: FromRequest + 'static, R: Future + 'static, R::Output: Responder + 'static, ::Body: MessageBody, diff --git a/src/web.rs b/src/web.rs index 22877692d..46e7704b6 100644 --- a/src/web.rs +++ b/src/web.rs @@ -146,10 +146,10 @@ pub fn method(method: Method) -> Route { /// web::to(index)) /// ); /// ``` -pub fn to(handler: F) -> Route +pub fn to(handler: F) -> Route where - F: Handler, - I: FromRequest + 'static, + F: Handler, + Args: FromRequest + 'static, R: Future + 'static, R::Output: Responder + 'static, ::Body: MessageBody + 'static,