http_types/content/
encoding_proposal.rs1use crate::content::Encoding;
2use crate::ensure;
3use crate::headers::HeaderValue;
4use crate::utils::parse_weight;
5
6use std::cmp::{Ordering, PartialEq};
7use std::ops::{Deref, DerefMut};
8
9#[derive(Debug, Clone, Copy, PartialEq)]
11pub struct EncodingProposal {
12 pub(crate) encoding: Encoding,
14
15 weight: Option<f32>,
19}
20
21impl EncodingProposal {
22 pub fn new(encoding: impl Into<Encoding>, weight: Option<f32>) -> crate::Result<Self> {
24 if let Some(weight) = weight {
25 ensure!(
26 weight.is_sign_positive() && weight <= 1.0,
27 "EncodingProposal should have a weight between 0.0 and 1.0"
28 )
29 }
30
31 Ok(Self {
32 encoding: encoding.into(),
33 weight,
34 })
35 }
36
37 pub fn encoding(&self) -> &Encoding {
39 &self.encoding
40 }
41
42 pub fn weight(&self) -> Option<f32> {
44 self.weight
45 }
46
47 pub(crate) fn from_str(s: &str) -> crate::Result<Option<Self>> {
48 let mut parts = s.split(';');
49 let encoding = match Encoding::from_str(parts.next().unwrap()) {
50 Some(encoding) => encoding,
51 None => return Ok(None),
52 };
53 let weight = parts.next().map(parse_weight).transpose()?;
54
55 Ok(Some(Self::new(encoding, weight)?))
56 }
57}
58
59impl From<Encoding> for EncodingProposal {
60 fn from(encoding: Encoding) -> Self {
61 Self {
62 encoding,
63 weight: None,
64 }
65 }
66}
67
68impl PartialEq<Encoding> for EncodingProposal {
69 fn eq(&self, other: &Encoding) -> bool {
70 self.encoding == *other
71 }
72}
73
74impl PartialEq<Encoding> for &EncodingProposal {
75 fn eq(&self, other: &Encoding) -> bool {
76 self.encoding == *other
77 }
78}
79
80impl Deref for EncodingProposal {
81 type Target = Encoding;
82 fn deref(&self) -> &Self::Target {
83 &self.encoding
84 }
85}
86
87impl DerefMut for EncodingProposal {
88 fn deref_mut(&mut self) -> &mut Self::Target {
89 &mut self.encoding
90 }
91}
92
93impl PartialOrd for EncodingProposal {
101 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
102 match (self.weight, other.weight) {
103 (Some(left), Some(right)) => left.partial_cmp(&right),
104 (Some(_), None) => Some(Ordering::Greater),
105 (None, Some(_)) => Some(Ordering::Less),
106 (None, None) => None,
107 }
108 }
109}
110
111impl From<EncodingProposal> for HeaderValue {
112 fn from(entry: EncodingProposal) -> HeaderValue {
113 let s = match entry.weight {
114 Some(weight) => format!("{};q={:.3}", entry.encoding, weight),
115 None => entry.encoding.to_string(),
116 };
117 unsafe { HeaderValue::from_bytes_unchecked(s.into_bytes()) }
118 }
119}
120
121#[cfg(test)]
122mod test {
123 use super::*;
124
125 #[test]
126 fn smoke() {
127 let _ = EncodingProposal::new(Encoding::Gzip, Some(0.0)).unwrap();
128 let _ = EncodingProposal::new(Encoding::Gzip, Some(0.5)).unwrap();
129 let _ = EncodingProposal::new(Encoding::Gzip, Some(1.0)).unwrap();
130 }
131
132 #[test]
133 fn error_code_500() {
134 let err = EncodingProposal::new(Encoding::Gzip, Some(1.1)).unwrap_err();
135 assert_eq!(err.status(), 500);
136
137 let err = EncodingProposal::new(Encoding::Gzip, Some(-0.1)).unwrap_err();
138 assert_eq!(err.status(), 500);
139
140 let err = EncodingProposal::new(Encoding::Gzip, Some(-0.0)).unwrap_err();
141 assert_eq!(err.status(), 500);
142 }
143}