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| {
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::error::Error as StdError;
31 use std::fmt;
32
33 use http::{
34 self,
35 header::{HeaderValue, CONTENT_TYPE},
36 StatusCode,
37 };
38 use hyper::Body;
39 use serde;
40 use serde_json;
41
42 use never::Never;
43
44 pub(crate) use self::sealed::{CombineRejection, Reject};
45
46 //TODO(v0.2): This should just be `type Cause = StdError + Send + Sync + 'static`,
47 //and not include the `Box`.
48 #[doc(hidden)]
49 pub type Cause = Box<dyn StdError + Send + Sync>;
50
51 #[doc(hidden)]
52 #[deprecated(
53 note = "this will be changed to return a NotFound rejection, use warp::reject::custom for custom bad requests"
54 )]
55 #[allow(deprecated)]
56 #[inline]
reject() -> Rejection57 pub fn reject() -> Rejection {
58 bad_request()
59 }
60
61 #[doc(hidden)]
62 #[deprecated(note = "use warp::reject::custom and Filter::recover to send a 401 error")]
bad_request() -> Rejection63 pub fn bad_request() -> Rejection {
64 Rejection::known_status(StatusCode::BAD_REQUEST)
65 }
66
67 #[doc(hidden)]
68 #[deprecated(note = "use warp::reject::custom and Filter::recover to send a 403 error")]
forbidden() -> Rejection69 pub fn forbidden() -> Rejection {
70 Rejection::known_status(StatusCode::FORBIDDEN)
71 }
72
73 /// Rejects a request with `404 Not Found`.
74 #[inline]
not_found() -> Rejection75 pub fn not_found() -> Rejection {
76 Rejection {
77 reason: Reason::NotFound,
78 }
79 }
80
81 // 400 Bad Request
82 #[inline]
invalid_query() -> Rejection83 pub(crate) fn invalid_query() -> Rejection {
84 known(InvalidQuery(()))
85 }
86
87 // 400 Bad Request
88 #[inline]
missing_header(name: &'static str) -> Rejection89 pub(crate) fn missing_header(name: &'static str) -> Rejection {
90 known(MissingHeader(name))
91 }
92
93 // 400 Bad Request
94 #[inline]
invalid_header(name: &'static str) -> Rejection95 pub(crate) fn invalid_header(name: &'static str) -> Rejection {
96 known(InvalidHeader(name))
97 }
98
99 // 400 Bad Request
100 #[inline]
missing_cookie(name: &'static str) -> Rejection101 pub(crate) fn missing_cookie(name: &'static str) -> Rejection {
102 known(MissingCookie(name))
103 }
104
105 // 405 Method Not Allowed
106 #[inline]
method_not_allowed() -> Rejection107 pub(crate) fn method_not_allowed() -> Rejection {
108 known(MethodNotAllowed(()))
109 }
110
111 // 411 Length Required
112 #[inline]
length_required() -> Rejection113 pub(crate) fn length_required() -> Rejection {
114 known(LengthRequired(()))
115 }
116
117 // 413 Payload Too Large
118 #[inline]
payload_too_large() -> Rejection119 pub(crate) fn payload_too_large() -> Rejection {
120 known(PayloadTooLarge(()))
121 }
122
123 // 415 Unsupported Media Type
124 //
125 // Used by the body filters if the request payload content-type doesn't match
126 // what can be deserialized.
127 #[inline]
unsupported_media_type() -> Rejection128 pub(crate) fn unsupported_media_type() -> Rejection {
129 known(UnsupportedMediaType(()))
130 }
131
132 #[doc(hidden)]
133 #[deprecated(note = "use warp::reject::custom and Filter::recover to send a 500 error")]
server_error() -> Rejection134 pub fn server_error() -> Rejection {
135 Rejection::known_status(StatusCode::INTERNAL_SERVER_ERROR)
136 }
137
138 /// Rejects a request with a custom cause.
139 ///
140 /// A [`recover`][] filter should convert this `Rejection` into a `Reply`,
141 /// or else this will be returned as a `500 Internal Server Error`.
142 ///
143 /// [`recover`]: ../../trait.Filter.html#method.recover
custom(err: impl Into<Cause>) -> Rejection144 pub fn custom(err: impl Into<Cause>) -> Rejection {
145 Rejection::custom(err.into())
146 }
147
known(err: impl Into<Cause>) -> Rejection148 pub(crate) fn known(err: impl Into<Cause>) -> Rejection {
149 Rejection::known(err.into())
150 }
151
152 /// Rejection of a request by a [`Filter`](::Filter).
153 ///
154 /// See the [`reject`](index.html) documentation for more.
155 pub struct Rejection {
156 reason: Reason,
157 }
158
159 enum Reason {
160 NotFound,
161 Other(Box<Rejections>),
162 }
163
164 enum Rejections {
165 //TODO(v0.2): For 0.1, this needs to hold a Box<StdError>, in order to support
166 //cause() returning a `&Box<StdError>`. With 0.2, this should no longer need
167 //to be boxed.
168 Known(Cause),
169 KnownStatus(StatusCode),
170 With(Rejection, Cause),
171 Custom(Cause),
172 Combined(Box<Rejections>, Box<Rejections>),
173 }
174
175 impl Rejection {
known(other: Cause) -> Self176 fn known(other: Cause) -> Self {
177 Rejection {
178 reason: Reason::Other(Box::new(Rejections::Known(other))),
179 }
180 }
181
known_status(status: StatusCode) -> Self182 fn known_status(status: StatusCode) -> Self {
183 Rejection {
184 reason: Reason::Other(Box::new(Rejections::KnownStatus(status))),
185 }
186 }
187
custom(other: Cause) -> Self188 fn custom(other: Cause) -> Self {
189 Rejection {
190 reason: Reason::Other(Box::new(Rejections::Custom(other))),
191 }
192 }
193
194 /// Searches this `Rejection` for a specific cause.
195 ///
196 /// A `Rejection` will accumulate causes over a `Filter` chain. This method
197 /// can search through them and return the first cause of this type.
198 ///
199 /// # Example
200 ///
201 /// ```
202 /// use std::io;
203 ///
204 /// let err = io::Error::new(
205 /// io::ErrorKind::Other,
206 /// "could be any std::error::Error"
207 /// );
208 /// let reject = warp::reject::custom(err);
209 ///
210 /// if let Some(cause) = reject.find_cause::<io::Error>() {
211 /// println!("found the io::Error: {}", cause);
212 /// }
213 /// ```
find_cause<T: StdError + 'static>(&self) -> Option<&T>214 pub fn find_cause<T: StdError + 'static>(&self) -> Option<&T> {
215 if let Reason::Other(ref rejections) = self.reason {
216 return rejections.find_cause();
217 }
218 None
219 }
220
221 /// Returns true if this Rejection was made via `warp::reject::not_found`.
222 ///
223 /// # Example
224 ///
225 /// ```
226 /// let rejection = warp::reject::not_found();
227 ///
228 /// assert!(rejection.is_not_found());
229 /// ```
is_not_found(&self) -> bool230 pub fn is_not_found(&self) -> bool {
231 if let Reason::NotFound = self.reason {
232 true
233 } else {
234 false
235 }
236 }
237
238 #[doc(hidden)]
status(&self) -> StatusCode239 pub fn status(&self) -> StatusCode {
240 Reject::status(self)
241 }
242
243 #[doc(hidden)]
244 #[deprecated(note = "Custom rejections should use `warp::reject::custom()`.")]
with<E>(self, err: E) -> Self where E: Into<Cause>,245 pub fn with<E>(self, err: E) -> Self
246 where
247 E: Into<Cause>,
248 {
249 let cause = err.into();
250
251 Self {
252 reason: Reason::Other(Box::new(Rejections::With(self, cause))),
253 }
254 }
255
256 #[doc(hidden)]
257 #[deprecated(note = "Use warp::reply::json and warp::reply::with_status instead.")]
json(&self) -> ::reply::Response258 pub fn json(&self) -> ::reply::Response {
259 let code = self.status();
260 let mut res = http::Response::default();
261 *res.status_mut() = code;
262
263 res.headers_mut()
264 .insert(CONTENT_TYPE, HeaderValue::from_static("application/json"));
265
266 *res.body_mut() = match serde_json::to_string(&self) {
267 Ok(body) => Body::from(body),
268 Err(_) => Body::from("{}"),
269 };
270
271 res
272 }
273
274 /// Returns an optional error cause for this rejection.
275 ///
276 /// If this `Rejection` is actuall a combination of rejections, then the
277 /// returned cause is determined by an internal ranking system. If you'd
278 /// rather handle different causes with different priorities, use
279 /// `find_cause`.
280 ///
281 /// # Note
282 ///
283 /// The return type will change from `&Box<Error>` to `&Error` in v0.2.
284 /// This method isn't marked deprecated, however, since most people aren't
285 /// actually using the `Box` part, and so a deprecation warning would just
286 /// annoy people who didn't need to make any changes.
cause(&self) -> Option<&Cause>287 pub fn cause(&self) -> Option<&Cause> {
288 if let Reason::Other(ref err) = self.reason {
289 return err.cause();
290 }
291 None
292 }
293
294 #[doc(hidden)]
295 #[deprecated(note = "into_cause can no longer be provided")]
into_cause<T>(self) -> Result<Box<T>, Self> where T: StdError + Send + Sync + 'static,296 pub fn into_cause<T>(self) -> Result<Box<T>, Self>
297 where
298 T: StdError + Send + Sync + 'static,
299 {
300 Err(self)
301 }
302 }
303
304 impl From<Never> for Rejection {
305 #[inline]
from(never: Never) -> Rejection306 fn from(never: Never) -> Rejection {
307 match never {}
308 }
309 }
310
311 impl Reject for Never {
status(&self) -> StatusCode312 fn status(&self) -> StatusCode {
313 match *self {}
314 }
315
into_response(&self) -> ::reply::Response316 fn into_response(&self) -> ::reply::Response {
317 match *self {}
318 }
319
cause(&self) -> Option<&Cause>320 fn cause(&self) -> Option<&Cause> {
321 None
322 }
323 }
324
325 impl Reject for Rejection {
status(&self) -> StatusCode326 fn status(&self) -> StatusCode {
327 match self.reason {
328 Reason::NotFound => StatusCode::NOT_FOUND,
329 Reason::Other(ref other) => other.status(),
330 }
331 }
332
into_response(&self) -> ::reply::Response333 fn into_response(&self) -> ::reply::Response {
334 match self.reason {
335 Reason::NotFound => {
336 let mut res = http::Response::default();
337 *res.status_mut() = StatusCode::NOT_FOUND;
338 res
339 }
340 Reason::Other(ref other) => other.into_response(),
341 }
342 }
343
cause(&self) -> Option<&Cause>344 fn cause(&self) -> Option<&Cause> {
345 Rejection::cause(&self)
346 }
347 }
348
349 impl fmt::Debug for Rejection {
fmt(&self, f: &mut fmt::Formatter) -> fmt::Result350 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
351 f.debug_tuple("Rejection").field(&self.reason).finish()
352 }
353 }
354
355 impl fmt::Debug for Reason {
fmt(&self, f: &mut fmt::Formatter) -> fmt::Result356 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
357 match *self {
358 Reason::NotFound => f.write_str("NotFound"),
359 Reason::Other(ref other) => fmt::Debug::fmt(other, f),
360 }
361 }
362 }
363
364 #[doc(hidden)]
365 #[deprecated(note = "Use warp::reply::json and warp::reply::with_status instead.")]
366 impl serde::Serialize for Rejection {
serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> where S: serde::Serializer,367 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
368 where
369 S: serde::Serializer,
370 {
371 use serde::ser::SerializeMap;
372
373 let mut map = serializer.serialize_map(None)?;
374 let err = match self.cause() {
375 Some(err) => err,
376 None => return map.end(),
377 };
378
379 map.serialize_key("description")
380 .and_then(|_| map.serialize_value(err.description()))?;
381 map.serialize_key("message")
382 .and_then(|_| map.serialize_value(&err.to_string()))?;
383 map.end()
384 }
385 }
386
387 // ===== Rejections =====
388
389 impl Rejections {
status(&self) -> StatusCode390 fn status(&self) -> StatusCode {
391 match *self {
392 Rejections::Known(ref e) => {
393 if e.is::<MethodNotAllowed>() {
394 StatusCode::METHOD_NOT_ALLOWED
395 } else if e.is::<InvalidHeader>() {
396 StatusCode::BAD_REQUEST
397 } else if e.is::<MissingHeader>() {
398 StatusCode::BAD_REQUEST
399 } else if e.is::<MissingCookie>() {
400 StatusCode::BAD_REQUEST
401 } else if e.is::<InvalidQuery>() {
402 StatusCode::BAD_REQUEST
403 } else if e.is::<LengthRequired>() {
404 StatusCode::LENGTH_REQUIRED
405 } else if e.is::<PayloadTooLarge>() {
406 StatusCode::PAYLOAD_TOO_LARGE
407 } else if e.is::<UnsupportedMediaType>() {
408 StatusCode::UNSUPPORTED_MEDIA_TYPE
409 } else if e.is::<::body::BodyReadError>() {
410 StatusCode::BAD_REQUEST
411 } else if e.is::<::body::BodyDeserializeError>() {
412 StatusCode::BAD_REQUEST
413 } else if e.is::<::cors::CorsForbidden>() {
414 StatusCode::FORBIDDEN
415 } else if e.is::<::ext::MissingExtension>() {
416 StatusCode::INTERNAL_SERVER_ERROR
417 } else if e.is::<::reply::ReplyHttpError>() {
418 StatusCode::INTERNAL_SERVER_ERROR
419 } else if e.is::<::reply::ReplyJsonError>() {
420 StatusCode::INTERNAL_SERVER_ERROR
421 } else if e.is::<::body::BodyConsumedMultipleTimes>() {
422 StatusCode::INTERNAL_SERVER_ERROR
423 } else if e.is::<::fs::FsNeedsTokioThreadpool>() {
424 StatusCode::INTERNAL_SERVER_ERROR
425 } else {
426 unreachable!("unexpected 'Known' rejection: {:?}", e);
427 }
428 }
429 Rejections::KnownStatus(status) => status,
430 Rejections::With(ref rej, _) => rej.status(),
431 Rejections::Custom(..) => StatusCode::INTERNAL_SERVER_ERROR,
432 Rejections::Combined(ref a, ref b) => preferred(a, b).status(),
433 }
434 }
435
into_response(&self) -> ::reply::Response436 fn into_response(&self) -> ::reply::Response {
437 match *self {
438 Rejections::Known(ref e) => {
439 let mut res = http::Response::new(Body::from(e.to_string()));
440 *res.status_mut() = self.status();
441 res.headers_mut().insert(
442 CONTENT_TYPE,
443 HeaderValue::from_static("text/plain; charset=utf-8"),
444 );
445 res
446 }
447 Rejections::KnownStatus(ref s) => {
448 use reply::Reply;
449 s.into_response()
450 }
451 Rejections::With(ref rej, ref e) => {
452 let mut res = rej.into_response();
453
454 let bytes = e.to_string();
455 res.headers_mut().insert(
456 CONTENT_TYPE,
457 HeaderValue::from_static("text/plain; charset=utf-8"),
458 );
459 *res.body_mut() = Body::from(bytes);
460
461 res
462 }
463 Rejections::Custom(ref e) => {
464 error!(
465 "unhandled custom rejection, returning 500 response: {:?}",
466 e
467 );
468 let body = format!("Unhandled rejection: {}", e);
469 let mut res = http::Response::new(Body::from(body));
470 *res.status_mut() = self.status();
471 res.headers_mut().insert(
472 CONTENT_TYPE,
473 HeaderValue::from_static("text/plain; charset=utf-8"),
474 );
475 res
476 }
477 Rejections::Combined(ref a, ref b) => preferred(a, b).into_response(),
478 }
479 }
480
cause(&self) -> Option<&Cause>481 fn cause(&self) -> Option<&Cause> {
482 match *self {
483 Rejections::Known(ref e) => Some(e),
484 Rejections::KnownStatus(_) => None,
485 Rejections::With(_, ref e) => Some(e),
486 Rejections::Custom(ref e) => Some(e),
487 Rejections::Combined(ref a, ref b) => preferred(a, b).cause(),
488 }
489 }
490
find_cause<T: StdError + 'static>(&self) -> Option<&T>491 pub fn find_cause<T: StdError + 'static>(&self) -> Option<&T> {
492 match *self {
493 Rejections::Known(ref e) => e.downcast_ref(),
494 Rejections::KnownStatus(_) => None,
495 Rejections::With(_, ref e) => e.downcast_ref(),
496 Rejections::Custom(ref e) => e.downcast_ref(),
497 Rejections::Combined(ref a, ref b) => a.find_cause().or_else(|| b.find_cause()),
498 }
499 }
500 }
501
preferred<'a>(a: &'a Rejections, b: &'a Rejections) -> &'a Rejections502 fn preferred<'a>(a: &'a Rejections, b: &'a Rejections) -> &'a Rejections {
503 // Compare status codes, with this priority:
504 // - NOT_FOUND is lowest
505 // - METHOD_NOT_ALLOWED is second
506 // - if one status code is greater than the other
507 // - otherwise, prefer A...
508 match (a.status(), b.status()) {
509 (_, StatusCode::NOT_FOUND) => a,
510 (StatusCode::NOT_FOUND, _) => b,
511 (_, StatusCode::METHOD_NOT_ALLOWED) => a,
512 (StatusCode::METHOD_NOT_ALLOWED, _) => b,
513 (sa, sb) if sa < sb => b,
514 _ => a,
515 }
516 }
517
518 impl fmt::Debug for Rejections {
fmt(&self, f: &mut fmt::Formatter) -> fmt::Result519 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
520 match *self {
521 Rejections::Known(ref e) => fmt::Debug::fmt(e, f),
522 Rejections::KnownStatus(ref s) => f.debug_tuple("Status").field(s).finish(),
523 Rejections::With(ref rej, ref e) => f.debug_tuple("With").field(rej).field(e).finish(),
524 Rejections::Custom(ref e) => f.debug_tuple("Custom").field(e).finish(),
525 Rejections::Combined(ref a, ref b) => {
526 f.debug_tuple("Combined").field(a).field(b).finish()
527 }
528 }
529 }
530 }
531
532 /// Invalid query
533 #[derive(Debug)]
534 pub struct InvalidQuery(());
535
536 impl ::std::fmt::Display for InvalidQuery {
fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result537 fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
538 f.write_str("Invalid query string")
539 }
540 }
541
542 impl StdError for InvalidQuery {
description(&self) -> &str543 fn description(&self) -> &str {
544 "Invalid query string"
545 }
546 }
547
548 /// HTTP method not allowed
549 #[derive(Debug)]
550 pub struct MethodNotAllowed(());
551
552 impl fmt::Display for MethodNotAllowed {
fmt(&self, f: &mut fmt::Formatter) -> fmt::Result553 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
554 f.write_str("HTTP method not allowed")
555 }
556 }
557
558 impl StdError for MethodNotAllowed {
description(&self) -> &str559 fn description(&self) -> &str {
560 "HTTP method not allowed"
561 }
562 }
563
564 /// A content-length header is required
565 #[derive(Debug)]
566 pub struct LengthRequired(());
567
568 impl fmt::Display for LengthRequired {
fmt(&self, f: &mut fmt::Formatter) -> fmt::Result569 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
570 f.write_str("A content-length header is required")
571 }
572 }
573
574 impl StdError for LengthRequired {
description(&self) -> &str575 fn description(&self) -> &str {
576 "A content-length header is required"
577 }
578 }
579
580 /// The request payload is too large
581 #[derive(Debug)]
582 pub struct PayloadTooLarge(());
583
584 impl fmt::Display for PayloadTooLarge {
fmt(&self, f: &mut fmt::Formatter) -> fmt::Result585 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
586 f.write_str("The request payload is too large")
587 }
588 }
589
590 impl StdError for PayloadTooLarge {
description(&self) -> &str591 fn description(&self) -> &str {
592 "The request payload is too large"
593 }
594 }
595
596 /// The request's content-type is not supported
597 #[derive(Debug)]
598 pub struct UnsupportedMediaType(());
599
600 impl fmt::Display for UnsupportedMediaType {
fmt(&self, f: &mut fmt::Formatter) -> fmt::Result601 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
602 f.write_str("The request's content-type is not supported")
603 }
604 }
605
606 impl StdError for UnsupportedMediaType {
description(&self) -> &str607 fn description(&self) -> &str {
608 "The request's content-type is not supported"
609 }
610 }
611
612 /// Missing request header
613 #[derive(Debug)]
614 pub struct MissingHeader(&'static str);
615
616 impl ::std::fmt::Display for MissingHeader {
fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result617 fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
618 write!(f, "Missing request header '{}'", self.0)
619 }
620 }
621
622 impl StdError for MissingHeader {
description(&self) -> &str623 fn description(&self) -> &str {
624 "Missing request header"
625 }
626 }
627
628 /// Invalid request header
629 #[derive(Debug)]
630 pub struct InvalidHeader(&'static str);
631
632 impl ::std::fmt::Display for InvalidHeader {
fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result633 fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
634 write!(f, "Invalid request header '{}'", self.0)
635 }
636 }
637
638 impl StdError for InvalidHeader {
description(&self) -> &str639 fn description(&self) -> &str {
640 "Invalid request header"
641 }
642 }
643
644
645 /// Missing cookie
646 #[derive(Debug)]
647 pub struct MissingCookie(&'static str);
648
649 impl ::std::fmt::Display for MissingCookie {
fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result650 fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
651 write!(f, "Missing request cookie '{}'", self.0)
652 }
653 }
654
655 impl StdError for MissingCookie {
description(&self) -> &str656 fn description(&self) -> &str {
657 "Missing request cookie"
658 }
659 }
660
661 trait Typed: StdError + 'static {
type_id(&self) -> ::std::any::TypeId662 fn type_id(&self) -> ::std::any::TypeId;
663 }
664
665 mod sealed {
666 use super::{Cause, Reason, Rejection, Rejections};
667 use http::StatusCode;
668 use never::Never;
669 use std::fmt;
670
671 pub trait Reject: fmt::Debug + Send + Sync {
status(&self) -> StatusCode672 fn status(&self) -> StatusCode;
into_response(&self) -> ::reply::Response673 fn into_response(&self) -> ::reply::Response;
cause(&self) -> Option<&Cause>674 fn cause(&self) -> Option<&Cause> {
675 None
676 }
677 }
678
_assert_object_safe()679 fn _assert_object_safe() {
680 fn _assert(_: &dyn Reject) {}
681 }
682
683 pub trait CombineRejection<E>: Send + Sized {
684 type Rejection: Reject + From<Self> + From<E> + Into<Rejection>;
685
combine(self, other: E) -> Self::Rejection686 fn combine(self, other: E) -> Self::Rejection;
687 }
688
689 impl CombineRejection<Rejection> for Rejection {
690 type Rejection = Rejection;
691
combine(self, other: Rejection) -> Self::Rejection692 fn combine(self, other: Rejection) -> Self::Rejection {
693 let reason = match (self.reason, other.reason) {
694 (Reason::Other(left), Reason::Other(right)) => {
695 Reason::Other(Box::new(Rejections::Combined(left, right)))
696 }
697 (Reason::Other(other), Reason::NotFound)
698 | (Reason::NotFound, Reason::Other(other)) => {
699 // ignore the NotFound
700 Reason::Other(other)
701 }
702 (Reason::NotFound, Reason::NotFound) => Reason::NotFound,
703 };
704
705 Rejection { reason }
706 }
707 }
708
709 impl CombineRejection<Never> for Rejection {
710 type Rejection = Rejection;
711
combine(self, other: Never) -> Self::Rejection712 fn combine(self, other: Never) -> Self::Rejection {
713 match other {}
714 }
715 }
716
717 impl CombineRejection<Rejection> for Never {
718 type Rejection = Rejection;
719
combine(self, _: Rejection) -> Self::Rejection720 fn combine(self, _: Rejection) -> Self::Rejection {
721 match self {}
722 }
723 }
724
725 impl CombineRejection<Never> for Never {
726 type Rejection = Never;
727
combine(self, _: Never) -> Self::Rejection728 fn combine(self, _: Never) -> Self::Rejection {
729 match self {}
730 }
731 }
732 }
733
734 #[cfg(test)]
735 mod tests {
736 use http::header::CONTENT_TYPE;
737
738 use super::*;
739 use http::StatusCode;
740
741 #[allow(deprecated)]
742 #[test]
rejection_status()743 fn rejection_status() {
744 assert_eq!(bad_request().status(), StatusCode::BAD_REQUEST);
745 assert_eq!(forbidden().status(), StatusCode::FORBIDDEN);
746 assert_eq!(not_found().status(), StatusCode::NOT_FOUND);
747 assert_eq!(
748 method_not_allowed().status(),
749 StatusCode::METHOD_NOT_ALLOWED
750 );
751 assert_eq!(length_required().status(), StatusCode::LENGTH_REQUIRED);
752 assert_eq!(payload_too_large().status(), StatusCode::PAYLOAD_TOO_LARGE);
753 assert_eq!(
754 unsupported_media_type().status(),
755 StatusCode::UNSUPPORTED_MEDIA_TYPE
756 );
757 assert_eq!(server_error().status(), StatusCode::INTERNAL_SERVER_ERROR);
758 assert_eq!(custom("boom").status(), StatusCode::INTERNAL_SERVER_ERROR);
759 }
760
761 #[allow(deprecated)]
762 #[test]
combine_rejections()763 fn combine_rejections() {
764 let left = bad_request().with("left");
765 let right = server_error().with("right");
766 let reject = left.combine(right);
767
768 assert_eq!(reject.status(), StatusCode::INTERNAL_SERVER_ERROR);
769 assert_eq!(reject.cause().unwrap().to_string(), "right");
770 }
771
772 #[allow(deprecated)]
773 #[test]
combine_rejection_causes_with_some_left_and_none_server_error()774 fn combine_rejection_causes_with_some_left_and_none_server_error() {
775 let left = bad_request().with("left");
776 let right = server_error();
777 let reject = left.combine(right);
778
779 assert_eq!(reject.status(), StatusCode::INTERNAL_SERVER_ERROR);
780 assert!(reject.cause().is_none());
781 }
782
783 #[allow(deprecated)]
784 #[test]
combine_rejection_causes_with_some_left_and_none_right()785 fn combine_rejection_causes_with_some_left_and_none_right() {
786 let left = bad_request().with("left");
787 let right = bad_request();
788 let reject = left.combine(right);
789
790 assert_eq!(reject.status(), StatusCode::BAD_REQUEST);
791 assert_eq!(reject.cause().unwrap().to_string(), "left");
792 }
793
794 #[allow(deprecated)]
795 #[test]
combine_rejection_causes_with_none_left_and_some_right()796 fn combine_rejection_causes_with_none_left_and_some_right() {
797 let left = bad_request();
798 let right = server_error().with("right");
799 let reject = left.combine(right);
800
801 assert_eq!(reject.status(), StatusCode::INTERNAL_SERVER_ERROR);
802 assert_eq!(reject.cause().unwrap().to_string(), "right");
803 }
804
805 #[allow(deprecated)]
806 #[test]
unhandled_customs()807 fn unhandled_customs() {
808 let reject = bad_request().combine(custom("right"));
809
810 let resp = reject.into_response();
811 assert_eq!(resp.status(), StatusCode::INTERNAL_SERVER_ERROR);
812 assert_eq!(response_body_string(resp), "Unhandled rejection: right");
813
814 // There's no real way to determine which is worse, since both are a 500,
815 // so pick the first one.
816 let reject = server_error().combine(custom("right"));
817
818 let resp = reject.into_response();
819 assert_eq!(resp.status(), StatusCode::INTERNAL_SERVER_ERROR);
820 assert_eq!(response_body_string(resp), "");
821
822 // With many rejections, custom still is top priority.
823 let reject = bad_request()
824 .combine(bad_request())
825 .combine(not_found())
826 .combine(custom("right"))
827 .combine(bad_request());
828
829 let resp = reject.into_response();
830 assert_eq!(resp.status(), StatusCode::INTERNAL_SERVER_ERROR);
831 assert_eq!(response_body_string(resp), "Unhandled rejection: right");
832 }
833
834 #[test]
into_response_with_none_cause()835 fn into_response_with_none_cause() {
836 let resp = not_found().into_response();
837 assert_eq!(404, resp.status());
838 assert!(resp.headers().get(CONTENT_TYPE).is_none());
839 assert_eq!("", response_body_string(resp))
840 }
841
842 #[allow(deprecated)]
843 #[test]
into_response_with_some_cause()844 fn into_response_with_some_cause() {
845 let resp = server_error().with("boom").into_response();
846 assert_eq!(500, resp.status());
847 assert_eq!(
848 "text/plain; charset=utf-8",
849 resp.headers().get(CONTENT_TYPE).unwrap()
850 );
851 assert_eq!("boom", response_body_string(resp))
852 }
853
854 #[allow(deprecated)]
855 #[test]
into_json_with_none_cause()856 fn into_json_with_none_cause() {
857 let resp = not_found().json();
858 assert_eq!(404, resp.status());
859 assert_eq!(
860 "application/json",
861 resp.headers().get(CONTENT_TYPE).unwrap()
862 );
863 assert_eq!("{}", response_body_string(resp))
864 }
865
866 #[allow(deprecated)]
867 #[test]
into_json_with_some_cause()868 fn into_json_with_some_cause() {
869 let resp = bad_request().with("boom").json();
870 assert_eq!(400, resp.status());
871 assert_eq!(
872 "application/json",
873 resp.headers().get(CONTENT_TYPE).unwrap()
874 );
875 let expected = "{\"description\":\"boom\",\"message\":\"boom\"}";
876 assert_eq!(expected, response_body_string(resp))
877 }
878
response_body_string(resp: ::reply::Response) -> String879 fn response_body_string(resp: ::reply::Response) -> String {
880 use futures::{Async, Future, Stream};
881
882 let (_, body) = resp.into_parts();
883 match body.concat2().poll() {
884 Ok(Async::Ready(chunk)) => String::from_utf8_lossy(&chunk).to_string(),
885 err => unreachable!("{:?}", err),
886 }
887 }
888
889 #[test]
890 #[allow(deprecated)]
into_cause()891 fn into_cause() {
892 use std::io;
893
894 let reject = bad_request().with(io::Error::new(io::ErrorKind::Other, "boom"));
895
896 reject.into_cause::<io::Error>().unwrap_err();
897 }
898
899 #[allow(deprecated)]
900 #[test]
find_cause()901 fn find_cause() {
902 use std::io;
903
904 let rej = bad_request().with(io::Error::new(io::ErrorKind::Other, "boom"));
905
906 assert_eq!(rej.find_cause::<io::Error>().unwrap().to_string(), "boom");
907
908 let rej = bad_request()
909 .with(io::Error::new(io::ErrorKind::Other, "boom"))
910 .combine(method_not_allowed());
911
912 assert_eq!(rej.find_cause::<io::Error>().unwrap().to_string(), "boom");
913 assert!(
914 rej.find_cause::<MethodNotAllowed>().is_some(),
915 "MethodNotAllowed"
916 );
917 }
918
919 #[test]
size_of_rejection()920 fn size_of_rejection() {
921 assert_eq!(
922 ::std::mem::size_of::<Rejection>(),
923 ::std::mem::size_of::<usize>(),
924 );
925 }
926 }
927