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