1use std::fmt;
4
5#[derive(Debug, Clone, PartialEq, Eq)]
7#[non_exhaustive]
8pub struct SipAcceptEntry {
9 media_range: String,
10 slash_pos: usize,
11 params: Vec<(String, String)>,
12}
13
14impl SipAcceptEntry {
15 pub fn media_type(&self) -> &str {
17 &self.media_range[..self.slash_pos]
18 }
19
20 pub fn subtype(&self) -> &str {
22 &self.media_range[self.slash_pos + 1..]
23 }
24
25 pub fn media_range(&self) -> &str {
27 &self.media_range
28 }
29
30 pub fn params(&self) -> &[(String, String)] {
32 &self.params
33 }
34
35 pub fn param(&self, key: &str) -> Option<&str> {
37 self.params
38 .iter()
39 .find(|(k, _)| k.eq_ignore_ascii_case(key))
40 .map(|(_, v)| v.as_str())
41 }
42
43 pub fn q(&self) -> Option<&str> {
45 self.param("q")
46 }
47}
48
49impl fmt::Display for SipAcceptEntry {
50 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
51 write!(f, "{}", self.media_range)?;
52 write_accept_params(f, &self.params)
53 }
54}
55
56#[derive(Debug, Clone, PartialEq, Eq)]
58#[non_exhaustive]
59pub enum SipAcceptError {
60 Empty,
63 InvalidFormat(String),
65}
66
67impl fmt::Display for SipAcceptError {
68 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
69 match self {
70 Self::Empty => write!(f, "empty Accept header value"),
71 Self::InvalidFormat(raw) => write!(f, "invalid Accept entry ({} bytes)", raw.len()),
72 }
73 }
74}
75
76impl std::error::Error for SipAcceptError {}
77
78fn parse_accept_entry(raw: &str) -> Result<SipAcceptEntry, SipAcceptError> {
79 let raw = raw.trim();
80 if raw.is_empty() {
81 return Err(SipAcceptError::InvalidFormat(raw.to_string()));
82 }
83
84 let (media_part, params_part) = match raw.split_once(';') {
85 Some((m, p)) => (m.trim(), Some(p)),
86 None => (raw, None),
87 };
88
89 let (type_str, subtype_str) = media_part
90 .split_once('/')
91 .ok_or_else(|| SipAcceptError::InvalidFormat(raw.to_string()))?;
92
93 let type_str = type_str.trim();
94 let subtype_str = subtype_str.trim();
95
96 if type_str.is_empty() || subtype_str.is_empty() {
97 return Err(SipAcceptError::InvalidFormat(raw.to_string()));
98 }
99
100 let mut media_range = type_str.to_ascii_lowercase();
101 let slash_pos = media_range.len();
102 media_range.push('/');
103 media_range.push_str(&subtype_str.to_ascii_lowercase());
104
105 Ok(SipAcceptEntry {
106 media_range,
107 slash_pos,
108 params: read_accept_params(params_part.unwrap_or("")),
109 })
110}
111
112pub(crate) fn read_accept_params(s: &str) -> Vec<(String, String)> {
114 crate::parse_params(s)
115 .into_iter()
116 .map(|p| {
117 (
118 p.key
119 .to_ascii_lowercase(),
120 p.value
121 .unwrap_or("")
122 .to_string(),
123 )
124 })
125 .collect()
126}
127
128pub(crate) fn write_accept_params(
130 f: &mut fmt::Formatter<'_>,
131 params: &[(String, String)],
132) -> fmt::Result {
133 for (key, value) in params {
134 crate::write_param(
135 f,
136 key,
137 Some(value.as_str()).filter(|v| !v.is_empty()),
138 false,
139 )?;
140 }
141 Ok(())
142}
143
144pub(crate) fn all_blank(entries: &[&str]) -> bool {
147 entries
148 .iter()
149 .all(|e| {
150 e.trim()
151 .is_empty()
152 })
153}
154
155#[derive(Debug, Clone, PartialEq, Eq)]
157#[non_exhaustive]
158pub struct SipAccept(Vec<SipAcceptEntry>);
159
160impl SipAccept {
161 pub fn parse(raw: &str) -> Result<Self, SipAcceptError> {
165 Self::from_entries(crate::split_comma_entries(raw))
166 }
167
168 pub fn from_entries<'a>(
173 entries: impl IntoIterator<Item = &'a str>,
174 ) -> Result<Self, SipAcceptError> {
175 let entries: Vec<&str> = entries
176 .into_iter()
177 .collect();
178 if all_blank(&entries) {
179 return Ok(Self(Vec::new()));
180 }
181 entries
182 .into_iter()
183 .map(parse_accept_entry)
184 .collect::<Result<_, _>>()
185 .map(Self)
186 }
187
188 pub fn entries(&self) -> &[SipAcceptEntry] {
190 &self.0
191 }
192
193 pub fn into_entries(self) -> Vec<SipAcceptEntry> {
195 self.0
196 }
197
198 pub fn len(&self) -> usize {
200 self.0
201 .len()
202 }
203
204 pub fn is_empty(&self) -> bool {
206 self.0
207 .is_empty()
208 }
209}
210
211impl fmt::Display for SipAccept {
212 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
213 crate::fmt_joined(f, &self.0, ", ")
214 }
215}
216
217impl_from_str_via_parse!(SipAccept, SipAcceptError);
218
219impl<'a> IntoIterator for &'a SipAccept {
220 type Item = &'a SipAcceptEntry;
221 type IntoIter = std::slice::Iter<'a, SipAcceptEntry>;
222
223 fn into_iter(self) -> Self::IntoIter {
224 self.0
225 .iter()
226 }
227}
228
229impl IntoIterator for SipAccept {
230 type Item = SipAcceptEntry;
231 type IntoIter = std::vec::IntoIter<SipAcceptEntry>;
232
233 fn into_iter(self) -> Self::IntoIter {
234 self.0
235 .into_iter()
236 }
237}
238
239#[cfg(test)]
240mod tests {
241 use super::*;
242
243 #[test]
244 fn single_media_type() {
245 let accept = SipAccept::parse("application/sdp").unwrap();
246 assert_eq!(accept.len(), 1);
247 assert_eq!(accept.entries()[0].media_type(), "application");
248 assert_eq!(accept.entries()[0].subtype(), "sdp");
249 }
250
251 #[test]
252 fn multiple_types() {
253 let accept = SipAccept::parse("application/sdp, application/pidf+xml;q=0.5").unwrap();
254 assert_eq!(accept.len(), 2);
255 assert_eq!(accept.entries()[0].media_range(), "application/sdp");
256 assert_eq!(accept.entries()[1].q(), Some("0.5"));
257 }
258
259 #[test]
260 fn wildcard_type() {
261 let accept = SipAccept::parse("*/*").unwrap();
262 assert_eq!(accept.entries()[0].media_type(), "*");
263 assert_eq!(accept.entries()[0].subtype(), "*");
264 }
265
266 #[test]
267 fn wildcard_subtype() {
268 let accept = SipAccept::parse("application/*").unwrap();
269 assert_eq!(accept.entries()[0].media_type(), "application");
270 assert_eq!(accept.entries()[0].subtype(), "*");
271 }
272
273 #[test]
274 fn empty_input_is_empty_list() {
275 assert!(SipAccept::parse("")
276 .unwrap()
277 .is_empty());
278 assert!(SipAccept::parse(" ")
279 .unwrap()
280 .is_empty());
281 }
282
283 #[test]
284 fn flag_param_roundtrip() {
285 let raw = "application/sdp;foo";
286 let accept = SipAccept::parse(raw).unwrap();
287 assert_eq!(accept.entries()[0].param("foo"), Some(""));
288 assert_eq!(accept.to_string(), raw);
289 }
290
291 #[test]
292 fn quoted_param_keeps_semicolon() {
293 let raw = r#"application/sdp;x="a;b";q=0.5"#;
294 let accept = SipAccept::parse(raw).unwrap();
295 assert_eq!(accept.entries()[0].param("x"), Some(r#""a;b""#));
296 assert_eq!(accept.entries()[0].q(), Some("0.5"));
297 assert_eq!(accept.to_string(), raw);
298 }
299
300 #[test]
301 fn error_display_omits_input() {
302 let err = SipAccept::parse("secretvalue").unwrap_err();
303 assert!(!err
304 .to_string()
305 .contains("secretvalue"));
306 }
307
308 #[test]
309 fn missing_slash() {
310 assert!(matches!(
311 SipAccept::parse("application"),
312 Err(SipAcceptError::InvalidFormat(_))
313 ));
314 }
315
316 #[test]
317 fn from_str() {
318 let accept: SipAccept = "application/sdp"
319 .parse()
320 .unwrap();
321 assert_eq!(accept.len(), 1);
322 }
323
324 #[test]
325 fn display_roundtrip() {
326 let raw = "application/sdp;q=0.8";
327 let accept = SipAccept::parse(raw).unwrap();
328 assert_eq!(accept.to_string(), raw);
329 }
330
331 #[test]
332 fn from_entries_matches_parse() {
333 let a = "application/sdp";
334 let b = "application/pidf+xml;q=0.5";
335 let split = SipAccept::from_entries([a, b]).unwrap();
336 let joined = SipAccept::parse(&format!("{a}, {b}")).unwrap();
337 assert_eq!(split, joined);
338 }
339
340 #[test]
341 fn from_entries_bad_entry_is_error() {
342 assert!(matches!(
343 SipAccept::from_entries(["application/sdp", "noslash"]),
344 Err(SipAcceptError::InvalidFormat(_))
345 ));
346 }
347
348 #[test]
349 fn from_entries_empty_is_empty_list() {
350 assert!(SipAccept::from_entries(std::iter::empty::<&str>())
351 .unwrap()
352 .is_empty());
353 assert!(SipAccept::from_entries(["", " "])
354 .unwrap()
355 .is_empty());
356 }
357}