mirror of https://github.com/rwf2/Rocket.git
Improve forwarding status code precision.
Previously, the `NotFound` status code was used to signal many kinds of recoverable, forwarding errors. This included validation errors, incorrect Content-Type errors, and more. This commit modifies the status code used to forward in these instances to more precisely indicate the forwarding condition. In particular: * Parameter `FromParam` errors now forward as 422 (`UnprocessableEntity`). * Query paramater errors now forward as 422 (`UnprocessableEntity`). * Use of incorrect form content-type forwards as 413 (`UnsupportedMediaType`). * `WebSocket` guard now forwards as 400 (`BadRequest`). * `&Host`, `&Accept`, `&ContentType`, `IpAddr`, and `SocketAddr` all forward with a 500 (`InternalServerError`). Additionally, the `IntoOutcome` trait was overhauled to support functionality previously offered by methods on `Outcome`. The `Outcome::forward()` method now requires a status code to use for the forwarding outcome. Finally, logging of `Outcome`s now includes the relevant status code. Resolves #2626.
This commit is contained in:
parent
c90812051e
commit
fbd1a0d069
|
@ -210,7 +210,7 @@ impl<'r, K: 'static, C: Poolable> FromRequest<'r> for Connection<K, C> {
|
||||||
#[inline]
|
#[inline]
|
||||||
async fn from_request(request: &'r Request<'_>) -> Outcome<Self, ()> {
|
async fn from_request(request: &'r Request<'_>) -> Outcome<Self, ()> {
|
||||||
match request.rocket().state::<ConnectionPool<K, C>>() {
|
match request.rocket().state::<ConnectionPool<K, C>>() {
|
||||||
Some(c) => c.get().await.into_outcome((Status::ServiceUnavailable, ())),
|
Some(c) => c.get().await.or_error((Status::ServiceUnavailable, ())),
|
||||||
None => {
|
None => {
|
||||||
error_!("Missing database fairing for `{}`", std::any::type_name::<K>());
|
error_!("Missing database fairing for `{}`", std::any::type_name::<K>());
|
||||||
Outcome::Error((Status::InternalServerError, ()))
|
Outcome::Error((Status::InternalServerError, ()))
|
||||||
|
|
|
@ -30,7 +30,7 @@ use crate::result::{Result, Error};
|
||||||
/// ### Forwarding
|
/// ### Forwarding
|
||||||
///
|
///
|
||||||
/// If the incoming request is not a valid WebSocket request, the guard
|
/// If the incoming request is not a valid WebSocket request, the guard
|
||||||
/// forwards. The guard never fails.
|
/// forwards with a status of `BadRequest`. The guard never fails.
|
||||||
pub struct WebSocket {
|
pub struct WebSocket {
|
||||||
config: Config,
|
config: Config,
|
||||||
key: String,
|
key: String,
|
||||||
|
@ -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(Status::NotFound)
|
Some(_) | None => Outcome::Forward(Status::BadRequest)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -105,7 +105,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, #Status::NotFound));
|
return #Outcome::Forward((#__data, #Status::UnprocessableEntity));
|
||||||
}
|
}
|
||||||
|
|
||||||
(#(#ident.unwrap()),*)
|
(#(#ident.unwrap()),*)
|
||||||
|
@ -146,7 +146,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, #Status::NotFound))
|
#Outcome::Forward((#__data, #Status::UnprocessableEntity))
|
||||||
});
|
});
|
||||||
|
|
||||||
// All dynamic parameters should be found if this function is being called;
|
// All dynamic parameters should be found if this function is being called;
|
||||||
|
|
|
@ -269,7 +269,7 @@ fn test_query_collection() {
|
||||||
|
|
||||||
let colors = &["red"];
|
let colors = &["red"];
|
||||||
let dog = &["name=Fido"];
|
let dog = &["name=Fido"];
|
||||||
assert_eq!(run(&client, colors, dog).0, Status::NotFound);
|
assert_eq!(run(&client, colors, dog).0, Status::UnprocessableEntity);
|
||||||
|
|
||||||
let colors = &["red"];
|
let colors = &["red"];
|
||||||
let dog = &["name=Fido", "age=2"];
|
let dog = &["name=Fido", "age=2"];
|
||||||
|
|
|
@ -112,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, Status::NotFound))
|
/// return Outcome::Forward((data, Status::BadRequest))
|
||||||
/// }
|
/// }
|
||||||
///
|
///
|
||||||
/// /* .. */
|
/// /* .. */
|
||||||
|
|
|
@ -9,27 +9,6 @@ use crate::outcome::{self, IntoOutcome, try_outcome, Outcome::*};
|
||||||
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>, Status)>;
|
= outcome::Outcome<T, (Status, E), (Data<'r>, Status)>;
|
||||||
|
|
||||||
impl<'r, S, E> IntoOutcome<S, (Status, E), (Data<'r>, Status)> for Result<S, E> {
|
|
||||||
type Error = Status;
|
|
||||||
type Forward = (Data<'r>, Status);
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn into_outcome(self, status: Status) -> Outcome<'r, S, E> {
|
|
||||||
match self {
|
|
||||||
Ok(val) => Success(val),
|
|
||||||
Err(err) => Error((status, err))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn or_forward(self, (data, status): (Data<'r>, Status)) -> Outcome<'r, S, E> {
|
|
||||||
match self {
|
|
||||||
Ok(val) => Success(val),
|
|
||||||
Err(_) => Forward((data, status))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Trait implemented by data guards to derive a value from request body data.
|
/// Trait implemented by data guards to derive a value from request body data.
|
||||||
///
|
///
|
||||||
/// # Data Guards
|
/// # Data Guards
|
||||||
|
@ -271,7 +250,7 @@ impl<'r, S, E> IntoOutcome<S, (Status, E), (Data<'r>, Status)> 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 Outcome::Forward((data, Status::NotFound));
|
/// return Outcome::Forward((data, Status::UnsupportedMediaType));
|
||||||
/// }
|
/// }
|
||||||
///
|
///
|
||||||
/// // Use a configured limit with name 'person' or fallback to default.
|
/// // Use a configured limit with name 'person' or fallback to default.
|
||||||
|
@ -343,7 +322,7 @@ impl<'r> FromData<'r> for Capped<String> {
|
||||||
|
|
||||||
async fn from_data(req: &'r Request<'_>, data: Data<'r>) -> Outcome<'r, Self> {
|
async fn from_data(req: &'r Request<'_>, data: Data<'r>) -> Outcome<'r, Self> {
|
||||||
let limit = req.limits().get("string").unwrap_or(Limits::STRING);
|
let limit = req.limits().get("string").unwrap_or(Limits::STRING);
|
||||||
data.open(limit).into_string().await.into_outcome(Status::BadRequest)
|
data.open(limit).into_string().await.or_error(Status::BadRequest)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -406,7 +385,7 @@ impl<'r> FromData<'r> for Capped<Vec<u8>> {
|
||||||
|
|
||||||
async fn from_data(req: &'r Request<'_>, data: Data<'r>) -> Outcome<'r, Self> {
|
async fn from_data(req: &'r Request<'_>, data: Data<'r>) -> Outcome<'r, Self> {
|
||||||
let limit = req.limits().get("bytes").unwrap_or(Limits::BYTES);
|
let limit = req.limits().get("bytes").unwrap_or(Limits::BYTES);
|
||||||
data.open(limit).into_bytes().await.into_outcome(Status::BadRequest)
|
data.open(limit).into_bytes().await.or_error(Status::BadRequest)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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, Status::NotFound)),
|
_ => return Outcome::Forward((data, Status::UnsupportedMediaType)),
|
||||||
};
|
};
|
||||||
|
|
||||||
match parser {
|
match parser {
|
||||||
|
|
|
@ -1,9 +1,10 @@
|
||||||
use std::path::{PathBuf, Path};
|
use std::path::{PathBuf, Path};
|
||||||
|
|
||||||
use crate::{Request, Data};
|
use crate::{Request, Data};
|
||||||
use crate::http::{Method, uri::Segments, ext::IntoOwned};
|
use crate::http::{Method, Status, uri::Segments, ext::IntoOwned};
|
||||||
use crate::route::{Route, Handler, Outcome};
|
use crate::route::{Route, Handler, Outcome};
|
||||||
use crate::response::Redirect;
|
use crate::response::{Redirect, Responder};
|
||||||
|
use crate::outcome::IntoOutcome;
|
||||||
use crate::fs::NamedFile;
|
use crate::fs::NamedFile;
|
||||||
|
|
||||||
/// Custom handler for serving static files.
|
/// Custom handler for serving static files.
|
||||||
|
@ -203,10 +204,10 @@ impl Handler for FileServer {
|
||||||
};
|
};
|
||||||
|
|
||||||
if segments.is_empty() {
|
if segments.is_empty() {
|
||||||
let file = NamedFile::open(&self.root).await.ok();
|
let file = NamedFile::open(&self.root).await;
|
||||||
return Outcome::from_or_forward(req, data, file);
|
return file.respond_to(req).or_forward((data, Status::NotFound));
|
||||||
} else {
|
} else {
|
||||||
return Outcome::forward(data);
|
return Outcome::forward(data, Status::NotFound);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -224,18 +225,23 @@ impl Handler for FileServer {
|
||||||
.expect("adding a trailing slash to a known good path => valid path")
|
.expect("adding a trailing slash to a known good path => valid path")
|
||||||
.into_owned();
|
.into_owned();
|
||||||
|
|
||||||
return Outcome::from_or_forward(req, data, Redirect::permanent(normal));
|
return Redirect::permanent(normal)
|
||||||
|
.respond_to(req)
|
||||||
|
.or_forward((data, Status::InternalServerError));
|
||||||
}
|
}
|
||||||
|
|
||||||
if !options.contains(Options::Index) {
|
if !options.contains(Options::Index) {
|
||||||
return Outcome::forward(data);
|
return Outcome::forward(data, Status::NotFound);
|
||||||
}
|
}
|
||||||
|
|
||||||
let index = NamedFile::open(p.join("index.html")).await.ok();
|
let index = NamedFile::open(p.join("index.html")).await;
|
||||||
Outcome::from_or_forward(req, data, index)
|
index.respond_to(req).or_forward((data, Status::NotFound))
|
||||||
},
|
},
|
||||||
Some(p) => Outcome::from_or_forward(req, data, NamedFile::open(p).await.ok()),
|
Some(p) => {
|
||||||
None => Outcome::forward(data),
|
let file = NamedFile::open(p).await;
|
||||||
|
file.respond_to(req).or_forward((data, Status::NotFound))
|
||||||
|
}
|
||||||
|
None => Outcome::forward(data, Status::NotFound),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -543,7 +543,7 @@ impl<'r> FromData<'r> for Capped<TempFile<'_>> {
|
||||||
}
|
}
|
||||||
|
|
||||||
TempFile::from(req, data, None, req.content_type().cloned()).await
|
TempFile::from(req, data, None, req.content_type().cloned()).await
|
||||||
.into_outcome(Status::BadRequest)
|
.or_error(Status::BadRequest)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -20,6 +20,6 @@ impl<'r> FromRequest<'r> for Certificate<'r> {
|
||||||
async fn from_request(req: &'r Request<'_>) -> Outcome<Self, Self::Error> {
|
async fn from_request(req: &'r Request<'_>) -> Outcome<Self, Self::Error> {
|
||||||
let certs = req.connection.client_certificates.as_ref().or_forward(Status::Unauthorized);
|
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));
|
let data = try_outcome!(try_outcome!(certs).chain_data().or_forward(Status::Unauthorized));
|
||||||
Certificate::parse(data).into_outcome(Status::Unauthorized)
|
Certificate::parse(data).or_error(Status::Unauthorized)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -90,6 +90,10 @@ use std::fmt;
|
||||||
|
|
||||||
use yansi::{Paint, Color};
|
use yansi::{Paint, Color};
|
||||||
|
|
||||||
|
use crate::{route, request, response};
|
||||||
|
use crate::data::{self, Data, FromData};
|
||||||
|
use crate::http::Status;
|
||||||
|
|
||||||
use self::Outcome::*;
|
use self::Outcome::*;
|
||||||
|
|
||||||
/// An enum representing success (`Success`), error (`Error`), or forwarding
|
/// An enum representing success (`Success`), error (`Error`), or forwarding
|
||||||
|
@ -107,46 +111,6 @@ pub enum Outcome<S, E, F> {
|
||||||
Forward(F),
|
Forward(F),
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Conversion trait from some type into an Outcome type.
|
|
||||||
pub trait IntoOutcome<S, E, F> {
|
|
||||||
/// The type to use when returning an `Outcome::Error`.
|
|
||||||
type Error: Sized;
|
|
||||||
|
|
||||||
/// The type to use when returning an `Outcome::Forward`.
|
|
||||||
type Forward: Sized;
|
|
||||||
|
|
||||||
/// Converts `self` into an `Outcome`. If `self` represents a success, an
|
|
||||||
/// `Outcome::Success` is returned. Otherwise, an `Outcome::Error` is
|
|
||||||
/// returned with `error` as the inner value.
|
|
||||||
fn into_outcome(self, error: Self::Error) -> Outcome<S, E, F>;
|
|
||||||
|
|
||||||
/// Converts `self` into an `Outcome`. If `self` represents a success, an
|
|
||||||
/// `Outcome::Success` is returned. Otherwise, an `Outcome::Forward` is
|
|
||||||
/// returned with `forward` as the inner value.
|
|
||||||
fn or_forward(self, forward: Self::Forward) -> Outcome<S, E, F>;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<S, E, F> IntoOutcome<S, E, F> for Option<S> {
|
|
||||||
type Error = E;
|
|
||||||
type Forward = F;
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn into_outcome(self, error: E) -> Outcome<S, E, F> {
|
|
||||||
match self {
|
|
||||||
Some(val) => Success(val),
|
|
||||||
None => Error(error)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn or_forward(self, forward: F) -> Outcome<S, E, F> {
|
|
||||||
match self {
|
|
||||||
Some(val) => Success(val),
|
|
||||||
None => Forward(forward)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<S, E, F> Outcome<S, E, F> {
|
impl<S, E, F> Outcome<S, E, F> {
|
||||||
/// Unwraps the Outcome, yielding the contents of a Success.
|
/// Unwraps the Outcome, yielding the contents of a Success.
|
||||||
///
|
///
|
||||||
|
@ -651,15 +615,6 @@ impl<S, E, F> Outcome<S, E, F> {
|
||||||
Outcome::Forward(v) => Err(v),
|
Outcome::Forward(v) => Err(v),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn formatting(&self) -> (Color, &'static str) {
|
|
||||||
match *self {
|
|
||||||
Success(..) => (Color::Green, "Success"),
|
|
||||||
Error(..) => (Color::Red, "Error"),
|
|
||||||
Forward(..) => (Color::Yellow, "Forward"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, S: Send + 'a, E: Send + 'a, F: Send + 'a> Outcome<S, E, F> {
|
impl<'a, S: Send + 'a, E: Send + 'a, F: Send + 'a> Outcome<S, E, F> {
|
||||||
|
@ -755,15 +710,158 @@ crate::export! {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<S, E, F> Outcome<S, E, F> {
|
||||||
|
#[inline]
|
||||||
|
fn dbg_str(&self) -> &'static str {
|
||||||
|
match self {
|
||||||
|
Success(..) => "Success",
|
||||||
|
Error(..) => "Error",
|
||||||
|
Forward(..) => "Forward",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn color(&self) -> Color {
|
||||||
|
match self {
|
||||||
|
Success(..) => Color::Green,
|
||||||
|
Error(..) => Color::Red,
|
||||||
|
Forward(..) => Color::Yellow,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) struct Display<'a, 'r>(&'a route::Outcome<'r>);
|
||||||
|
|
||||||
|
impl<'r> route::Outcome<'r> {
|
||||||
|
pub(crate) fn log_display(&self) -> Display<'_, 'r> {
|
||||||
|
impl fmt::Display for Display<'_, '_> {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
write!(f, "{}", "Outcome: ".primary().bold())?;
|
||||||
|
|
||||||
|
let color = self.0.color();
|
||||||
|
match self.0 {
|
||||||
|
Success(r) => write!(f, "{}({})", "Success".paint(color), r.status().primary()),
|
||||||
|
Error(s) => write!(f, "{}({})", "Error".paint(color), s.primary()),
|
||||||
|
Forward((_, s)) => write!(f, "{}({})", "Forward".paint(color), s.primary()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Display(self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl<S, E, F> fmt::Debug for Outcome<S, E, F> {
|
impl<S, E, F> fmt::Debug for Outcome<S, E, F> {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
write!(f, "Outcome::{}", self.formatting().1)
|
write!(f, "Outcome::{}", self.dbg_str())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<S, E, F> fmt::Display for Outcome<S, E, F> {
|
impl<S, E, F> fmt::Display for Outcome<S, E, F> {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
let (color, string) = self.formatting();
|
write!(f, "{}", self.dbg_str().paint(self.color()))
|
||||||
write!(f, "{}", string.paint(color))
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Conversion trait from some type into an Outcome type.
|
||||||
|
pub trait IntoOutcome<Outcome> {
|
||||||
|
/// The type to use when returning an `Outcome::Error`.
|
||||||
|
type Error: Sized;
|
||||||
|
|
||||||
|
/// The type to use when returning an `Outcome::Forward`.
|
||||||
|
type Forward: Sized;
|
||||||
|
|
||||||
|
/// Converts `self` into an `Outcome`. If `self` represents a success, an
|
||||||
|
/// `Outcome::Success` is returned. Otherwise, an `Outcome::Error` is
|
||||||
|
/// returned with `error` as the inner value.
|
||||||
|
fn or_error(self, error: Self::Error) -> Outcome;
|
||||||
|
|
||||||
|
/// Converts `self` into an `Outcome`. If `self` represents a success, an
|
||||||
|
/// `Outcome::Success` is returned. Otherwise, an `Outcome::Forward` is
|
||||||
|
/// returned with `forward` as the inner value.
|
||||||
|
fn or_forward(self, forward: Self::Forward) -> Outcome;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<S, E, F> IntoOutcome<Outcome<S, E, F>> for Option<S> {
|
||||||
|
type Error = E;
|
||||||
|
type Forward = F;
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn or_error(self, error: E) -> Outcome<S, E, F> {
|
||||||
|
match self {
|
||||||
|
Some(val) => Success(val),
|
||||||
|
None => Error(error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn or_forward(self, forward: F) -> Outcome<S, E, F> {
|
||||||
|
match self {
|
||||||
|
Some(val) => Success(val),
|
||||||
|
None => Forward(forward)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'r, T: FromData<'r>> IntoOutcome<data::Outcome<'r, T>> for Result<T, T::Error> {
|
||||||
|
type Error = Status;
|
||||||
|
type Forward = (Data<'r>, Status);
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn or_error(self, error: Status) -> data::Outcome<'r, T> {
|
||||||
|
match self {
|
||||||
|
Ok(val) => Success(val),
|
||||||
|
Err(err) => Error((error, err))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn or_forward(self, (data, forward): (Data<'r>, Status)) -> data::Outcome<'r, T> {
|
||||||
|
match self {
|
||||||
|
Ok(val) => Success(val),
|
||||||
|
Err(_) => Forward((data, forward))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<S, E> IntoOutcome<request::Outcome<S, E>> for Result<S, E> {
|
||||||
|
type Error = Status;
|
||||||
|
type Forward = Status;
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn or_error(self, error: Status) -> request::Outcome<S, E> {
|
||||||
|
match self {
|
||||||
|
Ok(val) => Success(val),
|
||||||
|
Err(err) => Error((error, err))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn or_forward(self, status: Status) -> request::Outcome<S, E> {
|
||||||
|
match self {
|
||||||
|
Ok(val) => Success(val),
|
||||||
|
Err(_) => Forward(status)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'r, 'o: 'r> IntoOutcome<route::Outcome<'r>> for response::Result<'o> {
|
||||||
|
type Error = ();
|
||||||
|
type Forward = (Data<'r>, Status);
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn or_error(self, _: ()) -> route::Outcome<'r> {
|
||||||
|
match self {
|
||||||
|
Ok(val) => Success(val),
|
||||||
|
Err(status) => Error(status),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn or_forward(self, (data, forward): (Data<'r>, Status)) -> route::Outcome<'r> {
|
||||||
|
match self {
|
||||||
|
Ok(val) => Success(val),
|
||||||
|
Err(_) => Forward((data, forward))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,36 +3,14 @@ use std::fmt::Debug;
|
||||||
use std::net::{IpAddr, SocketAddr};
|
use std::net::{IpAddr, SocketAddr};
|
||||||
|
|
||||||
use crate::{Request, Route};
|
use crate::{Request, Route};
|
||||||
use crate::outcome::{self, IntoOutcome};
|
use crate::outcome::{self, Outcome::*};
|
||||||
use crate::outcome::Outcome::*;
|
|
||||||
|
|
||||||
use crate::http::{Status, ContentType, Accept, Method, CookieJar};
|
|
||||||
use crate::http::uri::{Host, Origin};
|
use crate::http::uri::{Host, Origin};
|
||||||
|
use crate::http::{Status, ContentType, Accept, Method, CookieJar};
|
||||||
|
|
||||||
/// 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), Status>;
|
pub type Outcome<S, E> = outcome::Outcome<S, (Status, E), Status>;
|
||||||
|
|
||||||
impl<S, E> IntoOutcome<S, (Status, E), Status> for Result<S, E> {
|
|
||||||
type Error = Status;
|
|
||||||
type Forward = Status;
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn into_outcome(self, status: Status) -> Outcome<S, E> {
|
|
||||||
match self {
|
|
||||||
Ok(val) => Success(val),
|
|
||||||
Err(err) => Error((status, err))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn or_forward(self, status: Status) -> Outcome<S, E> {
|
|
||||||
match self {
|
|
||||||
Ok(val) => Success(val),
|
|
||||||
Err(_) => Forward(status)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Trait implemented by request guards to derive a value from incoming
|
/// Trait implemented by request guards to derive a value from incoming
|
||||||
/// requests.
|
/// requests.
|
||||||
///
|
///
|
||||||
|
@ -136,7 +114,8 @@ impl<S, E> IntoOutcome<S, (Status, E), Status> for Result<S, E> {
|
||||||
/// * **&Host**
|
/// * **&Host**
|
||||||
///
|
///
|
||||||
/// Extracts the [`Host`] from the incoming request, if it exists. See
|
/// Extracts the [`Host`] from the incoming request, if it exists. See
|
||||||
/// [`Request::host()`] for details.
|
/// [`Request::host()`] for details. If it does not exist, the request is
|
||||||
|
/// forwarded with a 500 Internal Server Error status.
|
||||||
///
|
///
|
||||||
/// * **&Route**
|
/// * **&Route**
|
||||||
///
|
///
|
||||||
|
@ -162,23 +141,30 @@ impl<S, E> IntoOutcome<S, (Status, E), Status> for Result<S, E> {
|
||||||
///
|
///
|
||||||
/// _This implementation always returns successfully._
|
/// _This implementation always returns successfully._
|
||||||
///
|
///
|
||||||
/// * **ContentType**
|
/// * **&ContentType**
|
||||||
///
|
///
|
||||||
/// Extracts the [`ContentType`] from the incoming request via
|
/// Extracts the [`ContentType`] from the incoming request via
|
||||||
/// [`Request::content_type()`]. If the request didn't specify a
|
/// [`Request::content_type()`]. If the request didn't specify a
|
||||||
/// Content-Type, the request is forwarded with a 404 Not Found status.
|
/// Content-Type, the request is forwarded with a 500 Internal Server Error
|
||||||
|
/// status.
|
||||||
///
|
///
|
||||||
/// * **IpAddr**
|
/// * **&ContentType**
|
||||||
|
///
|
||||||
|
/// Extracts the [`Accept`] from the incoming request via
|
||||||
|
/// [`Request::accept()`]. If the request didn't specify an `Accept`, the
|
||||||
|
/// request is forwarded with a 500 Internal Server Error status.
|
||||||
|
///
|
||||||
|
/// * ***IpAddr**
|
||||||
///
|
///
|
||||||
/// Extracts the client ip address of the incoming request as an [`IpAddr`]
|
/// Extracts the client ip address of the incoming request as an [`IpAddr`]
|
||||||
/// via [`Request::client_ip()`]. If the client's IP address is not known,
|
/// via [`Request::client_ip()`]. If the client's IP address is not known,
|
||||||
/// the request is forwarded with a 404 Not Found status.
|
/// the request is forwarded with a 500 Internal Server Error status.
|
||||||
///
|
///
|
||||||
/// * **SocketAddr**
|
/// * **SocketAddr**
|
||||||
///
|
///
|
||||||
/// Extracts the remote address of the incoming request as a [`SocketAddr`]
|
/// Extracts the remote address of the incoming request as a [`SocketAddr`]
|
||||||
/// via [`Request::remote()`]. If the remote address is not known, the
|
/// via [`Request::remote()`]. If the remote address is not known, the
|
||||||
/// request is forwarded with a 404 Not Found status.
|
/// request is forwarded with a 500 Internal Server Error status.
|
||||||
///
|
///
|
||||||
/// * **Option<T>** _where_ **T: FromRequest**
|
/// * **Option<T>** _where_ **T: FromRequest**
|
||||||
///
|
///
|
||||||
|
@ -422,7 +408,7 @@ impl<'r> FromRequest<'r> for &'r Host<'r> {
|
||||||
async fn from_request(request: &'r Request<'_>) -> Outcome<Self, Infallible> {
|
async fn from_request(request: &'r Request<'_>) -> Outcome<Self, Infallible> {
|
||||||
match request.host() {
|
match request.host() {
|
||||||
Some(host) => Success(host),
|
Some(host) => Success(host),
|
||||||
None => Forward(Status::NotFound)
|
None => Forward(Status::InternalServerError)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -455,7 +441,7 @@ impl<'r> FromRequest<'r> for &'r Accept {
|
||||||
async fn from_request(request: &'r Request<'_>) -> Outcome<Self, Infallible> {
|
async fn from_request(request: &'r Request<'_>) -> Outcome<Self, Infallible> {
|
||||||
match request.accept() {
|
match request.accept() {
|
||||||
Some(accept) => Success(accept),
|
Some(accept) => Success(accept),
|
||||||
None => Forward(Status::NotFound)
|
None => Forward(Status::InternalServerError)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -467,7 +453,7 @@ impl<'r> FromRequest<'r> for &'r ContentType {
|
||||||
async fn from_request(request: &'r Request<'_>) -> Outcome<Self, Infallible> {
|
async fn from_request(request: &'r Request<'_>) -> Outcome<Self, Infallible> {
|
||||||
match request.content_type() {
|
match request.content_type() {
|
||||||
Some(content_type) => Success(content_type),
|
Some(content_type) => Success(content_type),
|
||||||
None => Forward(Status::NotFound)
|
None => Forward(Status::InternalServerError)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -479,7 +465,7 @@ impl<'r> FromRequest<'r> for IpAddr {
|
||||||
async fn from_request(request: &'r Request<'_>) -> Outcome<Self, Infallible> {
|
async fn from_request(request: &'r Request<'_>) -> Outcome<Self, Infallible> {
|
||||||
match request.client_ip() {
|
match request.client_ip() {
|
||||||
Some(addr) => Success(addr),
|
Some(addr) => Success(addr),
|
||||||
None => Forward(Status::NotFound)
|
None => Forward(Status::InternalServerError)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -491,7 +477,7 @@ impl<'r> FromRequest<'r> for SocketAddr {
|
||||||
async fn from_request(request: &'r Request<'_>) -> Outcome<Self, Infallible> {
|
async fn from_request(request: &'r Request<'_>) -> Outcome<Self, Infallible> {
|
||||||
match request.remote() {
|
match request.remote() {
|
||||||
Some(addr) => Success(addr),
|
Some(addr) => Success(addr),
|
||||||
None => Forward(Status::NotFound)
|
None => Forward(Status::InternalServerError)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -259,7 +259,7 @@ impl<'r> FromRequest<'r> for FlashMessage<'r> {
|
||||||
Ok(i) if i <= kv.len() => Ok(Flash::named(&kv[..i], &kv[i..], req)),
|
Ok(i) if i <= kv.len() => Ok(Flash::named(&kv[..i], &kv[i..], req)),
|
||||||
_ => Err(())
|
_ => Err(())
|
||||||
}
|
}
|
||||||
}).into_outcome(Status::BadRequest)
|
}).or_error(Status::BadRequest)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -218,31 +218,6 @@ impl<'r, 'o: 'r> Outcome<'o> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return the `Outcome` of response to `req` from `responder`.
|
|
||||||
///
|
|
||||||
/// If the responder returns `Ok`, an outcome of `Success` is returned with
|
|
||||||
/// the response. If the responder returns `Err`, an outcome of `Forward`
|
|
||||||
/// with a status of `404 Not Found` is returned.
|
|
||||||
///
|
|
||||||
/// # Example
|
|
||||||
///
|
|
||||||
/// ```rust
|
|
||||||
/// use rocket::{Request, Data, route};
|
|
||||||
///
|
|
||||||
/// fn str_responder<'r>(req: &'r Request, data: Data<'r>) -> route::Outcome<'r> {
|
|
||||||
/// route::Outcome::from_or_forward(req, data, "Hello, world!")
|
|
||||||
/// }
|
|
||||||
/// ```
|
|
||||||
#[inline]
|
|
||||||
pub fn from_or_forward<R>(req: &'r Request<'_>, data: Data<'r>, responder: R) -> Outcome<'r>
|
|
||||||
where R: Responder<'r, 'o>
|
|
||||||
{
|
|
||||||
match responder.respond_to(req) {
|
|
||||||
Ok(response) => Outcome::Success(response),
|
|
||||||
Err(_) => Outcome::Forward((data, Status::NotFound))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Return an `Outcome` of `Error` with the status code `code`. This is
|
/// Return an `Outcome` of `Error` with the status code `code`. This is
|
||||||
/// equivalent to `Outcome::Error(code)`.
|
/// equivalent to `Outcome::Error(code)`.
|
||||||
///
|
///
|
||||||
|
@ -263,8 +238,8 @@ impl<'r, 'o: 'r> Outcome<'o> {
|
||||||
Outcome::Error(code)
|
Outcome::Error(code)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return an `Outcome` of `Forward` with the data `data`. This is
|
/// Return an `Outcome` of `Forward` with the data `data` and status
|
||||||
/// equivalent to `Outcome::Forward((data, Status::NotFound))`.
|
/// `status`. This is equivalent to `Outcome::Forward((data, status))`.
|
||||||
///
|
///
|
||||||
/// This method exists to be used during manual routing.
|
/// This method exists to be used during manual routing.
|
||||||
///
|
///
|
||||||
|
@ -272,14 +247,15 @@ impl<'r, 'o: 'r> Outcome<'o> {
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ```rust
|
||||||
/// use rocket::{Request, Data, route};
|
/// use rocket::{Request, Data, route};
|
||||||
|
/// use rocket::http::Status;
|
||||||
///
|
///
|
||||||
/// fn always_forward<'r>(_: &'r Request, data: Data<'r>) -> route::Outcome<'r> {
|
/// fn always_forward<'r>(_: &'r Request, data: Data<'r>) -> route::Outcome<'r> {
|
||||||
/// route::Outcome::forward(data)
|
/// route::Outcome::forward(data, Status::InternalServerError)
|
||||||
/// }
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn forward(data: Data<'r>) -> Outcome<'r> {
|
pub fn forward(data: Data<'r>, status: Status) -> Outcome<'r> {
|
||||||
Outcome::Forward((data, Status::NotFound))
|
Outcome::Forward((data, status))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -334,7 +334,7 @@ impl Rocket<Orbit> {
|
||||||
// Check if the request processing completed (Some) or if the
|
// Check if the request processing completed (Some) or if the
|
||||||
// request needs to be forwarded. If it does, continue the loop
|
// request needs to be forwarded. If it does, continue the loop
|
||||||
// (None) to try again.
|
// (None) to try again.
|
||||||
info_!("{} {}", "Outcome:".primary().bold(), outcome);
|
info_!("{}", outcome.log_display());
|
||||||
match outcome {
|
match outcome {
|
||||||
o@Outcome::Success(_) | o@Outcome::Error(_) => return o,
|
o@Outcome::Success(_) | o@Outcome::Error(_) => return o,
|
||||||
Outcome::Forward(forwarded) => (data, status) = forwarded,
|
Outcome::Forward(forwarded) => (data, status) = forwarded,
|
||||||
|
|
|
@ -46,7 +46,15 @@ fn forced_error() {
|
||||||
fn test_hello_invalid_age() {
|
fn test_hello_invalid_age() {
|
||||||
let client = Client::tracked(super::rocket()).unwrap();
|
let client = Client::tracked(super::rocket()).unwrap();
|
||||||
|
|
||||||
for path in &["Ford/-129", "Trillian/128", "foo/bar/baz"] {
|
for path in &["Ford/-129", "Trillian/128"] {
|
||||||
|
let request = client.get(format!("/hello/{}", path));
|
||||||
|
let expected = super::default_catcher(Status::UnprocessableEntity, request.inner());
|
||||||
|
let response = request.dispatch();
|
||||||
|
assert_eq!(response.status(), Status::UnprocessableEntity);
|
||||||
|
assert_eq!(response.into_string().unwrap(), expected.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
for path in &["foo/bar/baz"] {
|
||||||
let request = client.get(format!("/hello/{}", path));
|
let request = client.get(format!("/hello/{}", path));
|
||||||
let expected = super::hello_not_found(request.inner());
|
let expected = super::hello_not_found(request.inner());
|
||||||
let response = request.dispatch();
|
let response = request.dispatch();
|
||||||
|
@ -59,7 +67,15 @@ fn test_hello_invalid_age() {
|
||||||
fn test_hello_sergio() {
|
fn test_hello_sergio() {
|
||||||
let client = Client::tracked(super::rocket()).unwrap();
|
let client = Client::tracked(super::rocket()).unwrap();
|
||||||
|
|
||||||
for path in &["oops", "-129", "foo/bar", "/foo/bar/baz"] {
|
for path in &["oops", "-129"] {
|
||||||
|
let request = client.get(format!("/hello/Sergio/{}", path));
|
||||||
|
let expected = super::sergio_error();
|
||||||
|
let response = request.dispatch();
|
||||||
|
assert_eq!(response.status(), Status::UnprocessableEntity);
|
||||||
|
assert_eq!(response.into_string().unwrap(), expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
for path in &["foo/bar", "/foo/bar/baz"] {
|
||||||
let request = client.get(format!("/hello/Sergio/{}", path));
|
let request = client.get(format!("/hello/Sergio/{}", path));
|
||||||
let expected = super::sergio_error();
|
let expected = super::sergio_error();
|
||||||
let response = request.dispatch();
|
let response = request.dispatch();
|
||||||
|
|
|
@ -62,10 +62,10 @@ fn wave() {
|
||||||
let response = client.get(uri).dispatch();
|
let response = client.get(uri).dispatch();
|
||||||
assert_eq!(response.into_string().unwrap(), expected);
|
assert_eq!(response.into_string().unwrap(), expected);
|
||||||
|
|
||||||
for bad_age in &["1000", "-1", "bird", "?"] {
|
for bad_age in &["1000", "-1", "bird"] {
|
||||||
let bad_uri = format!("/wave/{}/{}", name, bad_age);
|
let bad_uri = format!("/wave/{}/{}", name, bad_age);
|
||||||
let response = client.get(bad_uri).dispatch();
|
let response = client.get(bad_uri).dispatch();
|
||||||
assert_eq!(response.status(), Status::NotFound);
|
assert_eq!(response.status(), Status::UnprocessableEntity);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,7 +9,7 @@ use rocket::outcome::{try_outcome, IntoOutcome};
|
||||||
use rocket::tokio::fs::File;
|
use rocket::tokio::fs::File;
|
||||||
|
|
||||||
fn forward<'r>(_req: &'r Request, data: Data<'r>) -> route::BoxFuture<'r> {
|
fn forward<'r>(_req: &'r Request, data: Data<'r>) -> route::BoxFuture<'r> {
|
||||||
Box::pin(async move { route::Outcome::forward(data) })
|
Box::pin(async move { route::Outcome::forward(data, Status::NotFound) })
|
||||||
}
|
}
|
||||||
|
|
||||||
fn hi<'r>(req: &'r Request, _: Data<'r>) -> route::BoxFuture<'r> {
|
fn hi<'r>(req: &'r Request, _: Data<'r>) -> route::BoxFuture<'r> {
|
||||||
|
@ -27,7 +27,7 @@ fn name<'r>(req: &'r Request, _: Data<'r>) -> route::BoxFuture<'r> {
|
||||||
fn echo_url<'r>(req: &'r Request, _: Data<'r>) -> route::BoxFuture<'r> {
|
fn echo_url<'r>(req: &'r Request, _: Data<'r>) -> route::BoxFuture<'r> {
|
||||||
let param_outcome = req.param::<&str>(1)
|
let param_outcome = req.param::<&str>(1)
|
||||||
.and_then(Result::ok)
|
.and_then(Result::ok)
|
||||||
.into_outcome(Status::BadRequest);
|
.or_error(Status::BadRequest);
|
||||||
|
|
||||||
Box::pin(async move {
|
Box::pin(async move {
|
||||||
route::Outcome::from(req, try_outcome!(param_outcome))
|
route::Outcome::from(req, try_outcome!(param_outcome))
|
||||||
|
|
|
@ -34,8 +34,7 @@ fn json_bad_get_put() {
|
||||||
|
|
||||||
// Try to get a message with an invalid ID.
|
// Try to get a message with an invalid ID.
|
||||||
let res = client.get("/json/hi").header(ContentType::JSON).dispatch();
|
let res = client.get("/json/hi").header(ContentType::JSON).dispatch();
|
||||||
assert_eq!(res.status(), Status::NotFound);
|
assert_eq!(res.status(), Status::UnprocessableEntity);
|
||||||
assert!(res.into_string().unwrap().contains("error"));
|
|
||||||
|
|
||||||
// Try to put a message without a proper body.
|
// Try to put a message without a proper body.
|
||||||
let res = client.put("/json/80").header(ContentType::JSON).dispatch();
|
let res = client.put("/json/80").header(ContentType::JSON).dispatch();
|
||||||
|
@ -134,5 +133,5 @@ fn uuid() {
|
||||||
}
|
}
|
||||||
|
|
||||||
let res = client.get("/people/not-a-uuid").dispatch();
|
let res = client.get("/people/not-a-uuid").dispatch();
|
||||||
assert_eq!(res.status(), Status::NotFound);
|
assert_eq!(res.status(), Status::UnprocessableEntity);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue