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 let entries: Vec<_> = crate::split_comma_entries(raw)
148 .into_iter()
149 .map(parse_accept_entry)
150 .collect::<Result<_, _>>()?;
151 if entries.is_empty() {
152 return Err(SipAcceptError::Empty);
153 }
154 Ok(Self(entries))
155 }
156
157 pub fn entries(&self) -> &[SipAcceptEntry] {
159 &self.0
160 }
161
162 pub fn into_entries(self) -> Vec<SipAcceptEntry> {
164 self.0
165 }
166
167 pub fn len(&self) -> usize {
169 self.0
170 .len()
171 }
172
173 pub fn is_empty(&self) -> bool {
175 self.0
176 .is_empty()
177 }
178}
179
180impl fmt::Display for SipAccept {
181 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
182 crate::fmt_joined(f, &self.0, ", ")
183 }
184}
185
186impl_from_str_via_parse!(SipAccept, SipAcceptError);
187
188impl<'a> IntoIterator for &'a SipAccept {
189 type Item = &'a SipAcceptEntry;
190 type IntoIter = std::slice::Iter<'a, SipAcceptEntry>;
191
192 fn into_iter(self) -> Self::IntoIter {
193 self.0
194 .iter()
195 }
196}
197
198impl IntoIterator for SipAccept {
199 type Item = SipAcceptEntry;
200 type IntoIter = std::vec::IntoIter<SipAcceptEntry>;
201
202 fn into_iter(self) -> Self::IntoIter {
203 self.0
204 .into_iter()
205 }
206}
207
208#[cfg(test)]
209mod tests {
210 use super::*;
211
212 #[test]
213 fn single_media_type() {
214 let accept = SipAccept::parse("application/sdp").unwrap();
215 assert_eq!(accept.len(), 1);
216 assert_eq!(accept.entries()[0].media_type(), "application");
217 assert_eq!(accept.entries()[0].subtype(), "sdp");
218 }
219
220 #[test]
221 fn multiple_types() {
222 let accept = SipAccept::parse("application/sdp, application/pidf+xml;q=0.5").unwrap();
223 assert_eq!(accept.len(), 2);
224 assert_eq!(accept.entries()[0].media_range(), "application/sdp");
225 assert_eq!(accept.entries()[1].q(), Some("0.5"));
226 }
227
228 #[test]
229 fn wildcard_type() {
230 let accept = SipAccept::parse("*/*").unwrap();
231 assert_eq!(accept.entries()[0].media_type(), "*");
232 assert_eq!(accept.entries()[0].subtype(), "*");
233 }
234
235 #[test]
236 fn wildcard_subtype() {
237 let accept = SipAccept::parse("application/*").unwrap();
238 assert_eq!(accept.entries()[0].media_type(), "application");
239 assert_eq!(accept.entries()[0].subtype(), "*");
240 }
241
242 #[test]
243 fn empty_input() {
244 assert!(matches!(SipAccept::parse(""), Err(SipAcceptError::Empty)));
245 }
246
247 #[test]
248 fn missing_slash() {
249 assert!(matches!(
250 SipAccept::parse("application"),
251 Err(SipAcceptError::InvalidFormat(_))
252 ));
253 }
254
255 #[test]
256 fn from_str() {
257 let accept: SipAccept = "application/sdp"
258 .parse()
259 .unwrap();
260 assert_eq!(accept.len(), 1);
261 }
262
263 #[test]
264 fn display_roundtrip() {
265 let raw = "application/sdp;q=0.8";
266 let accept = SipAccept::parse(raw).unwrap();
267 assert_eq!(accept.to_string(), raw);
268 }
269}