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::data::{IoHandler, IoStream};
|
||||||
use rocket::futures::{self, StreamExt, SinkExt, future::BoxFuture, stream::SplitStream};
|
use rocket::futures::{self, StreamExt, SinkExt, future::BoxFuture, stream::SplitStream};
|
||||||
use rocket::response::{self, Responder, Response};
|
use rocket::response::{self, Responder, Response};
|
||||||
use rocket::request::{FromRequest, Outcome};
|
use rocket::request::{FromRequest, Request, Outcome};
|
||||||
use rocket::request::Request;
|
use rocket::http::Status;
|
||||||
|
|
||||||
use crate::{Config, Message};
|
use crate::{Config, Message};
|
||||||
use crate::stream::DuplexStream;
|
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()));
|
let key = headers.get_one("Sec-WebSocket-Key").map(|k| derive_accept_key(k.as_bytes()));
|
||||||
match key {
|
match key {
|
||||||
Some(key) if is_upgrade && is_ws && is_13 => Outcome::Success(WebSocket::new(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() =>
|
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.
|
// Record all of the static parameters for later filtering.
|
||||||
|
@ -104,7 +104,7 @@ fn query_decls(route: &Route) -> Option<TokenStream> {
|
||||||
if !__e.is_empty() {
|
if !__e.is_empty() {
|
||||||
#_log::warn_!("Query string failed to match route declaration.");
|
#_log::warn_!("Query string failed to match route declaration.");
|
||||||
for _err in __e { #_log::warn_!("{}", _err); }
|
for _err in __e { #_log::warn_!("{}", _err); }
|
||||||
return #Outcome::Forward(#__data);
|
return #Outcome::Forward((#__data, #Status::NotFound));
|
||||||
}
|
}
|
||||||
|
|
||||||
(#(#ident.unwrap()),*)
|
(#(#ident.unwrap()),*)
|
||||||
|
@ -121,9 +121,9 @@ fn request_guard_decl(guard: &Guard) -> TokenStream {
|
||||||
quote_spanned! { ty.span() =>
|
quote_spanned! { ty.span() =>
|
||||||
let #ident: #ty = match <#ty as #FromRequest>::from_request(#__req).await {
|
let #ident: #ty = match <#ty as #FromRequest>::from_request(#__req).await {
|
||||||
#Outcome::Success(__v) => __v,
|
#Outcome::Success(__v) => __v,
|
||||||
#Outcome::Forward(_) => {
|
#Outcome::Forward(__e) => {
|
||||||
#_log::warn_!("Request guard `{}` is forwarding.", stringify!(#ty));
|
#_log::warn_!("Request guard `{}` is forwarding.", stringify!(#ty));
|
||||||
return #Outcome::Forward(#__data);
|
return #Outcome::Forward((#__data, __e));
|
||||||
},
|
},
|
||||||
#Outcome::Failure((__c, __e)) => {
|
#Outcome::Failure((__c, __e)) => {
|
||||||
#_log::warn_!("Request guard `{}` failed: {:?}.", stringify!(#ty), __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);
|
let (i, name, ty) = (guard.index, &guard.name, &guard.ty);
|
||||||
define_spanned_export!(ty.span() =>
|
define_spanned_export!(ty.span() =>
|
||||||
__req, __data, _log, _None, _Some, _Ok, _Err,
|
__req, __data, _log, _None, _Some, _Ok, _Err,
|
||||||
Outcome, FromSegments, FromParam
|
Outcome, FromSegments, FromParam, Status
|
||||||
);
|
);
|
||||||
|
|
||||||
// Returned when a dynamic parameter fails to parse.
|
// 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: {:?}.",
|
#_log::warn_!("Parameter guard `{}: {}` is forwarding: {:?}.",
|
||||||
#name, stringify!(#ty), __error);
|
#name, stringify!(#ty), __error);
|
||||||
|
|
||||||
#Outcome::Forward(#__data)
|
#Outcome::Forward((#__data, #Status::NotFound))
|
||||||
});
|
});
|
||||||
|
|
||||||
// All dynamic parameters should be found if this function is being called;
|
// 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_!("Internal invariant broken: dyn param {} not found.", #i);
|
||||||
#_log::error_!("Please report this to the Rocket issue tracker.");
|
#_log::error_!("Please report this to the Rocket issue tracker.");
|
||||||
#_log::error_!("https://github.com/SergioBenitez/Rocket/issues");
|
#_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() =>
|
quote_spanned! { ty.span() =>
|
||||||
let #ident: #ty = match <#ty as #FromData>::from_data(#__req, #__data).await {
|
let #ident: #ty = match <#ty as #FromData>::from_data(#__req, #__data).await {
|
||||||
#Outcome::Success(__d) => __d,
|
#Outcome::Success(__d) => __d,
|
||||||
#Outcome::Forward(__d) => {
|
#Outcome::Forward((__d, __e)) => {
|
||||||
#_log::warn_!("Data guard `{}` is forwarding.", stringify!(#ty));
|
#_log::warn_!("Data guard `{}` is forwarding.", stringify!(#ty));
|
||||||
return #Outcome::Forward(__d);
|
return #Outcome::Forward((__d, __e));
|
||||||
}
|
}
|
||||||
#Outcome::Failure((__c, __e)) => {
|
#Outcome::Failure((__c, __e)) => {
|
||||||
#_log::warn_!("Data guard `{}` failed: {:?}.", stringify!(#ty), __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;
|
/// # #[macro_use] extern crate rocket;
|
||||||
/// # #[cfg(feature = "secrets")] {
|
/// # #[cfg(feature = "secrets")] {
|
||||||
/// use rocket::http::Status;
|
/// use rocket::http::Status;
|
||||||
/// use rocket::outcome::IntoOutcome;
|
|
||||||
/// use rocket::request::{self, Request, FromRequest};
|
/// use rocket::request::{self, Request, FromRequest};
|
||||||
|
/// use rocket::outcome::IntoOutcome;
|
||||||
///
|
///
|
||||||
/// // In practice, we'd probably fetch the user from the database.
|
/// // In practice, we'd probably fetch the user from the database.
|
||||||
/// struct User(usize);
|
/// struct User(usize);
|
||||||
|
@ -116,7 +116,7 @@ pub use self::cookie::{Cookie, SameSite, Iter};
|
||||||
/// .get_private("user_id")
|
/// .get_private("user_id")
|
||||||
/// .and_then(|c| c.value().parse().ok())
|
/// .and_then(|c| c.value().parse().ok())
|
||||||
/// .map(|id| User(id))
|
/// .map(|id| User(id))
|
||||||
/// .or_forward(())
|
/// .or_forward(Status::Unauthorized)
|
||||||
/// }
|
/// }
|
||||||
/// }
|
/// }
|
||||||
/// # }
|
/// # }
|
||||||
|
|
|
@ -102,6 +102,7 @@ impl<'r> Data<'r> {
|
||||||
/// ```rust
|
/// ```rust
|
||||||
/// use rocket::request::{self, Request, FromRequest};
|
/// use rocket::request::{self, Request, FromRequest};
|
||||||
/// use rocket::data::{Data, FromData, Outcome};
|
/// use rocket::data::{Data, FromData, Outcome};
|
||||||
|
/// use rocket::http::Status;
|
||||||
/// # struct MyType;
|
/// # struct MyType;
|
||||||
/// # type MyError = String;
|
/// # 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> {
|
/// async fn from_data(r: &'r Request<'_>, mut data: Data<'r>) -> Outcome<'r, Self> {
|
||||||
/// if data.peek(2).await != b"hi" {
|
/// 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
|
/// [`FromData`]: crate::data::FromData
|
||||||
pub type Outcome<'r, T, E = <T as FromData<'r>>::Error>
|
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 Failure = Status;
|
||||||
type Forward = Data<'r>;
|
type Forward = (Data<'r>, Status);
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn into_outcome(self, status: Status) -> Outcome<'r, S, E> {
|
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]
|
#[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 {
|
match self {
|
||||||
Ok(val) => Success(val),
|
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.
|
/// // Ensure the content type is correct before opening the data.
|
||||||
/// let person_ct = ContentType::new("application", "x-person");
|
/// let person_ct = ContentType::new("application", "x-person");
|
||||||
/// if req.content_type() != Some(&person_ct) {
|
/// 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.
|
/// // 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::request::{Request, local_cache_once};
|
||||||
use crate::data::{Data, Limits, Outcome};
|
use crate::data::{Data, Limits, Outcome};
|
||||||
use crate::form::{SharedStack, prelude::*};
|
use crate::form::{SharedStack, prelude::*};
|
||||||
use crate::http::RawStr;
|
use crate::http::{RawStr, Status};
|
||||||
|
|
||||||
type Result<'r, T> = std::result::Result<T, Error<'r>>;
|
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() {
|
let parser = match req.content_type() {
|
||||||
Some(c) if c.is_form() => Self::from_form(req, data).await,
|
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,
|
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 {
|
match parser {
|
||||||
|
|
|
@ -18,8 +18,8 @@ impl<'r> FromRequest<'r> for Certificate<'r> {
|
||||||
type Error = Error;
|
type Error = Error;
|
||||||
|
|
||||||
async fn from_request(req: &'r Request<'_>) -> Outcome<Self, Self::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 certs = req.connection.client_certificates.as_ref().or_forward(Status::Unauthorized);
|
||||||
let data = try_outcome!(certs.chain_data().or_forward(()));
|
let data = try_outcome!(try_outcome!(certs).chain_data().or_forward(Status::Unauthorized));
|
||||||
Certificate::parse(data).into_outcome(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};
|
use crate::http::uri::{Host, Origin};
|
||||||
|
|
||||||
/// Type alias for the `Outcome` of a `FromRequest` conversion.
|
/// 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 Failure = Status;
|
||||||
type Forward = ();
|
type Forward = Status;
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn into_outcome(self, status: Status) -> Outcome<S, E> {
|
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]
|
#[inline]
|
||||||
fn or_forward(self, _: ()) -> Outcome<S, E> {
|
fn or_forward(self, status: Status) -> Outcome<S, E> {
|
||||||
match self {
|
match self {
|
||||||
Ok(val) => Success(val),
|
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)
|
/// * **Failure**(Status, E)
|
||||||
///
|
///
|
||||||
/// If the `Outcome` is [`Failure`], the request will fail with the given
|
/// If the `Outcome` is [`Failure`], the request will fail with the given
|
||||||
/// status code and error. The designated error [`Catcher`](crate::Catcher) will be
|
/// status code and error. The designated error [`Catcher`](crate::Catcher)
|
||||||
/// used to respond to the request. Note that users can request types of
|
/// will be used to respond to the request. Note that users can request types
|
||||||
/// `Result<S, E>` and `Option<S>` to catch `Failure`s and retrieve the error
|
/// of `Result<S, E>` and `Option<S>` to catch `Failure`s and retrieve the
|
||||||
/// value.
|
/// error value.
|
||||||
///
|
///
|
||||||
/// * **Forward**
|
/// * **Forward**(Status)
|
||||||
///
|
///
|
||||||
/// If the `Outcome` is [`Forward`], the request will be forwarded to the next
|
/// 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
|
/// matching route until either one succeds or there are no further matching
|
||||||
/// `Forward`s.
|
/// 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
|
/// # Provided Implementations
|
||||||
///
|
///
|
||||||
|
@ -137,10 +139,12 @@ impl<S, E> IntoOutcome<S, (Status, E), ()> for Result<S, E> {
|
||||||
///
|
///
|
||||||
/// * **&Route**
|
/// * **&Route**
|
||||||
///
|
///
|
||||||
/// Extracts the [`Route`] from the request if one is available. If a route
|
/// Extracts the [`Route`] from the request if one is available. When used
|
||||||
/// is not available, the request is forwarded.
|
/// 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()`].
|
/// [`Request::route()`].
|
||||||
///
|
///
|
||||||
/// * **&CookieJar**
|
/// * **&CookieJar**
|
||||||
|
@ -256,6 +260,7 @@ impl<S, E> IntoOutcome<S, (Status, E), ()> for Result<S, E> {
|
||||||
/// # #[cfg(feature = "secrets")] mod wrapper {
|
/// # #[cfg(feature = "secrets")] mod wrapper {
|
||||||
/// # use rocket::outcome::{IntoOutcome, try_outcome};
|
/// # use rocket::outcome::{IntoOutcome, try_outcome};
|
||||||
/// # use rocket::request::{self, Outcome, FromRequest, Request};
|
/// # use rocket::request::{self, Outcome, FromRequest, Request};
|
||||||
|
/// # use rocket::http::Status;
|
||||||
/// # struct User { id: String, is_admin: bool }
|
/// # struct User { id: String, is_admin: bool }
|
||||||
/// # struct Database;
|
/// # struct Database;
|
||||||
/// # impl Database {
|
/// # impl Database {
|
||||||
|
@ -283,7 +288,7 @@ impl<S, E> IntoOutcome<S, (Status, E), ()> for Result<S, E> {
|
||||||
/// .get_private("user_id")
|
/// .get_private("user_id")
|
||||||
/// .and_then(|cookie| cookie.value().parse().ok())
|
/// .and_then(|cookie| cookie.value().parse().ok())
|
||||||
/// .and_then(|id| db.get_user(id).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 {
|
/// if user.is_admin {
|
||||||
/// Outcome::Success(Admin { user })
|
/// Outcome::Success(Admin { user })
|
||||||
/// } else {
|
/// } 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 {
|
/// # #[cfg(feature = "secrets")] mod wrapper {
|
||||||
/// # use rocket::outcome::{IntoOutcome, try_outcome};
|
/// # use rocket::outcome::{IntoOutcome, try_outcome};
|
||||||
/// # use rocket::request::{self, Outcome, FromRequest, Request};
|
/// # use rocket::request::{self, Outcome, FromRequest, Request};
|
||||||
|
/// # use rocket::http::Status;
|
||||||
/// # struct User { id: String, is_admin: bool }
|
/// # struct User { id: String, is_admin: bool }
|
||||||
/// # struct Database;
|
/// # struct Database;
|
||||||
/// # impl 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())
|
/// .and_then(|id| db.get_user(id).ok())
|
||||||
/// }).await;
|
/// }).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 {
|
/// if user.is_admin {
|
||||||
/// Outcome::Success(Admin { user })
|
/// Outcome::Success(Admin { user })
|
||||||
/// } else {
|
/// } 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> {
|
async fn from_request(request: &'r Request<'_>) -> Outcome<Self, Self::Error> {
|
||||||
match request.host() {
|
match request.host() {
|
||||||
Some(host) => Success(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> {
|
async fn from_request(request: &'r Request<'_>) -> Outcome<Self, Self::Error> {
|
||||||
match request.route() {
|
match request.route() {
|
||||||
Some(route) => Success(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> {
|
async fn from_request(request: &'r Request<'_>) -> Outcome<Self, Self::Error> {
|
||||||
match request.accept() {
|
match request.accept() {
|
||||||
Some(accept) => Success(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> {
|
async fn from_request(request: &'r Request<'_>) -> Outcome<Self, Self::Error> {
|
||||||
match request.content_type() {
|
match request.content_type() {
|
||||||
Some(content_type) => Success(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> {
|
async fn from_request(request: &'r Request<'_>) -> Outcome<Self, Self::Error> {
|
||||||
match request.client_ip() {
|
match request.client_ip() {
|
||||||
Some(addr) => Success(addr),
|
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> {
|
async fn from_request(request: &'r Request<'_>) -> Outcome<Self, Self::Error> {
|
||||||
match request.remote() {
|
match request.remote() {
|
||||||
Some(addr) => Success(addr),
|
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 {
|
match T::from_request(request).await {
|
||||||
Success(val) => Success(Ok(val)),
|
Success(val) => Success(Ok(val)),
|
||||||
Failure((_, e)) => Success(Err(e)),
|
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
|
/// Type alias for the return type of a [`Route`](crate::Route)'s
|
||||||
/// [`Handler::handle()`].
|
/// [`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
|
/// Type alias for the return type of a _raw_ [`Route`](crate::Route)'s
|
||||||
/// [`Handler`].
|
/// [`Handler`].
|
||||||
|
@ -239,7 +239,7 @@ impl<'r, 'o: 'r> Outcome<'o> {
|
||||||
{
|
{
|
||||||
match responder.respond_to(req) {
|
match responder.respond_to(req) {
|
||||||
Ok(response) => Outcome::Success(response),
|
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
|
/// 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.
|
/// This method exists to be used during manual routing.
|
||||||
///
|
///
|
||||||
|
@ -279,7 +279,7 @@ impl<'r, 'o: 'r> Outcome<'o> {
|
||||||
/// ```
|
/// ```
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn forward(data: Data<'r>) -> Outcome<'r> {
|
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> {
|
) -> Response<'r> {
|
||||||
let mut response = match self.route(request, data).await {
|
let mut response = match self.route(request, data).await {
|
||||||
Outcome::Success(response) => response,
|
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());
|
info_!("Autohandling {} request.", Paint::default("HEAD").bold());
|
||||||
|
|
||||||
// Dispatch the request again with Method `GET`.
|
// Dispatch the request again with Method `GET`.
|
||||||
|
@ -291,10 +291,10 @@ impl Rocket<Orbit> {
|
||||||
match self.route(request, data).await {
|
match self.route(request, data).await {
|
||||||
Outcome::Success(response) => response,
|
Outcome::Success(response) => response,
|
||||||
Outcome::Failure(status) => self.handle_error(status, request).await,
|
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,
|
Outcome::Failure(status) => self.handle_error(status, request).await,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -319,7 +319,9 @@ impl Rocket<Orbit> {
|
||||||
request: &'r Request<'s>,
|
request: &'r Request<'s>,
|
||||||
mut data: Data<'r>,
|
mut data: Data<'r>,
|
||||||
) -> route::Outcome<'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) {
|
for route in self.router.route(request) {
|
||||||
// Retrieve and set the requests parameters.
|
// Retrieve and set the requests parameters.
|
||||||
info_!("Matched: {}", route);
|
info_!("Matched: {}", route);
|
||||||
|
@ -335,12 +337,12 @@ impl Rocket<Orbit> {
|
||||||
info_!("{} {}", Paint::default("Outcome:").bold(), outcome);
|
info_!("{} {}", Paint::default("Outcome:").bold(), outcome);
|
||||||
match outcome {
|
match outcome {
|
||||||
o@Outcome::Success(_) | o@Outcome::Failure(_) => return o,
|
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);
|
error_!("No matching routes for {}.", request);
|
||||||
Outcome::Forward(data)
|
Outcome::Forward((data, status))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Invokes the handler with `req` for catcher with status `status`.
|
/// Invokes the handler with `req` for catcher with status `status`.
|
||||||
|
|
|
@ -63,6 +63,7 @@ use crate::http::Status;
|
||||||
/// use rocket::State;
|
/// use rocket::State;
|
||||||
/// use rocket::request::{self, Request, FromRequest};
|
/// use rocket::request::{self, Request, FromRequest};
|
||||||
/// use rocket::outcome::IntoOutcome;
|
/// use rocket::outcome::IntoOutcome;
|
||||||
|
/// use rocket::http::Status;
|
||||||
///
|
///
|
||||||
/// # struct MyConfig { user_val: String };
|
/// # struct MyConfig { user_val: String };
|
||||||
/// struct Item<'r>(&'r str);
|
/// struct Item<'r>(&'r str);
|
||||||
|
@ -79,7 +80,7 @@ use crate::http::Status;
|
||||||
/// // Or alternatively, using `Rocket::state()`:
|
/// // Or alternatively, using `Rocket::state()`:
|
||||||
/// let outcome = request.rocket().state::<MyConfig>()
|
/// let outcome = request.rocket().state::<MyConfig>()
|
||||||
/// .map(|my_config| Item(&my_config.user_val))
|
/// .map(|my_config| Item(&my_config.user_val))
|
||||||
/// .or_forward(());
|
/// .or_forward(Status::NotFound);
|
||||||
///
|
///
|
||||||
/// outcome
|
/// 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, Data};
|
||||||
use rocket::request::{self, FromRequest};
|
use rocket::request::{self, FromRequest};
|
||||||
use rocket::outcome::IntoOutcome;
|
use rocket::outcome::IntoOutcome;
|
||||||
|
use rocket::http::Status;
|
||||||
|
|
||||||
struct HasContentType;
|
struct HasContentType;
|
||||||
|
|
||||||
|
@ -11,7 +12,7 @@ impl<'r> FromRequest<'r> for HasContentType {
|
||||||
type Error = ();
|
type Error = ();
|
||||||
|
|
||||||
async fn from_request(req: &'r Request<'_>) -> request::Outcome<Self, ()> {
|
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 = ();
|
type Error = ();
|
||||||
|
|
||||||
async fn from_data(req: &'r Request<'_>, data: Data<'r>) -> data::Outcome<'r, Self> {
|
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::outcome::IntoOutcome;
|
||||||
use rocket::request::{self, FlashMessage, FromRequest, Request};
|
use rocket::request::{self, FlashMessage, FromRequest, Request};
|
||||||
use rocket::response::{Redirect, Flash};
|
use rocket::response::{Redirect, Flash};
|
||||||
use rocket::http::{Cookie, CookieJar};
|
use rocket::http::{Cookie, CookieJar, Status};
|
||||||
use rocket::form::Form;
|
use rocket::form::Form;
|
||||||
|
|
||||||
use rocket_dyn_templates::{Template, context};
|
use rocket_dyn_templates::{Template, context};
|
||||||
|
@ -24,7 +24,7 @@ impl<'r> FromRequest<'r> for User {
|
||||||
.get_private("user_id")
|
.get_private("user_id")
|
||||||
.and_then(|cookie| cookie.value().parse().ok())
|
.and_then(|cookie| cookie.value().parse().ok())
|
||||||
.map(User)
|
.map(User)
|
||||||
.or_forward(())
|
.or_forward(Status::NotFound)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -84,7 +84,7 @@ impl route::Handler for CustomHandler {
|
||||||
let self_data = self.data;
|
let self_data = self.data;
|
||||||
let id = req.param::<&str>(0)
|
let id = req.param::<&str>(0)
|
||||||
.and_then(Result::ok)
|
.and_then(Result::ok)
|
||||||
.or_forward(data);
|
.or_forward((data, Status::NotFound));
|
||||||
|
|
||||||
route::Outcome::from(req, format!("{} - {}", self_data, try_outcome!(id)))
|
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::State;
|
||||||
use rocket::request::{self, Request, FromRequest};
|
use rocket::request::{self, Request, FromRequest};
|
||||||
use rocket::outcome::IntoOutcome;
|
use rocket::outcome::IntoOutcome;
|
||||||
|
use rocket::http::Status;
|
||||||
|
|
||||||
# struct MyConfig { user_val: String };
|
# struct MyConfig { user_val: String };
|
||||||
struct Item<'r>(&'r str);
|
struct Item<'r>(&'r str);
|
||||||
|
@ -140,7 +141,7 @@ impl<'r> FromRequest<'r> for Item<'r> {
|
||||||
// Or alternatively, using `Rocket::state()`:
|
// Or alternatively, using `Rocket::state()`:
|
||||||
let outcome = request.rocket().state::<MyConfig>()
|
let outcome = request.rocket().state::<MyConfig>()
|
||||||
.map(|my_config| Item(&my_config.user_val))
|
.map(|my_config| Item(&my_config.user_val))
|
||||||
.or_forward(());
|
.or_forward(Status::NotFound);
|
||||||
|
|
||||||
outcome
|
outcome
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue