rama_http_headers/common/
accept.rs

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