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 => write!(f, "; q=0.{}", format!("{x:03}").trim_end_matches('0')),
193        }
194    }
195}
196
197impl<T: str::FromStr> str::FromStr for QualityValue<T> {
198    type Err = Error;
199    fn from_str(s: &str) -> Result<Self, Error> {
200        // Set defaults used if parsing fails.
201        let mut raw_item = s;
202        let mut quality = Quality::one();
203
204        let mut parts = s.rsplitn(2, ';').map(|x| x.trim());
205        if let (Some(first), Some(second), None) = (parts.next(), parts.next(), parts.next()) {
206            if first.len() < 2 {
207                return Err(Error::invalid());
208            }
209            if first.starts_with("q=") || first.starts_with("Q=") {
210                quality = Quality::from_str(first)?;
211                raw_item = second;
212            }
213        }
214        match raw_item.parse::<T>() {
215            // we already checked above that the quality is within range
216            Ok(item) => Ok(Self::new(item, quality)),
217            Err(_) => Err(Error::invalid()),
218        }
219    }
220}
221
222#[inline]
223fn from_f32(f: f32) -> Quality {
224    Quality((f.clamp(0f32, 1f32) * 1000f32) as u16)
225}
226
227#[cfg(test)]
228fn q<T: IntoQuality>(val: T) -> Quality {
229    val.into_quality()
230}
231
232impl<T> From<T> for Quality
233where
234    T: IntoQuality,
235{
236    fn from(x: T) -> Self {
237        x.into_quality()
238    }
239}
240
241mod internal {
242    use super::Quality;
243
244    // TryFrom is probably better, but it's not stable. For now, we want to
245    // keep the functionality of the `q` function, while allowing it to be
246    // generic over `f32` and `u16`.
247    //
248    // `q` would panic before, so keep that behavior. `TryFrom` can be
249    // introduced later for a non-panicking conversion.
250
251    pub trait IntoQuality: Sealed + Sized {
252        fn into_quality(self) -> Quality;
253    }
254
255    impl IntoQuality for f32 {
256        fn into_quality(self) -> Quality {
257            super::from_f32(self)
258        }
259    }
260
261    impl IntoQuality for u16 {
262        #[inline(always)]
263        fn into_quality(self) -> Quality {
264            Quality::new_clamped(self)
265        }
266    }
267
268    pub trait Sealed {}
269    impl Sealed for u16 {}
270    impl Sealed for f32 {}
271}
272
273#[cfg(test)]
274mod tests {
275    use super::*;
276
277    #[test]
278    fn test_quality_item_fmt_q_1() {
279        let x = QualityValue::from("foo");
280        assert_eq!(x.to_string(), "foo");
281    }
282    #[test]
283    fn test_quality_item_fmt_q_0001() {
284        let x = QualityValue::new("foo", Quality(1));
285        assert_eq!(x.to_string(), "foo; q=0.001");
286    }
287    #[test]
288    fn test_quality_item_fmt_q_05() {
289        let x = QualityValue::new("foo", Quality(500));
290        assert_eq!(x.to_string(), "foo; q=0.5");
291    }
292
293    #[test]
294    fn test_quality_item_fmt_q_0() {
295        let x = QualityValue::new("foo", Quality(0));
296        assert_eq!(x.to_string(), "foo; q=0");
297    }
298
299    #[test]
300    fn test_quality_item_from_str1() {
301        let x: QualityValue<String> = "chunked".parse().unwrap();
302        assert_eq!(
303            x,
304            QualityValue {
305                value: "chunked".to_owned(),
306                quality: Quality(1000),
307            }
308        );
309    }
310    #[test]
311    fn test_quality_item_from_str2() {
312        let x: QualityValue<String> = "chunked; q=1".parse().unwrap();
313        assert_eq!(
314            x,
315            QualityValue {
316                value: "chunked".to_owned(),
317                quality: Quality(1000),
318            }
319        );
320    }
321    #[test]
322    fn test_quality_item_from_str3() {
323        let x: QualityValue<String> = "gzip; q=0.5".parse().unwrap();
324        assert_eq!(
325            x,
326            QualityValue {
327                value: "gzip".to_owned(),
328                quality: Quality(500),
329            }
330        );
331    }
332    #[test]
333    fn test_quality_item_from_str4() {
334        let x: QualityValue<String> = "gzip; q=0.273".parse().unwrap();
335        assert_eq!(
336            x,
337            QualityValue {
338                value: "gzip".to_owned(),
339                quality: Quality(273),
340            }
341        );
342    }
343    #[test]
344    fn test_quality_item_from_str5() {
345        "gzip; q=0.2739999"
346            .parse::<QualityValue<String>>()
347            .unwrap_err();
348    }
349
350    #[test]
351    fn test_quality_item_from_str6() {
352        "gzip; q=2".parse::<QualityValue<String>>().unwrap_err();
353    }
354    #[test]
355    fn test_quality_item_ordering() {
356        let x: QualityValue<String> = "gzip; q=0.5".parse().unwrap();
357        let y: QualityValue<String> = "gzip; q=0.273".parse().unwrap();
358        assert!(x > y)
359    }
360
361    #[test]
362    fn test_quality() {
363        assert_eq!(q(0.5), Quality(500));
364    }
365
366    #[test]
367    fn test_fuzzing_bugs() {
368        "99999;".parse::<QualityValue<String>>().unwrap_err();
369        "\x0d;;;=\u{d6aa}=="
370            .parse::<QualityValue<String>>()
371            .unwrap();
372    }
373}