gotham/router/route/matcher/
access_control_request_method.rs

1//! Defines the `AccessControlRequestMethodMatcher`.
2
3use crate::router::non_match::RouteNonMatch;
4use crate::router::route::matcher::RouteMatcher;
5use crate::state::{FromState, State};
6use hyper::header::{HeaderMap, ACCESS_CONTROL_REQUEST_METHOD};
7use hyper::{Method, StatusCode};
8
9/// A route matcher that checks whether the value of the `Access-Control-Request-Method` header matches the defined value.
10///
11/// Usage:
12///
13/// ```rust
14/// # use gotham::{helpers::http::response::create_empty_response,
15/// #   hyper::{header::ACCESS_CONTROL_ALLOW_METHODS, Method, StatusCode},
16/// #   router::{builder::*, route::matcher::AccessControlRequestMethodMatcher}
17/// # };
18/// let matcher = AccessControlRequestMethodMatcher::new(Method::PUT);
19///
20/// # build_simple_router(|route| {
21/// // use the matcher for your request
22/// route
23///     .options("/foo")
24///     .extend_route_matcher(matcher)
25///     .to(|state| {
26///         // we know that this is a CORS preflight for a PUT request
27///         let mut res = create_empty_response(&state, StatusCode::NO_CONTENT);
28///         res.headers_mut()
29///             .insert(ACCESS_CONTROL_ALLOW_METHODS, "PUT".parse().unwrap());
30///         (state, res)
31///     });
32/// # });
33/// ```
34#[derive(Clone, Debug)]
35pub struct AccessControlRequestMethodMatcher {
36    method: Method,
37}
38
39impl AccessControlRequestMethodMatcher {
40    /// Construct a new matcher that matches if the `Access-Control-Request-Method` header matches `method`.
41    /// Note that during matching the method is normalized according to the fetch specification, that is,
42    /// byte-uppercased. This means that when using a custom `method` instead of a predefined one, make sure
43    /// it is uppercased or this matcher will never succeed.
44    pub fn new(method: Method) -> Self {
45        Self { method }
46    }
47}
48
49impl RouteMatcher for AccessControlRequestMethodMatcher {
50    fn is_match(&self, state: &State) -> Result<(), RouteNonMatch> {
51        // according to the fetch specification, methods should be normalized by byte-uppercase
52        // https://fetch.spec.whatwg.org/#concept-method
53        match HeaderMap::borrow_from(state)
54            .get(ACCESS_CONTROL_REQUEST_METHOD)
55            .and_then(|value| value.to_str().ok())
56            .and_then(|str| str.to_ascii_uppercase().parse::<Method>().ok())
57        {
58            Some(m) if m == self.method => Ok(()),
59            _ => Err(RouteNonMatch::new(StatusCode::NOT_FOUND)),
60        }
61    }
62}
63
64#[cfg(test)]
65mod test {
66    use super::*;
67
68    fn with_state<F>(accept: Option<&str>, block: F)
69    where
70        F: FnOnce(&mut State),
71    {
72        State::with_new(|state| {
73            let mut headers = HeaderMap::new();
74            if let Some(acc) = accept {
75                headers.insert(ACCESS_CONTROL_REQUEST_METHOD, acc.parse().unwrap());
76            }
77            state.put(headers);
78            block(state);
79        });
80    }
81
82    #[test]
83    fn no_acrm_header() {
84        let matcher = AccessControlRequestMethodMatcher::new(Method::PUT);
85        with_state(None, |state| assert!(matcher.is_match(state).is_err()));
86    }
87
88    #[test]
89    fn correct_acrm_header() {
90        let matcher = AccessControlRequestMethodMatcher::new(Method::PUT);
91        with_state(Some("PUT"), |state| {
92            assert!(matcher.is_match(state).is_ok())
93        });
94        with_state(Some("put"), |state| {
95            assert!(matcher.is_match(state).is_ok())
96        });
97    }
98
99    #[test]
100    fn incorrect_acrm_header() {
101        let matcher = AccessControlRequestMethodMatcher::new(Method::PUT);
102        with_state(Some("DELETE"), |state| {
103            assert!(matcher.is_match(state).is_err())
104        });
105    }
106}