From 56e7fa6e59778bff2a3898edd9f364d3003539d4 Mon Sep 17 00:00:00 2001 From: Matthew Pomes Date: Wed, 19 Jun 2024 21:56:10 -0500 Subject: [PATCH] Initial brush --- core/codegen/src/attribute/catch/mod.rs | 16 ++- core/codegen/src/attribute/route/mod.rs | 16 +-- core/codegen/src/exports.rs | 3 + core/lib/Cargo.toml | 1 + core/lib/src/catcher/catcher.rs | 28 +++--- core/lib/src/catcher/handler.rs | 18 ++-- core/lib/src/catcher/mod.rs | 2 + core/lib/src/catcher/types.rs | 110 +++++++++++++++++++++ core/lib/src/lifecycle.rs | 19 ++-- core/lib/src/local/asynchronous/request.rs | 3 +- core/lib/src/outcome.rs | 3 +- core/lib/src/route/handler.rs | 31 +++++- core/lib/src/server.rs | 3 +- core/lib/src/trace/traceable.rs | 2 +- core/lib/tests/panic-handling.rs | 3 +- 15 files changed, 212 insertions(+), 46 deletions(-) create mode 100644 core/lib/src/catcher/types.rs diff --git a/core/codegen/src/attribute/catch/mod.rs b/core/codegen/src/attribute/catch/mod.rs index 57c898a0..13c4660c 100644 --- a/core/codegen/src/attribute/catch/mod.rs +++ b/core/codegen/src/attribute/catch/mod.rs @@ -34,15 +34,25 @@ pub fn _catch( .map(|ty| ty.span()) .unwrap_or_else(Span::call_site); + // TODO: how to handle request? + // - Right now: (), (&Req), (Status, &Req) allowed + // - New: (), (&E), (&Req, &E), (Status, &Req, &E) // Set the `req` and `status` spans to that of their respective function // arguments for a more correct `wrong type` error span. `rev` to be cute. - let codegen_args = &[__req, __status]; + let codegen_args = &[__req, __status, __error]; let inputs = catch.function.sig.inputs.iter().rev() .zip(codegen_args.iter()) .map(|(fn_arg, codegen_arg)| match fn_arg { syn::FnArg::Receiver(_) => codegen_arg.respanned(fn_arg.span()), syn::FnArg::Typed(a) => codegen_arg.respanned(a.ty.span()) }).rev(); + let make_error = if let Some(arg) = catch.function.sig.inputs.iter().rev().next() { + quote_spanned!(arg.span() => + // let + ) + } else { + quote! {} + }; // We append `.await` to the function call if this is `async`. let dot_await = catch.function.sig.asyncness @@ -68,9 +78,11 @@ pub fn _catch( fn into_info(self) -> #_catcher::StaticInfo { fn monomorphized_function<'__r>( #__status: #Status, - #__req: &'__r #Request<'_> + #__req: &'__r #Request<'_>, + __error_init: &#ErasedErrorRef<'__r>, ) -> #_catcher::BoxFuture<'__r> { #_Box::pin(async move { + #make_error let __response = #catcher_response; #Response::build() .status(#__status) diff --git a/core/codegen/src/attribute/route/mod.rs b/core/codegen/src/attribute/route/mod.rs index cb501e43..2b110c7e 100644 --- a/core/codegen/src/attribute/route/mod.rs +++ b/core/codegen/src/attribute/route/mod.rs @@ -125,7 +125,7 @@ fn query_decls(route: &Route) -> Option { fn request_guard_decl(guard: &Guard) -> TokenStream { let (ident, ty) = (guard.fn_ident.rocketized(), &guard.ty); define_spanned_export!(ty.span() => - __req, __data, _request, display_hack, FromRequest, Outcome + __req, __data, _request, display_hack, FromRequest, Outcome, ErrorResolver, ErrorDefault ); quote_spanned! { ty.span() => @@ -150,11 +150,13 @@ fn request_guard_decl(guard: &Guard) -> TokenStream { target: concat!("rocket::codegen::route::", module_path!()), parameter = stringify!(#ident), type_name = stringify!(#ty), - reason = %#display_hack!(__e), + reason = %#display_hack!(&__e), "request guard failed" ); - return #Outcome::Error(__c); + #[allow(unused)] + use #ErrorDefault; + return #Outcome::Error((__c, #ErrorResolver::new(__e).cast())); } }; } @@ -219,7 +221,7 @@ fn param_guard_decl(guard: &Guard) -> TokenStream { fn data_guard_decl(guard: &Guard) -> TokenStream { let (ident, ty) = (guard.fn_ident.rocketized(), &guard.ty); - define_spanned_export!(ty.span() => __req, __data, display_hack, FromData, Outcome); + define_spanned_export!(ty.span() => __req, __data, display_hack, FromData, Outcome, ErrorResolver, ErrorDefault); quote_spanned! { ty.span() => let #ident: #ty = match <#ty as #FromData>::from_data(#__req, #__data).await { @@ -243,11 +245,13 @@ fn data_guard_decl(guard: &Guard) -> TokenStream { target: concat!("rocket::codegen::route::", module_path!()), parameter = stringify!(#ident), type_name = stringify!(#ty), - reason = %#display_hack!(__e), + reason = %#display_hack!(&__e), "data guard failed" ); - return #Outcome::Error(__c); + #[allow(unused)] + use #ErrorDefault; + return #Outcome::Error((__c, #ErrorResolver::new(__e).cast())); } }; } diff --git a/core/codegen/src/exports.rs b/core/codegen/src/exports.rs index 50470b46..27a0f71f 100644 --- a/core/codegen/src/exports.rs +++ b/core/codegen/src/exports.rs @@ -102,6 +102,9 @@ define_exported_paths! { Route => ::rocket::Route, Catcher => ::rocket::Catcher, Status => ::rocket::http::Status, + ErrorResolver => ::rocket::catcher::resolution::Resolve, + ErrorDefault => ::rocket::catcher::resolution::DefaultTypeErase, + ErasedErrorRef => ::rocket::catcher::ErasedErrorRef, } macro_rules! define_spanned_export { diff --git a/core/lib/Cargo.toml b/core/lib/Cargo.toml index 004115a6..4244b183 100644 --- a/core/lib/Cargo.toml +++ b/core/lib/Cargo.toml @@ -74,6 +74,7 @@ tokio-stream = { version = "0.1.6", features = ["signal", "time"] } cookie = { version = "0.18", features = ["percent-encode"] } futures = { version = "0.3.30", default-features = false, features = ["std"] } state = "0.6" +transient = { version = "0.2.0", path = "../../../transient" } # tracing tracing = { version = "0.1.40", default-features = false, features = ["std", "attributes"] } diff --git a/core/lib/src/catcher/catcher.rs b/core/lib/src/catcher/catcher.rs index 2aa1402a..a2746d89 100644 --- a/core/lib/src/catcher/catcher.rs +++ b/core/lib/src/catcher/catcher.rs @@ -8,6 +8,8 @@ use crate::request::Request; use crate::http::{Status, ContentType, uri}; use crate::catcher::{Handler, BoxFuture}; +use super::ErasedErrorRef; + /// An error catching route. /// /// Catchers are routes that run when errors are produced by the application. @@ -147,20 +149,20 @@ impl Catcher { /// /// ```rust /// use rocket::request::Request; - /// use rocket::catcher::{Catcher, BoxFuture}; + /// use rocket::catcher::{Catcher, BoxFuture, ErasedErrorRef}; /// use rocket::response::Responder; /// use rocket::http::Status; /// - /// fn handle_404<'r>(status: Status, req: &'r Request<'_>) -> BoxFuture<'r> { + /// fn handle_404<'r>(status: Status, req: &'r Request<'_>, _e: &ErasedErrorRef<'r>) -> BoxFuture<'r> { /// let res = (status, format!("404: {}", req.uri())); /// Box::pin(async move { res.respond_to(req) }) /// } /// - /// fn handle_500<'r>(_: Status, req: &'r Request<'_>) -> BoxFuture<'r> { + /// fn handle_500<'r>(_: Status, req: &'r Request<'_>, _e: &ErasedErrorRef<'r>) -> BoxFuture<'r> { /// Box::pin(async move{ "Whoops, we messed up!".respond_to(req) }) /// } /// - /// fn handle_default<'r>(status: Status, req: &'r Request<'_>) -> BoxFuture<'r> { + /// fn handle_default<'r>(status: Status, req: &'r Request<'_>, _e: &ErasedErrorRef<'r>) -> BoxFuture<'r> { /// let res = (status, format!("{}: {}", status, req.uri())); /// Box::pin(async move { res.respond_to(req) }) /// } @@ -199,11 +201,11 @@ impl Catcher { /// /// ```rust /// use rocket::request::Request; - /// use rocket::catcher::{Catcher, BoxFuture}; + /// use rocket::catcher::{Catcher, BoxFuture, ErasedErrorRef}; /// use rocket::response::Responder; /// use rocket::http::Status; /// - /// fn handle_404<'r>(status: Status, req: &'r Request<'_>) -> BoxFuture<'r> { + /// fn handle_404<'r>(status: Status, req: &'r Request<'_>, _e: &ErasedErrorRef<'r>) -> BoxFuture<'r> { /// let res = (status, format!("404: {}", req.uri())); /// Box::pin(async move { res.respond_to(req) }) /// } @@ -225,12 +227,12 @@ impl Catcher { /// /// ```rust /// use rocket::request::Request; - /// use rocket::catcher::{Catcher, BoxFuture}; + /// use rocket::catcher::{Catcher, BoxFuture, ErasedErrorRef}; /// use rocket::response::Responder; /// use rocket::http::Status; /// # use rocket::uri; /// - /// fn handle_404<'r>(status: Status, req: &'r Request<'_>) -> BoxFuture<'r> { + /// fn handle_404<'r>(status: Status, req: &'r Request<'_>, _e: &ErasedErrorRef<'r>) -> BoxFuture<'r> { /// let res = (status, format!("404: {}", req.uri())); /// Box::pin(async move { res.respond_to(req) }) /// } @@ -279,11 +281,11 @@ impl Catcher { /// /// ```rust /// use rocket::request::Request; - /// use rocket::catcher::{Catcher, BoxFuture}; + /// use rocket::catcher::{Catcher, BoxFuture, ErasedErrorRef}; /// use rocket::response::Responder; /// use rocket::http::Status; /// - /// fn handle_404<'r>(status: Status, req: &'r Request<'_>) -> BoxFuture<'r> { + /// fn handle_404<'r>(status: Status, req: &'r Request<'_>, _e: &ErasedErrorRef<'r>) -> BoxFuture<'r> { /// let res = (status, format!("404: {}", req.uri())); /// Box::pin(async move { res.respond_to(req) }) /// } @@ -313,7 +315,7 @@ impl Catcher { impl Default for Catcher { fn default() -> Self { - fn handler<'r>(s: Status, req: &'r Request<'_>) -> BoxFuture<'r> { + fn handler<'r>(s: Status, req: &'r Request<'_>, _e: &ErasedErrorRef<'r>) -> BoxFuture<'r> { Box::pin(async move { Ok(default_handler(s, req)) }) } @@ -331,7 +333,7 @@ pub struct StaticInfo { /// The catcher's status code. pub code: Option, /// The catcher's handler, i.e, the annotated function. - pub handler: for<'r> fn(Status, &'r Request<'_>) -> BoxFuture<'r>, + pub handler: for<'r> fn(Status, &'r Request<'_>, &ErasedErrorRef<'r>) -> BoxFuture<'r>, /// The file, line, and column where the catcher was defined. pub location: (&'static str, u32, u32), } @@ -418,7 +420,7 @@ macro_rules! default_handler_fn { pub(crate) fn default_handler<'r>( status: Status, - req: &'r Request<'_> + req: &'r Request<'_>, ) -> Response<'r> { let preferred = req.accept().map(|a| a.preferred()); let (mime, text) = if preferred.map_or(false, |a| a.is_json()) { diff --git a/core/lib/src/catcher/handler.rs b/core/lib/src/catcher/handler.rs index f33ceba0..c56f8a61 100644 --- a/core/lib/src/catcher/handler.rs +++ b/core/lib/src/catcher/handler.rs @@ -1,6 +1,8 @@ use crate::{Request, Response}; use crate::http::Status; +use super::ErasedErrorRef; + /// Type alias for the return type of a [`Catcher`](crate::Catcher)'s /// [`Handler::handle()`]. pub type Result<'r> = std::result::Result, crate::http::Status>; @@ -29,7 +31,7 @@ pub type BoxFuture<'r, T = Result<'r>> = futures::future::BoxFuture<'r, T>; /// and used as follows: /// /// ```rust,no_run -/// use rocket::{Request, Catcher, catcher}; +/// use rocket::{Request, Catcher, catcher::{self, ErasedErrorRef}}; /// use rocket::response::{Response, Responder}; /// use rocket::http::Status; /// @@ -45,7 +47,7 @@ pub type BoxFuture<'r, T = Result<'r>> = futures::future::BoxFuture<'r, T>; /// /// #[rocket::async_trait] /// impl catcher::Handler for CustomHandler { -/// async fn handle<'r>(&self, status: Status, req: &'r Request<'_>) -> catcher::Result<'r> { +/// async fn handle<'r>(&self, status: Status, req: &'r Request<'_>, _e: &ErasedErrorRef<'r>) -> catcher::Result<'r> { /// let inner = match self.0 { /// Kind::Simple => "simple".respond_to(req)?, /// Kind::Intermediate => "intermediate".respond_to(req)?, @@ -97,30 +99,32 @@ pub trait Handler: Cloneable + Send + Sync + 'static { /// Nevertheless, failure is allowed, both for convenience and necessity. If /// an error handler fails, Rocket's default `500` catcher is invoked. If it /// succeeds, the returned `Response` is used to respond to the client. - async fn handle<'r>(&self, status: Status, req: &'r Request<'_>) -> Result<'r>; + async fn handle<'r>(&self, status: Status, req: &'r Request<'_>, error: &ErasedErrorRef<'r>) -> Result<'r>; } // We write this manually to avoid double-boxing. impl Handler for F - where for<'x> F: Fn(Status, &'x Request<'_>) -> BoxFuture<'x>, + where for<'x> F: Fn(Status, &'x Request<'_>, &ErasedErrorRef<'x>) -> BoxFuture<'x>, { - fn handle<'r, 'life0, 'life1, 'async_trait>( + fn handle<'r, 'life0, 'life1, 'life2, 'async_trait>( &'life0 self, status: Status, req: &'r Request<'life1>, + error: &'life2 ErasedErrorRef<'r>, ) -> BoxFuture<'r> where 'r: 'async_trait, 'life0: 'async_trait, 'life1: 'async_trait, + 'life2: 'async_trait, Self: 'async_trait, { - self(status, req) + self(status, req, error) } } // Used in tests! Do not use, please. #[doc(hidden)] -pub fn dummy_handler<'r>(_: Status, _: &'r Request<'_>) -> BoxFuture<'r> { +pub fn dummy_handler<'r>(_: Status, _: &'r Request<'_>, _: &ErasedErrorRef<'r>) -> BoxFuture<'r> { Box::pin(async move { Ok(Response::new()) }) } diff --git a/core/lib/src/catcher/mod.rs b/core/lib/src/catcher/mod.rs index 4f5fefa1..d9bbb48d 100644 --- a/core/lib/src/catcher/mod.rs +++ b/core/lib/src/catcher/mod.rs @@ -2,6 +2,8 @@ mod catcher; mod handler; +mod types; pub use catcher::*; pub use handler::*; +pub use types::*; diff --git a/core/lib/src/catcher/types.rs b/core/lib/src/catcher/types.rs new file mode 100644 index 00000000..21ee21c5 --- /dev/null +++ b/core/lib/src/catcher/types.rs @@ -0,0 +1,110 @@ +use transient::{Any, CanRecoverFrom, Co, Transient, Downcast}; + +pub type ErasedError<'r> = Box> + Send + Sync + 'r>; +pub type ErasedErrorRef<'r> = dyn Any> + Send + Sync + 'r; + +pub fn default_error_type<'r>() -> ErasedError<'r> { + Box::new(()) +} + +pub fn downcast<'a, 'r, T: Transient + 'r>(v: &'a ErasedErrorRef<'r>) -> Option<&'a T> + where T::Transience: CanRecoverFrom> +{ + v.downcast_ref() +} + +// /// Chosen not to expose this macro, since it's pretty short and sweet +// #[doc(hidden)] +// #[macro_export] +// macro_rules! resolve_typed_catcher { +// ($T:expr) => ({ +// #[allow(unused_imports)] +// use $crate::catcher::types::Resolve; +// +// Resolve::new($T).cast() +// }) +// } + +// pub use resolve_typed_catcher; + +pub mod resolution { + use std::marker::PhantomData; + + use transient::{CanTranscendTo, Transient}; + + use super::*; + + /// The *magic*. + /// + /// `Resolve::item` for `T: Transient` is `::item`. + /// `Resolve::item` for `T: !Transient` is `DefaultTypeErase::item`. + /// + /// This _must_ be used as `Resolve:::item` for resolution to work. This + /// is a fun, static dispatch hack for "specialization" that works because + /// Rust prefers inherent methods over blanket trait impl methods. + pub struct Resolve<'r, T: 'r>(T, PhantomData<&'r ()>); + + impl<'r, T: 'r> Resolve<'r, T> { + pub fn new(val: T) -> Self { + Self(val, PhantomData) + } + } + + /// Fallback trait "implementing" `Transient` for all types. This is what + /// Rust will resolve `Resolve::item` to when `T: !Transient`. + pub trait DefaultTypeErase<'r>: Sized { + const SPECIALIZED: bool = false; + + fn cast(self) -> ErasedError<'r> { Box::new(()) } + } + + impl<'r, T: 'r> DefaultTypeErase<'r> for Resolve<'r, T> {} + + /// "Specialized" "implementation" of `Transient` for `T: Transient`. This is + /// what Rust will resolve `Resolve::item` to when `T: Transient`. + impl<'r, T: Transient + Send + Sync + 'r> Resolve<'r, T> + where T::Transience: CanTranscendTo> + { + pub const SPECIALIZED: bool = true; + + pub fn cast(self) -> ErasedError<'r> { Box::new(self.0) } + } +} + +#[cfg(test)] +mod test { + // use std::any::TypeId; + + use transient::{Transient, TypeId}; + + use super::resolution::{Resolve, DefaultTypeErase}; + + struct NotAny; + #[derive(Transient)] + struct YesAny; + + #[test] + fn check_can_determine() { + let not_any = Resolve::new(NotAny).cast(); + assert_eq!(not_any.type_id(), TypeId::of::<()>()); + + let yes_any = Resolve::new(YesAny).cast(); + assert_ne!(yes_any.type_id(), TypeId::of::<()>()); + } + + // struct HasSentinel(T); + + // #[test] + // fn parent_works() { + // let child = resolve!(YesASentinel, HasSentinel); + // assert!(child.type_name.ends_with("YesASentinel")); + // assert_eq!(child.parent.unwrap(), TypeId::of::>()); + // assert!(child.specialized); + + // let not_a_direct_sentinel = resolve!(HasSentinel); + // assert!(not_a_direct_sentinel.type_name.contains("HasSentinel")); + // assert!(not_a_direct_sentinel.type_name.contains("YesASentinel")); + // assert!(not_a_direct_sentinel.parent.is_none()); + // assert!(!not_a_direct_sentinel.specialized); + // } +} diff --git a/core/lib/src/lifecycle.rs b/core/lib/src/lifecycle.rs index 6f51c959..221cda44 100644 --- a/core/lib/src/lifecycle.rs +++ b/core/lib/src/lifecycle.rs @@ -1,5 +1,6 @@ use futures::future::{FutureExt, Future}; +use crate::catcher::{default_error_type, ErasedError, ErasedErrorRef}; use crate::trace::Trace; use crate::util::Formatter; use crate::data::IoHandler; @@ -108,12 +109,12 @@ impl Rocket { request._set_method(Method::Get); match self.route(request, data).await { Outcome::Success(response) => response, - Outcome::Error(status) => self.dispatch_error(status, request).await, - Outcome::Forward((_, status)) => self.dispatch_error(status, request).await, + Outcome::Error((status, error)) => self.dispatch_error(status, request, error).await, + Outcome::Forward((_, status)) => self.dispatch_error(status, request, default_error_type()).await, } } - Outcome::Forward((_, status)) => self.dispatch_error(status, request).await, - Outcome::Error(status) => self.dispatch_error(status, request).await, + Outcome::Forward((_, status)) => self.dispatch_error(status, request, default_error_type()).await, + Outcome::Error((status, error)) => self.dispatch_error(status, request, error).await, }; // Set the cookies. Note that error responses will only include cookies @@ -204,7 +205,7 @@ impl Rocket { let name = route.name.as_deref(); let outcome = catch_handle(name, || route.handler.handle(request, data)).await - .unwrap_or(Outcome::Error(Status::InternalServerError)); + .unwrap_or(Outcome::error(Status::InternalServerError)); // Check if the request processing completed (Some) or if the // request needs to be forwarded. If it does, continue the loop @@ -229,14 +230,15 @@ impl Rocket { pub(crate) async fn dispatch_error<'r, 's: 'r>( &'s self, mut status: Status, - req: &'r Request<'s> + req: &'r Request<'s>, + error: ErasedError<'r>, ) -> Response<'r> { // We may wish to relax this in the future. req.cookies().reset_delta(); loop { // Dispatch to the `status` catcher. - match self.invoke_catcher(status, req).await { + match self.invoke_catcher(status, error.as_ref(), req).await { Ok(r) => return r, // If the catcher failed, try `500` catcher, unless this is it. Err(e) if status.code != 500 => { @@ -265,11 +267,12 @@ impl Rocket { async fn invoke_catcher<'s, 'r: 's>( &'s self, status: Status, + error: &ErasedErrorRef<'r>, req: &'r Request<'s> ) -> Result, Option> { if let Some(catcher) = self.router.catch(status, req) { catcher.trace_info(); - catch_handle(catcher.name.as_deref(), || catcher.handler.handle(status, req)).await + catch_handle(catcher.name.as_deref(), || catcher.handler.handle(status, req, error)).await .map(|result| result.map_err(Some)) .unwrap_or_else(|| Err(None)) } else { diff --git a/core/lib/src/local/asynchronous/request.rs b/core/lib/src/local/asynchronous/request.rs index 4c85c020..8b0096ef 100644 --- a/core/lib/src/local/asynchronous/request.rs +++ b/core/lib/src/local/asynchronous/request.rs @@ -1,5 +1,6 @@ use std::fmt; +use crate::catcher::default_error_type; use crate::{Request, Data}; use crate::http::{Status, Method}; use crate::http::uri::Origin; @@ -86,7 +87,7 @@ impl<'c> LocalRequest<'c> { if self.inner().uri() == invalid { error!("invalid request URI: {:?}", invalid.path()); return LocalResponse::new(self.request, move |req| { - rocket.dispatch_error(Status::BadRequest, req) + rocket.dispatch_error(Status::BadRequest, req, default_error_type()) }).await } } diff --git a/core/lib/src/outcome.rs b/core/lib/src/outcome.rs index 35521aa3..a5bf5ade 100644 --- a/core/lib/src/outcome.rs +++ b/core/lib/src/outcome.rs @@ -86,6 +86,7 @@ //! a type of `Option`. If an `Outcome` is a `Forward`, the `Option` will be //! `None`. +use crate::catcher::default_error_type; use crate::{route, request, response}; use crate::data::{self, Data, FromData}; use crate::http::Status; @@ -796,7 +797,7 @@ impl<'r, 'o: 'r> IntoOutcome> for response::Result<'o> { fn or_error(self, _: ()) -> route::Outcome<'r> { match self { Ok(val) => Success(val), - Err(status) => Error(status), + Err(status) => Error((status, default_error_type())), } } diff --git a/core/lib/src/route/handler.rs b/core/lib/src/route/handler.rs index b42d81e0..ce266f0f 100644 --- a/core/lib/src/route/handler.rs +++ b/core/lib/src/route/handler.rs @@ -1,10 +1,11 @@ +use crate::catcher::ErasedError; use crate::{Request, Data}; use crate::response::{Response, Responder}; use crate::http::Status; /// Type alias for the return type of a [`Route`](crate::Route)'s /// [`Handler::handle()`]. -pub type Outcome<'r> = crate::outcome::Outcome, Status, (Data<'r>, Status)>; +pub type Outcome<'r> = crate::outcome::Outcome, (Status, ErasedError<'r>), (Data<'r>, Status)>; /// Type alias for the return type of a _raw_ [`Route`](crate::Route)'s /// [`Handler`]. @@ -187,7 +188,7 @@ impl<'r, 'o: 'r> Outcome<'o> { pub fn from>(req: &'r Request<'_>, responder: R) -> Outcome<'r> { match responder.respond_to(req) { Ok(response) => Outcome::Success(response), - Err(status) => Outcome::Error(status) + Err(status) => Outcome::Error((status, Box::new(()))), } } @@ -213,12 +214,12 @@ impl<'r, 'o: 'r> Outcome<'o> { let responder = result.map_err(crate::response::Debug); match responder.respond_to(req) { Ok(response) => Outcome::Success(response), - Err(status) => Outcome::Error(status) + Err(status) => Outcome::Error((status, Box::new(()))), } } /// Return an `Outcome` of `Error` with the status code `code`. This is - /// equivalent to `Outcome::Error(code)`. + /// equivalent to `Outcome::error_val(code, ())`. /// /// This method exists to be used during manual routing. /// @@ -234,7 +235,27 @@ impl<'r, 'o: 'r> Outcome<'o> { /// ``` #[inline(always)] pub fn error(code: Status) -> Outcome<'r> { - Outcome::Error(code) + Outcome::Error((code, Box::new(()))) + } + /// Return an `Outcome` of `Error` with the status code `code`. This adds + /// the + /// + /// This method exists to be used during manual routing. + /// + /// # Example + /// + /// ```rust + /// use rocket::{Request, Data, route}; + /// use rocket::http::Status; + /// + /// fn bad_req_route<'r>(_: &'r Request, _: Data<'r>) -> route::Outcome<'r> { + /// route::Outcome::error_val(Status::BadRequest, "Some data to go with") + /// } + /// ``` + #[inline(always)] + pub fn error_val(code: Status, val: T) -> Outcome<'r> { + use crate::catcher::resolution::{Resolve, DefaultTypeErase}; + Outcome::Error((code, Resolve::new(val).cast())) } /// Return an `Outcome` of `Forward` with the data `data` and status diff --git a/core/lib/src/server.rs b/core/lib/src/server.rs index badfb44c..76ef132b 100644 --- a/core/lib/src/server.rs +++ b/core/lib/src/server.rs @@ -9,6 +9,7 @@ use hyper_util::server::conn::auto::Builder; use futures::{Future, TryFutureExt}; use tokio::io::{AsyncRead, AsyncWrite}; +use crate::catcher::default_error_type; use crate::{Ignite, Orbit, Request, Rocket}; use crate::request::ConnectionMeta; use crate::erased::{ErasedRequest, ErasedResponse, ErasedIoHandler}; @@ -45,7 +46,7 @@ impl Rocket { |rocket, request, data| Box::pin(rocket.preprocess(request, data)), |token, rocket, request, data| Box::pin(async move { if !request.errors.is_empty() { - return rocket.dispatch_error(Status::BadRequest, request).await; + return rocket.dispatch_error(Status::BadRequest, request, default_error_type()).await; } rocket.dispatch(token, request, data).await diff --git a/core/lib/src/trace/traceable.rs b/core/lib/src/trace/traceable.rs index 19b8c8b5..f1685043 100644 --- a/core/lib/src/trace/traceable.rs +++ b/core/lib/src/trace/traceable.rs @@ -244,7 +244,7 @@ impl Trace for route::Outcome<'_> { }, status = match self { Self::Success(r) => r.status().code, - Self::Error(s) => s.code, + Self::Error((s, _)) => s.code, Self::Forward((_, s)) => s.code, }, ) diff --git a/core/lib/tests/panic-handling.rs b/core/lib/tests/panic-handling.rs index f5e8c1ae..f8940ee0 100644 --- a/core/lib/tests/panic-handling.rs +++ b/core/lib/tests/panic-handling.rs @@ -1,5 +1,6 @@ #[macro_use] extern crate rocket; +use rocket::catcher::ErasedErrorRef; use rocket::{Request, Rocket, Route, Catcher, Build, route, catcher}; use rocket::data::Data; use rocket::http::{Method, Status}; @@ -73,7 +74,7 @@ fn catches_early_route_panic() { #[test] fn catches_early_catcher_panic() { - fn pre_future_catcher<'r>(_: Status, _: &'r Request<'_>) -> catcher::BoxFuture<'r> { + fn pre_future_catcher<'r>(_: Status, _: &'r Request<'_>, _: &ErasedErrorRef<'r>) -> catcher::BoxFuture<'r> { panic!("a panicking pre-future catcher") }