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}