kutil_http/headers/preferences/
weight.rs

1use std::fmt;
2
3//
4// Weight
5//
6
7/// Weight ("q") value in an HTTP header. Used to specify quality, priority, etc.
8///
9/// See [IETF RFC 7231 section 5.3.1](https://datatracker.ietf.org/doc/html/rfc7231#section-5.3.1).
10///
11/// Stored as an integer value from 0 to 1000. We use an integer rather than a float in order
12/// to avoid comparison issues.
13#[derive(Copy, Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
14pub struct Weight(u16);
15
16impl Weight {
17    /// Max [Weight] (1000).
18    pub const MAX: Weight = Weight(1000);
19
20    /// Constructor.
21    pub const fn new(weight: u16) -> Self {
22        Self(weight)
23    }
24
25    /// Parse.
26    pub fn parse(representation: &str) -> Option<Self> {
27        const MAX: u16 = 1000;
28
29        // Based on:
30        // https://github.com/tower-rs/tower-http/blob/main/tower-http/src/content_encoding.rs
31
32        let mut chars = representation.trim().chars();
33
34        // Parse "q=" (case-insensitively).
35        match chars.next() {
36            Some('q' | 'Q') => (),
37            _ => return None,
38        };
39
40        match chars.next() {
41            Some('=') => (),
42            _ => return None,
43        };
44
45        // Parse leading digit. Since valid q-values are between 0.000 and 1.000, only "0" and "1"
46        // are allowed.
47        let mut value = match chars.next() {
48            Some('0') => 0,
49            Some('1') => MAX,
50            _ => return None,
51        };
52
53        // Parse optional decimal point.
54        match chars.next() {
55            Some('.') => {}
56            None => return Some(Self(value)),
57            _ => return None,
58        };
59
60        // Parse optional fractional digits. The value of each digit is multiplied by `factor`.
61        // Since the q-value is represented as an integer between 0 and 1000, `factor` is `100` for
62        // the first digit, `10` for the next, and `1` for the digit after that.
63        let mut factor = 100;
64        loop {
65            match chars.next() {
66                Some(n @ '0'..='9') => {
67                    // If `factor` is less than `1`, three digits have already been parsed. A
68                    // q-value having more than 3 fractional digits is invalid.
69                    if factor < 1 {
70                        return None;
71                    }
72
73                    // Add the digit's value multiplied by `factor` to `value`.
74                    value += factor * (n as u16 - '0' as u16);
75                }
76
77                None => {
78                    // No more characters to parse. Check that the value representing the q-value is
79                    // in the valid range.
80                    return if value <= MAX { Some(Self(value)) } else { None };
81                }
82
83                _ => return None,
84            };
85
86            factor /= 10;
87        }
88    }
89}
90
91impl fmt::Display for Weight {
92    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
93        match self.0 {
94            1000 => fmt::Display::fmt("q=1", formatter),
95
96            0 => fmt::Display::fmt("q=0", formatter),
97
98            mut weight => {
99                if weight % 100 == 0 {
100                    // e.g. .800 -> .8
101                    weight /= 100;
102                } else if weight % 10 == 0 {
103                    // e.g. .830 -> .83
104                    weight /= 10;
105                }
106
107                write!(formatter, "q=0.{}", weight)
108            }
109        }
110    }
111}