From 1f06bb0b734f4a7c34f8903568bbaa8538212b6e Mon Sep 17 00:00:00 2001 From: Benedikt Weber Date: Tue, 11 Apr 2023 11:54:05 -0700 Subject: [PATCH] Allow status customization in 'Forward' outcomes. Prior to this commit, all forward outcomes resulted in a 404. This commit changes request and data guards so that they are able to provide a `Status` on `Forward` outcomes. The router uses this status, if the final outcome is to forward, to identify the catcher to invoke. The net effect is that guards can now customize the status code of a forward and thus the error catcher invoked if the final outcome of a request is to forward. Resolves #1560. --- contrib/ws/src/websocket.rs | 6 +- core/codegen/src/attribute/route/mod.rs | 18 ++--- core/lib/src/cookies.rs | 4 +- core/lib/src/data/data.rs | 3 +- core/lib/src/data/from_data.rs | 12 +-- core/lib/src/form/parser.rs | 4 +- core/lib/src/mtls.rs | 4 +- core/lib/src/request/from_request.rs | 58 ++++++++------ core/lib/src/route/handler.rs | 8 +- core/lib/src/server.rs | 14 ++-- core/lib/src/state.rs | 3 +- core/lib/tests/forward-includes-error-1560.rs | 79 +++++++++++++++++++ .../local-request-content-type-issue-505.rs | 5 +- examples/cookies/src/session.rs | 4 +- examples/manual-routing/src/main.rs | 2 +- site/guide/6-state.md | 3 +- 16 files changed, 159 insertions(+), 68 deletions(-) create mode 100644 core/lib/tests/forward-includes-error-1560.rs diff --git a/contrib/ws/src/websocket.rs b/contrib/ws/src/websocket.rs index 7e2f2c49..6ad295ac 100644 --- a/contrib/ws/src/websocket.rs +++ b/contrib/ws/src/websocket.rs @@ -4,8 +4,8 @@ use std::pin::Pin; use rocket::data::{IoHandler, IoStream}; use rocket::futures::{self, StreamExt, SinkExt, future::BoxFuture, stream::SplitStream}; use rocket::response::{self, Responder, Response}; -use rocket::request::{FromRequest, Outcome}; -use rocket::request::Request; +use rocket::request::{FromRequest, Request, Outcome}; +use rocket::http::Status; use crate::{Config, Message}; use crate::stream::DuplexStream; @@ -203,7 +203,7 @@ impl<'r> FromRequest<'r> for WebSocket { let key = headers.get_one("Sec-WebSocket-Key").map(|k| derive_accept_key(k.as_bytes())); match key { Some(key) if is_upgrade && is_ws && is_13 => Outcome::Success(WebSocket::new(key)), - Some(_) | None => Outcome::Forward(()) + Some(_) | None => Outcome::Forward(Status::NotFound) } } } diff --git a/core/codegen/src/attribute/route/mod.rs b/core/codegen/src/attribute/route/mod.rs index 40797e5d..717469c0 100644 --- a/core/codegen/src/attribute/route/mod.rs +++ b/core/codegen/src/attribute/route/mod.rs @@ -38,7 +38,7 @@ fn query_decls(route: &Route) -> Option { } define_spanned_export!(Span::call_site() => - __req, __data, _log, _form, Outcome, _Ok, _Err, _Some, _None + __req, __data, _log, _form, Outcome, _Ok, _Err, _Some, _None, Status ); // Record all of the static parameters for later filtering. @@ -104,7 +104,7 @@ fn query_decls(route: &Route) -> Option { if !__e.is_empty() { #_log::warn_!("Query string failed to match route declaration."); for _err in __e { #_log::warn_!("{}", _err); } - return #Outcome::Forward(#__data); + return #Outcome::Forward((#__data, #Status::NotFound)); } (#(#ident.unwrap()),*) @@ -121,9 +121,9 @@ fn request_guard_decl(guard: &Guard) -> TokenStream { quote_spanned! { ty.span() => let #ident: #ty = match <#ty as #FromRequest>::from_request(#__req).await { #Outcome::Success(__v) => __v, - #Outcome::Forward(_) => { + #Outcome::Forward(__e) => { #_log::warn_!("Request guard `{}` is forwarding.", stringify!(#ty)); - return #Outcome::Forward(#__data); + return #Outcome::Forward((#__data, __e)); }, #Outcome::Failure((__c, __e)) => { #_log::warn_!("Request guard `{}` failed: {:?}.", stringify!(#ty), __e); @@ -137,7 +137,7 @@ fn param_guard_decl(guard: &Guard) -> TokenStream { let (i, name, ty) = (guard.index, &guard.name, &guard.ty); define_spanned_export!(ty.span() => __req, __data, _log, _None, _Some, _Ok, _Err, - Outcome, FromSegments, FromParam + Outcome, FromSegments, FromParam, Status ); // Returned when a dynamic parameter fails to parse. @@ -145,7 +145,7 @@ fn param_guard_decl(guard: &Guard) -> TokenStream { #_log::warn_!("Parameter guard `{}: {}` is forwarding: {:?}.", #name, stringify!(#ty), __error); - #Outcome::Forward(#__data) + #Outcome::Forward((#__data, #Status::NotFound)) }); // All dynamic parameters should be found if this function is being called; @@ -161,7 +161,7 @@ fn param_guard_decl(guard: &Guard) -> TokenStream { #_log::error_!("Internal invariant broken: dyn param not found."); #_log::error_!("Please report this to the Rocket issue tracker."); #_log::error_!("https://github.com/SergioBenitez/Rocket/issues"); - return #Outcome::Forward(#__data); + return #Outcome::Forward((#__data, #Status::InternalServerError)); } } }, @@ -184,9 +184,9 @@ fn data_guard_decl(guard: &Guard) -> TokenStream { quote_spanned! { ty.span() => let #ident: #ty = match <#ty as #FromData>::from_data(#__req, #__data).await { #Outcome::Success(__d) => __d, - #Outcome::Forward(__d) => { + #Outcome::Forward((__d, __e)) => { #_log::warn_!("Data guard `{}` is forwarding.", stringify!(#ty)); - return #Outcome::Forward(__d); + return #Outcome::Forward((__d, __e)); } #Outcome::Failure((__c, __e)) => { #_log::warn_!("Data guard `{}` failed: {:?}.", stringify!(#ty), __e); diff --git a/core/lib/src/cookies.rs b/core/lib/src/cookies.rs index 032d4eae..d52a31f5 100644 --- a/core/lib/src/cookies.rs +++ b/core/lib/src/cookies.rs @@ -101,8 +101,8 @@ pub use self::cookie::{Cookie, SameSite, Iter}; /// # #[macro_use] extern crate rocket; /// # #[cfg(feature = "secrets")] { /// use rocket::http::Status; -/// use rocket::outcome::IntoOutcome; /// use rocket::request::{self, Request, FromRequest}; +/// use rocket::outcome::IntoOutcome; /// /// // In practice, we'd probably fetch the user from the database. /// struct User(usize); @@ -116,7 +116,7 @@ pub use self::cookie::{Cookie, SameSite, Iter}; /// .get_private("user_id") /// .and_then(|c| c.value().parse().ok()) /// .map(|id| User(id)) -/// .or_forward(()) +/// .or_forward(Status::Unauthorized) /// } /// } /// # } diff --git a/core/lib/src/data/data.rs b/core/lib/src/data/data.rs index cb3d5c5d..3f393c55 100644 --- a/core/lib/src/data/data.rs +++ b/core/lib/src/data/data.rs @@ -102,6 +102,7 @@ impl<'r> Data<'r> { /// ```rust /// use rocket::request::{self, Request, FromRequest}; /// use rocket::data::{Data, FromData, Outcome}; + /// use rocket::http::Status; /// # struct MyType; /// # type MyError = String; /// @@ -111,7 +112,7 @@ impl<'r> Data<'r> { /// /// async fn from_data(r: &'r Request<'_>, mut data: Data<'r>) -> Outcome<'r, Self> { /// if data.peek(2).await != b"hi" { - /// return Outcome::Forward(data) + /// return Outcome::Forward((data, Status::NotFound)) /// } /// /// /* .. */ diff --git a/core/lib/src/data/from_data.rs b/core/lib/src/data/from_data.rs index 8f5fe90f..ac724609 100644 --- a/core/lib/src/data/from_data.rs +++ b/core/lib/src/data/from_data.rs @@ -7,11 +7,11 @@ use crate::outcome::{self, IntoOutcome, try_outcome, Outcome::*}; /// /// [`FromData`]: crate::data::FromData pub type Outcome<'r, T, E = >::Error> - = outcome::Outcome>; + = outcome::Outcome, Status)>; -impl<'r, S, E> IntoOutcome> for Result { +impl<'r, S, E> IntoOutcome, Status)> for Result { type Failure = Status; - type Forward = Data<'r>; + type Forward = (Data<'r>, Status); #[inline] fn into_outcome(self, status: Status) -> Outcome<'r, S, E> { @@ -22,10 +22,10 @@ impl<'r, S, E> IntoOutcome> for Result { } #[inline] - fn or_forward(self, data: Data<'r>) -> Outcome<'r, S, E> { + fn or_forward(self, (data, error): (Data<'r>, Status)) -> Outcome<'r, S, E> { match self { Ok(val) => Success(val), - Err(_) => Forward(data) + Err(_) => Forward((data, error)) } } } @@ -130,7 +130,7 @@ impl<'r, S, E> IntoOutcome> for Result { /// // Ensure the content type is correct before opening the data. /// let person_ct = ContentType::new("application", "x-person"); /// if req.content_type() != Some(&person_ct) { -/// return Forward(data); +/// return Forward((data, Status::NotFound)); /// } /// /// // Use a configured limit with name 'person' or fallback to default. diff --git a/core/lib/src/form/parser.rs b/core/lib/src/form/parser.rs index 8c670dad..89c42eb8 100644 --- a/core/lib/src/form/parser.rs +++ b/core/lib/src/form/parser.rs @@ -4,7 +4,7 @@ use either::Either; use crate::request::{Request, local_cache_once}; use crate::data::{Data, Limits, Outcome}; use crate::form::{SharedStack, prelude::*}; -use crate::http::RawStr; +use crate::http::{RawStr, Status}; type Result<'r, T> = std::result::Result>; @@ -35,7 +35,7 @@ impl<'r, 'i> Parser<'r, 'i> { let parser = match req.content_type() { Some(c) if c.is_form() => Self::from_form(req, data).await, Some(c) if c.is_form_data() => Self::from_multipart(req, data).await, - _ => return Outcome::Forward(data), + _ => return Outcome::Forward((data, Status::NotFound)), }; match parser { diff --git a/core/lib/src/mtls.rs b/core/lib/src/mtls.rs index aabdab4a..07367bcf 100644 --- a/core/lib/src/mtls.rs +++ b/core/lib/src/mtls.rs @@ -18,8 +18,8 @@ impl<'r> FromRequest<'r> for Certificate<'r> { type Error = Error; async fn from_request(req: &'r Request<'_>) -> Outcome { - let certs = try_outcome!(req.connection.client_certificates.as_ref().or_forward(())); - let data = try_outcome!(certs.chain_data().or_forward(())); + let certs = req.connection.client_certificates.as_ref().or_forward(Status::Unauthorized); + let data = try_outcome!(try_outcome!(certs).chain_data().or_forward(Status::Unauthorized)); Certificate::parse(data).into_outcome(Status::Unauthorized) } } diff --git a/core/lib/src/request/from_request.rs b/core/lib/src/request/from_request.rs index c164303c..e50a4742 100644 --- a/core/lib/src/request/from_request.rs +++ b/core/lib/src/request/from_request.rs @@ -9,11 +9,11 @@ use crate::http::{Status, ContentType, Accept, Method, CookieJar}; use crate::http::uri::{Host, Origin}; /// Type alias for the `Outcome` of a `FromRequest` conversion. -pub type Outcome = outcome::Outcome; +pub type Outcome = outcome::Outcome; -impl IntoOutcome for Result { +impl IntoOutcome for Result { type Failure = Status; - type Forward = (); + type Forward = Status; #[inline] fn into_outcome(self, status: Status) -> Outcome { @@ -24,10 +24,10 @@ impl IntoOutcome for Result { } #[inline] - fn or_forward(self, _: ()) -> Outcome { + fn or_forward(self, status: Status) -> Outcome { match self { Ok(val) => Success(val), - Err(_) => Forward(()) + Err(_) => Forward(status) } } } @@ -102,16 +102,18 @@ impl IntoOutcome for Result { /// * **Failure**(Status, E) /// /// If the `Outcome` is [`Failure`], the request will fail with the given -/// status code and error. The designated error [`Catcher`](crate::Catcher) will be -/// used to respond to the request. Note that users can request types of -/// `Result` and `Option` to catch `Failure`s and retrieve the error -/// value. +/// status code and error. The designated error [`Catcher`](crate::Catcher) +/// will be used to respond to the request. Note that users can request types +/// of `Result` and `Option` to catch `Failure`s and retrieve the +/// error value. /// -/// * **Forward** +/// * **Forward**(Status) /// /// If the `Outcome` is [`Forward`], the request will be forwarded to the next -/// matching route. Note that users can request an `Option` to catch -/// `Forward`s. +/// matching route until either one succeds or there are no further matching +/// routes to attempt. In the latter case, the request will be sent to the +/// [`Catcher`](crate::Catcher) for the designated `Status`. Note that users +/// can request an `Option` to catch `Forward`s. /// /// # Provided Implementations /// @@ -137,10 +139,12 @@ impl IntoOutcome for Result { /// /// * **&Route** /// -/// Extracts the [`Route`] from the request if one is available. If a route -/// is not available, the request is forwarded. +/// Extracts the [`Route`] from the request if one is available. When used +/// as a request guard in a route handler, this will always succeed. Outside +/// of a route handler, a route may not be available, and the request is +/// forwarded with a 500 status. /// -/// For information on when an `&Route` is available, see +/// For more information on when an `&Route` is available, see /// [`Request::route()`]. /// /// * **&CookieJar** @@ -256,6 +260,7 @@ impl IntoOutcome for Result { /// # #[cfg(feature = "secrets")] mod wrapper { /// # use rocket::outcome::{IntoOutcome, try_outcome}; /// # use rocket::request::{self, Outcome, FromRequest, Request}; +/// # use rocket::http::Status; /// # struct User { id: String, is_admin: bool } /// # struct Database; /// # impl Database { @@ -283,7 +288,7 @@ impl IntoOutcome for Result { /// .get_private("user_id") /// .and_then(|cookie| cookie.value().parse().ok()) /// .and_then(|id| db.get_user(id).ok()) -/// .or_forward(()) +/// .or_forward(Status::Unauthorized) /// } /// } /// @@ -297,7 +302,7 @@ impl IntoOutcome for Result { /// if user.is_admin { /// Outcome::Success(Admin { user }) /// } else { -/// Outcome::Forward(()) +/// Outcome::Forward(Status::Unauthorized) /// } /// } /// } @@ -320,6 +325,7 @@ impl IntoOutcome for Result { /// # #[cfg(feature = "secrets")] mod wrapper { /// # use rocket::outcome::{IntoOutcome, try_outcome}; /// # use rocket::request::{self, Outcome, FromRequest, Request}; +/// # use rocket::http::Status; /// # struct User { id: String, is_admin: bool } /// # struct Database; /// # impl Database { @@ -352,7 +358,7 @@ impl IntoOutcome for Result { /// .and_then(|id| db.get_user(id).ok()) /// }).await; /// -/// user_result.as_ref().or_forward(()) +/// user_result.as_ref().or_forward(Status::Unauthorized) /// } /// } /// @@ -365,7 +371,7 @@ impl IntoOutcome for Result { /// if user.is_admin { /// Outcome::Success(Admin { user }) /// } else { -/// Outcome::Forward(()) +/// Outcome::Forward(Status::Unauthorized) /// } /// } /// } @@ -415,7 +421,7 @@ impl<'r> FromRequest<'r> for &'r Host<'r> { async fn from_request(request: &'r Request<'_>) -> Outcome { match request.host() { Some(host) => Success(host), - None => Forward(()) + None => Forward(Status::NotFound) } } } @@ -427,7 +433,7 @@ impl<'r> FromRequest<'r> for &'r Route { async fn from_request(request: &'r Request<'_>) -> Outcome { match request.route() { Some(route) => Success(route), - None => Forward(()) + None => Forward(Status::InternalServerError) } } } @@ -448,7 +454,7 @@ impl<'r> FromRequest<'r> for &'r Accept { async fn from_request(request: &'r Request<'_>) -> Outcome { match request.accept() { Some(accept) => Success(accept), - None => Forward(()) + None => Forward(Status::NotFound) } } } @@ -460,7 +466,7 @@ impl<'r> FromRequest<'r> for &'r ContentType { async fn from_request(request: &'r Request<'_>) -> Outcome { match request.content_type() { Some(content_type) => Success(content_type), - None => Forward(()) + None => Forward(Status::NotFound) } } } @@ -472,7 +478,7 @@ impl<'r> FromRequest<'r> for IpAddr { async fn from_request(request: &'r Request<'_>) -> Outcome { match request.client_ip() { Some(addr) => Success(addr), - None => Forward(()) + None => Forward(Status::NotFound) } } } @@ -484,7 +490,7 @@ impl<'r> FromRequest<'r> for SocketAddr { async fn from_request(request: &'r Request<'_>) -> Outcome { match request.remote() { Some(addr) => Success(addr), - None => Forward(()) + None => Forward(Status::NotFound) } } } @@ -497,7 +503,7 @@ impl<'r, T: FromRequest<'r>> FromRequest<'r> for Result { match T::from_request(request).await { Success(val) => Success(Ok(val)), Failure((_, e)) => Success(Err(e)), - Forward(_) => Forward(()), + Forward(_) => Forward(Status::NotFound), } } } diff --git a/core/lib/src/route/handler.rs b/core/lib/src/route/handler.rs index 5e4e62ff..739639d0 100644 --- a/core/lib/src/route/handler.rs +++ b/core/lib/src/route/handler.rs @@ -4,7 +4,7 @@ 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>>; +pub type Outcome<'r> = crate::outcome::Outcome, Status, (Data<'r>, Status)>; /// Type alias for the return type of a _raw_ [`Route`](crate::Route)'s /// [`Handler`]. @@ -239,7 +239,7 @@ impl<'r, 'o: 'r> Outcome<'o> { { match responder.respond_to(req) { Ok(response) => Outcome::Success(response), - Err(_) => Outcome::Forward(data) + Err(_) => Outcome::Forward((data, Status::NotFound)) } } @@ -264,7 +264,7 @@ impl<'r, 'o: 'r> Outcome<'o> { } /// Return an `Outcome` of `Forward` with the data `data`. This is - /// equivalent to `Outcome::Forward(data)`. + /// equivalent to `Outcome::Forward((data, Status::NotFound))`. /// /// This method exists to be used during manual routing. /// @@ -279,7 +279,7 @@ impl<'r, 'o: 'r> Outcome<'o> { /// ``` #[inline(always)] pub fn forward(data: Data<'r>) -> Outcome<'r> { - Outcome::Forward(data) + Outcome::Forward((data, Status::NotFound)) } } diff --git a/core/lib/src/server.rs b/core/lib/src/server.rs index faae6ddc..b0011135 100644 --- a/core/lib/src/server.rs +++ b/core/lib/src/server.rs @@ -283,7 +283,7 @@ impl Rocket { ) -> Response<'r> { let mut response = match self.route(request, data).await { Outcome::Success(response) => response, - Outcome::Forward(data) if request.method() == Method::Head => { + Outcome::Forward((data, _)) if request.method() == Method::Head => { info_!("Autohandling {} request.", Paint::default("HEAD").bold()); // Dispatch the request again with Method `GET`. @@ -291,10 +291,10 @@ impl Rocket { match self.route(request, data).await { Outcome::Success(response) => response, Outcome::Failure(status) => self.handle_error(status, request).await, - Outcome::Forward(_) => self.handle_error(Status::NotFound, request).await, + Outcome::Forward((_, status)) => self.handle_error(status, request).await, } } - Outcome::Forward(_) => self.handle_error(Status::NotFound, request).await, + Outcome::Forward((_, status)) => self.handle_error(status, request).await, Outcome::Failure(status) => self.handle_error(status, request).await, }; @@ -319,7 +319,9 @@ impl Rocket { request: &'r Request<'s>, mut data: Data<'r>, ) -> route::Outcome<'r> { - // Go through the list of matching routes until we fail or succeed. + // Go through all matching routes until we fail or succeed or run out of + // routes to try, in which case we forward with the last status. + let mut status = Status::NotFound; for route in self.router.route(request) { // Retrieve and set the requests parameters. info_!("Matched: {}", route); @@ -335,12 +337,12 @@ impl Rocket { info_!("{} {}", Paint::default("Outcome:").bold(), outcome); match outcome { o@Outcome::Success(_) | o@Outcome::Failure(_) => return o, - Outcome::Forward(unused_data) => data = unused_data, + Outcome::Forward(forwarded) => (data, status) = forwarded, } } error_!("No matching routes for {}.", request); - Outcome::Forward(data) + Outcome::Forward((data, status)) } /// Invokes the handler with `req` for catcher with status `status`. diff --git a/core/lib/src/state.rs b/core/lib/src/state.rs index 17f4b238..b323f77d 100644 --- a/core/lib/src/state.rs +++ b/core/lib/src/state.rs @@ -63,6 +63,7 @@ use crate::http::Status; /// use rocket::State; /// use rocket::request::{self, Request, FromRequest}; /// use rocket::outcome::IntoOutcome; +/// use rocket::http::Status; /// /// # struct MyConfig { user_val: String }; /// struct Item<'r>(&'r str); @@ -79,7 +80,7 @@ use crate::http::Status; /// // Or alternatively, using `Rocket::state()`: /// let outcome = request.rocket().state::() /// .map(|my_config| Item(&my_config.user_val)) -/// .or_forward(()); +/// .or_forward(Status::NotFound); /// /// outcome /// } diff --git a/core/lib/tests/forward-includes-error-1560.rs b/core/lib/tests/forward-includes-error-1560.rs new file mode 100644 index 00000000..73cb145b --- /dev/null +++ b/core/lib/tests/forward-includes-error-1560.rs @@ -0,0 +1,79 @@ +#[macro_use] extern crate rocket; + +use rocket::http::Status; +use rocket::request::{self, Request, FromRequest}; + +pub struct Authenticated; + +#[rocket::async_trait] +impl<'r> FromRequest<'r> for Authenticated { + type Error = std::convert::Infallible; + + async fn from_request(request: &'r Request<'_>) -> request::Outcome { + if request.headers().contains("Authenticated") { + request::Outcome::Success(Authenticated) + } else { + request::Outcome::Forward(Status::Unauthorized) + } + } +} + +#[get("/one")] +pub async fn get_protected_one(_user: Authenticated) -> &'static str { + "Protected" +} + +#[get("/one", rank = 2)] +pub async fn get_public_one() -> &'static str { + "Public" +} + +#[get("/two")] +pub async fn get_protected_two(_user: Authenticated) -> &'static str { + "Protected" +} + +mod tests { + use super::*; + use rocket::routes; + use rocket::local::blocking::Client; + use rocket::http::{Header, Status}; + + #[test] + fn one_protected_returned_for_authenticated() { + let rocket = rocket::build().mount("/", + routes![get_protected_one, get_public_one, get_protected_two]); + + let client = Client::debug(rocket).unwrap(); + let req = client.get("/one").header(Header::new("Authenticated", "true")); + let response = req.dispatch(); + + assert_eq!(response.status(), Status::Ok); + assert_eq!(response.into_string(), Some("Protected".into())); + } + + #[test] + fn one_public_returned_for_unauthenticated() { + let rocket = rocket::build().mount("/", + routes![get_protected_one, get_public_one, get_protected_two]); + + let client = Client::debug(rocket).unwrap(); + let req = client.get("/one"); + let response = req.dispatch(); + + assert_eq!(response.status(), Status::Ok); + assert_eq!(response.into_string(), Some("Public".into())); + } + + #[test] + fn two_unauthorized_returned_for_unauthenticated() { + let rocket = rocket::build().mount("/", + routes![get_protected_one, get_public_one, get_protected_two]); + + let client = Client::debug(rocket).unwrap(); + let req = client.get("/two"); + let response = req.dispatch(); + + assert_eq!(response.status(), Status::Unauthorized); + } +} diff --git a/core/lib/tests/local-request-content-type-issue-505.rs b/core/lib/tests/local-request-content-type-issue-505.rs index b95f8985..d5042803 100644 --- a/core/lib/tests/local-request-content-type-issue-505.rs +++ b/core/lib/tests/local-request-content-type-issue-505.rs @@ -3,6 +3,7 @@ use rocket::{Request, Data}; use rocket::request::{self, FromRequest}; use rocket::outcome::IntoOutcome; +use rocket::http::Status; struct HasContentType; @@ -11,7 +12,7 @@ impl<'r> FromRequest<'r> for HasContentType { type Error = (); async fn from_request(req: &'r Request<'_>) -> request::Outcome { - req.content_type().map(|_| HasContentType).or_forward(()) + req.content_type().map(|_| HasContentType).or_forward(Status::NotFound) } } @@ -22,7 +23,7 @@ impl<'r> FromData<'r> for HasContentType { type Error = (); async fn from_data(req: &'r Request<'_>, data: Data<'r>) -> data::Outcome<'r, Self> { - req.content_type().map(|_| HasContentType).or_forward(data) + req.content_type().map(|_| HasContentType).or_forward((data, Status::NotFound)) } } diff --git a/examples/cookies/src/session.rs b/examples/cookies/src/session.rs index 04ebeffe..de74831b 100644 --- a/examples/cookies/src/session.rs +++ b/examples/cookies/src/session.rs @@ -1,7 +1,7 @@ use rocket::outcome::IntoOutcome; use rocket::request::{self, FlashMessage, FromRequest, Request}; use rocket::response::{Redirect, Flash}; -use rocket::http::{Cookie, CookieJar}; +use rocket::http::{Cookie, CookieJar, Status}; use rocket::form::Form; use rocket_dyn_templates::{Template, context}; @@ -24,7 +24,7 @@ impl<'r> FromRequest<'r> for User { .get_private("user_id") .and_then(|cookie| cookie.value().parse().ok()) .map(User) - .or_forward(()) + .or_forward(Status::NotFound) } } diff --git a/examples/manual-routing/src/main.rs b/examples/manual-routing/src/main.rs index 69e03a1b..704b69c7 100644 --- a/examples/manual-routing/src/main.rs +++ b/examples/manual-routing/src/main.rs @@ -84,7 +84,7 @@ impl route::Handler for CustomHandler { let self_data = self.data; let id = req.param::<&str>(0) .and_then(Result::ok) - .or_forward(data); + .or_forward((data, Status::NotFound)); route::Outcome::from(req, format!("{} - {}", self_data, try_outcome!(id))) } diff --git a/site/guide/6-state.md b/site/guide/6-state.md index e8d568c2..e884788a 100644 --- a/site/guide/6-state.md +++ b/site/guide/6-state.md @@ -124,6 +124,7 @@ retrieves `MyConfig` from managed state using both methods: use rocket::State; use rocket::request::{self, Request, FromRequest}; use rocket::outcome::IntoOutcome; +use rocket::http::Status; # struct MyConfig { user_val: String }; struct Item<'r>(&'r str); @@ -140,7 +141,7 @@ impl<'r> FromRequest<'r> for Item<'r> { // Or alternatively, using `Rocket::state()`: let outcome = request.rocket().state::() .map(|my_config| Item(&my_config.user_val)) - .or_forward(()); + .or_forward(Status::NotFound); outcome }