http_types_rs/transfer/
te.rs

1use crate::headers::{self, Header, HeaderName, HeaderValue, Headers};
2use crate::transfer::{Encoding, EncodingProposal, TransferEncoding};
3use crate::utils::sort_by_weight;
4use crate::{Error, StatusCode};
5
6use std::fmt::{self, Debug, Write};
7
8use std::slice;
9
10/// Client header advertising the transfer encodings the user agent is willing to
11/// accept.
12///
13/// [MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/TE)
14///
15/// # Specifications
16///
17/// - [RFC 7230, section 4.3: TE](https://tools.ietf.org/html/rfc7230#section-4.3)
18///
19/// # Examples
20///
21/// ```
22/// # fn main() -> http_types_rs::Result<()> {
23/// #
24/// use http_types_rs::transfer::{TE, TransferEncoding, Encoding, EncodingProposal};
25/// use http_types_rs::Response;
26///
27/// let mut te = TE::new();
28/// te.push(EncodingProposal::new(Encoding::Brotli, Some(0.8))?);
29/// te.push(EncodingProposal::new(Encoding::Gzip, Some(0.4))?);
30/// te.push(EncodingProposal::new(Encoding::Identity, None)?);
31///
32/// let mut res = Response::new(200);
33/// let encoding = te.negotiate(&[Encoding::Brotli, Encoding::Gzip])?;
34/// res.insert_header(&encoding, &encoding);
35///
36/// assert_eq!(res["Transfer-Encoding"], "br");
37/// #
38/// # Ok(()) }
39/// ```
40pub struct TE {
41    wildcard: bool,
42    entries: Vec<EncodingProposal>,
43}
44
45impl TE {
46    /// Create a new instance of `TE`.
47    pub fn new() -> Self {
48        Self {
49            entries: vec![],
50            wildcard: false,
51        }
52    }
53
54    /// Create an instance of `TE` from a `Headers` instance.
55    pub fn from_headers(headers: impl AsRef<Headers>) -> crate::Result<Option<Self>> {
56        let mut entries = vec![];
57        let headers = match headers.as_ref().get(headers::TE) {
58            Some(headers) => headers,
59            None => return Ok(None),
60        };
61
62        let mut wildcard = false;
63
64        for value in headers {
65            for part in value.as_str().trim().split(',') {
66                let part = part.trim();
67
68                // Handle empty strings, and wildcard directives.
69                if part.is_empty() {
70                    continue;
71                } else if part == "*" {
72                    wildcard = true;
73                    continue;
74                }
75
76                // Try and parse a directive from a str. If the directive is
77                // unkown we skip it.
78                if let Some(entry) = EncodingProposal::from_str(part)? {
79                    entries.push(entry);
80                }
81            }
82        }
83
84        Ok(Some(Self { wildcard, entries }))
85    }
86
87    /// Push a directive into the list of entries.
88    pub fn push(&mut self, prop: impl Into<EncodingProposal>) {
89        self.entries.push(prop.into());
90    }
91
92    /// Returns `true` if a wildcard directive was passed.
93    pub fn wildcard(&self) -> bool {
94        self.wildcard
95    }
96
97    /// Set the wildcard directive.
98    pub fn set_wildcard(&mut self, wildcard: bool) {
99        self.wildcard = wildcard
100    }
101
102    /// Sort the header directives by weight.
103    ///
104    /// Headers with a higher `q=` value will be returned first. If two
105    /// directives have the same weight, the directive that was declared later
106    /// will be returned first.
107    pub fn sort(&mut self) {
108        sort_by_weight(&mut self.entries);
109    }
110
111    /// Determine the most suitable `Transfer-Encoding` encoding.
112    ///
113    /// # Errors
114    ///
115    /// If no suitable encoding is found, an error with the status of `406` will be returned.
116    pub fn negotiate(&mut self, available: &[Encoding]) -> crate::Result<TransferEncoding> {
117        // Start by ordering the encodings.
118        self.sort();
119
120        // Try and find the first encoding that matches.
121        for encoding in &self.entries {
122            if available.contains(encoding) {
123                return Ok(encoding.into());
124            }
125        }
126
127        // If no encoding matches and wildcard is set, send whichever encoding we got.
128        if self.wildcard {
129            if let Some(encoding) = available.iter().next() {
130                return Ok(encoding.into());
131            }
132        }
133
134        let mut err = Error::new_adhoc("No suitable Transfer-Encoding found");
135        err.set_status(StatusCode::NotAcceptable);
136        Err(err)
137    }
138
139    /// An iterator visiting all entries.
140    pub fn iter(&self) -> Iter<'_> {
141        Iter { inner: self.entries.iter() }
142    }
143
144    /// An iterator visiting all entries.
145    pub fn iter_mut(&mut self) -> IterMut<'_> {
146        IterMut {
147            inner: self.entries.iter_mut(),
148        }
149    }
150}
151
152impl Header for TE {
153    fn header_name(&self) -> HeaderName {
154        headers::TE
155    }
156
157    fn header_value(&self) -> HeaderValue {
158        let mut output = String::new();
159        for (n, directive) in self.entries.iter().enumerate() {
160            let directive: HeaderValue = (*directive).into();
161            match n {
162                0 => write!(output, "{}", directive).unwrap(),
163                _ => write!(output, ", {}", directive).unwrap(),
164            };
165        }
166
167        if self.wildcard {
168            match output.len() {
169                0 => write!(output, "*").unwrap(),
170                _ => write!(output, ", *").unwrap(),
171            }
172        }
173
174        // SAFETY: the internal string is validated to be ASCII.
175        unsafe { HeaderValue::from_bytes_unchecked(output.into()) }
176    }
177}
178
179impl IntoIterator for TE {
180    type Item = EncodingProposal;
181    type IntoIter = IntoIter;
182
183    #[inline]
184    fn into_iter(self) -> Self::IntoIter {
185        IntoIter {
186            inner: self.entries.into_iter(),
187        }
188    }
189}
190
191impl<'a> IntoIterator for &'a TE {
192    type Item = &'a EncodingProposal;
193    type IntoIter = Iter<'a>;
194
195    #[inline]
196    fn into_iter(self) -> Self::IntoIter {
197        self.iter()
198    }
199}
200
201impl<'a> IntoIterator for &'a mut TE {
202    type Item = &'a mut EncodingProposal;
203    type IntoIter = IterMut<'a>;
204
205    #[inline]
206    fn into_iter(self) -> Self::IntoIter {
207        self.iter_mut()
208    }
209}
210
211/// A borrowing iterator over entries in `TE`.
212#[derive(Debug)]
213pub struct IntoIter {
214    inner: std::vec::IntoIter<EncodingProposal>,
215}
216
217impl Iterator for IntoIter {
218    type Item = EncodingProposal;
219
220    fn next(&mut self) -> Option<Self::Item> {
221        self.inner.next()
222    }
223
224    #[inline]
225    fn size_hint(&self) -> (usize, Option<usize>) {
226        self.inner.size_hint()
227    }
228}
229
230/// A lending iterator over entries in `TE`.
231#[derive(Debug)]
232pub struct Iter<'a> {
233    inner: slice::Iter<'a, EncodingProposal>,
234}
235
236impl<'a> Iterator for Iter<'a> {
237    type Item = &'a EncodingProposal;
238
239    fn next(&mut self) -> Option<Self::Item> {
240        self.inner.next()
241    }
242
243    #[inline]
244    fn size_hint(&self) -> (usize, Option<usize>) {
245        self.inner.size_hint()
246    }
247}
248
249/// A mutable iterator over entries in `TE`.
250#[derive(Debug)]
251pub struct IterMut<'a> {
252    inner: slice::IterMut<'a, EncodingProposal>,
253}
254
255impl<'a> Iterator for IterMut<'a> {
256    type Item = &'a mut EncodingProposal;
257
258    fn next(&mut self) -> Option<Self::Item> {
259        self.inner.next()
260    }
261
262    #[inline]
263    fn size_hint(&self) -> (usize, Option<usize>) {
264        self.inner.size_hint()
265    }
266}
267
268impl Debug for TE {
269    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
270        let mut list = f.debug_list();
271        for directive in &self.entries {
272            list.entry(directive);
273        }
274        list.finish()
275    }
276}
277
278#[cfg(test)]
279mod test {
280    use super::*;
281    use crate::transfer::Encoding;
282    use crate::Response;
283
284    #[test]
285    fn smoke() -> crate::Result<()> {
286        let mut accept = TE::new();
287        accept.push(Encoding::Gzip);
288
289        let mut headers = Response::new(200);
290        accept.apply_header(&mut headers);
291
292        let accept = TE::from_headers(headers)?.unwrap();
293        assert_eq!(accept.iter().next().unwrap(), Encoding::Gzip);
294        Ok(())
295    }
296
297    #[test]
298    fn wildcard() -> crate::Result<()> {
299        let mut accept = TE::new();
300        accept.set_wildcard(true);
301
302        let mut headers = Response::new(200);
303        accept.apply_header(&mut headers);
304
305        let accept = TE::from_headers(headers)?.unwrap();
306        assert!(accept.wildcard());
307        Ok(())
308    }
309
310    #[test]
311    fn wildcard_and_header() -> crate::Result<()> {
312        let mut accept = TE::new();
313        accept.push(Encoding::Gzip);
314        accept.set_wildcard(true);
315
316        let mut headers = Response::new(200);
317        accept.apply_header(&mut headers);
318
319        let accept = TE::from_headers(headers)?.unwrap();
320        assert!(accept.wildcard());
321        assert_eq!(accept.iter().next().unwrap(), Encoding::Gzip);
322        Ok(())
323    }
324
325    #[test]
326    fn iter() -> crate::Result<()> {
327        let mut accept = TE::new();
328        accept.push(Encoding::Gzip);
329        accept.push(Encoding::Brotli);
330
331        let mut headers = Response::new(200);
332        accept.apply_header(&mut headers);
333
334        let accept = TE::from_headers(headers)?.unwrap();
335        let mut accept = accept.iter();
336        assert_eq!(accept.next().unwrap(), Encoding::Gzip);
337        assert_eq!(accept.next().unwrap(), Encoding::Brotli);
338        Ok(())
339    }
340
341    #[test]
342    fn reorder_based_on_weight() -> crate::Result<()> {
343        let mut accept = TE::new();
344        accept.push(EncodingProposal::new(Encoding::Gzip, Some(0.4))?);
345        accept.push(EncodingProposal::new(Encoding::Identity, None)?);
346        accept.push(EncodingProposal::new(Encoding::Brotli, Some(0.8))?);
347
348        let mut headers = Response::new(200);
349        accept.apply_header(&mut headers);
350
351        let mut accept = TE::from_headers(headers)?.unwrap();
352        accept.sort();
353        let mut accept = accept.iter();
354        assert_eq!(accept.next().unwrap(), Encoding::Brotli);
355        assert_eq!(accept.next().unwrap(), Encoding::Gzip);
356        assert_eq!(accept.next().unwrap(), Encoding::Identity);
357        Ok(())
358    }
359
360    #[test]
361    fn reorder_based_on_weight_and_location() -> crate::Result<()> {
362        let mut accept = TE::new();
363        accept.push(EncodingProposal::new(Encoding::Identity, None)?);
364        accept.push(EncodingProposal::new(Encoding::Gzip, None)?);
365        accept.push(EncodingProposal::new(Encoding::Brotli, Some(0.8))?);
366
367        let mut res = Response::new(200);
368        accept.apply_header(&mut res);
369
370        let mut accept = TE::from_headers(res)?.unwrap();
371        accept.sort();
372        let mut accept = accept.iter();
373        assert_eq!(accept.next().unwrap(), Encoding::Brotli);
374        assert_eq!(accept.next().unwrap(), Encoding::Gzip);
375        assert_eq!(accept.next().unwrap(), Encoding::Identity);
376        Ok(())
377    }
378
379    #[test]
380    fn negotiate() -> crate::Result<()> {
381        let mut accept = TE::new();
382        accept.push(EncodingProposal::new(Encoding::Brotli, Some(0.8))?);
383        accept.push(EncodingProposal::new(Encoding::Gzip, Some(0.4))?);
384        accept.push(EncodingProposal::new(Encoding::Identity, None)?);
385
386        assert_eq!(accept.negotiate(&[Encoding::Brotli, Encoding::Gzip])?, Encoding::Brotli,);
387        Ok(())
388    }
389
390    #[test]
391    fn negotiate_not_acceptable() -> crate::Result<()> {
392        let mut accept = TE::new();
393        let err = accept.negotiate(&[Encoding::Gzip]).unwrap_err();
394        assert_eq!(err.status(), 406);
395
396        let mut accept = TE::new();
397        accept.push(EncodingProposal::new(Encoding::Brotli, Some(0.8))?);
398        let err = accept.negotiate(&[Encoding::Gzip]).unwrap_err();
399        assert_eq!(err.status(), 406);
400        Ok(())
401    }
402
403    #[test]
404    fn negotiate_wildcard() -> crate::Result<()> {
405        let mut accept = TE::new();
406        accept.push(EncodingProposal::new(Encoding::Brotli, Some(0.8))?);
407        accept.set_wildcard(true);
408
409        assert_eq!(accept.negotiate(&[Encoding::Gzip])?, Encoding::Gzip);
410        Ok(())
411    }
412}