rama_http/headers/util/
quality_value.rs1#[allow(unused, deprecated)]
2use std::ascii::AsciiExt;
3use std::cmp;
4use std::default::Default;
5use std::fmt;
6use std::str;
7
8use self::internal::IntoQuality;
9
10#[derive(Copy, Clone, Debug, Eq, Ord, PartialEq, PartialOrd, Hash)]
25pub struct Quality(u16);
26
27impl Default for Quality {
28 fn default() -> Quality {
29 Quality(1000)
30 }
31}
32
33#[derive(Clone, PartialEq, Eq, Debug)]
36pub struct QualityValue<T> {
37 pub value: T,
39 pub quality: Quality,
41}
42
43impl<T> QualityValue<T> {
44 pub const fn new(value: T, quality: Quality) -> QualityValue<T> {
46 QualityValue { value, quality }
47 }
48
49 }
63
64impl<T> From<T> for QualityValue<T> {
65 fn from(value: T) -> QualityValue<T> {
66 QualityValue {
67 value,
68 quality: Quality::default(),
69 }
70 }
71}
72
73impl<T: PartialEq> cmp::PartialOrd for QualityValue<T> {
74 fn partial_cmp(&self, other: &QualityValue<T>) -> Option<cmp::Ordering> {
75 self.quality.partial_cmp(&other.quality)
76 }
77}
78
79impl<T: fmt::Display> fmt::Display for QualityValue<T> {
80 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
81 fmt::Display::fmt(&self.value, f)?;
82 match self.quality.0 {
83 1000 => Ok(()),
84 0 => f.write_str("; q=0"),
85 x => write!(f, "; q=0.{}", format!("{:03}", x).trim_end_matches('0')),
86 }
87 }
88}
89
90impl<T: str::FromStr> str::FromStr for QualityValue<T> {
91 type Err = crate::headers::Error;
92 fn from_str(s: &str) -> Result<QualityValue<T>, crate::headers::Error> {
93 let mut raw_item = s;
95 let mut quality = 1f32;
96
97 let mut parts = s.rsplitn(2, ';').map(|x| x.trim());
98 if let (Some(first), Some(second), None) = (parts.next(), parts.next(), parts.next()) {
99 if first.len() < 2 {
100 return Err(crate::headers::Error::invalid());
101 }
102 if first.starts_with("q=") || first.starts_with("Q=") {
103 let q_part = &first[2..];
104 if q_part.len() > 5 {
105 return Err(crate::headers::Error::invalid());
106 }
107 match q_part.parse::<f32>() {
108 Ok(q_value) => {
109 if (0f32..=1f32).contains(&q_value) {
110 quality = q_value;
111 raw_item = second;
112 } else {
113 return Err(crate::headers::Error::invalid());
114 }
115 }
116 Err(_) => return Err(crate::headers::Error::invalid()),
117 }
118 }
119 }
120 match raw_item.parse::<T>() {
121 Ok(item) => Ok(QualityValue::new(item, from_f32(quality))),
123 Err(_) => Err(crate::headers::Error::invalid()),
124 }
125 }
126}
127
128#[inline]
129fn from_f32(f: f32) -> Quality {
130 debug_assert!(
134 (0f32..=1f32).contains(&f),
135 "q value must be between 0.0 and 1.0"
136 );
137 Quality((f * 1000f32) as u16)
138}
139
140#[cfg(test)]
141fn q<T: IntoQuality>(val: T) -> Quality {
142 val.into_quality()
143}
144
145impl<T> From<T> for Quality
146where
147 T: IntoQuality,
148{
149 fn from(x: T) -> Self {
150 x.into_quality()
151 }
152}
153
154mod internal {
155 use super::Quality;
156
157 pub trait IntoQuality: Sealed + Sized {
165 fn into_quality(self) -> Quality;
166 }
167
168 impl IntoQuality for f32 {
169 fn into_quality(self) -> Quality {
170 assert!(
171 (0f32..=1f32).contains(&self),
172 "float must be between 0.0 and 1.0"
173 );
174 super::from_f32(self)
175 }
176 }
177
178 impl IntoQuality for u16 {
179 fn into_quality(self) -> Quality {
180 assert!(self <= 1000, "u16 must be between 0 and 1000");
181 Quality(self)
182 }
183 }
184
185 pub trait Sealed {}
186 impl Sealed for u16 {}
187 impl Sealed for f32 {}
188}
189
190#[cfg(test)]
191mod tests {
192 use super::*;
193
194 #[test]
195 fn test_quality_item_fmt_q_1() {
196 let x = QualityValue::from("foo");
197 assert_eq!(format!("{}", x), "foo");
198 }
199 #[test]
200 fn test_quality_item_fmt_q_0001() {
201 let x = QualityValue::new("foo", Quality(1));
202 assert_eq!(format!("{}", x), "foo; q=0.001");
203 }
204 #[test]
205 fn test_quality_item_fmt_q_05() {
206 let x = QualityValue::new("foo", Quality(500));
207 assert_eq!(format!("{}", x), "foo; q=0.5");
208 }
209
210 #[test]
211 fn test_quality_item_fmt_q_0() {
212 let x = QualityValue::new("foo", Quality(0));
213 assert_eq!(x.to_string(), "foo; q=0");
214 }
215
216 #[test]
217 fn test_quality_item_from_str1() {
218 let x: QualityValue<String> = "chunked".parse().unwrap();
219 assert_eq!(
220 x,
221 QualityValue {
222 value: "chunked".to_owned(),
223 quality: Quality(1000),
224 }
225 );
226 }
227 #[test]
228 fn test_quality_item_from_str2() {
229 let x: QualityValue<String> = "chunked; q=1".parse().unwrap();
230 assert_eq!(
231 x,
232 QualityValue {
233 value: "chunked".to_owned(),
234 quality: Quality(1000),
235 }
236 );
237 }
238 #[test]
239 fn test_quality_item_from_str3() {
240 let x: QualityValue<String> = "gzip; q=0.5".parse().unwrap();
241 assert_eq!(
242 x,
243 QualityValue {
244 value: "gzip".to_owned(),
245 quality: Quality(500),
246 }
247 );
248 }
249 #[test]
250 fn test_quality_item_from_str4() {
251 let x: QualityValue<String> = "gzip; q=0.273".parse().unwrap();
252 assert_eq!(
253 x,
254 QualityValue {
255 value: "gzip".to_owned(),
256 quality: Quality(273),
257 }
258 );
259 }
260 #[test]
261 fn test_quality_item_from_str5() {
262 assert!("gzip; q=0.2739999".parse::<QualityValue<String>>().is_err());
263 }
264
265 #[test]
266 fn test_quality_item_from_str6() {
267 assert!("gzip; q=2".parse::<QualityValue<String>>().is_err());
268 }
269 #[test]
270 fn test_quality_item_ordering() {
271 let x: QualityValue<String> = "gzip; q=0.5".parse().unwrap();
272 let y: QualityValue<String> = "gzip; q=0.273".parse().unwrap();
273 assert!(x > y)
274 }
275
276 #[test]
277 fn test_quality() {
278 assert_eq!(q(0.5), Quality(500));
279 }
280
281 #[test]
282 #[should_panic]
283 fn test_quality_invalid() {
284 q(-1.0);
285 }
286
287 #[test]
288 #[should_panic]
289 fn test_quality_invalid2() {
290 q(2.0);
291 }
292
293 #[test]
294 fn test_fuzzing_bugs() {
295 assert!("99999;".parse::<QualityValue<String>>().is_err());
296 assert!("\x0d;;;=\u{d6aa}==".parse::<QualityValue<String>>().is_ok())
297 }
298}