sip_header/
accept_encoding.rs1use std::fmt;
4
5#[derive(Debug, Clone, PartialEq, Eq)]
7#[non_exhaustive]
8pub struct SipAcceptEncodingEntry {
9 encoding: String,
10 params: Vec<(String, String)>,
11}
12
13impl SipAcceptEncodingEntry {
14 pub fn encoding(&self) -> &str {
16 &self.encoding
17 }
18
19 pub fn params(&self) -> &[(String, String)] {
21 &self.params
22 }
23
24 pub fn param(&self, key: &str) -> Option<&str> {
26 self.params
27 .iter()
28 .find(|(k, _)| k.eq_ignore_ascii_case(key))
29 .map(|(_, v)| v.as_str())
30 }
31
32 pub fn q(&self) -> Option<&str> {
34 self.param("q")
35 }
36}
37
38impl fmt::Display for SipAcceptEncodingEntry {
39 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
40 write!(f, "{}", self.encoding)?;
41 for (key, value) in &self.params {
42 write!(f, ";{key}={value}")?;
43 }
44 Ok(())
45 }
46}
47
48#[derive(Debug, Clone, PartialEq, Eq)]
50#[non_exhaustive]
51pub enum SipAcceptEncodingError {
52 Empty,
54 InvalidFormat(String),
56}
57
58impl fmt::Display for SipAcceptEncodingError {
59 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
60 match self {
61 Self::Empty => write!(f, "empty Accept-Encoding header value"),
62 Self::InvalidFormat(raw) => write!(f, "invalid Accept-Encoding entry: {raw}"),
63 }
64 }
65}
66
67impl std::error::Error for SipAcceptEncodingError {}
68
69fn parse_entry(raw: &str) -> Result<SipAcceptEncodingEntry, SipAcceptEncodingError> {
70 let raw = raw.trim();
71 if raw.is_empty() {
72 return Err(SipAcceptEncodingError::InvalidFormat(raw.to_string()));
73 }
74
75 let (encoding_part, params_part) = match raw.split_once(';') {
76 Some((e, p)) => (e.trim(), Some(p)),
77 None => (raw, None),
78 };
79
80 if encoding_part.is_empty() {
81 return Err(SipAcceptEncodingError::InvalidFormat(raw.to_string()));
82 }
83
84 let encoding = encoding_part.to_ascii_lowercase();
85 let mut params = Vec::new();
86
87 if let Some(params_str) = params_part {
88 for segment in params_str.split(';') {
89 let segment = segment.trim();
90 if segment.is_empty() {
91 continue;
92 }
93 if let Some((key, value)) = segment.split_once('=') {
94 params.push((
95 key.trim()
96 .to_ascii_lowercase(),
97 value
98 .trim()
99 .to_string(),
100 ));
101 } else {
102 params.push((segment.to_ascii_lowercase(), String::new()));
103 }
104 }
105 }
106
107 Ok(SipAcceptEncodingEntry { encoding, params })
108}
109
110#[derive(Debug, Clone, PartialEq, Eq)]
112#[non_exhaustive]
113pub struct SipAcceptEncoding(Vec<SipAcceptEncodingEntry>);
114
115impl SipAcceptEncoding {
116 pub fn parse(raw: &str) -> Result<Self, SipAcceptEncodingError> {
118 let raw = raw.trim();
119 if raw.is_empty() {
120 return Err(SipAcceptEncodingError::Empty);
121 }
122 Self::from_entries(crate::split_comma_entries(raw))
123 }
124
125 pub fn from_entries<'a>(
127 entries: impl IntoIterator<Item = &'a str>,
128 ) -> Result<Self, SipAcceptEncodingError> {
129 let entries: Vec<_> = entries
130 .into_iter()
131 .map(parse_entry)
132 .collect::<Result<_, _>>()?;
133 if entries.is_empty() {
134 return Err(SipAcceptEncodingError::Empty);
135 }
136 Ok(Self(entries))
137 }
138
139 pub fn entries(&self) -> &[SipAcceptEncodingEntry] {
141 &self.0
142 }
143
144 pub fn into_entries(self) -> Vec<SipAcceptEncodingEntry> {
146 self.0
147 }
148
149 pub fn len(&self) -> usize {
151 self.0
152 .len()
153 }
154
155 pub fn is_empty(&self) -> bool {
157 self.0
158 .is_empty()
159 }
160}
161
162impl fmt::Display for SipAcceptEncoding {
163 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
164 crate::fmt_joined(f, &self.0, ", ")
165 }
166}
167
168impl_from_str_via_parse!(SipAcceptEncoding, SipAcceptEncodingError);
169
170impl<'a> IntoIterator for &'a SipAcceptEncoding {
171 type Item = &'a SipAcceptEncodingEntry;
172 type IntoIter = std::slice::Iter<'a, SipAcceptEncodingEntry>;
173
174 fn into_iter(self) -> Self::IntoIter {
175 self.0
176 .iter()
177 }
178}
179
180impl IntoIterator for SipAcceptEncoding {
181 type Item = SipAcceptEncodingEntry;
182 type IntoIter = std::vec::IntoIter<SipAcceptEncodingEntry>;
183
184 fn into_iter(self) -> Self::IntoIter {
185 self.0
186 .into_iter()
187 }
188}
189
190#[cfg(test)]
191mod tests {
192 use super::*;
193
194 #[test]
195 fn single_encoding() {
196 let ae = SipAcceptEncoding::parse("gzip").unwrap();
197 assert_eq!(ae.len(), 1);
198 assert_eq!(ae.entries()[0].encoding(), "gzip");
199 }
200
201 #[test]
202 fn multiple_encodings_with_q() {
203 let ae = SipAcceptEncoding::parse("gzip;q=1.0, identity;q=0.5").unwrap();
204 assert_eq!(ae.len(), 2);
205 assert_eq!(ae.entries()[0].q(), Some("1.0"));
206 assert_eq!(ae.entries()[1].encoding(), "identity");
207 }
208
209 #[test]
210 fn wildcard() {
211 let ae = SipAcceptEncoding::parse("*").unwrap();
212 assert_eq!(ae.entries()[0].encoding(), "*");
213 }
214
215 #[test]
216 fn empty_input() {
217 assert!(matches!(
218 SipAcceptEncoding::parse(""),
219 Err(SipAcceptEncodingError::Empty)
220 ));
221 }
222
223 #[test]
224 fn from_str() {
225 let ae: SipAcceptEncoding = "gzip"
226 .parse()
227 .unwrap();
228 assert_eq!(ae.len(), 1);
229 }
230
231 #[test]
232 fn display_roundtrip() {
233 let raw = "gzip;q=0.8";
234 let ae = SipAcceptEncoding::parse(raw).unwrap();
235 assert_eq!(ae.to_string(), raw);
236 }
237
238 #[test]
239 fn from_entries_matches_parse() {
240 let split = SipAcceptEncoding::from_entries(["gzip;q=0.8", "identity"]).unwrap();
241 let joined = SipAcceptEncoding::parse("gzip;q=0.8, identity").unwrap();
242 assert_eq!(split, joined);
243 }
244
245 #[test]
246 fn from_entries_bad_entry_is_error() {
247 assert!(matches!(
248 SipAcceptEncoding::from_entries(["gzip", " "]),
249 Err(SipAcceptEncodingError::InvalidFormat(_))
250 ));
251 }
252
253 #[test]
254 fn from_entries_empty_is_empty_error() {
255 assert!(matches!(
256 SipAcceptEncoding::from_entries(std::iter::empty::<&str>()),
257 Err(SipAcceptEncodingError::Empty)
258 ));
259 }
260}