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