Skip to main content

rama_http_headers/specifier/
quality_value.rs

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