http_types/content/
accept.rs

1//! Client header advertising which media types the client is able to understand.
2
3use crate::content::{ContentType, MediaTypeProposal};
4use crate::headers::{HeaderName, HeaderValue, Headers, ToHeaderValues, ACCEPT};
5use crate::utils::sort_by_weight;
6use crate::{Error, Mime, StatusCode};
7
8use std::fmt::{self, Debug, Write};
9use std::option;
10use std::slice;
11
12/// Client header advertising which media types the client is able to understand.
13///
14/// Using content negotiation, the server then selects one of the proposals, uses
15/// it and informs the client of its choice with the `Content-Type` response
16/// header. Browsers set adequate values for this header depending on the context
17/// where the request is done: when fetching a CSS stylesheet a different value
18/// is set for the request than when fetching an image, video or a script.
19///
20/// [MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Accept)
21///
22/// # Specifications
23///
24/// - [RFC 7231, section 5.3.2: Accept](https://tools.ietf.org/html/rfc7231#section-5.3.2)
25///
26/// # Examples
27///
28/// ```
29/// # fn main() -> http_types::Result<()> {
30/// #
31/// use http_types::content::{Accept, MediaTypeProposal};
32/// use http_types::{mime, Response};
33///
34/// let mut accept = Accept::new();
35/// accept.push(MediaTypeProposal::new(mime::HTML, Some(0.8))?);
36/// accept.push(MediaTypeProposal::new(mime::XML, Some(0.4))?);
37/// accept.push(mime::PLAIN);
38///
39/// let mut res = Response::new(200);
40/// let content_type = accept.negotiate(&[mime::XML])?;
41/// content_type.apply(&mut res);
42///
43/// assert_eq!(res["Content-Type"], "application/xml;charset=utf-8");
44/// #
45/// # Ok(()) }
46/// ```
47pub struct Accept {
48    wildcard: bool,
49    entries: Vec<MediaTypeProposal>,
50}
51
52impl Accept {
53    /// Create a new instance of `Accept`.
54    pub fn new() -> Self {
55        Self {
56            entries: vec![],
57            wildcard: false,
58        }
59    }
60
61    /// Create an instance of `Accept` from a `Headers` instance.
62    pub fn from_headers(headers: impl AsRef<Headers>) -> crate::Result<Option<Self>> {
63        let mut entries = vec![];
64        let headers = match headers.as_ref().get(ACCEPT) {
65            Some(headers) => headers,
66            None => return Ok(None),
67        };
68
69        let mut wildcard = false;
70
71        for value in headers {
72            for part in value.as_str().trim().split(',') {
73                let part = part.trim();
74
75                // Handle empty strings, and wildcard directives.
76                if part.is_empty() {
77                    continue;
78                } else if part == "*" {
79                    wildcard = true;
80                    continue;
81                }
82
83                // Try and parse a directive from a str. If the directive is
84                // unkown we skip it.
85                let entry = MediaTypeProposal::from_str(part)?;
86                entries.push(entry);
87            }
88        }
89
90        Ok(Some(Self { entries, wildcard }))
91    }
92
93    /// Push a directive into the list of entries.
94    pub fn push(&mut self, prop: impl Into<MediaTypeProposal>) {
95        self.entries.push(prop.into());
96    }
97
98    /// Returns `true` if a wildcard directive was passed.
99    pub fn wildcard(&self) -> bool {
100        self.wildcard
101    }
102
103    /// Set the wildcard directive.
104    pub fn set_wildcard(&mut self, wildcard: bool) {
105        self.wildcard = wildcard
106    }
107
108    /// Sort the header directives by weight.
109    ///
110    /// Headers with a higher `q=` value will be returned first. If two
111    /// directives have the same weight, the directive that was declared later
112    /// will be returned first.
113    pub fn sort(&mut self) {
114        sort_by_weight(&mut self.entries);
115    }
116
117    /// Determine the most suitable `Content-Type` encoding.
118    ///
119    /// # Errors
120    ///
121    /// If no suitable encoding is found, an error with the status of `406` will be returned.
122    pub fn negotiate(&mut self, available: &[Mime]) -> crate::Result<ContentType> {
123        // Start by ordering the encodings.
124        self.sort();
125
126        // Try and find the first encoding that matches.
127        for accept in &self.entries {
128            if available.contains(accept) {
129                return Ok(accept.media_type.clone().into());
130            }
131        }
132
133        // If no encoding matches and wildcard is set, send whichever encoding we got.
134        if self.wildcard {
135            if let Some(accept) = available.iter().next() {
136                return Ok(accept.clone().into());
137            }
138        }
139
140        let mut err = Error::new_adhoc("No suitable Content-Type found");
141        err.set_status(StatusCode::NotAcceptable);
142        Err(err)
143    }
144
145    /// Sets the `Accept-Encoding` header.
146    pub fn apply(&self, mut headers: impl AsMut<Headers>) {
147        headers.as_mut().insert(ACCEPT, self.value());
148    }
149
150    /// Get the `HeaderName`.
151    pub fn name(&self) -> HeaderName {
152        ACCEPT
153    }
154
155    /// Get the `HeaderValue`.
156    pub fn value(&self) -> HeaderValue {
157        let mut output = String::new();
158        for (n, directive) in self.entries.iter().enumerate() {
159            let directive: HeaderValue = directive.clone().into();
160            match n {
161                0 => write!(output, "{}", directive).unwrap(),
162                _ => write!(output, ", {}", directive).unwrap(),
163            };
164        }
165
166        if self.wildcard {
167            match output.len() {
168                0 => write!(output, "*").unwrap(),
169                _ => write!(output, ", *").unwrap(),
170            }
171        }
172
173        // SAFETY: the internal string is validated to be ASCII.
174        unsafe { HeaderValue::from_bytes_unchecked(output.into()) }
175    }
176
177    /// An iterator visiting all entries.
178    pub fn iter(&self) -> Iter<'_> {
179        Iter {
180            inner: self.entries.iter(),
181        }
182    }
183
184    /// An iterator visiting all entries.
185    pub fn iter_mut(&mut self) -> IterMut<'_> {
186        IterMut {
187            inner: self.entries.iter_mut(),
188        }
189    }
190}
191
192impl IntoIterator for Accept {
193    type Item = MediaTypeProposal;
194    type IntoIter = IntoIter;
195
196    #[inline]
197    fn into_iter(self) -> Self::IntoIter {
198        IntoIter {
199            inner: self.entries.into_iter(),
200        }
201    }
202}
203
204impl<'a> IntoIterator for &'a Accept {
205    type Item = &'a MediaTypeProposal;
206    type IntoIter = Iter<'a>;
207
208    #[inline]
209    fn into_iter(self) -> Self::IntoIter {
210        self.iter()
211    }
212}
213
214impl<'a> IntoIterator for &'a mut Accept {
215    type Item = &'a mut MediaTypeProposal;
216    type IntoIter = IterMut<'a>;
217
218    #[inline]
219    fn into_iter(self) -> Self::IntoIter {
220        self.iter_mut()
221    }
222}
223
224/// A borrowing iterator over entries in `Accept`.
225#[derive(Debug)]
226pub struct IntoIter {
227    inner: std::vec::IntoIter<MediaTypeProposal>,
228}
229
230impl Iterator for IntoIter {
231    type Item = MediaTypeProposal;
232
233    fn next(&mut self) -> Option<Self::Item> {
234        self.inner.next()
235    }
236
237    #[inline]
238    fn size_hint(&self) -> (usize, Option<usize>) {
239        self.inner.size_hint()
240    }
241}
242
243/// A lending iterator over entries in `Accept`.
244#[derive(Debug)]
245pub struct Iter<'a> {
246    inner: slice::Iter<'a, MediaTypeProposal>,
247}
248
249impl<'a> Iterator for Iter<'a> {
250    type Item = &'a MediaTypeProposal;
251
252    fn next(&mut self) -> Option<Self::Item> {
253        self.inner.next()
254    }
255
256    #[inline]
257    fn size_hint(&self) -> (usize, Option<usize>) {
258        self.inner.size_hint()
259    }
260}
261
262/// A mutable iterator over entries in `Accept`.
263#[derive(Debug)]
264pub struct IterMut<'a> {
265    inner: slice::IterMut<'a, MediaTypeProposal>,
266}
267
268impl<'a> Iterator for IterMut<'a> {
269    type Item = &'a mut MediaTypeProposal;
270
271    fn next(&mut self) -> Option<Self::Item> {
272        self.inner.next()
273    }
274
275    #[inline]
276    fn size_hint(&self) -> (usize, Option<usize>) {
277        self.inner.size_hint()
278    }
279}
280
281impl ToHeaderValues for Accept {
282    type Iter = option::IntoIter<HeaderValue>;
283    fn to_header_values(&self) -> crate::Result<Self::Iter> {
284        // A HeaderValue will always convert into itself.
285        Ok(self.value().to_header_values().unwrap())
286    }
287}
288
289impl Debug for Accept {
290    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
291        let mut list = f.debug_list();
292        for directive in &self.entries {
293            list.entry(directive);
294        }
295        list.finish()
296    }
297}
298
299#[cfg(test)]
300mod test {
301    use super::*;
302    use crate::mime;
303    use crate::Response;
304
305    #[test]
306    fn smoke() -> crate::Result<()> {
307        let mut accept = Accept::new();
308        accept.push(mime::HTML);
309
310        let mut headers = Response::new(200);
311        accept.apply(&mut headers);
312
313        let accept = Accept::from_headers(headers)?.unwrap();
314        assert_eq!(accept.iter().next().unwrap(), mime::HTML);
315        Ok(())
316    }
317
318    #[test]
319    fn wildcard() -> crate::Result<()> {
320        let mut accept = Accept::new();
321        accept.set_wildcard(true);
322
323        let mut headers = Response::new(200);
324        accept.apply(&mut headers);
325
326        let accept = Accept::from_headers(headers)?.unwrap();
327        assert!(accept.wildcard());
328        Ok(())
329    }
330
331    #[test]
332    fn wildcard_and_header() -> crate::Result<()> {
333        let mut accept = Accept::new();
334        accept.push(mime::HTML);
335        accept.set_wildcard(true);
336
337        let mut headers = Response::new(200);
338        accept.apply(&mut headers);
339
340        let accept = Accept::from_headers(headers)?.unwrap();
341        assert!(accept.wildcard());
342        assert_eq!(accept.iter().next().unwrap(), mime::HTML);
343        Ok(())
344    }
345
346    #[test]
347    fn iter() -> crate::Result<()> {
348        let mut accept = Accept::new();
349        accept.push(mime::HTML);
350        accept.push(mime::XML);
351
352        let mut headers = Response::new(200);
353        accept.apply(&mut headers);
354
355        let accept = Accept::from_headers(headers)?.unwrap();
356        let mut accept = accept.iter();
357        assert_eq!(accept.next().unwrap(), mime::HTML);
358        assert_eq!(accept.next().unwrap(), mime::XML);
359        Ok(())
360    }
361
362    #[test]
363    fn reorder_based_on_weight() -> crate::Result<()> {
364        let mut accept = Accept::new();
365        accept.push(MediaTypeProposal::new(mime::HTML, Some(0.4))?);
366        accept.push(MediaTypeProposal::new(mime::XML, None)?);
367        accept.push(MediaTypeProposal::new(mime::PLAIN, Some(0.8))?);
368
369        let mut headers = Response::new(200);
370        accept.apply(&mut headers);
371
372        let mut accept = Accept::from_headers(headers)?.unwrap();
373        accept.sort();
374        let mut accept = accept.iter();
375        assert_eq!(accept.next().unwrap(), mime::PLAIN);
376        assert_eq!(accept.next().unwrap(), mime::HTML);
377        assert_eq!(accept.next().unwrap(), mime::XML);
378        Ok(())
379    }
380
381    #[test]
382    fn reorder_based_on_weight_and_location() -> crate::Result<()> {
383        let mut accept = Accept::new();
384        accept.push(MediaTypeProposal::new(mime::HTML, None)?);
385        accept.push(MediaTypeProposal::new(mime::XML, None)?);
386        accept.push(MediaTypeProposal::new(mime::PLAIN, Some(0.8))?);
387
388        let mut res = Response::new(200);
389        accept.apply(&mut res);
390
391        let mut accept = Accept::from_headers(res)?.unwrap();
392        accept.sort();
393        let mut accept = accept.iter();
394        assert_eq!(accept.next().unwrap(), mime::PLAIN);
395        assert_eq!(accept.next().unwrap(), mime::XML);
396        assert_eq!(accept.next().unwrap(), mime::HTML);
397        Ok(())
398    }
399
400    #[test]
401    fn negotiate() -> crate::Result<()> {
402        let mut accept = Accept::new();
403        accept.push(MediaTypeProposal::new(mime::HTML, Some(0.4))?);
404        accept.push(MediaTypeProposal::new(mime::PLAIN, Some(0.8))?);
405        accept.push(MediaTypeProposal::new(mime::XML, None)?);
406
407        assert_eq!(accept.negotiate(&[mime::HTML, mime::XML])?, mime::HTML);
408        Ok(())
409    }
410
411    #[test]
412    fn negotiate_not_acceptable() -> crate::Result<()> {
413        let mut accept = Accept::new();
414        let err = accept.negotiate(&[mime::JSON]).unwrap_err();
415        assert_eq!(err.status(), 406);
416
417        let mut accept = Accept::new();
418        accept.push(MediaTypeProposal::new(mime::JSON, Some(0.8))?);
419        let err = accept.negotiate(&[mime::XML]).unwrap_err();
420        assert_eq!(err.status(), 406);
421        Ok(())
422    }
423
424    #[test]
425    fn negotiate_wildcard() -> crate::Result<()> {
426        let mut accept = Accept::new();
427        accept.push(MediaTypeProposal::new(mime::JSON, Some(0.8))?);
428        accept.set_wildcard(true);
429
430        assert_eq!(accept.negotiate(&[mime::XML])?, mime::XML);
431        Ok(())
432    }
433}