rama_http_headers/specifier/
quality_value.rs1#[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#[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 fn from_str(s: &str) -> Result<Self, Self::Err> {
57 let mut c = s.chars();
58 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 let mut value = match c.next() {
71 Some('0') => 0,
72 Some('1') => 1000,
73 _ => return Err(Error::invalid()),
74 };
75
76 match c.next() {
78 Some('.') => (),
79 None => return Ok(Self(value)),
80 _ => return Err(Error::invalid()),
81 };
82
83 let mut factor = 100;
87 loop {
88 match c.next() {
89 Some(n @ '0'..='9') => {
90 if factor < 1 {
93 return Err(Error::invalid());
94 }
95 value += factor * (n as u16 - '0' as u16);
97 }
98 None => {
99 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#[derive(Clone, PartialEq, Eq, Debug)]
123pub struct QualityValue<T> {
124 pub value: T,
126 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 pub const fn new(value: T, quality: Quality) -> Self {
145 Self { value, quality }
146 }
147
148 pub const fn new_value(value: T) -> Self {
150 Self {
151 value,
152 quality: Quality::one(),
153 }
154 }
155
156 }
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 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 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 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}