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}