1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
//! Defines the `ContentTypeHeaderRouteMatcher`.

use hyper::header::{HeaderMap, CONTENT_TYPE};
use hyper::StatusCode;
use mime;

use router::non_match::RouteNonMatch;
use router::route::RouteMatcher;
use state::{request_id, FromState, State};

/// A `RouteMatcher` that succeeds when the `Request` has been made with a `Content-Type` header
/// that includes a supported media type. The matcher will fail if the Content-Type
/// header is missing.
///
/// # Examples
///
/// ```rust
/// # extern crate gotham;
/// # extern crate hyper;
/// # extern crate mime;
/// # fn main() {
/// #   use hyper::header::{HeaderMap, CONTENT_TYPE};
/// #   use gotham::state::State;
/// #   use gotham::router::route::matcher::RouteMatcher;
/// #   use gotham::router::route::matcher::content_type::ContentTypeHeaderRouteMatcher;
/// #
/// #   State::with_new(|state| {
/// #
/// let supported_media_types = vec![mime::APPLICATION_JSON];
/// let matcher = ContentTypeHeaderRouteMatcher::new(supported_media_types);
///
/// // No content type header
/// state.put(HeaderMap::new());
/// assert!(matcher.is_match(&state).is_err());
///
/// // Content type header of `application/json`
/// let mut headers = HeaderMap::new();
/// headers.insert(CONTENT_TYPE, "application/json".parse().unwrap());
/// state.put(headers);
/// assert!(matcher.is_match(&state).is_ok());
///
/// // Not a valid Content-Type header
/// let mut headers = HeaderMap::new();
/// headers.insert(CONTENT_TYPE, "text/plain".parse().unwrap());
/// state.put(headers);
/// assert!(matcher.is_match(&state).is_err());
///
/// // At least one supported content type header
/// let mut headers = HeaderMap::new();
/// headers.insert(CONTENT_TYPE, "text/plain".parse().unwrap());
/// headers.insert(CONTENT_TYPE, "application/json".parse().unwrap());
/// state.put(headers);
/// assert!(matcher.is_match(&state).is_ok());
/// #
/// #   });
/// # }
/// ```
#[derive(Clone)]
pub struct ContentTypeHeaderRouteMatcher {
    supported_media_types: Vec<mime::Mime>,
}
impl ContentTypeHeaderRouteMatcher {
    /// Creates a new `ContentTypeHeaderRouteMatcher`
    pub fn new(supported_media_types: Vec<mime::Mime>) -> Self {
        ContentTypeHeaderRouteMatcher {
            supported_media_types,
        }
    }
}

impl RouteMatcher for ContentTypeHeaderRouteMatcher {
    /// Determines if the `Request` was made using a `Content-Type` header that includes a
    /// supported media type. A missing `Content-Type` header will not match.
    fn is_match(&self, state: &State) -> Result<(), RouteNonMatch> {
        match HeaderMap::borrow_from(state).get(CONTENT_TYPE) {
            // The client has not specified a `Content-Type` header.
            None => Err(RouteNonMatch::new(StatusCode::UNSUPPORTED_MEDIA_TYPE)),

            // Header was provided.
            Some(content_type) => {
                let mime = content_type.to_str().unwrap().parse().unwrap();

                if self.supported_media_types.contains(&mime) {
                    return Ok(());
                }

                trace!(
                    "[{}] did not specify a Content-Type with a media type supported by this Route",
                    request_id(&state)
                );

                Err(RouteNonMatch::new(StatusCode::UNSUPPORTED_MEDIA_TYPE))
            }
        }
    }
}