requiem_http/header/shared/
quality_item.rs

1use std::{cmp, fmt, str};
2
3use self::internal::IntoQuality;
4
5/// Represents a quality used in quality values.
6///
7/// Can be created with the `q` function.
8///
9/// # Implementation notes
10///
11/// The quality value is defined as a number between 0 and 1 with three decimal
12/// places. This means there are 1001 possible values. Since floating point
13/// numbers are not exact and the smallest floating point data type (`f32`)
14/// consumes four bytes, hyper uses an `u16` value to store the
15/// quality internally. For performance reasons you may set quality directly to
16/// a value between 0 and 1000 e.g. `Quality(532)` matches the quality
17/// `q=0.532`.
18///
19/// [RFC7231 Section 5.3.1](https://tools.ietf.org/html/rfc7231#section-5.3.1)
20/// gives more information on quality values in HTTP header fields.
21#[derive(Copy, Clone, Debug, Eq, Ord, PartialEq, PartialOrd)]
22pub struct Quality(u16);
23
24impl Default for Quality {
25    fn default() -> Quality {
26        Quality(1000)
27    }
28}
29
30/// Represents an item with a quality value as defined in
31/// [RFC7231](https://tools.ietf.org/html/rfc7231#section-5.3.1).
32#[derive(Clone, PartialEq, Debug)]
33pub struct QualityItem<T> {
34    /// The actual contents of the field.
35    pub item: T,
36    /// The quality (client or server preference) for the value.
37    pub quality: Quality,
38}
39
40impl<T> QualityItem<T> {
41    /// Creates a new `QualityItem` from an item and a quality.
42    /// The item can be of any type.
43    /// The quality should be a value in the range [0, 1].
44    pub fn new(item: T, quality: Quality) -> QualityItem<T> {
45        QualityItem { item, quality }
46    }
47}
48
49impl<T: PartialEq> cmp::PartialOrd for QualityItem<T> {
50    fn partial_cmp(&self, other: &QualityItem<T>) -> Option<cmp::Ordering> {
51        self.quality.partial_cmp(&other.quality)
52    }
53}
54
55impl<T: fmt::Display> fmt::Display for QualityItem<T> {
56    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
57        fmt::Display::fmt(&self.item, f)?;
58        match self.quality.0 {
59            1000 => Ok(()),
60            0 => f.write_str("; q=0"),
61            x => write!(f, "; q=0.{}", format!("{:03}", x).trim_end_matches('0')),
62        }
63    }
64}
65
66impl<T: str::FromStr> str::FromStr for QualityItem<T> {
67    type Err = crate::error::ParseError;
68
69    fn from_str(s: &str) -> Result<QualityItem<T>, crate::error::ParseError> {
70        if !s.is_ascii() {
71            return Err(crate::error::ParseError::Header);
72        }
73        // Set defaults used if parsing fails.
74        let mut raw_item = s;
75        let mut quality = 1f32;
76
77        let parts: Vec<&str> = s.rsplitn(2, ';').map(|x| x.trim()).collect();
78        if parts.len() == 2 {
79            if parts[0].len() < 2 {
80                return Err(crate::error::ParseError::Header);
81            }
82            let start = &parts[0][0..2];
83            if start == "q=" || start == "Q=" {
84                let q_part = &parts[0][2..parts[0].len()];
85                if q_part.len() > 5 {
86                    return Err(crate::error::ParseError::Header);
87                }
88                match q_part.parse::<f32>() {
89                    Ok(q_value) => {
90                        if 0f32 <= q_value && q_value <= 1f32 {
91                            quality = q_value;
92                            raw_item = parts[1];
93                        } else {
94                            return Err(crate::error::ParseError::Header);
95                        }
96                    }
97                    Err(_) => return Err(crate::error::ParseError::Header),
98                }
99            }
100        }
101        match raw_item.parse::<T>() {
102            // we already checked above that the quality is within range
103            Ok(item) => Ok(QualityItem::new(item, from_f32(quality))),
104            Err(_) => Err(crate::error::ParseError::Header),
105        }
106    }
107}
108
109#[inline]
110fn from_f32(f: f32) -> Quality {
111    // this function is only used internally. A check that `f` is within range
112    // should be done before calling this method. Just in case, this
113    // debug_assert should catch if we were forgetful
114    debug_assert!(
115        f >= 0f32 && f <= 1f32,
116        "q value must be between 0.0 and 1.0"
117    );
118    Quality((f * 1000f32) as u16)
119}
120
121/// Convenience function to wrap a value in a `QualityItem`
122/// Sets `q` to the default 1.0
123pub fn qitem<T>(item: T) -> QualityItem<T> {
124    QualityItem::new(item, Default::default())
125}
126
127/// Convenience function to create a `Quality` from a float or integer.
128///
129/// Implemented for `u16` and `f32`. Panics if value is out of range.
130pub fn q<T: IntoQuality>(val: T) -> Quality {
131    val.into_quality()
132}
133
134mod internal {
135    use super::Quality;
136
137    // TryFrom is probably better, but it's not stable. For now, we want to
138    // keep the functionality of the `q` function, while allowing it to be
139    // generic over `f32` and `u16`.
140    //
141    // `q` would panic before, so keep that behavior. `TryFrom` can be
142    // introduced later for a non-panicking conversion.
143
144    pub trait IntoQuality: Sealed + Sized {
145        fn into_quality(self) -> Quality;
146    }
147
148    impl IntoQuality for f32 {
149        fn into_quality(self) -> Quality {
150            assert!(
151                self >= 0f32 && self <= 1f32,
152                "float must be between 0.0 and 1.0"
153            );
154            super::from_f32(self)
155        }
156    }
157
158    impl IntoQuality for u16 {
159        fn into_quality(self) -> Quality {
160            assert!(self <= 1000, "u16 must be between 0 and 1000");
161            Quality(self)
162        }
163    }
164
165    pub trait Sealed {}
166    impl Sealed for u16 {}
167    impl Sealed for f32 {}
168}
169
170#[cfg(test)]
171mod tests {
172    use super::super::encoding::*;
173    use super::*;
174
175    #[test]
176    fn test_quality_item_fmt_q_1() {
177        let x = qitem(Chunked);
178        assert_eq!(format!("{}", x), "chunked");
179    }
180    #[test]
181    fn test_quality_item_fmt_q_0001() {
182        let x = QualityItem::new(Chunked, Quality(1));
183        assert_eq!(format!("{}", x), "chunked; q=0.001");
184    }
185    #[test]
186    fn test_quality_item_fmt_q_05() {
187        // Custom value
188        let x = QualityItem {
189            item: EncodingExt("identity".to_owned()),
190            quality: Quality(500),
191        };
192        assert_eq!(format!("{}", x), "identity; q=0.5");
193    }
194
195    #[test]
196    fn test_quality_item_fmt_q_0() {
197        // Custom value
198        let x = QualityItem {
199            item: EncodingExt("identity".to_owned()),
200            quality: Quality(0),
201        };
202        assert_eq!(x.to_string(), "identity; q=0");
203    }
204
205    #[test]
206    fn test_quality_item_from_str1() {
207        let x: Result<QualityItem<Encoding>, _> = "chunked".parse();
208        assert_eq!(
209            x.unwrap(),
210            QualityItem {
211                item: Chunked,
212                quality: Quality(1000),
213            }
214        );
215    }
216    #[test]
217    fn test_quality_item_from_str2() {
218        let x: Result<QualityItem<Encoding>, _> = "chunked; q=1".parse();
219        assert_eq!(
220            x.unwrap(),
221            QualityItem {
222                item: Chunked,
223                quality: Quality(1000),
224            }
225        );
226    }
227    #[test]
228    fn test_quality_item_from_str3() {
229        let x: Result<QualityItem<Encoding>, _> = "gzip; q=0.5".parse();
230        assert_eq!(
231            x.unwrap(),
232            QualityItem {
233                item: Gzip,
234                quality: Quality(500),
235            }
236        );
237    }
238    #[test]
239    fn test_quality_item_from_str4() {
240        let x: Result<QualityItem<Encoding>, _> = "gzip; q=0.273".parse();
241        assert_eq!(
242            x.unwrap(),
243            QualityItem {
244                item: Gzip,
245                quality: Quality(273),
246            }
247        );
248    }
249    #[test]
250    fn test_quality_item_from_str5() {
251        let x: Result<QualityItem<Encoding>, _> = "gzip; q=0.2739999".parse();
252        assert!(x.is_err());
253    }
254    #[test]
255    fn test_quality_item_from_str6() {
256        let x: Result<QualityItem<Encoding>, _> = "gzip; q=2".parse();
257        assert!(x.is_err());
258    }
259    #[test]
260    fn test_quality_item_ordering() {
261        let x: QualityItem<Encoding> = "gzip; q=0.5".parse().ok().unwrap();
262        let y: QualityItem<Encoding> = "gzip; q=0.273".parse().ok().unwrap();
263        let comparision_result: bool = x.gt(&y);
264        assert!(comparision_result)
265    }
266
267    #[test]
268    fn test_quality() {
269        assert_eq!(q(0.5), Quality(500));
270    }
271
272    #[test]
273    #[should_panic] // FIXME - 32-bit msvc unwinding broken
274    #[cfg_attr(all(target_arch = "x86", target_env = "msvc"), ignore)]
275    fn test_quality_invalid() {
276        q(-1.0);
277    }
278
279    #[test]
280    #[should_panic] // FIXME - 32-bit msvc unwinding broken
281    #[cfg_attr(all(target_arch = "x86", target_env = "msvc"), ignore)]
282    fn test_quality_invalid2() {
283        q(2.0);
284    }
285
286    #[test]
287    fn test_fuzzing_bugs() {
288        assert!("99999;".parse::<QualityItem<String>>().is_err());
289        assert!("\x0d;;;=\u{d6aa}==".parse::<QualityItem<String>>().is_err())
290    }
291}