http_types_2/content/
accept.rs1use crate::headers::{HeaderName, HeaderValue, Headers, ACCEPT};
4use crate::mime::Mime;
5use crate::utils::sort_by_weight;
6use crate::{
7 content::{ContentType, MediaTypeProposal},
8 headers::Header,
9};
10use crate::{Error, StatusCode};
11
12use std::fmt::{self, Debug, Write};
13
14use std::slice;
15
16pub struct Accept {
52 wildcard: bool,
53 entries: Vec<MediaTypeProposal>,
54}
55
56impl Accept {
57 pub fn new() -> Self {
59 Self {
60 entries: vec![],
61 wildcard: false,
62 }
63 }
64
65 pub fn from_headers(headers: impl AsRef<Headers>) -> crate::Result<Option<Self>> {
67 let mut entries = vec![];
68 let headers = match headers.as_ref().get(ACCEPT) {
69 Some(headers) => headers,
70 None => return Ok(None),
71 };
72
73 let mut wildcard = false;
74
75 for value in headers {
76 for part in value.as_str().trim().split(',') {
77 let part = part.trim();
78
79 if part.is_empty() {
81 continue;
82 } else if part == "*" {
83 wildcard = true;
84 continue;
85 }
86
87 let entry = MediaTypeProposal::from_str(part)?;
90 entries.push(entry);
91 }
92 }
93
94 Ok(Some(Self { wildcard, entries }))
95 }
96
97 pub fn push(&mut self, prop: impl Into<MediaTypeProposal>) {
99 self.entries.push(prop.into());
100 }
101
102 pub fn wildcard(&self) -> bool {
104 self.wildcard
105 }
106
107 pub fn set_wildcard(&mut self, wildcard: bool) {
109 self.wildcard = wildcard
110 }
111
112 pub fn sort(&mut self) {
118 sort_by_weight(&mut self.entries);
119 }
120
121 pub fn negotiate(&mut self, available: &[Mime]) -> crate::Result<ContentType> {
127 self.sort();
129
130 for accept in &self.entries {
132 if let Some(accept) = available.iter().find(|m| m.subset_eq(accept.media_type())) {
133 return Ok(accept.clone().into());
134 }
135 }
136
137 if self.wildcard {
139 if let Some(accept) = available.iter().next() {
140 return Ok(accept.clone().into());
141 }
142 }
143
144 let mut err = Error::new_adhoc("No suitable Content-Type found");
145 err.set_status(StatusCode::NotAcceptable);
146 Err(err)
147 }
148
149 pub fn iter(&self) -> Iter<'_> {
151 Iter {
152 inner: self.entries.iter(),
153 }
154 }
155
156 pub fn iter_mut(&mut self) -> IterMut<'_> {
158 IterMut {
159 inner: self.entries.iter_mut(),
160 }
161 }
162}
163
164impl Header for Accept {
165 fn header_name(&self) -> HeaderName {
166 ACCEPT
167 }
168 fn header_value(&self) -> HeaderValue {
169 let mut output = String::new();
170 for (n, directive) in self.entries.iter().enumerate() {
171 let directive: HeaderValue = directive.clone().into();
172 match n {
173 0 => write!(output, "{directive}").unwrap(),
174 _ => write!(output, ", {directive}").unwrap(),
175 };
176 }
177
178 if self.wildcard {
179 match output.len() {
180 0 => write!(output, "*").unwrap(),
181 _ => write!(output, ", *").unwrap(),
182 }
183 }
184
185 unsafe { HeaderValue::from_bytes_unchecked(output.into()) }
187 }
188}
189
190impl IntoIterator for Accept {
191 type Item = MediaTypeProposal;
192 type IntoIter = IntoIter;
193
194 #[inline]
195 fn into_iter(self) -> Self::IntoIter {
196 IntoIter {
197 inner: self.entries.into_iter(),
198 }
199 }
200}
201
202impl<'a> IntoIterator for &'a Accept {
203 type Item = &'a MediaTypeProposal;
204 type IntoIter = Iter<'a>;
205
206 #[inline]
207 fn into_iter(self) -> Self::IntoIter {
208 self.iter()
209 }
210}
211
212impl<'a> IntoIterator for &'a mut Accept {
213 type Item = &'a mut MediaTypeProposal;
214 type IntoIter = IterMut<'a>;
215
216 #[inline]
217 fn into_iter(self) -> Self::IntoIter {
218 self.iter_mut()
219 }
220}
221
222#[derive(Debug)]
224pub struct IntoIter {
225 inner: std::vec::IntoIter<MediaTypeProposal>,
226}
227
228impl Iterator for IntoIter {
229 type Item = MediaTypeProposal;
230
231 fn next(&mut self) -> Option<Self::Item> {
232 self.inner.next()
233 }
234
235 #[inline]
236 fn size_hint(&self) -> (usize, Option<usize>) {
237 self.inner.size_hint()
238 }
239}
240
241#[derive(Debug)]
243pub struct Iter<'a> {
244 inner: slice::Iter<'a, MediaTypeProposal>,
245}
246
247impl<'a> Iterator for Iter<'a> {
248 type Item = &'a MediaTypeProposal;
249
250 fn next(&mut self) -> Option<Self::Item> {
251 self.inner.next()
252 }
253
254 #[inline]
255 fn size_hint(&self) -> (usize, Option<usize>) {
256 self.inner.size_hint()
257 }
258}
259
260#[derive(Debug)]
262pub struct IterMut<'a> {
263 inner: slice::IterMut<'a, MediaTypeProposal>,
264}
265
266impl<'a> Iterator for IterMut<'a> {
267 type Item = &'a mut MediaTypeProposal;
268
269 fn next(&mut self) -> Option<Self::Item> {
270 self.inner.next()
271 }
272
273 #[inline]
274 fn size_hint(&self) -> (usize, Option<usize>) {
275 self.inner.size_hint()
276 }
277}
278
279impl Debug for Accept {
280 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
281 let mut list = f.debug_list();
282 for directive in &self.entries {
283 list.entry(directive);
284 }
285 list.finish()
286 }
287}
288
289#[cfg(test)]
290mod test {
291 use super::*;
292 use crate::mime;
293 use crate::Response;
294
295 #[test]
296 fn smoke() -> crate::Result<()> {
297 let mut accept = Accept::new();
298 accept.push(mime::HTML);
299
300 let mut headers = Response::new(200);
301 accept.apply_header(&mut headers);
302
303 let accept = Accept::from_headers(headers)?.unwrap();
304 assert_eq!(accept.iter().next().unwrap(), mime::HTML);
305 Ok(())
306 }
307
308 #[test]
309 fn wildcard() -> crate::Result<()> {
310 let mut accept = Accept::new();
311 accept.set_wildcard(true);
312
313 let mut headers = Response::new(200);
314 accept.apply_header(&mut headers);
315
316 let accept = Accept::from_headers(headers)?.unwrap();
317 assert!(accept.wildcard());
318 Ok(())
319 }
320
321 #[test]
322 fn wildcard_and_header() -> crate::Result<()> {
323 let mut accept = Accept::new();
324 accept.push(mime::HTML);
325 accept.set_wildcard(true);
326
327 let mut headers = Response::new(200);
328 accept.apply_header(&mut headers);
329
330 let accept = Accept::from_headers(headers)?.unwrap();
331 assert!(accept.wildcard());
332 assert_eq!(accept.iter().next().unwrap(), mime::HTML);
333 Ok(())
334 }
335
336 #[test]
337 fn iter() -> crate::Result<()> {
338 let mut accept = Accept::new();
339 accept.push(mime::HTML);
340 accept.push(mime::XML);
341
342 let mut headers = Response::new(200);
343 accept.apply_header(&mut headers);
344
345 let accept = Accept::from_headers(headers)?.unwrap();
346 let mut accept = accept.iter();
347 assert_eq!(accept.next().unwrap(), mime::HTML);
348 assert_eq!(accept.next().unwrap(), mime::XML);
349 Ok(())
350 }
351
352 #[test]
353 fn reorder_based_on_weight() -> crate::Result<()> {
354 let mut accept = Accept::new();
355 accept.push(MediaTypeProposal::new(mime::HTML, Some(0.4))?);
356 accept.push(MediaTypeProposal::new(mime::XML, None)?);
357 accept.push(MediaTypeProposal::new(mime::PLAIN, Some(0.8))?);
358
359 let mut headers = Response::new(200);
360 accept.apply_header(&mut headers);
361
362 let mut accept = Accept::from_headers(headers)?.unwrap();
363 accept.sort();
364 let mut accept = accept.iter();
365 assert_eq!(accept.next().unwrap(), mime::XML);
366 assert_eq!(accept.next().unwrap(), mime::PLAIN);
367 assert_eq!(accept.next().unwrap(), mime::HTML);
368 Ok(())
369 }
370
371 #[test]
372 fn reorder_based_on_weight_and_location() -> crate::Result<()> {
373 let mut accept = Accept::new();
374 accept.push(MediaTypeProposal::new(mime::HTML, None)?);
375 accept.push(MediaTypeProposal::new(mime::XML, None)?);
376 accept.push(MediaTypeProposal::new(mime::PLAIN, Some(0.8))?);
377
378 let mut res = Response::new(200);
379 accept.apply_header(&mut res);
380
381 let mut accept = Accept::from_headers(res)?.unwrap();
382 accept.sort();
383 let mut accept = accept.iter();
384 assert_eq!(accept.next().unwrap(), mime::XML);
385 assert_eq!(accept.next().unwrap(), mime::HTML);
386 assert_eq!(accept.next().unwrap(), mime::PLAIN);
387 Ok(())
388 }
389
390 #[test]
391 fn negotiate() -> crate::Result<()> {
392 let mut accept = Accept::new();
393 accept.push(MediaTypeProposal::new(mime::HTML, Some(0.4))?);
394 accept.push(MediaTypeProposal::new(mime::PLAIN, Some(0.8))?);
395 accept.push(MediaTypeProposal::new(mime::XML, None)?);
396
397 assert_eq!(accept.negotiate(&[mime::HTML, mime::XML])?, mime::XML);
398 Ok(())
399 }
400
401 #[test]
402 fn negotiate_not_acceptable() -> crate::Result<()> {
403 let mut accept = Accept::new();
404 let err = accept.negotiate(&[mime::JSON]).unwrap_err();
405 assert_eq!(err.status(), 406);
406
407 let mut accept = Accept::new();
408 accept.push(MediaTypeProposal::new(mime::JSON, Some(0.8))?);
409 let err = accept.negotiate(&[mime::XML]).unwrap_err();
410 assert_eq!(err.status(), 406);
411 Ok(())
412 }
413
414 #[test]
415 fn negotiate_wildcard() -> crate::Result<()> {
416 let mut accept = Accept::new();
417 accept.push(MediaTypeProposal::new(mime::JSON, Some(0.8))?);
418 accept.set_wildcard(true);
419
420 assert_eq!(accept.negotiate(&[mime::XML])?, mime::XML);
421 Ok(())
422 }
423
424 #[test]
425 fn negotiate_missing_encoding() -> crate::Result<()> {
426 let mime_html = "text/html".parse::<Mime>()?;
427
428 let mut browser_accept = Accept::new();
429 browser_accept.push(MediaTypeProposal::new(mime_html, None)?);
430
431 let acceptable = &[mime::HTML];
432
433 let content_type = browser_accept.negotiate(acceptable);
434
435 assert!(
436 content_type.is_ok(),
437 "server is expected to return HTML content"
438 );
439 Ok(())
440 }
441}