Skip to content

Commit

Permalink
Refactor Rejection system
Browse files Browse the repository at this point in the history
- Improved performance of "known" rejections, by not needing to store
  them as `Box<dyn std::error::Error>` in order to satisfy
  `Rejection::cause()`.
- `reject::custom()` no longer requires `std::error::Error`, just
  `Reject + Debug`. This should make it less annoying to construct new
  custom rejections.
- Removed deprecated features:
  - `Rejection::cause()`
  - `Rejection::into_cause()`
  - `Rejection::status()`
  - `Rejection::with()`
  - `Rejection::json()`
  - `impl Serialize for Rejection`
  - `reject::bad_request()`
  - `reject::forbidden()`
  - `reject::server_error()`
- Removed `path::param2()`.
  • Loading branch information
seanmonstar committed Nov 12, 2019
1 parent 8e92124 commit f2b1c56
Show file tree
Hide file tree
Showing 13 changed files with 280 additions and 474 deletions.
80 changes: 34 additions & 46 deletions examples/errors.rs
@@ -1,44 +1,36 @@
#![deny(warnings)]

use std::error::Error as StdError;
use std::fmt::{self, Display};

use serde_derive::Serialize;
use warp::http::StatusCode;
use warp::{Future, Filter, Rejection, Reply};
use warp::{reject, Filter, Rejection, Reply};

#[derive(Copy, Clone, Debug)]
/// A custom `Reject` type.
#[derive(Debug)]
enum Error {
Oops,
Nope,
}

impl reject::Reject for Error {}

/// A serialized message to report in JSON format.
#[derive(Serialize)]
struct ErrorMessage {
struct ErrorMessage<'a> {
code: u16,
message: String,
}

impl Display for Error {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.write_str(match self {
Error::Oops => ":fire: this is fine",
Error::Nope => "Nope!",
})
}
message: &'a str,
}

impl StdError for Error {}

#[tokio::main]
async fn main() {
let hello = warp::path::end().map(warp::reply);

let oops =
warp::path("oops").and_then(|| futures::future::err::<StatusCode, _>(warp::reject::custom(Error::Oops)));
let oops = warp::path("oops").and_then(|| async {
Err::<StatusCode, _>(reject::custom(Error::Oops))
});

let nope =
warp::path("nope").and_then(|| futures::future::err::<StatusCode, _>(warp::reject::custom(Error::Nope)));
let nope = warp::path("nope").and_then(|| async {
Err::<StatusCode, _>(reject::custom(Error::Nope))
});

let routes = warp::get()
.and(hello.or(oops).or(nope))
Expand All @@ -49,34 +41,30 @@ async fn main() {

// This function receives a `Rejection` and tries to return a custom
// value, othewise simply passes the rejection along.
fn customize_error(err: Rejection) -> impl Future< Output = Result<impl Reply, Rejection>> {
let err = {
if let Some(&err) = err.find_cause::<Error>() {
let code = match err {
Error::Nope => StatusCode::BAD_REQUEST,
Error::Oops => StatusCode::INTERNAL_SERVER_ERROR,
};
let msg = err.to_string();
async fn customize_error(err: Rejection) -> Result<impl Reply, Rejection> {
if let Some(err) = err.find::<Error>() {
let (code, msg) = match err {
Error::Nope => (StatusCode::BAD_REQUEST, "Nope!"),
Error::Oops => (StatusCode::INTERNAL_SERVER_ERROR, ":fire: this is fine"),
};

let json = warp::reply::json(&ErrorMessage {
code: code.as_u16(),
message: msg,
});
Ok(warp::reply::with_status(json, code))
} else if let Some(_) = err.find_cause::<warp::reject::MethodNotAllowed>() {
// We can handle a specific error, here METHOD_NOT_ALLOWED,
// and render it however we want
let code = StatusCode::METHOD_NOT_ALLOWED;
let json = warp::reply::json(&ErrorMessage {
code: code.as_u16(),
message: "oops, you aren't allowed to use this method.".into(),
});
Ok(warp::reply::with_status(json, code))
} else {
// Could be a NOT_FOUND, or any other internal error... here we just
// let warp use its default rendering.
Err(err)
}
};
futures::future::ready(err)
} else if let Some(_) = err.find::<warp::reject::MethodNotAllowed>() {
// We can handle a specific error, here METHOD_NOT_ALLOWED,
// and render it however we want
let code = StatusCode::METHOD_NOT_ALLOWED;
let json = warp::reply::json(&ErrorMessage {
code: code.as_u16(),
message: "oops, you aren't allowed to use this method.".into(),
});
Ok(warp::reply::with_status(json, code))
} else {
// Could be a NOT_FOUND, or any other internal error... here we just
// let warp use its default rendering.
Err(err)
}
}
2 changes: 1 addition & 1 deletion examples/futures.rs
Expand Up @@ -23,7 +23,7 @@ impl FromStr for Seconds {

#[tokio::main]
async fn main() {
// Match `/:u32`...
// Match `/:Seconds`...
let routes = warp::path::param()
// and_then create a `Future` that will simply wait N seconds...
.and_then(|Seconds(seconds): Seconds| async move {
Expand Down
12 changes: 8 additions & 4 deletions examples/sse_chat.rs
@@ -1,4 +1,4 @@
use futures::{future, Stream, StreamExt};
use futures::{Stream, StreamExt};
use std::collections::HashMap;
use std::sync::{
atomic::{AtomicUsize, Ordering},
Expand All @@ -16,6 +16,10 @@ enum Message {
Reply(String),
}

#[derive(Debug)]
struct NotUtf8;
impl warp::reject::Reject for NotUtf8 {}

/// Our state of currently connected users.
///
/// - Key is their id
Expand All @@ -37,10 +41,10 @@ async fn main() {
.and(warp::post())
.and(warp::path::param::<usize>())
.and(warp::body::content_length_limit(500))
.and(warp::body::concat().and_then(|body: warp::body::FullBody| {
future::ready(std::str::from_utf8(body.bytes())
.and(warp::body::concat().and_then(|body: warp::body::FullBody| async move {
std::str::from_utf8(body.bytes())
.map(String::from)
.map_err(warp::reject::custom))
.map_err(|_e| warp::reject::custom(NotUtf8))
}))
.and(users.clone())
.map(|my_id, msg, users| {
Expand Down
4 changes: 2 additions & 2 deletions src/filter/map_err.rs
Expand Up @@ -6,7 +6,7 @@ use pin_project::pin_project;
use futures::TryFuture;

use super::{Filter, FilterBase};
use crate::reject::Reject;
use crate::reject::IsReject;

#[derive(Clone, Copy, Debug)]
pub struct MapErr<T, F> {
Expand All @@ -18,7 +18,7 @@ impl<T, F, E> FilterBase for MapErr<T, F>
where
T: Filter,
F: Fn(T::Error) -> E + Clone + Send,
E: Reject,
E: IsReject,
{
type Extract = T::Extract;
type Error = E;
Expand Down
10 changes: 5 additions & 5 deletions src/filter/mod.rs
Expand Up @@ -17,7 +17,7 @@ use std::future::Future;
use futures::{future, TryFuture, TryFutureExt};

pub(crate) use crate::generic::{one, Combine, Either, Func, HList, One, Tuple};
use crate::reject::{CombineRejection, Reject, Rejection};
use crate::reject::{CombineRejection, IsReject, Rejection};
use crate::route::{self, Route};

pub(crate) use self::and::And;
Expand All @@ -36,7 +36,7 @@ pub(crate) use self::wrap::{Wrap, WrapSealed};
// signatures without it being a breaking change.
pub trait FilterBase {
type Extract: Tuple; // + Send;
type Error: Reject;
type Error: IsReject;
type Future: Future<Output = Result<Self::Extract, Self::Error>> + Send;

fn filter(&self) -> Self::Future;
Expand Down Expand Up @@ -416,7 +416,7 @@ where
F: Fn(&mut Route) -> U,
U: TryFuture,
U::Ok: Tuple,
U::Error: Reject,
U::Error: IsReject,
{
FilterFn { func }
}
Expand All @@ -427,7 +427,7 @@ pub(crate) fn filter_fn_one<F, U>(
where
F: Fn(&mut Route) -> U + Copy,
U: TryFuture,
U::Error: Reject,
U::Error: IsReject,
{
filter_fn(move |route| func(route).map_ok(tup_one as _))
}
Expand All @@ -448,7 +448,7 @@ where
F: Fn(&mut Route) -> U,
U: TryFuture + Send + 'static,
U::Ok: Tuple + Send,
U::Error: Reject,
U::Error: IsReject,
{
type Extract = U::Ok;
type Error = U::Error;
Expand Down
12 changes: 6 additions & 6 deletions src/filter/service.rs
Expand Up @@ -6,7 +6,7 @@ use std::future::Future;
use pin_project::pin_project;
use futures::future::TryFuture;

use crate::reject::Reject;
use crate::reject::IsReject;
use crate::reply::Reply;
use crate::route::{self, Route};
use crate::server::{IntoWarpService, WarpService};
Expand All @@ -21,7 +21,7 @@ impl<F> WarpService for FilteredService<F>
where
F: Filter,
<F::Future as TryFuture>::Ok: Reply,
<F::Future as TryFuture>::Error: Reject,
<F::Future as TryFuture>::Error: IsReject,
{
type Reply = FilteredFuture<F::Future>;

Expand Down Expand Up @@ -66,8 +66,8 @@ impl<F> IntoWarpService for FilteredService<F>
where
F: Filter + Send + Sync + 'static,
F::Extract: Reply,
F::Error: Reject,
{
F::Error: IsReject,
{
type Service = FilteredService<F>;

#[inline]
Expand All @@ -80,8 +80,8 @@ impl<F> IntoWarpService for F
where
F: Filter + Send + Sync + 'static,
F::Extract: Reply,
F::Error: Reject,
{
F::Error: IsReject,
{
type Service = FilteredService<F>;

#[inline]
Expand Down
10 changes: 5 additions & 5 deletions src/filters/log.rs
Expand Up @@ -8,7 +8,7 @@ use http::{self, header, StatusCode};
use tokio::clock;

use crate::filter::{Filter, WrapSealed};
use crate::reject::Reject;
use crate::reject::IsReject;
use crate::reply::Reply;
use crate::route::Route;

Expand Down Expand Up @@ -97,7 +97,7 @@ where
FN: Fn(Info) + Clone + Send,
F: Filter + Clone + Send,
F::Extract: Reply,
F::Error: Reject,
F::Error: IsReject,
{
type Wrapped = WithLog<FN, F>;

Expand Down Expand Up @@ -188,7 +188,7 @@ mod internal {

use super::{Info, Log};
use crate::filter::{Filter, FilterBase};
use crate::reject::Reject;
use crate::reject::IsReject;
use crate::reply::{Reply, Response};
use crate::route;

Expand All @@ -214,7 +214,7 @@ mod internal {
FN: Fn(Info) + Clone + Send,
F: Filter + Clone + Send,
F::Extract: Reply,
F::Error: Reject,
F::Error: IsReject,
{
type Extract = (Logged,);
type Error = F::Error;
Expand Down Expand Up @@ -244,7 +244,7 @@ mod internal {
FN: Fn(Info),
F: TryFuture,
F::Ok: Reply,
F::Error: Reject,
F::Error: IsReject,
{
type Output = Result<(Logged,), F::Error>;

Expand Down
36 changes: 0 additions & 36 deletions src/filters/path.rs
Expand Up @@ -238,42 +238,6 @@ pub fn param<T: FromStr + Send + 'static>() -> impl Filter<Extract = One<T>, Err
})
}

/// Extract a parameter from a path segment.
///
/// This will try to parse a value from the current request path
/// segment, and if successful, the value is returned as the `Filter`'s
/// "extracted" value.
///
/// If the value could not be parsed, rejects with a `404 Not Found`. In
/// contrast of `param` method, it reports an error cause in response.
///
/// # Example
///
/// ```
/// use warp::Filter;
///
/// let route = warp::path::param2()
/// .map(|id: u32| {
/// format!("You asked for /{}", id)
/// });
/// ```
pub fn param2<T>() -> impl Filter<Extract = One<T>, Error = Rejection> + Copy
where
T: FromStr + Send + 'static,
T::Err: Into<crate::reject::Cause>,
{
segment(|seg| {
log::trace!("param?: {:?}", seg);
if seg.is_empty() {
return Err(reject::not_found());
}
T::from_str(seg).map(one).map_err(|err| {
#[allow(deprecated)]
reject::not_found().with(err.into())
})
})
}

/// Extract the unmatched tail of the path.
///
/// This will return a `Tail`, which allows access to the rest of the path
Expand Down
10 changes: 2 additions & 8 deletions src/filters/sse.rs
Expand Up @@ -304,10 +304,7 @@ where
header::header("last-event-id")
.map(Some)
.or_else(|rejection: Rejection| {
if rejection
.find_cause::<crate::reject::MissingHeader>()
.is_some()
{
if rejection.find::<crate::reject::MissingHeader>().is_some() {
return future::ok((None,));
}
future::err(rejection)
Expand Down Expand Up @@ -336,10 +333,7 @@ pub fn sse() -> impl Filter<Extract = One<Sse>, Error = Rejection> + Copy {
.and(
header::exact_ignore_case("connection", "keep-alive").or_else(
|rejection: Rejection| {
if rejection
.find_cause::<crate::reject::MissingHeader>()
.is_some()
{
if rejection.find::<crate::reject::MissingHeader>().is_some() {
return future::ok(());
}
future::err(rejection)
Expand Down

0 comments on commit f2b1c56

Please sign in to comment.