cogo_http/header/shared/
quality_item.rs

1use std::cmp;
2use std::default::Default;
3use std::fmt;
4use std::str;
5
6/// Represents a quality used in quality values.
7///
8/// Can be created with the `q` function.
9///
10/// # Implementation notes
11///
12/// The quality value is defined as a number between 0 and 1 with three decimal places. This means
13/// there are 1000 possible values. Since floating point numbers are not exact and the smallest
14/// floating point data type (`f32`) consumes four bytes, hyper uses an `u16` value to store the
15/// quality internally. For performance reasons you may set quality directly to a value between
16/// 0 and 1000 e.g. `Quality(532)` matches the quality `q=0.532`.
17///
18/// [RFC7231 Section 5.3.1](https://tools.ietf.org/html/rfc7231#section-5.3.1)
19/// gives more information on quality values in HTTP header fields.
20#[derive(Copy, Clone, Debug, Eq, Ord, PartialEq, PartialOrd)]
21pub struct Quality(pub u16);
22
23impl fmt::Display for Quality {
24    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
25        match self.0 {
26            1000 => Ok(()),
27            0 => f.write_str("; q=0"),
28            x => write!(f, "; q=0.{}", format!("{:03}", x).trim_right_matches('0'))
29        }
30    }
31}
32
33impl Default for Quality {
34    fn default() -> Quality {
35        Quality(1000)
36    }
37}
38
39/// Represents an item with a quality value as defined in
40/// [RFC7231](https://tools.ietf.org/html/rfc7231#section-5.3.1).
41#[derive(Clone, PartialEq, Debug)]
42pub struct QualityItem<T> {
43    /// The actual contents of the field.
44    pub item: T,
45    /// The quality (client or server preference) for the value.
46    pub quality: Quality,
47}
48
49impl<T> QualityItem<T> {
50    /// Creates a new `QualityItem` from an item and a quality.
51    /// The item can be of any type.
52    /// The quality should be a value in the range [0, 1].
53    pub fn new(item: T, quality: Quality) -> QualityItem<T> {
54        QualityItem {
55            item: item,
56            quality: quality
57        }
58    }
59}
60
61impl<T: PartialEq> cmp::PartialOrd for QualityItem<T> {
62    fn partial_cmp(&self, other: &QualityItem<T>) -> Option<cmp::Ordering> {
63        self.quality.partial_cmp(&other.quality)
64    }
65}
66
67impl<T: fmt::Display> fmt::Display for QualityItem<T> {
68    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
69        write!(f, "{}{}", self.item, format!("{}", self.quality))
70    }
71}
72
73impl<T: str::FromStr> str::FromStr for QualityItem<T> {
74    type Err = crate::Error;
75    fn from_str(s: &str) -> crate::Result<QualityItem<T>> {
76        // Set defaults used if parsing fails.
77        let mut raw_item = s;
78        let mut quality = 1f32;
79
80        let parts: Vec<&str> = s.rsplitn(2, ';').map(|x| x.trim()).collect();
81        if parts.len() == 2 {
82            let start = &parts[0][0..2];
83            if start == "q=" || start == "Q=" {
84                let q_part = &parts[0][2..parts[0].len()];
85                if q_part.len() > 5 {
86                    return Err(crate::Error::Header);
87                }
88                match q_part.parse::<f32>() {
89                    Ok(q_value) => {
90                        if 0f32 <= q_value && q_value <= 1f32 {
91                            quality = q_value;
92                            raw_item = parts[1];
93                            } else {
94                                return Err(crate::Error::Header);
95                            }
96                        },
97                    Err(_) => return Err(crate::Error::Header),
98                }
99            }
100        }
101        match raw_item.parse::<T>() {
102            // we already checked above that the quality is within range
103            Ok(item) => Ok(QualityItem::new(item, from_f32(quality))),
104            Err(_) => Err(crate::Error::Header),
105        }
106    }
107}
108
109fn from_f32(f: f32) -> Quality {
110    // this function is only used internally. A check that `f` is within range
111    // should be done before calling this method. Just in case, this
112    // debug_assert should catch if we were forgetful
113    debug_assert!(f >= 0f32 && f <= 1f32, "q value must be between 0.0 and 1.0");
114    Quality((f * 1000f32) as u16)
115}
116
117/// Convinience function to wrap a value in a `QualityItem`
118/// Sets `q` to the default 1.0
119pub fn qitem<T>(item: T) -> QualityItem<T> {
120    QualityItem::new(item, Default::default())
121}
122
123/// Convenience function to create a `Quality` from a float.
124pub fn q(f: f32) -> Quality {
125    assert!(f >= 0f32 && f <= 1f32, "q value must be between 0.0 and 1.0");
126    from_f32(f)
127}
128
129#[cfg(test)]
130mod tests {
131    use super::*;
132    use super::super::encoding::*;
133
134    #[test]
135    fn test_quality_item_show1() {
136        let x = qitem(Chunked);
137        assert_eq!(format!("{}", x), "chunked");
138    }
139    #[test]
140    fn test_quality_item_show2() {
141        let x = QualityItem::new(Chunked, Quality(1));
142        assert_eq!(format!("{}", x), "chunked; q=0.001");
143    }
144    #[test]
145    fn test_quality_item_show3() {
146        // Custom value
147        let x = QualityItem{
148            item: EncodingExt("identity".to_owned()),
149            quality: Quality(500),
150        };
151        assert_eq!(format!("{}", x), "identity; q=0.5");
152    }
153
154    #[test]
155    fn test_quality_item_from_str1() {
156        let x: crate::Result<QualityItem<Encoding>> = "chunked".parse();
157        assert_eq!(x.unwrap(), QualityItem{ item: Chunked, quality: Quality(1000), });
158    }
159    #[test]
160    fn test_quality_item_from_str2() {
161        let x: crate::Result<QualityItem<Encoding>> = "chunked; q=1".parse();
162        assert_eq!(x.unwrap(), QualityItem{ item: Chunked, quality: Quality(1000), });
163    }
164    #[test]
165    fn test_quality_item_from_str3() {
166        let x: crate::Result<QualityItem<Encoding>> = "gzip; q=0.5".parse();
167        assert_eq!(x.unwrap(), QualityItem{ item: Gzip, quality: Quality(500), });
168    }
169    #[test]
170    fn test_quality_item_from_str4() {
171        let x: crate::Result<QualityItem<Encoding>> = "gzip; q=0.273".parse();
172        assert_eq!(x.unwrap(), QualityItem{ item: Gzip, quality: Quality(273), });
173    }
174    #[test]
175    fn test_quality_item_from_str5() {
176        let x: crate::Result<QualityItem<Encoding>> = "gzip; q=0.2739999".parse();
177        assert!(x.is_err());
178    }
179    #[test]
180    fn test_quality_item_from_str6() {
181        let x: crate::Result<QualityItem<Encoding>> = "gzip; q=2".parse();
182        assert!(x.is_err());
183    }
184    #[test]
185    fn test_quality_item_ordering() {
186        let x: QualityItem<Encoding> = "gzip; q=0.5".parse().ok().unwrap();
187        let y: QualityItem<Encoding> = "gzip; q=0.273".parse().ok().unwrap();
188        let comparision_result: bool = x.gt(&y);
189        assert!(comparision_result)
190    }
191
192    #[test]
193    fn test_quality() {
194        assert_eq!(q(0.5), Quality(500));
195    }
196
197    #[test]
198    fn test_quality2() {
199        assert_eq!(format!("{}", q(0.0)), "; q=0");
200    }
201
202    #[test]
203    #[should_panic] // FIXME - 32-bit msvc unwinding broken
204    #[cfg_attr(all(target_arch="x86", target_env="msvc"), ignore)]
205    fn test_quality_invalid() {
206        q(-1.0);
207    }
208
209    #[test]
210    #[should_panic] // FIXME - 32-bit msvc unwinding broken
211    #[cfg_attr(all(target_arch="x86", target_env="msvc"), ignore)]
212    fn test_quality_invalid2() {
213        q(2.0);
214    }
215}