1 //! Rejections
2 //!
3 //! Part of the power of the [`Filter`](../trait.Filter.html) system is being able to
4 //! reject a request from a filter chain. This allows for filters to be
5 //! combined with `or`, so that if one side of the chain finds that a request
6 //! doesn't fulfill its requirements, the other side can try to process
7 //! the request.
8 //!
9 //! Many of the built-in [`filters`](../filters) will automatically reject
10 //! the request with an appropriate rejection. However, you can also build
11 //! new custom [`Filter`](../trait.Filter.html)s and still want other routes to be
12 //! matchable in the case a predicate doesn't hold.
13 //!
14 //! # Example
15 //!
16 //! ```
17 //! use warp::Filter;
18 //!
19 //! // Filter on `/:id`, but reject with 404 if the `id` is `0`.
20 //! let route = warp::path::param()
21 //!     .and_then(|id: u32| async move {
22 //!         if id == 0 {
23 //!             Err(warp::reject::not_found())
24 //!         } else {
25 //!             Ok("something since id is valid")
26 //!         }
27 //!     });
28 //! ```
29 
30 use std::any::Any;
31 use std::convert::Infallible;
32 use std::error::Error as StdError;
33 use std::fmt;
34 
35 use http::{
36     self,
37     header::{HeaderValue, CONTENT_TYPE},
38     StatusCode,
39 };
40 use hyper::Body;
41 
42 pub(crate) use self::sealed::{CombineRejection, IsReject};
43 
44 /// Rejects a request with `404 Not Found`.
45 #[inline]
reject() -> Rejection46 pub fn reject() -> Rejection {
47     not_found()
48 }
49 
50 /// Rejects a request with `404 Not Found`.
51 #[inline]
not_found() -> Rejection52 pub fn not_found() -> Rejection {
53     Rejection {
54         reason: Reason::NotFound,
55     }
56 }
57 
58 // 400 Bad Request
59 #[inline]
invalid_query() -> Rejection60 pub(crate) fn invalid_query() -> Rejection {
61     known(InvalidQuery { _p: () })
62 }
63 
64 // 400 Bad Request
65 #[inline]
missing_header(name: &'static str) -> Rejection66 pub(crate) fn missing_header(name: &'static str) -> Rejection {
67     known(MissingHeader { name })
68 }
69 
70 // 400 Bad Request
71 #[inline]
invalid_header(name: &'static str) -> Rejection72 pub(crate) fn invalid_header(name: &'static str) -> Rejection {
73     known(InvalidHeader { name })
74 }
75 
76 // 400 Bad Request
77 #[inline]
missing_cookie(name: &'static str) -> Rejection78 pub(crate) fn missing_cookie(name: &'static str) -> Rejection {
79     known(MissingCookie { name })
80 }
81 
82 // 405 Method Not Allowed
83 #[inline]
method_not_allowed() -> Rejection84 pub(crate) fn method_not_allowed() -> Rejection {
85     known(MethodNotAllowed { _p: () })
86 }
87 
88 // 411 Length Required
89 #[inline]
length_required() -> Rejection90 pub(crate) fn length_required() -> Rejection {
91     known(LengthRequired { _p: () })
92 }
93 
94 // 413 Payload Too Large
95 #[inline]
payload_too_large() -> Rejection96 pub(crate) fn payload_too_large() -> Rejection {
97     known(PayloadTooLarge { _p: () })
98 }
99 
100 // 415 Unsupported Media Type
101 //
102 // Used by the body filters if the request payload content-type doesn't match
103 // what can be deserialized.
104 #[inline]
unsupported_media_type() -> Rejection105 pub(crate) fn unsupported_media_type() -> Rejection {
106     known(UnsupportedMediaType { _p: () })
107 }
108 
109 /// Rejects a request with a custom cause.
110 ///
111 /// A [`recover`][] filter should convert this `Rejection` into a `Reply`,
112 /// or else this will be returned as a `500 Internal Server Error`.
113 ///
114 /// [`recover`]: ../trait.Filter.html#method.recover
custom<T: Reject>(err: T) -> Rejection115 pub fn custom<T: Reject>(err: T) -> Rejection {
116     Rejection::custom(Box::new(err))
117 }
118 
119 /// Protect against re-rejecting a rejection.
120 ///
121 /// ```compile_fail
122 /// fn with(r: warp::Rejection) {
123 ///     let _wat = warp::reject::custom(r);
124 /// }
125 /// ```
__reject_custom_compilefail()126 fn __reject_custom_compilefail() {}
127 
128 /// A marker trait to ensure proper types are used for custom rejections.
129 ///
130 /// # Example
131 ///
132 /// ```
133 /// use warp::{Filter, reject::Reject};
134 ///
135 /// #[derive(Debug)]
136 /// struct RateLimited;
137 ///
138 /// impl Reject for RateLimited {}
139 ///
140 /// let route = warp::any().and_then(|| async {
141 ///     Err::<(), _>(warp::reject::custom(RateLimited))
142 /// });
143 /// ```
144 // Require `Sized` for now to prevent passing a `Box<dyn Reject>`, since we
145 // would be double-boxing it, and the downcasting wouldn't work as expected.
146 pub trait Reject: fmt::Debug + Sized + Send + Sync + 'static {}
147 
148 trait Cause: fmt::Debug + Send + Sync + 'static {
as_any(&self) -> &dyn Any149     fn as_any(&self) -> &dyn Any;
150 }
151 
152 impl<T> Cause for T
153 where
154     T: fmt::Debug + Send + Sync + 'static,
155 {
as_any(&self) -> &dyn Any156     fn as_any(&self) -> &dyn Any {
157         self
158     }
159 }
160 
161 impl dyn Cause {
downcast_ref<T: Any>(&self) -> Option<&T>162     fn downcast_ref<T: Any>(&self) -> Option<&T> {
163         self.as_any().downcast_ref::<T>()
164     }
165 }
166 
known<T: Into<Known>>(err: T) -> Rejection167 pub(crate) fn known<T: Into<Known>>(err: T) -> Rejection {
168     Rejection::known(err.into())
169 }
170 
171 /// Rejection of a request by a [`Filter`](crate::Filter).
172 ///
173 /// See the [`reject`](module@crate::reject) documentation for more.
174 pub struct Rejection {
175     reason: Reason,
176 }
177 
178 enum Reason {
179     NotFound,
180     Other(Box<Rejections>),
181 }
182 
183 enum Rejections {
184     Known(Known),
185     Custom(Box<dyn Cause>),
186     Combined(Box<Rejections>, Box<Rejections>),
187 }
188 
189 macro_rules! enum_known {
190      ($($(#[$attr:meta])* $var:ident($ty:path),)+) => (
191         pub(crate) enum Known {
192             $(
193             $(#[$attr])*
194             $var($ty),
195             )+
196         }
197 
198         impl Known {
199             fn inner_as_any(&self) -> &dyn Any {
200                 match *self {
201                     $(
202                     $(#[$attr])*
203                     Known::$var(ref t) => t,
204                     )+
205                 }
206             }
207         }
208 
209         impl fmt::Debug for Known {
210             fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
211                 match *self {
212                     $(
213                     $(#[$attr])*
214                     Known::$var(ref t) => t.fmt(f),
215                     )+
216                 }
217             }
218         }
219 
220         impl fmt::Display for Known {
221             fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
222                 match *self {
223                     $(
224                     $(#[$attr])*
225                     Known::$var(ref t) => t.fmt(f),
226                     )+
227                 }
228             }
229         }
230 
231         $(
232         #[doc(hidden)]
233         $(#[$attr])*
234         impl From<$ty> for Known {
235             fn from(ty: $ty) -> Known {
236                 Known::$var(ty)
237             }
238         }
239         )+
240     );
241 }
242 
243 enum_known! {
244     MethodNotAllowed(MethodNotAllowed),
245     InvalidHeader(InvalidHeader),
246     MissingHeader(MissingHeader),
247     MissingCookie(MissingCookie),
248     InvalidQuery(InvalidQuery),
249     LengthRequired(LengthRequired),
250     PayloadTooLarge(PayloadTooLarge),
251     UnsupportedMediaType(UnsupportedMediaType),
252     FileOpenError(crate::fs::FileOpenError),
253     FilePermissionError(crate::fs::FilePermissionError),
254     BodyReadError(crate::body::BodyReadError),
255     BodyDeserializeError(crate::body::BodyDeserializeError),
256     CorsForbidden(crate::cors::CorsForbidden),
257     #[cfg(feature = "websocket")]
258     MissingConnectionUpgrade(crate::ws::MissingConnectionUpgrade),
259     MissingExtension(crate::ext::MissingExtension),
260     BodyConsumedMultipleTimes(crate::body::BodyConsumedMultipleTimes),
261 }
262 
263 impl Rejection {
known(known: Known) -> Self264     fn known(known: Known) -> Self {
265         Rejection {
266             reason: Reason::Other(Box::new(Rejections::Known(known))),
267         }
268     }
269 
custom(other: Box<dyn Cause>) -> Self270     fn custom(other: Box<dyn Cause>) -> Self {
271         Rejection {
272             reason: Reason::Other(Box::new(Rejections::Custom(other))),
273         }
274     }
275 
276     /// Searches this `Rejection` for a specific cause.
277     ///
278     /// A `Rejection` will accumulate causes over a `Filter` chain. This method
279     /// can search through them and return the first cause of this type.
280     ///
281     /// # Example
282     ///
283     /// ```
284     /// #[derive(Debug)]
285     /// struct Nope;
286     ///
287     /// impl warp::reject::Reject for Nope {}
288     ///
289     /// let reject = warp::reject::custom(Nope);
290     ///
291     /// if let Some(nope) = reject.find::<Nope>() {
292     ///    println!("found it: {:?}", nope);
293     /// }
294     /// ```
find<T: 'static>(&self) -> Option<&T>295     pub fn find<T: 'static>(&self) -> Option<&T> {
296         if let Reason::Other(ref rejections) = self.reason {
297             return rejections.find();
298         }
299         None
300     }
301 
302     /// Returns true if this Rejection was made via `warp::reject::not_found`.
303     ///
304     /// # Example
305     ///
306     /// ```
307     /// let rejection = warp::reject();
308     ///
309     /// assert!(rejection.is_not_found());
310     /// ```
is_not_found(&self) -> bool311     pub fn is_not_found(&self) -> bool {
312         if let Reason::NotFound = self.reason {
313             true
314         } else {
315             false
316         }
317     }
318 }
319 
320 impl From<Infallible> for Rejection {
321     #[inline]
from(infallible: Infallible) -> Rejection322     fn from(infallible: Infallible) -> Rejection {
323         match infallible {}
324     }
325 }
326 
327 impl IsReject for Infallible {
status(&self) -> StatusCode328     fn status(&self) -> StatusCode {
329         match *self {}
330     }
331 
into_response(&self) -> crate::reply::Response332     fn into_response(&self) -> crate::reply::Response {
333         match *self {}
334     }
335 }
336 
337 impl IsReject for Rejection {
status(&self) -> StatusCode338     fn status(&self) -> StatusCode {
339         match self.reason {
340             Reason::NotFound => StatusCode::NOT_FOUND,
341             Reason::Other(ref other) => other.status(),
342         }
343     }
344 
into_response(&self) -> crate::reply::Response345     fn into_response(&self) -> crate::reply::Response {
346         match self.reason {
347             Reason::NotFound => {
348                 let mut res = http::Response::default();
349                 *res.status_mut() = StatusCode::NOT_FOUND;
350                 res
351             }
352             Reason::Other(ref other) => other.into_response(),
353         }
354     }
355 }
356 
357 impl fmt::Debug for Rejection {
fmt(&self, f: &mut fmt::Formatter) -> fmt::Result358     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
359         f.debug_tuple("Rejection").field(&self.reason).finish()
360     }
361 }
362 
363 impl fmt::Debug for Reason {
fmt(&self, f: &mut fmt::Formatter) -> fmt::Result364     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
365         match *self {
366             Reason::NotFound => f.write_str("NotFound"),
367             Reason::Other(ref other) => match **other {
368                 Rejections::Known(ref e) => fmt::Debug::fmt(e, f),
369                 Rejections::Custom(ref e) => fmt::Debug::fmt(e, f),
370                 Rejections::Combined(ref a, ref b) => {
371                     let mut list = f.debug_list();
372                     a.debug_list(&mut list);
373                     b.debug_list(&mut list);
374                     list.finish()
375                 }
376             },
377         }
378     }
379 }
380 
381 // ===== Rejections =====
382 
383 impl Rejections {
status(&self) -> StatusCode384     fn status(&self) -> StatusCode {
385         match *self {
386             Rejections::Known(ref k) => match *k {
387                 Known::MethodNotAllowed(_) => StatusCode::METHOD_NOT_ALLOWED,
388                 Known::InvalidHeader(_)
389                 | Known::MissingHeader(_)
390                 | Known::MissingCookie(_)
391                 | Known::InvalidQuery(_)
392                 | Known::BodyReadError(_)
393                 | Known::BodyDeserializeError(_) => StatusCode::BAD_REQUEST,
394                 #[cfg(feature = "websocket")]
395                 Known::MissingConnectionUpgrade(_) => StatusCode::BAD_REQUEST,
396                 Known::LengthRequired(_) => StatusCode::LENGTH_REQUIRED,
397                 Known::PayloadTooLarge(_) => StatusCode::PAYLOAD_TOO_LARGE,
398                 Known::UnsupportedMediaType(_) => StatusCode::UNSUPPORTED_MEDIA_TYPE,
399                 Known::FilePermissionError(_) | Known::CorsForbidden(_) => StatusCode::FORBIDDEN,
400                 Known::FileOpenError(_)
401                 | Known::MissingExtension(_)
402                 | Known::BodyConsumedMultipleTimes(_) => StatusCode::INTERNAL_SERVER_ERROR,
403             },
404             Rejections::Custom(..) => StatusCode::INTERNAL_SERVER_ERROR,
405             Rejections::Combined(ref a, ref b) => preferred(a, b).status(),
406         }
407     }
408 
into_response(&self) -> crate::reply::Response409     fn into_response(&self) -> crate::reply::Response {
410         match *self {
411             Rejections::Known(ref e) => {
412                 let mut res = http::Response::new(Body::from(e.to_string()));
413                 *res.status_mut() = self.status();
414                 res.headers_mut().insert(
415                     CONTENT_TYPE,
416                     HeaderValue::from_static("text/plain; charset=utf-8"),
417                 );
418                 res
419             }
420             Rejections::Custom(ref e) => {
421                 log::error!(
422                     "unhandled custom rejection, returning 500 response: {:?}",
423                     e
424                 );
425                 let body = format!("Unhandled rejection: {:?}", e);
426                 let mut res = http::Response::new(Body::from(body));
427                 *res.status_mut() = self.status();
428                 res.headers_mut().insert(
429                     CONTENT_TYPE,
430                     HeaderValue::from_static("text/plain; charset=utf-8"),
431                 );
432                 res
433             }
434             Rejections::Combined(ref a, ref b) => preferred(a, b).into_response(),
435         }
436     }
437 
find<T: 'static>(&self) -> Option<&T>438     fn find<T: 'static>(&self) -> Option<&T> {
439         match *self {
440             Rejections::Known(ref e) => e.inner_as_any().downcast_ref(),
441             Rejections::Custom(ref e) => e.downcast_ref(),
442             Rejections::Combined(ref a, ref b) => a.find().or_else(|| b.find()),
443         }
444     }
445 
debug_list(&self, f: &mut fmt::DebugList<'_, '_>)446     fn debug_list(&self, f: &mut fmt::DebugList<'_, '_>) {
447         match *self {
448             Rejections::Known(ref e) => {
449                 f.entry(e);
450             }
451             Rejections::Custom(ref e) => {
452                 f.entry(e);
453             }
454             Rejections::Combined(ref a, ref b) => {
455                 a.debug_list(f);
456                 b.debug_list(f);
457             }
458         }
459     }
460 }
461 
preferred<'a>(a: &'a Rejections, b: &'a Rejections) -> &'a Rejections462 fn preferred<'a>(a: &'a Rejections, b: &'a Rejections) -> &'a Rejections {
463     // Compare status codes, with this priority:
464     // - NOT_FOUND is lowest
465     // - METHOD_NOT_ALLOWED is second
466     // - if one status code is greater than the other
467     // - otherwise, prefer A...
468     match (a.status(), b.status()) {
469         (_, StatusCode::NOT_FOUND) => a,
470         (StatusCode::NOT_FOUND, _) => b,
471         (_, StatusCode::METHOD_NOT_ALLOWED) => a,
472         (StatusCode::METHOD_NOT_ALLOWED, _) => b,
473         (sa, sb) if sa < sb => b,
474         _ => a,
475     }
476 }
477 
478 unit_error! {
479     /// Invalid query
480     pub InvalidQuery: "Invalid query string"
481 }
482 
483 unit_error! {
484     /// HTTP method not allowed
485     pub MethodNotAllowed: "HTTP method not allowed"
486 }
487 
488 unit_error! {
489     /// A content-length header is required
490     pub LengthRequired: "A content-length header is required"
491 }
492 
493 unit_error! {
494     /// The request payload is too large
495     pub PayloadTooLarge: "The request payload is too large"
496 }
497 
498 unit_error! {
499     /// The request's content-type is not supported
500     pub UnsupportedMediaType: "The request's content-type is not supported"
501 }
502 
503 /// Missing request header
504 #[derive(Debug)]
505 pub struct MissingHeader {
506     name: &'static str,
507 }
508 
509 impl MissingHeader {
510     /// Retrieve the name of the header that was missing
name(&self) -> &str511     pub fn name(&self) -> &str {
512         self.name
513     }
514 }
515 
516 impl ::std::fmt::Display for MissingHeader {
fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result517     fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
518         write!(f, "Missing request header {:?}", self.name)
519     }
520 }
521 
522 impl StdError for MissingHeader {}
523 
524 /// Invalid request header
525 #[derive(Debug)]
526 pub struct InvalidHeader {
527     name: &'static str,
528 }
529 
530 impl InvalidHeader {
531     /// Retrieve the name of the header that was invalid
name(&self) -> &str532     pub fn name(&self) -> &str {
533         self.name
534     }
535 }
536 
537 impl ::std::fmt::Display for InvalidHeader {
fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result538     fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
539         write!(f, "Invalid request header {:?}", self.name)
540     }
541 }
542 
543 impl StdError for InvalidHeader {}
544 
545 /// Missing cookie
546 #[derive(Debug)]
547 pub struct MissingCookie {
548     name: &'static str,
549 }
550 
551 impl MissingCookie {
552     /// Retrieve the name of the cookie that was missing
name(&self) -> &str553     pub fn name(&self) -> &str {
554         self.name
555     }
556 }
557 
558 impl ::std::fmt::Display for MissingCookie {
fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result559     fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
560         write!(f, "Missing request cookie {:?}", self.name)
561     }
562 }
563 
564 impl StdError for MissingCookie {}
565 
566 mod sealed {
567     use super::{Reason, Rejection, Rejections};
568     use http::StatusCode;
569     use std::convert::Infallible;
570     use std::fmt;
571 
572     // This sealed trait exists to allow Filters to return either `Rejection`
573     // or `!`. There are no other types that make sense, and so it is sealed.
574     pub trait IsReject: fmt::Debug + Send + Sync {
status(&self) -> StatusCode575         fn status(&self) -> StatusCode;
into_response(&self) -> crate::reply::Response576         fn into_response(&self) -> crate::reply::Response;
577     }
578 
_assert_object_safe()579     fn _assert_object_safe() {
580         fn _assert(_: &dyn IsReject) {}
581     }
582 
583     // This weird trait is to allow optimizations of propagating when a
584     // rejection can *never* happen (currently with the `Never` type,
585     // eventually to be replaced with `!`).
586     //
587     // Using this trait means the `Never` gets propagated to chained filters,
588     // allowing LLVM to eliminate more code paths. Without it, such as just
589     // requiring that `Rejection::from(Never)` were used in those filters,
590     // would mean that links later in the chain may assume a rejection *could*
591     // happen, and no longer eliminate those branches.
592     pub trait CombineRejection<E>: Send + Sized {
593         /// The type that should be returned when only 1 of the two
594         /// "rejections" occurs.
595         ///
596         /// # For example:
597         ///
598         /// `warp::any().and(warp::path("foo"))` has the following steps:
599         ///
600         /// 1. Since this is `and`, only **one** of the rejections will occur,
601         ///    and as soon as it does, it will be returned.
602         /// 2. `warp::any()` rejects with `Never`. So, it will never return `Never`.
603         /// 3. `warp::path()` rejects with `Rejection`. It may return `Rejection`.
604         ///
605         /// Thus, if the above filter rejects, it will definitely be `Rejection`.
606         type One: IsReject + From<Self> + From<E> + Into<Rejection>;
607 
608         /// The type that should be returned when both rejections occur,
609         /// and need to be combined.
610         type Combined: IsReject;
611 
combine(self, other: E) -> Self::Combined612         fn combine(self, other: E) -> Self::Combined;
613     }
614 
615     impl CombineRejection<Rejection> for Rejection {
616         type One = Rejection;
617         type Combined = Rejection;
618 
combine(self, other: Rejection) -> Self::Combined619         fn combine(self, other: Rejection) -> Self::Combined {
620             let reason = match (self.reason, other.reason) {
621                 (Reason::Other(left), Reason::Other(right)) => {
622                     Reason::Other(Box::new(Rejections::Combined(left, right)))
623                 }
624                 (Reason::Other(other), Reason::NotFound)
625                 | (Reason::NotFound, Reason::Other(other)) => {
626                     // ignore the NotFound
627                     Reason::Other(other)
628                 }
629                 (Reason::NotFound, Reason::NotFound) => Reason::NotFound,
630             };
631 
632             Rejection { reason }
633         }
634     }
635 
636     impl CombineRejection<Infallible> for Rejection {
637         type One = Rejection;
638         type Combined = Infallible;
639 
combine(self, other: Infallible) -> Self::Combined640         fn combine(self, other: Infallible) -> Self::Combined {
641             match other {}
642         }
643     }
644 
645     impl CombineRejection<Rejection> for Infallible {
646         type One = Rejection;
647         type Combined = Infallible;
648 
combine(self, _: Rejection) -> Self::Combined649         fn combine(self, _: Rejection) -> Self::Combined {
650             match self {}
651         }
652     }
653 
654     impl CombineRejection<Infallible> for Infallible {
655         type One = Infallible;
656         type Combined = Infallible;
657 
combine(self, _: Infallible) -> Self::Combined658         fn combine(self, _: Infallible) -> Self::Combined {
659             match self {}
660         }
661     }
662 }
663 
664 #[cfg(test)]
665 mod tests {
666     use super::*;
667     use http::StatusCode;
668 
669     #[derive(Debug, PartialEq)]
670     struct Left;
671 
672     #[derive(Debug, PartialEq)]
673     struct Right;
674 
675     impl Reject for Left {}
676     impl Reject for Right {}
677 
678     #[test]
rejection_status()679     fn rejection_status() {
680         assert_eq!(not_found().status(), StatusCode::NOT_FOUND);
681         assert_eq!(
682             method_not_allowed().status(),
683             StatusCode::METHOD_NOT_ALLOWED
684         );
685         assert_eq!(length_required().status(), StatusCode::LENGTH_REQUIRED);
686         assert_eq!(payload_too_large().status(), StatusCode::PAYLOAD_TOO_LARGE);
687         assert_eq!(
688             unsupported_media_type().status(),
689             StatusCode::UNSUPPORTED_MEDIA_TYPE
690         );
691         assert_eq!(custom(Left).status(), StatusCode::INTERNAL_SERVER_ERROR);
692     }
693 
694     #[tokio::test]
combine_rejection_causes_with_some_left_and_none_right()695     async fn combine_rejection_causes_with_some_left_and_none_right() {
696         let left = custom(Left);
697         let right = not_found();
698         let reject = left.combine(right);
699         let resp = reject.into_response();
700 
701         assert_eq!(resp.status(), StatusCode::INTERNAL_SERVER_ERROR);
702         assert_eq!(
703             response_body_string(resp).await,
704             "Unhandled rejection: Left"
705         )
706     }
707 
708     #[tokio::test]
combine_rejection_causes_with_none_left_and_some_right()709     async fn combine_rejection_causes_with_none_left_and_some_right() {
710         let left = not_found();
711         let right = custom(Right);
712         let reject = left.combine(right);
713         let resp = reject.into_response();
714 
715         assert_eq!(resp.status(), StatusCode::INTERNAL_SERVER_ERROR);
716         assert_eq!(
717             response_body_string(resp).await,
718             "Unhandled rejection: Right"
719         )
720     }
721 
722     #[tokio::test]
unhandled_customs()723     async fn unhandled_customs() {
724         let reject = not_found().combine(custom(Right));
725 
726         let resp = reject.into_response();
727         assert_eq!(resp.status(), StatusCode::INTERNAL_SERVER_ERROR);
728         assert_eq!(
729             response_body_string(resp).await,
730             "Unhandled rejection: Right"
731         );
732 
733         // There's no real way to determine which is worse, since both are a 500,
734         // so pick the first one.
735         let reject = custom(Left).combine(custom(Right));
736 
737         let resp = reject.into_response();
738         assert_eq!(resp.status(), StatusCode::INTERNAL_SERVER_ERROR);
739         assert_eq!(
740             response_body_string(resp).await,
741             "Unhandled rejection: Left"
742         );
743 
744         // With many rejections, custom still is top priority.
745         let reject = not_found()
746             .combine(not_found())
747             .combine(not_found())
748             .combine(custom(Right))
749             .combine(not_found());
750 
751         let resp = reject.into_response();
752         assert_eq!(resp.status(), StatusCode::INTERNAL_SERVER_ERROR);
753         assert_eq!(
754             response_body_string(resp).await,
755             "Unhandled rejection: Right"
756         );
757     }
758 
response_body_string(resp: crate::reply::Response) -> String759     async fn response_body_string(resp: crate::reply::Response) -> String {
760         let (_, body) = resp.into_parts();
761         let body_bytes = hyper::body::to_bytes(body).await.expect("failed concat");
762         String::from_utf8_lossy(&body_bytes).to_string()
763     }
764 
765     #[test]
find_cause()766     fn find_cause() {
767         let rej = custom(Left);
768 
769         assert_eq!(rej.find::<Left>(), Some(&Left));
770 
771         let rej = rej.combine(method_not_allowed());
772 
773         assert_eq!(rej.find::<Left>(), Some(&Left));
774         assert!(rej.find::<MethodNotAllowed>().is_some(), "MethodNotAllowed");
775     }
776 
777     #[test]
size_of_rejection()778     fn size_of_rejection() {
779         assert_eq!(
780             ::std::mem::size_of::<Rejection>(),
781             ::std::mem::size_of::<usize>(),
782         );
783     }
784 
785     #[derive(Debug)]
786     struct X(u32);
787     impl Reject for X {}
788 
combine_n<F, R>(n: u32, new_reject: F) -> Rejection where F: Fn(u32) -> R, R: Reject,789     fn combine_n<F, R>(n: u32, new_reject: F) -> Rejection
790     where
791         F: Fn(u32) -> R,
792         R: Reject,
793     {
794         let mut rej = not_found();
795 
796         for i in 0..n {
797             rej = rej.combine(custom(new_reject(i)));
798         }
799 
800         rej
801     }
802 
803     #[test]
test_debug()804     fn test_debug() {
805         let rej = combine_n(3, X);
806 
807         let s = format!("{:?}", rej);
808         assert_eq!(s, "Rejection([X(0), X(1), X(2)])");
809     }
810 }
811