Skip to main content

rama_http_headers/common/
accept.rs

1use crate::specifier::{QualityValue, sort_quality_values_non_empty_smallvec};
2use rama_http_types::mime::{self, Mime};
3
4derive_non_empty_flat_csv_header! {
5    #[header(name = ACCEPT, sep = Comma)]
6    /// `Accept` header, defined in [RFC7231](https://datatracker.ietf.org/doc/html/rfc7231#section-5.3.2)
7    ///
8    /// The `Accept` header field can be used by user agents to specify
9    /// response media types that are acceptable.  Accept header fields can
10    /// be used to indicate that the request is specifically limited to a
11    /// small set of desired types, as in the case of a request for an
12    /// in-line image
13    ///
14    /// # ABNF
15    ///
16    /// ```text
17    /// Accept = #( media-range [ accept-params ] )
18    ///
19    /// media-range    = ( "*/*"
20    ///                  / ( type "/" "*" )
21    ///                  / ( type "/" subtype )
22    ///                  ) *( OWS ";" OWS parameter )
23    /// accept-params  = weight *( accept-ext )
24    /// accept-ext = OWS ";" OWS token [ "=" ( token / quoted-string ) ]
25    /// ```
26    ///
27    /// # Example values
28    /// * `audio/*; q=0.2, audio/basic`
29    /// * `text/plain; q=0.5, text/html, text/x-dvi; q=0.8, text/x-c`
30    ///
31    /// # Examples
32    ///
33    /// ```
34    /// use std::iter::FromIterator;
35    /// use rama_http_headers::{Accept, specifier::QualityValue, HeaderMapExt};
36    /// use rama_http_types::mime;
37    ///
38    /// let mut headers = rama_http_types::HeaderMap::new();
39    ///
40    /// headers.typed_insert(
41    ///     Accept::new(
42    ///         QualityValue::new(mime::TEXT_HTML, Default::default()),
43    ///     )
44    /// );
45    /// ```
46    ///
47    /// ```
48    /// use std::iter::FromIterator;
49    /// use rama_http_headers::{Accept, specifier::QualityValue, HeaderMapExt};
50    /// use rama_http_types::mime;
51    ///
52    /// let mut headers = rama_http_types::HeaderMap::new();
53    /// headers.typed_insert(
54    ///     Accept::new(
55    ///         QualityValue::new(mime::APPLICATION_JSON, Default::default()),
56    ///     )
57    /// );
58    /// ```
59    ///
60    /// ```
61    /// use std::iter::FromIterator;
62    /// use rama_utils::collections::non_empty_smallvec;
63    /// use rama_http_headers::{Accept, specifier::QualityValue, HeaderMapExt};
64    /// use rama_http_types::mime;
65    ///
66    /// let mut headers = rama_http_types::HeaderMap::new();
67    ///
68    /// headers.typed_insert(
69    ///     Accept(non_empty_smallvec![
70    ///         QualityValue::from(mime::TEXT_HTML),
71    ///         QualityValue::from("application/xhtml+xml".parse::<mime::Mime>().unwrap()),
72    ///         QualityValue::new(
73    ///             mime::TEXT_XML,
74    ///             900.into()
75    ///         ),
76    ///         QualityValue::from("image/webp".parse::<mime::Mime>().unwrap()),
77    ///         QualityValue::new(
78    ///             mime::STAR_STAR,
79    ///             800.into()
80    ///         ),
81    ///     ])
82    /// );
83    /// ```
84    #[derive(Debug, Clone, PartialEq, Eq)]
85    pub struct Accept(pub NonEmptySmallVec<7, QualityValue<Mime>>);
86}
87
88impl Accept {
89    #[inline(always)]
90    #[must_use]
91    pub fn new_from_mime(mime: Mime) -> Self {
92        Self::new(QualityValue::new_value(mime))
93    }
94
95    /// A constructor to easily create `Accept: */*`.
96    #[must_use]
97    #[inline(always)]
98    pub fn star() -> Self {
99        Self::new_from_mime(mime::STAR_STAR)
100    }
101
102    /// A constructor to easily create `Accept: application/json`.
103    #[must_use]
104    #[inline(always)]
105    pub fn json() -> Self {
106        Self::new_from_mime(mime::APPLICATION_JSON)
107    }
108
109    /// A constructor to easily create `Accept: text/*`.
110    #[must_use]
111    #[inline(always)]
112    pub fn text() -> Self {
113        Self::new_from_mime(mime::TEXT_STAR)
114    }
115
116    /// A constructor to easily create `Accept: image/*`.
117    #[must_use]
118    #[inline(always)]
119    pub fn image() -> Self {
120        Self::new_from_mime(mime::IMAGE_STAR)
121    }
122
123    /// Sort (stable) the inner quality values by quality.
124    #[inline(always)]
125    pub fn sort_quality_values(&mut self) {
126        sort_quality_values_non_empty_smallvec(&mut self.0);
127    }
128}
129
130#[cfg(test)]
131mod tests {
132    use std::str::FromStr;
133
134    use super::*;
135    use crate::{HeaderDecode, specifier::Quality};
136    use rama_http_types::{
137        HeaderValue,
138        mime::{TEXT_HTML, TEXT_PLAIN, TEXT_PLAIN_UTF_8},
139    };
140    use rama_utils::collections::non_empty_smallvec;
141
142    macro_rules! test_header {
143        ($name: ident, $input: expr, $expected: expr) => {
144            #[test]
145            fn $name() {
146                assert_eq!(
147                    Accept::decode(
148                        &mut $input
149                            .into_iter()
150                            .map(|s| HeaderValue::from_bytes(s).unwrap())
151                            .collect::<Vec<_>>()
152                            .iter()
153                    )
154                    .ok(),
155                    $expected,
156                );
157            }
158        };
159    }
160
161    // Tests from the RFC
162    test_header!(
163        test1,
164        vec![b"audio/*; q=0.2, audio/basic"],
165        Some(Accept(non_empty_smallvec![
166            QualityValue::new("audio/*".parse().unwrap(), Quality::from(200)),
167            QualityValue::new("audio/basic".parse().unwrap(), Default::default()),
168        ]))
169    );
170    test_header!(
171        test2,
172        vec![b"text/plain; q=0.5, text/html, text/x-dvi; q=0.8, text/x-c"],
173        Some(Accept(non_empty_smallvec![
174            QualityValue::new(TEXT_PLAIN, Quality::from(500)),
175            QualityValue::new(TEXT_HTML, Default::default()),
176            QualityValue::new("text/x-dvi".parse().unwrap(), Quality::from(800)),
177            QualityValue::new("text/x-c".parse().unwrap(), Default::default()),
178        ]))
179    );
180    // Custom tests
181    test_header!(
182        test3,
183        vec![b"text/plain; charset=utf-8"],
184        Some(Accept(non_empty_smallvec![QualityValue::new(
185            TEXT_PLAIN_UTF_8,
186            Default::default()
187        )]))
188    );
189    test_header!(
190        test4,
191        vec![b"text/plain; charset=utf-8; q=0.5"],
192        Some(Accept(non_empty_smallvec![QualityValue::new(
193            TEXT_PLAIN_UTF_8,
194            Quality::from(500)
195        ),]))
196    );
197
198    #[test]
199    fn test_accept_sort() {
200        for (header_value, expected_first_mime) in [
201            (
202                "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
203                mime::TEXT_HTML,
204            ),
205            ("text/html", mime::TEXT_HTML),
206            (
207                "application/xml,text/html,application/xhtml+xml,*/*;q=0.8",
208                mime::Mime::from_str("application/xml").unwrap(),
209            ),
210            (
211                "application/xml",
212                mime::Mime::from_str("application/xml").unwrap(),
213            ),
214            (
215                "application/xml",
216                mime::Mime::from_str("application/xml").unwrap(),
217            ),
218            (
219                "text/html;q=0.8,application/xml",
220                mime::Mime::from_str("application/xml").unwrap(),
221            ),
222            (
223                "text/html;q=0.8,application/json;q=0.9,application/xml,text/plain",
224                mime::Mime::from_str("application/xml").unwrap(),
225            ),
226            (
227                "text/html;q=0.8,application/json;q=0.9,text/plain,application/xml",
228                mime::TEXT_PLAIN,
229            ),
230            (
231                "text/html;q=0.8,application/json;q=0.9,text/plain;q=0.2,application/xml",
232                mime::Mime::from_str("application/xml").unwrap(),
233            ),
234            ("text/plain", mime::TEXT_PLAIN),
235            ("text/plain; charset=utf8", mime::TEXT_PLAIN_UTF_8),
236            ("text/plain; charset=utf8; q=0.5", mime::TEXT_PLAIN_UTF_8),
237        ] {
238            let mut accept =
239                Accept::decode(&mut [&HeaderValue::from_static(header_value)].into_iter()).unwrap();
240            accept.sort_quality_values();
241            assert_eq!(
242                accept.0.head.value, expected_first_mime,
243                "header value: {header_value}; parsed qvs: {:?}",
244                accept.0
245            );
246        }
247    }
248}