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}