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 => 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 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 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 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}