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