rouille/input/
accept.rs

1// Copyright (c) 2016 The Rouille developers
2// Licensed under the Apache License, Version 2.0
3// <LICENSE-APACHE or
4// http://www.apache.org/licenses/LICENSE-2.0> or the MIT
5// license <LICENSE-MIT or http://opensource.org/licenses/MIT>,
6// at your option. All files in the project carrying such
7// notice may not be copied, modified, or distributed except
8// according to those terms.
9
10/// Dispatches between blocks depending on the value of the `Accept` header.
11///
12/// This macro takes as first parameter the request object, and then each additional parameter must
13/// be of the form `mime => value` where `mime` is a MIME type in quotes and `value` is an
14/// expression of any type.
15///
16/// The macro returns the value corresponding to the MIME type that has the highest priority in
17/// the request's `Accept` header. If multiple MIME types have the same priority, the earliest in
18/// the list passed to the macro is chosen. If no MIME matches the request, the first in the list
19/// is chosen. If there is no `Accept` header in the request, it is as if the header's value
20/// was `*/*`.
21///
22/// You can also use `*` in the MIME types you pass to the macro. The MIME `*/*` can be used as a
23/// default handler.
24///
25/// > **Note**: Using `|` just like in real match expressions is not yet supported because the
26/// > author didn't find a way to make it work with Rust macros.
27///
28/// # Basic example
29///
30/// ```
31/// # #[macro_use] extern crate rouille;
32/// use rouille::Request;
33/// use rouille::Response;
34///
35/// fn handle(request: &Request) -> Response {
36///     accept!(request,
37///         "text/html" => Response::html("<p>Hello world</p>"),
38///         "text/plain" => Response::text("Hello world"),
39///     )
40/// }
41/// # fn main() {}
42/// ```
43///
44/// # Example with a default handler
45///
46/// ```
47/// # #[macro_use] extern crate rouille;
48/// use rouille::Request;
49/// use rouille::Response;
50///
51/// fn handle(request: &Request) -> Response {
52///     accept!(request,
53///         "text/html" => Response::html("<p>Hello world</p>"),
54///         "text/plain" => Response::text("Hello world"),
55///         "*/*" => Response::empty_406()
56///     )
57/// }
58/// # fn main() {}
59/// ```
60#[macro_export]
61macro_rules! accept {
62    ($request:expr, $($mime:expr => $val:expr),+ $(,)*) => ({
63        use $crate::input;
64        use std::iter;
65
66        let header = $request.header("Accept").unwrap_or("*/*");
67
68        let handled = {
69            let i = iter::empty();
70            $(let i = i.chain(iter::once($mime));)+
71            i
72        };
73
74        let mut preferred = input::priority_header_preferred(header, handled).unwrap_or(0);
75
76        let mut outcome = None;
77
78        preferred += 1;
79        $(
80            if preferred >= 1 {
81                preferred -= 1;
82                if preferred == 0 {
83                    outcome = Some($val);
84                }
85            }
86        )+
87
88        outcome.unwrap()    // unwrap() can only panic if priority_header_preferred has a bug or
89                            // if the list of mimes is empty (which can't happen)
90    });
91}
92
93#[cfg(test)]
94mod tests {
95    use Request;
96
97    #[test]
98    fn basic() {
99        let request = Request::fake_http(
100            "GET",
101            "/",
102            vec![("Accept".to_owned(), "text/plain, */*".to_owned())],
103            vec![],
104        );
105
106        let result = accept!(&request,
107            "text/plain" => 5,
108            "*/*" => 12,
109        );
110
111        assert_eq!(result, 5);
112    }
113
114    #[test]
115    fn wildcard() {
116        let request = Request::fake_http(
117            "GET",
118            "/",
119            vec![("Accept".to_owned(), "image/gif".to_owned())],
120            vec![],
121        );
122
123        let result = accept!(&request,
124            "text/plain" => 5,
125            "*/*" => 12,
126        );
127
128        assert_eq!(result, 12);
129    }
130
131    #[test]
132    fn no_match() {
133        let request = Request::fake_http(
134            "GET",
135            "/",
136            vec![("Accept".to_owned(), "image/gif".to_owned())],
137            vec![],
138        );
139
140        let result = accept!(&request,
141            "text/plain" => 5,
142            "image/svg+xml" => 12,
143        );
144
145        assert_eq!(result, 5);
146    }
147
148    #[test]
149    fn multimatch_first() {
150        let request = Request::fake_http(
151            "GET",
152            "/",
153            vec![("Accept".to_owned(), "image/gif".to_owned())],
154            vec![],
155        );
156
157        let result = accept!(&request,
158            "text/plain" => 5,
159            "text/plain" => 12,
160            "text/plain" => 28,
161        );
162
163        assert_eq!(result, 5);
164    }
165
166    #[test]
167    fn no_header_first() {
168        let request = Request::fake_http("GET", "/", vec![], vec![]);
169
170        let result = accept!(&request,
171            "image/gif" => 5,
172            "text/plain" => 12,
173            "text/html" => 28,
174        );
175
176        assert_eq!(result, 5);
177    }
178
179    #[test]
180    fn no_header_wildcard() {
181        let request = Request::fake_http(
182            "GET",
183            "/",
184            vec![("Accept".to_owned(), "image/tiff".to_owned())],
185            vec![],
186        );
187
188        let result = accept!(&request,
189            "image/gif" => 5,
190            "text/plain" => 12,
191            "text/html" => 28,
192            "*/*" => 37
193        );
194
195        assert_eq!(result, 37);
196    }
197}