rama_http/headers/util/
quality_value.rs

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