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 for (key, value) in &self.params {
53 write!(f, ";{key}={value}")?;
54 }
55 Ok(())
56 }
57}
58
59#[derive(Debug, Clone, PartialEq, Eq)]
61#[non_exhaustive]
62pub enum SipAcceptError {
63 Empty,
65 InvalidFormat(String),
67}
68
69impl fmt::Display for SipAcceptError {
70 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
71 match self {
72 Self::Empty => write!(f, "empty Accept header value"),
73 Self::InvalidFormat(raw) => write!(f, "invalid Accept entry: {raw}"),
74 }
75 }
76}
77
78impl std::error::Error for SipAcceptError {}
79
80fn parse_accept_entry(raw: &str) -> Result<SipAcceptEntry, SipAcceptError> {
81 let raw = raw.trim();
82 if raw.is_empty() {
83 return Err(SipAcceptError::InvalidFormat(raw.to_string()));
84 }
85
86 let (media_part, params_part) = match raw.split_once(';') {
87 Some((m, p)) => (m.trim(), Some(p)),
88 None => (raw, None),
89 };
90
91 let (type_str, subtype_str) = media_part
92 .split_once('/')
93 .ok_or_else(|| SipAcceptError::InvalidFormat(raw.to_string()))?;
94
95 let type_str = type_str.trim();
96 let subtype_str = subtype_str.trim();
97
98 if type_str.is_empty() || subtype_str.is_empty() {
99 return Err(SipAcceptError::InvalidFormat(raw.to_string()));
100 }
101
102 let mut media_range = type_str.to_ascii_lowercase();
103 let slash_pos = media_range.len();
104 media_range.push('/');
105 media_range.push_str(&subtype_str.to_ascii_lowercase());
106
107 let mut params = Vec::new();
108 if let Some(params_str) = params_part {
109 for segment in params_str.split(';') {
110 let segment = segment.trim();
111 if segment.is_empty() {
112 continue;
113 }
114 if let Some((key, value)) = segment.split_once('=') {
115 params.push((
116 key.trim()
117 .to_ascii_lowercase(),
118 value
119 .trim()
120 .to_string(),
121 ));
122 } else {
123 params.push((segment.to_ascii_lowercase(), String::new()));
124 }
125 }
126 }
127
128 Ok(SipAcceptEntry {
129 media_range,
130 slash_pos,
131 params,
132 })
133}
134
135#[derive(Debug, Clone, PartialEq, Eq)]
137#[non_exhaustive]
138pub struct SipAccept(Vec<SipAcceptEntry>);
139
140impl SipAccept {
141 pub fn parse(raw: &str) -> Result<Self, SipAcceptError> {
143 let raw = raw.trim();
144 if raw.is_empty() {
145 return Err(SipAcceptError::Empty);
146 }
147 Self::from_entries(crate::split_comma_entries(raw))
148 }
149
150 pub fn from_entries<'a>(
152 entries: impl IntoIterator<Item = &'a str>,
153 ) -> Result<Self, SipAcceptError> {
154 let entries: Vec<_> = entries
155 .into_iter()
156 .map(parse_accept_entry)
157 .collect::<Result<_, _>>()?;
158 if entries.is_empty() {
159 return Err(SipAcceptError::Empty);
160 }
161 Ok(Self(entries))
162 }
163
164 pub fn entries(&self) -> &[SipAcceptEntry] {
166 &self.0
167 }
168
169 pub fn into_entries(self) -> Vec<SipAcceptEntry> {
171 self.0
172 }
173
174 pub fn len(&self) -> usize {
176 self.0
177 .len()
178 }
179
180 pub fn is_empty(&self) -> bool {
182 self.0
183 .is_empty()
184 }
185}
186
187impl fmt::Display for SipAccept {
188 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
189 crate::fmt_joined(f, &self.0, ", ")
190 }
191}
192
193impl_from_str_via_parse!(SipAccept, SipAcceptError);
194
195impl<'a> IntoIterator for &'a SipAccept {
196 type Item = &'a SipAcceptEntry;
197 type IntoIter = std::slice::Iter<'a, SipAcceptEntry>;
198
199 fn into_iter(self) -> Self::IntoIter {
200 self.0
201 .iter()
202 }
203}
204
205impl IntoIterator for SipAccept {
206 type Item = SipAcceptEntry;
207 type IntoIter = std::vec::IntoIter<SipAcceptEntry>;
208
209 fn into_iter(self) -> Self::IntoIter {
210 self.0
211 .into_iter()
212 }
213}
214
215#[cfg(test)]
216mod tests {
217 use super::*;
218
219 #[test]
220 fn single_media_type() {
221 let accept = SipAccept::parse("application/sdp").unwrap();
222 assert_eq!(accept.len(), 1);
223 assert_eq!(accept.entries()[0].media_type(), "application");
224 assert_eq!(accept.entries()[0].subtype(), "sdp");
225 }
226
227 #[test]
228 fn multiple_types() {
229 let accept = SipAccept::parse("application/sdp, application/pidf+xml;q=0.5").unwrap();
230 assert_eq!(accept.len(), 2);
231 assert_eq!(accept.entries()[0].media_range(), "application/sdp");
232 assert_eq!(accept.entries()[1].q(), Some("0.5"));
233 }
234
235 #[test]
236 fn wildcard_type() {
237 let accept = SipAccept::parse("*/*").unwrap();
238 assert_eq!(accept.entries()[0].media_type(), "*");
239 assert_eq!(accept.entries()[0].subtype(), "*");
240 }
241
242 #[test]
243 fn wildcard_subtype() {
244 let accept = SipAccept::parse("application/*").unwrap();
245 assert_eq!(accept.entries()[0].media_type(), "application");
246 assert_eq!(accept.entries()[0].subtype(), "*");
247 }
248
249 #[test]
250 fn empty_input() {
251 assert!(matches!(SipAccept::parse(""), Err(SipAcceptError::Empty)));
252 }
253
254 #[test]
255 fn missing_slash() {
256 assert!(matches!(
257 SipAccept::parse("application"),
258 Err(SipAcceptError::InvalidFormat(_))
259 ));
260 }
261
262 #[test]
263 fn from_str() {
264 let accept: SipAccept = "application/sdp"
265 .parse()
266 .unwrap();
267 assert_eq!(accept.len(), 1);
268 }
269
270 #[test]
271 fn display_roundtrip() {
272 let raw = "application/sdp;q=0.8";
273 let accept = SipAccept::parse(raw).unwrap();
274 assert_eq!(accept.to_string(), raw);
275 }
276
277 #[test]
278 fn from_entries_matches_parse() {
279 let a = "application/sdp";
280 let b = "application/pidf+xml;q=0.5";
281 let split = SipAccept::from_entries([a, b]).unwrap();
282 let joined = SipAccept::parse(&format!("{a}, {b}")).unwrap();
283 assert_eq!(split, joined);
284 }
285
286 #[test]
287 fn from_entries_bad_entry_is_error() {
288 assert!(matches!(
289 SipAccept::from_entries(["application/sdp", "noslash"]),
290 Err(SipAcceptError::InvalidFormat(_))
291 ));
292 }
293
294 #[test]
295 fn from_entries_empty_is_empty_error() {
296 assert!(matches!(
297 SipAccept::from_entries(std::iter::empty::<&str>()),
298 Err(SipAcceptError::Empty)
299 ));
300 }
301}