mirror of https://github.com/rwf2/Rocket.git
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.
This commit is contained in:
parent
b61ac6eb18
commit
055ad107df
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -38,7 +38,7 @@ fn query_decls(route: &Route) -> Option<TokenStream> {
|
|||
}
|
||||
|
||||
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<TokenStream> {
|
|||
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.", #i);
|
||||
#_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);
|
||||
|
|
|
@ -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)
|
||||
/// }
|
||||
/// }
|
||||
/// # }
|
||||
|
|
|
@ -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))
|
||||
/// }
|
||||
///
|
||||
/// /* .. */
|
||||
|
|
|
@ -7,11 +7,11 @@ use crate::outcome::{self, IntoOutcome, try_outcome, Outcome::*};
|
|||
///
|
||||
/// [`FromData`]: crate::data::FromData
|
||||
pub type Outcome<'r, T, E = <T as FromData<'r>>::Error>
|
||||
= outcome::Outcome<T, (Status, E), Data<'r>>;
|
||||
= outcome::Outcome<T, (Status, E), (Data<'r>, Status)>;
|
||||
|
||||
impl<'r, S, E> IntoOutcome<S, (Status, E), Data<'r>> for Result<S, E> {
|
||||
impl<'r, S, E> IntoOutcome<S, (Status, E), (Data<'r>, Status)> for Result<S, E> {
|
||||
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<S, (Status, E), Data<'r>> for Result<S, E> {
|
|||
}
|
||||
|
||||
#[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<S, (Status, E), Data<'r>> for Result<S, E> {
|
|||
/// // 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.
|
||||
|
|
|
@ -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<T, Error<'r>>;
|
||||
|
||||
|
@ -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 {
|
||||
|
|
|
@ -18,8 +18,8 @@ impl<'r> FromRequest<'r> for Certificate<'r> {
|
|||
type Error = Error;
|
||||
|
||||
async fn from_request(req: &'r Request<'_>) -> Outcome<Self, Self::Error> {
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<S, E> = outcome::Outcome<S, (Status, E), ()>;
|
||||
pub type Outcome<S, E> = outcome::Outcome<S, (Status, E), Status>;
|
||||
|
||||
impl<S, E> IntoOutcome<S, (Status, E), ()> for Result<S, E> {
|
||||
impl<S, E> IntoOutcome<S, (Status, E), Status> for Result<S, E> {
|
||||
type Failure = Status;
|
||||
type Forward = ();
|
||||
type Forward = Status;
|
||||
|
||||
#[inline]
|
||||
fn into_outcome(self, status: Status) -> Outcome<S, E> {
|
||||
|
@ -24,10 +24,10 @@ impl<S, E> IntoOutcome<S, (Status, E), ()> for Result<S, E> {
|
|||
}
|
||||
|
||||
#[inline]
|
||||
fn or_forward(self, _: ()) -> Outcome<S, E> {
|
||||
fn or_forward(self, status: Status) -> Outcome<S, E> {
|
||||
match self {
|
||||
Ok(val) => Success(val),
|
||||
Err(_) => Forward(())
|
||||
Err(_) => Forward(status)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -102,16 +102,18 @@ impl<S, E> IntoOutcome<S, (Status, E), ()> for Result<S, E> {
|
|||
/// * **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<S, E>` and `Option<S>` 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<S, E>` and `Option<S>` 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<S>` 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<S>` to catch `Forward`s.
|
||||
///
|
||||
/// # Provided Implementations
|
||||
///
|
||||
|
@ -137,10 +139,12 @@ impl<S, E> IntoOutcome<S, (Status, E), ()> for Result<S, E> {
|
|||
///
|
||||
/// * **&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<S, E> IntoOutcome<S, (Status, E), ()> for Result<S, E> {
|
|||
/// # #[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<S, E> IntoOutcome<S, (Status, E), ()> for Result<S, E> {
|
|||
/// .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<S, E> IntoOutcome<S, (Status, E), ()> for Result<S, E> {
|
|||
/// if user.is_admin {
|
||||
/// Outcome::Success(Admin { user })
|
||||
/// } else {
|
||||
/// Outcome::Forward(())
|
||||
/// Outcome::Forward(Status::Unauthorized)
|
||||
/// }
|
||||
/// }
|
||||
/// }
|
||||
|
@ -320,6 +325,7 @@ impl<S, E> IntoOutcome<S, (Status, E), ()> for Result<S, E> {
|
|||
/// # #[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<S, E> IntoOutcome<S, (Status, E), ()> for Result<S, E> {
|
|||
/// .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<S, E> IntoOutcome<S, (Status, E), ()> for Result<S, E> {
|
|||
/// 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<Self, Self::Error> {
|
||||
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<Self, Self::Error> {
|
||||
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<Self, Self::Error> {
|
||||
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<Self, Self::Error> {
|
||||
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<Self, Self::Error> {
|
||||
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<Self, Self::Error> {
|
||||
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<T, T::Error> {
|
|||
match T::from_request(request).await {
|
||||
Success(val) => Success(Ok(val)),
|
||||
Failure((_, e)) => Success(Err(e)),
|
||||
Forward(_) => Forward(()),
|
||||
Forward(_) => Forward(Status::NotFound),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<Response<'r>, Status, Data<'r>>;
|
||||
pub type Outcome<'r> = crate::outcome::Outcome<Response<'r>, 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))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -283,7 +283,7 @@ impl Rocket<Orbit> {
|
|||
) -> 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<Orbit> {
|
|||
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<Orbit> {
|
|||
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<Orbit> {
|
|||
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`.
|
||||
|
|
|
@ -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::<MyConfig>()
|
||||
/// .map(|my_config| Item(&my_config.user_val))
|
||||
/// .or_forward(());
|
||||
/// .or_forward(Status::NotFound);
|
||||
///
|
||||
/// outcome
|
||||
/// }
|
||||
|
|
|
@ -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<Self, Self::Error> {
|
||||
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);
|
||||
}
|
||||
}
|
|
@ -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<Self, ()> {
|
||||
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))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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)))
|
||||
}
|
||||
|
|
|
@ -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::<MyConfig>()
|
||||
.map(|my_config| Item(&my_config.user_val))
|
||||
.or_forward(());
|
||||
.or_forward(Status::NotFound);
|
||||
|
||||
outcome
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue