headers_accept/
lib.rs

1//! Provides a struct [`Accept`] which implements [`Header`] and owns a list of
2//! [`MediaTypeBuf`] in precedence order.
3//!
4//! See [RFC 9110, 12.5.1 Accept](https://www.rfc-editor.org/rfc/rfc9110.html#section-12.5.1).
5//!
6//! # Examples
7//!
8//! ```rust
9//! use std::str::FromStr;
10//!
11//! use headers_accept::Accept;
12//! use mediatype::MediaTypeBuf;
13//!
14//! let accept = Accept::from_str("audio/*; q=0.2, audio/basic").unwrap();
15//! let mut media_types = accept.media_types();
16//! assert_eq!(
17//!     media_types.next(),
18//!     Some(&MediaTypeBuf::from_str("audio/basic").unwrap())
19//! );
20//! assert_eq!(
21//!     media_types.next(),
22//!     Some(&MediaTypeBuf::from_str("audio/*; q=0.2").unwrap())
23//! );
24//! assert_eq!(media_types.next(), None);
25//! ```
26//!
27//! Content type negotiation is also facilitated through a method,
28//! [`negotiate`](Accept::negotiate), which allows a user agent and server to
29//! determine the best shared format.
30//!
31//! ```rust
32//! # use std::str::FromStr;
33//! # use headers_accept::Accept;
34//! # use mediatype::{names::*, values::*, MediaType, MediaTypeBuf};
35//! const TEXT_HTML: MediaType = MediaType::new(TEXT, HTML);
36//! const APPLICATION_JSON: MediaType = MediaType::new(APPLICATION, JSON);
37//!
38//! const AVAILABLE: &[MediaType] = &[TEXT_HTML, APPLICATION_JSON];
39//!
40//! let accept = Accept::from_str(
41//!     "text/html, application/xhtml+xml, application/xml;q=0.9, text/*;q=0.7, text/csv;q=0",
42//! )
43//! .unwrap();
44//!
45//! assert_eq!(accept.negotiate(AVAILABLE), Some(&TEXT_HTML));
46//! ```
47#![warn(
48    clippy::all,
49    nonstandard_style,
50    future_incompatible,
51    missing_debug_implementations
52)]
53#![deny(missing_docs)]
54#![forbid(unsafe_code)]
55
56use std::{
57    cmp::{Ordering, Reverse},
58    collections::BTreeMap,
59    fmt::{self, Display},
60    str::FromStr,
61};
62
63use headers_core::{Error as HeaderError, Header, HeaderName, HeaderValue};
64use mediatype::{names, MediaType, MediaTypeBuf, ReadParams};
65
66/// Represents a parsed `Accept` HTTP header.
67///
68/// This struct holds a list of `MediaTypeBuf` which are sorted based on
69/// their specificity and the value of the `q` (quality) parameter. In the
70/// absence of a `q` parameter, media types are assumed to have the highest
71/// priority. When media types have equal quality parameters, they maintain the
72/// order in which they were originally specified.
73#[derive(Debug)]
74pub struct Accept(Vec<MediaTypeBuf>);
75
76impl Accept {
77    /// Creates an iterator over the `MediaTypeBuf` entries in the `Accept`
78    /// header.
79    ///
80    /// The media types are returned in the order determined by their
81    /// specificity and the value of their `q` parameter. Media types with
82    /// the same `q` value retain their initial relative ordering from the
83    /// original header.
84    pub fn media_types(&self) -> impl Iterator<Item = &MediaTypeBuf> {
85        self.0.iter()
86    }
87
88    /// Determine the most acceptable media type from a list of media types
89    /// available from the server.
90    ///
91    /// The intent here is that the server knows what formats it is capable of
92    /// delivering, and passes that list to this method.  The `Accept`
93    /// instance knows what types the client is willing to accept, and works
94    /// through that list in order of quality until a match is found.
95    ///
96    /// If no agreement on a media type can be reached, then this method returns
97    /// `None`.
98    ///
99    /// # Tiebreaking
100    ///
101    /// Firstly, this method obeys RFC9110 s12.5.1's rules around media range
102    /// specificity:
103    ///
104    /// > Media ranges can be overridden by more specific media ranges or
105    /// > specific media types. If
106    /// > more than one media range applies to a given type, the most specific
107    /// > reference has
108    /// > precedence.
109    ///
110    /// Next, if two types in the list of acceptable types have the same quality
111    /// score, and both are in the `available` list, then the type that is
112    /// listed first in the list of acceptable types will be chosen.  For
113    /// example, if the client provides `Accept: text/html, text/plain`, and
114    /// the `available` list is `application/json, text/plain, text/html`,
115    /// then `text/html` will be chosen, as it is deemed to be the client's
116    /// preferred option, based on the order in the `Accept` header.
117    ///
118    /// Finally, the order of the types in the `available` parameter should
119    /// match the server's preference for delivery.  In the event that two
120    /// `available` types match the *same* entry in the list of acceptable
121    /// types, then the first entry in the `available` list will be chosen.
122    /// For example, if the client provides `Accept: text/html, image/*;q=0.8`,
123    /// and the `available` list is `image/png, image/gif`, then `image/png`
124    /// will be returned, because it is the first entry in the `available`
125    /// list.
126    ///
127    /// # Caveats
128    ///
129    /// Don't put wildcard types or the `q` parameter in the `available` list;
130    /// if you do, all bets are off as to what might happen.
131    pub fn negotiate<'a, 'mt: 'a, Available>(
132        &self,
133        available: Available,
134    ) -> Option<&'a MediaType<'mt>>
135    where
136        Available: IntoIterator<Item = &'a MediaType<'mt>>,
137    {
138        struct BestMediaType<'a, 'mt: 'a> {
139            quality: QValue,
140            parsed_priority: usize,
141            given_priority: usize,
142            media_type: &'a MediaType<'mt>,
143        }
144
145        available
146            .into_iter()
147            .enumerate()
148            .filter_map(|(given_priority, available_type)| {
149                if let Some(matched_range) = self
150                    .0
151                    .iter()
152                    .enumerate()
153                    .find(|(_, available_range)| MediaRange(available_range) == *available_type)
154                {
155                    let quality = Self::parse_q_value(matched_range.1);
156                    if quality.is_zero() {
157                        return None;
158                    }
159                    Some(BestMediaType {
160                        quality,
161                        parsed_priority: matched_range.0,
162                        given_priority,
163                        media_type: available_type,
164                    })
165                } else {
166                    None
167                }
168            })
169            .max_by_key(|x| (x.quality, Reverse((x.parsed_priority, x.given_priority))))
170            .map(|best| best.media_type)
171    }
172
173    fn parse(mut s: &str) -> Result<Self, HeaderError> {
174        let mut media_types = Vec::new();
175
176        // Parsing adapted from `mediatype::MediaTypeList`.
177        //
178        // See: https://github.com/picoHz/mediatype/blob/29921e91f7176784d4ed1fe42ca40f8a8f225941/src/media_type_list.rs#L34-L63
179        while !s.is_empty() {
180            // Skip initial whitespace.
181            if let Some(index) = s.find(|c: char| !is_ows(c)) {
182                s = &s[index..];
183            } else {
184                break;
185            }
186
187            let mut end = 0;
188            let mut quoted = false;
189            let mut escaped = false;
190            for c in s.chars() {
191                if escaped {
192                    escaped = false;
193                } else {
194                    match c {
195                        '"' => quoted = !quoted,
196                        '\\' if quoted => escaped = true,
197                        ',' if !quoted => break,
198                        _ => (),
199                    }
200                }
201                end += c.len_utf8();
202            }
203
204            // Parse the media type from the current segment.
205            match MediaTypeBuf::from_str(s[..end].trim()) {
206                Ok(mt) => media_types.push(mt),
207                Err(_) => return Err(HeaderError::invalid()),
208            }
209
210            // Move past the current segment.
211            s = s[end..].trim_start_matches(',');
212        }
213
214        // Sort media types relative to their specificity and `q` value.
215        media_types.sort_by_key(|x| {
216            let spec = Self::parse_specificity(x);
217            let q = Self::parse_q_value(x);
218            Reverse((spec, q))
219        });
220
221        Ok(Self(media_types))
222    }
223
224    fn parse_q_value(media_type: &MediaTypeBuf) -> QValue {
225        media_type
226            .get_param(names::Q)
227            .and_then(|v| v.as_str().parse().ok())
228            .unwrap_or_default()
229    }
230
231    fn parse_specificity(media_type: &MediaTypeBuf) -> usize {
232        let type_specificity = if media_type.ty() != names::_STAR {
233            1
234        } else {
235            0
236        };
237        let subtype_specificity = if media_type.subty() != names::_STAR {
238            1
239        } else {
240            0
241        };
242
243        let parameter_count = media_type
244            .params()
245            .filter(|&(name, _)| name != names::Q)
246            .count();
247
248        type_specificity + subtype_specificity + parameter_count
249    }
250}
251
252// See: https://docs.rs/headers/0.4.0/headers/#implementing-the-header-trait
253impl Header for Accept {
254    fn name() -> &'static HeaderName {
255        &http::header::ACCEPT
256    }
257
258    fn decode<'i, I>(values: &mut I) -> Result<Self, HeaderError>
259    where
260        I: Iterator<Item = &'i HeaderValue>,
261    {
262        let mut values_iter = values.map(|v| v.to_str().map_err(|_| HeaderError::invalid()));
263        // Expect at least one header
264        let mut value_str = String::from(values_iter.next().ok_or(HeaderError::invalid())??);
265        for v in values_iter {
266            value_str.push(',');
267            value_str.push_str(v?);
268        }
269        Self::parse(&value_str)
270    }
271
272    fn encode<E>(&self, values: &mut E)
273    where
274        E: Extend<HeaderValue>,
275    {
276        let value = HeaderValue::from_str(&self.to_string())
277            .expect("Header value should only contain visible ASCII characters (32-127)");
278        values.extend(std::iter::once(value));
279    }
280}
281
282impl FromStr for Accept {
283    type Err = HeaderError;
284
285    fn from_str(s: &str) -> Result<Self, Self::Err> {
286        Self::parse(s).map_err(|_| HeaderError::invalid())
287    }
288}
289
290impl TryFrom<&HeaderValue> for Accept {
291    type Error = HeaderError;
292
293    fn try_from(value: &HeaderValue) -> Result<Self, Self::Error> {
294        let s = value.to_str().map_err(|_| HeaderError::invalid())?;
295        s.parse().map_err(|_| HeaderError::invalid())
296    }
297}
298
299impl Display for Accept {
300    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
301        let media_types = self
302            .0
303            .iter()
304            .map(|mt| mt.to_string())
305            .collect::<Vec<_>>()
306            .join(", ");
307        write!(f, "{media_types}")
308    }
309}
310
311impl<'a> FromIterator<MediaType<'a>> for Accept {
312    fn from_iter<T: IntoIterator<Item = MediaType<'a>>>(iter: T) -> Self {
313        iter.into_iter().map(MediaTypeBuf::from).collect()
314    }
315}
316
317impl FromIterator<MediaTypeBuf> for Accept {
318    fn from_iter<T: IntoIterator<Item = MediaTypeBuf>>(iter: T) -> Self {
319        Self(iter.into_iter().collect())
320    }
321}
322
323// Copied directly from `mediatype::parse` as the module is private.
324//
325// See: https://github.com/picoHz/mediatype/blob/29921e91f7176784d4ed1fe42ca40f8a8f225941/src/parse.rs#L136-L138
326const fn is_ows(c: char) -> bool {
327    c == ' ' || c == '\t'
328}
329
330struct MediaRange<'a>(&'a MediaTypeBuf);
331
332impl PartialEq<MediaType<'_>> for MediaRange<'_> {
333    fn eq(&self, other: &MediaType<'_>) -> bool {
334        let (type_match, subtype_match, suffix_match) = (
335            self.0.ty() == other.ty,
336            self.0.subty() == other.subty,
337            self.0.suffix() == other.suffix,
338        );
339
340        let wildcard_type = self.0.ty() == names::_STAR;
341        let wildcard_subtype = self.0.subty() == names::_STAR && type_match;
342
343        let exact_match =
344            type_match && subtype_match && suffix_match && self.0.params().count() == 0;
345
346        let params_match = type_match && subtype_match && suffix_match && {
347            let self_params = self
348                .0
349                .params()
350                .filter(|&(name, _)| name != names::Q)
351                .collect::<BTreeMap<_, _>>();
352
353            let other_params = other
354                .params()
355                .filter(|&(name, _)| name != names::Q)
356                .collect::<BTreeMap<_, _>>();
357
358            self_params == other_params
359        };
360
361        wildcard_type || wildcard_subtype || exact_match || params_match
362    }
363}
364
365#[derive(Debug, Clone, Copy, PartialEq, Eq)]
366struct QValue(
367    /// "Kilo"-q, quality value, in the range 0-1000.
368    u16,
369);
370
371impl Default for QValue {
372    fn default() -> Self {
373        QValue(1000)
374    }
375}
376
377impl QValue {
378    /// Returns `true` if the quality value is zero.
379    pub fn is_zero(&self) -> bool {
380        self.0 == 0
381    }
382}
383
384impl FromStr for QValue {
385    type Err = HeaderError;
386
387    fn from_str(s: &str) -> Result<Self, Self::Err> {
388        // cf. https://www.rfc-editor.org/rfc/rfc9110.html#quality.values
389
390        fn parse_fractional(digits: &[u8]) -> Result<u16, HeaderError> {
391            digits
392                .iter()
393                .try_fold(0u16, |acc, &c| {
394                    if c.is_ascii_digit() {
395                        Some(acc * 10 + (c - b'0') as u16)
396                    } else {
397                        None
398                    }
399                })
400                .map(|num| match digits.len() {
401                    1 => num * 100,
402                    2 => num * 10,
403                    _ => num,
404                })
405                .ok_or_else(HeaderError::invalid)
406        }
407
408        match s.as_bytes() {
409            b"0" => Ok(QValue(0)),
410            b"1" => Ok(QValue(1000)),
411            [b'1', b'.', zeros @ ..] if zeros.len() <= 3 && zeros.iter().all(|d| *d == b'0') => {
412                Ok(QValue(1000))
413            }
414            [b'0', b'.', fractional @ ..] if fractional.len() <= 3 => {
415                parse_fractional(fractional).map(QValue)
416            }
417            _ => Err(HeaderError::invalid()),
418        }
419    }
420}
421
422impl Ord for QValue {
423    fn cmp(&self, other: &Self) -> Ordering {
424        self.0.cmp(&other.0)
425    }
426}
427
428impl PartialOrd for QValue {
429    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
430        Some(self.cmp(other))
431    }
432}
433
434#[cfg(test)]
435mod tests {
436    use super::*;
437
438    #[test]
439    fn reordering() {
440        let accept = Accept::from_str("audio/*; q=0.2, audio/basic").unwrap();
441        let mut media_types = accept.media_types();
442        assert_eq!(
443            media_types.next(),
444            Some(&MediaTypeBuf::from_str("audio/basic").unwrap())
445        );
446        assert_eq!(
447            media_types.next(),
448            Some(&MediaTypeBuf::from_str("audio/*; q=0.2").unwrap())
449        );
450        assert_eq!(media_types.next(), None);
451    }
452
453    #[test]
454    fn reordering_elaborate() {
455        let accept =
456            Accept::from_str("text/plain; q=0.5, text/html, text/x-dvi; q=0.8, text/x-c").unwrap();
457        let mut media_types = accept.media_types();
458        assert_eq!(
459            media_types.next(),
460            Some(&MediaTypeBuf::from_str("text/html").unwrap())
461        );
462        assert_eq!(
463            media_types.next(),
464            Some(&MediaTypeBuf::from_str("text/x-c").unwrap())
465        );
466        assert_eq!(
467            media_types.next(),
468            Some(&MediaTypeBuf::from_str("text/x-dvi; q=0.8").unwrap())
469        );
470        assert_eq!(
471            media_types.next(),
472            Some(&MediaTypeBuf::from_str("text/plain; q=0.5").unwrap())
473        );
474        assert_eq!(media_types.next(), None);
475    }
476
477    #[test]
478    fn preserve_ordering() {
479        let accept = Accept::from_str("x/y, a/b").unwrap();
480        let mut media_types = accept.media_types();
481        assert_eq!(
482            media_types.next(),
483            Some(&MediaTypeBuf::from_str("x/y").unwrap())
484        );
485        assert_eq!(
486            media_types.next(),
487            Some(&MediaTypeBuf::from_str("a/b").unwrap())
488        );
489        assert_eq!(media_types.next(), None);
490    }
491
492    #[test]
493    fn params() {
494        let accept =
495            Accept::from_str("text/html, application/xhtml+xml, application/xml;q=0.9, */*;q=0.8")
496                .unwrap();
497        let mut media_types = accept.media_types();
498        assert_eq!(
499            media_types.next(),
500            Some(&MediaTypeBuf::from_str("text/html").unwrap())
501        );
502        assert_eq!(
503            media_types.next(),
504            Some(&MediaTypeBuf::from_str("application/xhtml+xml").unwrap())
505        );
506        assert_eq!(
507            media_types.next(),
508            Some(&MediaTypeBuf::from_str("application/xml;q=0.9").unwrap())
509        );
510        assert_eq!(
511            media_types.next(),
512            Some(&MediaTypeBuf::from_str("*/*;q=0.8").unwrap())
513        );
514        assert_eq!(media_types.next(), None);
515    }
516
517    #[test]
518    fn quoted_params() {
519        let accept = Accept::from_str(
520            "text/html; message=\"Hello, world!\", application/xhtml+xml; message=\"Hello, \
521             world?\"",
522        )
523        .unwrap();
524        let mut media_types = accept.media_types();
525        assert_eq!(
526            media_types.next(),
527            Some(&MediaTypeBuf::from_str("text/html; message=\"Hello, world!\"").unwrap())
528        );
529        assert_eq!(
530            media_types.next(),
531            Some(
532                &MediaTypeBuf::from_str("application/xhtml+xml; message=\"Hello, world?\"")
533                    .unwrap()
534            )
535        );
536        assert_eq!(media_types.next(), None);
537    }
538
539    #[test]
540    fn more_specifics() {
541        let accept = Accept::from_str("text/*, text/plain, text/plain;format=flowed, */*").unwrap();
542        let mut media_types = accept.media_types();
543        assert_eq!(
544            media_types.next(),
545            Some(&MediaTypeBuf::from_str("text/plain;format=flowed").unwrap())
546        );
547        assert_eq!(
548            media_types.next(),
549            Some(&MediaTypeBuf::from_str("text/plain").unwrap())
550        );
551        assert_eq!(
552            media_types.next(),
553            Some(&MediaTypeBuf::from_str("text/*").unwrap())
554        );
555        assert_eq!(
556            media_types.next(),
557            Some(&MediaTypeBuf::from_str("*/*").unwrap())
558        );
559        assert_eq!(media_types.next(), None);
560    }
561
562    #[test]
563    fn variable_quality_more_specifics() {
564        let accept = Accept::from_str(
565            "text/*;q=0.3, text/plain;q=0.7, text/csv;q=0, text/plain;format=flowed, \
566             text/plain;format=fixed;q=0.4, */*;q=0.5",
567        )
568        .unwrap();
569        let mut media_types = accept.media_types();
570        assert_eq!(
571            media_types.next(),
572            Some(&MediaTypeBuf::from_str("text/plain;format=flowed").unwrap())
573        );
574        assert_eq!(
575            media_types.next(),
576            Some(&MediaTypeBuf::from_str("text/plain;format=fixed;q=0.4").unwrap())
577        );
578        assert_eq!(
579            media_types.next(),
580            Some(&MediaTypeBuf::from_str("text/plain;q=0.7").unwrap())
581        );
582        assert_eq!(
583            media_types.next(),
584            Some(&MediaTypeBuf::from_str("text/csv;q=0").unwrap())
585        );
586        assert_eq!(
587            media_types.next(),
588            Some(&MediaTypeBuf::from_str("text/*;q=0.3").unwrap())
589        );
590        assert_eq!(
591            media_types.next(),
592            Some(&MediaTypeBuf::from_str("*/*;q=0.5").unwrap())
593        );
594        assert_eq!(media_types.next(), None);
595    }
596
597    #[test]
598    fn negotiate() {
599        let accept = Accept::from_str(
600            "text/html, application/xhtml+xml, application/xml;q=0.9, text/*;q=0.7, text/csv;q=0",
601        )
602        .unwrap();
603
604        // Pick the only available type that's acceptable
605        assert_eq!(
606            accept
607                .negotiate(&vec![
608                    MediaType::parse("text/html").unwrap(),
609                    MediaType::parse("application/json").unwrap()
610                ])
611                .unwrap(),
612            &MediaType::parse("text/html").unwrap()
613        );
614        // Pick the type that's first in the acceptable list
615        assert_eq!(
616            accept
617                .negotiate(&vec![
618                    MediaType::parse("application/xhtml+xml").unwrap(),
619                    MediaType::parse("text/html").unwrap()
620                ])
621                .unwrap(),
622            &MediaType::parse("text/html").unwrap()
623        );
624        // Pick the only available type that's acceptable by wildcard subtype
625        assert_eq!(
626            accept
627                .negotiate(&vec![
628                    MediaType::parse("text/plain").unwrap(),
629                    MediaType::parse("image/gif").unwrap()
630                ])
631                .unwrap(),
632            &MediaType::parse("text/plain").unwrap()
633        );
634        // Pick the first available type that matches the wildcard
635        assert_eq!(
636            accept
637                .negotiate(&vec![
638                    MediaType::parse("image/gif").unwrap(),
639                    MediaType::parse("text/plain").unwrap(),
640                    MediaType::parse("text/troff").unwrap(),
641                ])
642                .unwrap(),
643            &MediaType::parse("text/plain").unwrap()
644        );
645        // No acceptable type
646        assert_eq!(
647            accept.negotiate(&vec![
648                MediaType::parse("image/gif").unwrap(),
649                MediaType::parse("image/png").unwrap()
650            ]),
651            None
652        );
653        // Type excluded by q=0
654        assert_eq!(
655            accept.negotiate(&vec![
656                MediaType::parse("image/gif").unwrap(),
657                MediaType::parse("text/csv").unwrap()
658            ]),
659            None
660        );
661    }
662
663    #[test]
664    fn negotiate_with_full_wildcard() {
665        let accept =
666            Accept::from_str("text/html, text/*;q=0.7, */*;q=0.1, text/csv;q=0.0").unwrap();
667
668        // Pick the literal match
669        assert_eq!(
670            accept
671                .negotiate(&vec![
672                    MediaType::parse("text/html").unwrap(),
673                    MediaType::parse("application/json").unwrap()
674                ])
675                .unwrap(),
676            &MediaType::parse("text/html").unwrap()
677        );
678        // Pick the only available type that's acceptable by wildcard subtype
679        assert_eq!(
680            accept
681                .negotiate(&vec![
682                    MediaType::parse("text/plain").unwrap(),
683                    MediaType::parse("image/gif").unwrap()
684                ])
685                .unwrap(),
686            &MediaType::parse("text/plain").unwrap()
687        );
688        // Pick the server's first match of subtype wildcard
689        assert_eq!(
690            accept
691                .negotiate(&vec![
692                    MediaType::parse("text/javascript").unwrap(),
693                    MediaType::parse("text/plain").unwrap()
694                ])
695                .unwrap(),
696            &MediaType::parse("text/javascript").unwrap()
697        );
698        // Pick the server's first match of full wildcard
699        assert_eq!(
700            accept
701                .negotiate(&vec![
702                    MediaType::parse("image/gif").unwrap(),
703                    MediaType::parse("image/png").unwrap()
704                ])
705                .unwrap(),
706            &MediaType::parse("image/gif").unwrap()
707        );
708        // Exclude q=0 type
709        assert_eq!(
710            accept
711                .negotiate(&vec![
712                    MediaType::parse("text/csv").unwrap(),
713                    MediaType::parse("text/javascript").unwrap()
714                ])
715                .unwrap(),
716            &MediaType::parse("text/javascript").unwrap()
717        );
718    }
719
720    #[test]
721    fn negotiate_diabolically() {
722        let accept = Accept::from_str(
723            "text/*;q=0.3, text/csv;q=0.2, text/plain;q=0.7, text/plain;format=rot13;q=0.7, \
724             text/plain;format=flowed, text/plain;format=fixed;q=0.4, */*;q=0.5",
725        )
726        .unwrap();
727
728        // Pick the highest available q
729        assert_eq!(
730            accept
731                .negotiate(&vec![
732                    MediaType::parse("text/html").unwrap(),
733                    MediaType::parse("text/plain").unwrap()
734                ])
735                .unwrap(),
736            &MediaType::parse("text/plain").unwrap()
737        );
738        // Pick the more-specific match with the same quality
739        assert_eq!(
740            accept
741                .negotiate(&vec![
742                    MediaType::parse("text/plain").unwrap(),
743                    MediaType::parse("text/plain;format=rot13").unwrap(),
744                ])
745                .unwrap(),
746            &MediaType::parse("text/plain;format=rot13").unwrap()
747        );
748        // Pick the higher-quality match, despite specificity
749        assert_eq!(
750            accept
751                .negotiate(&vec![
752                    MediaType::parse("text/plain").unwrap(),
753                    MediaType::parse("text/plain;format=fixed").unwrap()
754                ])
755                .unwrap(),
756            &MediaType::parse("text/plain").unwrap()
757        );
758        // This one is the real madness -- disregard a subtype wildcard with a lower
759        // quality in favour of a full wildcard match
760        assert_eq!(
761            accept
762                .negotiate(&vec![
763                    MediaType::parse("text/html").unwrap(),
764                    MediaType::parse("image/gif").unwrap()
765                ])
766                .unwrap(),
767            &MediaType::parse("image/gif").unwrap()
768        );
769    }
770
771    #[test]
772    fn try_from_header_value() {
773        let header_value = &HeaderValue::from_static("audio/*; q=0.2, audio/basic");
774        let accept: Accept = header_value.try_into().unwrap();
775
776        let mut media_types = accept.media_types();
777        assert_eq!(
778            media_types.next(),
779            Some(&MediaTypeBuf::from_str("audio/basic").unwrap())
780        );
781        assert_eq!(
782            media_types.next(),
783            Some(&MediaTypeBuf::from_str("audio/*; q=0.2").unwrap())
784        );
785        assert_eq!(media_types.next(), None);
786    }
787
788    #[test]
789    fn decode() {
790        let mut empty_iter = [].iter();
791        assert!(
792            Accept::decode(&mut empty_iter).is_err(),
793            "providing no headers results in an error"
794        );
795
796        let header_value_1 = HeaderValue::from_static("audio/*; q=0.2");
797        let header_value_2 = HeaderValue::from_static("audio/basic");
798        let header_value_combined = HeaderValue::from_static("audio/*; q=0.2, audio/basic");
799        let combined_accept_try_into: Accept = (&header_value_combined).try_into().unwrap();
800
801        // A single header should give the same result as [super::try_into]
802        let combined_accept_decode =
803            Accept::decode(&mut [&header_value_combined].into_iter()).unwrap();
804        let mut combined_iter_decode = combined_accept_decode.media_types();
805        let mut combined_iter_try_into = combined_accept_try_into.media_types();
806
807        for (m1, m2) in core::iter::zip(&mut combined_iter_decode, &mut combined_iter_try_into) {
808            assert_eq!(m1, m2, "same media type through `decode` and `try_into`");
809        }
810        assert_eq!(combined_iter_decode.next(), None);
811        assert_eq!(combined_iter_try_into.next(), None);
812
813        // Multiple headers are equivalent to a single, `,`-separated header
814        let separate_accept_decode =
815            Accept::decode(&mut [&header_value_1, &header_value_2].into_iter()).unwrap();
816        let mut separate_iter_decode = separate_accept_decode.media_types();
817        let mut separate_iter_try_into = combined_accept_try_into.media_types();
818
819        for (m1, m2) in core::iter::zip(&mut separate_iter_decode, &mut separate_iter_try_into) {
820            assert_eq!(m1, m2, "same media type through `decode` and `try_into`");
821        }
822        assert_eq!(separate_iter_decode.next(), None);
823        assert_eq!(separate_iter_try_into.next(), None);
824    }
825
826    #[test]
827    fn mixed_lifetime_from_iter() {
828        // this must type check
829        #[allow(unused)]
830        fn best<'a>(available: &'a [MediaType<'static>]) -> Option<&'a MediaType<'static>> {
831            let accept = Accept::from_str("*/*").unwrap();
832            accept.negotiate(available.iter())
833        }
834    }
835
836    #[test]
837    fn from_iterator() {
838        // MediaType
839        let accept = Accept::from_iter([
840            MediaType::parse("text/html").unwrap(),
841            MediaType::parse("image/gif").unwrap(),
842        ]);
843
844        assert_eq!(
845            accept.media_types().collect::<Vec<_>>(),
846            vec![
847                MediaType::parse("text/html").unwrap(),
848                MediaType::parse("image/gif").unwrap(),
849            ]
850        );
851
852        // MediaTypeBuf
853        let accept = Accept::from_iter([
854            MediaTypeBuf::from_str("text/html").unwrap(),
855            MediaTypeBuf::from_str("image/gif").unwrap(),
856        ]);
857
858        assert_eq!(
859            accept.media_types().collect::<Vec<_>>(),
860            vec![
861                MediaType::parse("text/html").unwrap(),
862                MediaType::parse("image/gif").unwrap(),
863            ]
864        );
865    }
866
867    #[test]
868    fn test_qvalue_parsing_one() {
869        assert_eq!(QValue(1000), "1".parse().unwrap());
870        assert_eq!(QValue(1000), "1.".parse().unwrap());
871        assert_eq!(QValue(1000), "1.0".parse().unwrap());
872        assert_eq!(QValue(1000), "1.00".parse().unwrap());
873        assert_eq!(QValue(1000), "1.000".parse().unwrap());
874    }
875
876    #[test]
877    fn test_qvalue_parsing_partial() {
878        assert_eq!(QValue(0), "0".parse().unwrap());
879        assert_eq!(QValue(0), "0.".parse().unwrap());
880        assert_eq!(QValue(0), "0.0".parse().unwrap());
881        assert_eq!(QValue(0), "0.00".parse().unwrap());
882        assert_eq!(QValue(0), "0.000".parse().unwrap());
883        assert_eq!(QValue(100), "0.1".parse().unwrap());
884        assert_eq!(QValue(120), "0.12".parse().unwrap());
885        assert_eq!(QValue(123), "0.123".parse().unwrap());
886        assert_eq!(QValue(23), "0.023".parse().unwrap());
887        assert_eq!(QValue(3), "0.003".parse().unwrap());
888    }
889
890    #[test]
891    fn qvalue_parsing_invalid() {
892        assert!("0.0000".parse::<QValue>().is_err());
893        assert!("0.1.".parse::<QValue>().is_err());
894        assert!("0.12.".parse::<QValue>().is_err());
895        assert!("0.123.".parse::<QValue>().is_err());
896        assert!("0.1234".parse::<QValue>().is_err());
897        assert!("1.123".parse::<QValue>().is_err());
898        assert!("1.1234".parse::<QValue>().is_err());
899        assert!("1.12345".parse::<QValue>().is_err());
900        assert!("2.0".parse::<QValue>().is_err());
901        assert!("-0.0".parse::<QValue>().is_err());
902        assert!("1.0000".parse::<QValue>().is_err());
903    }
904
905    #[test]
906    fn qvalue_ordering() {
907        assert!(QValue(1000) > QValue(0));
908        assert!(QValue(1000) > QValue(100));
909        assert!(QValue(100) > QValue(0));
910        assert!(QValue(120) > QValue(100));
911        assert!(QValue(123) > QValue(120));
912        assert!(QValue(23) < QValue(100));
913        assert!(QValue(3) < QValue(23));
914    }
915
916    #[test]
917    fn qvalue_default() {
918        let q: QValue = Default::default();
919        assert_eq!(q, QValue(1000));
920    }
921
922    #[test]
923    fn qvalue_is_zero() {
924        assert!("0.".parse::<QValue>().unwrap().is_zero());
925    }
926}