freeswitch_types/variables/
esl_headers.rs1use indexmap::IndexMap;
20use sip_header::{
21 HistoryInfo, HistoryInfoError, SipHeader, SipHeaderLookup, UriInfo, UriInfoError,
22};
23
24use crate::lookup::HeaderLookup;
25use crate::variables::{EslArray, EslArrayError};
26
27#[derive(Debug, Clone, Default, PartialEq, Eq)]
54#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
55pub struct EslHeaders(IndexMap<String, String>);
56
57impl EslHeaders {
58 pub fn new() -> Self {
60 Self(IndexMap::new())
61 }
62
63 pub fn from_map(map: IndexMap<String, String>) -> Self {
65 Self(map)
66 }
67
68 pub fn as_map(&self) -> &IndexMap<String, String> {
70 &self.0
71 }
72
73 pub fn into_map(self) -> IndexMap<String, String> {
75 self.0
76 }
77
78 pub fn insert(&mut self, key: impl Into<String>, value: impl Into<String>) {
80 self.0
81 .insert(key.into(), value.into());
82 }
83
84 pub fn remove(&mut self, key: &str) -> Option<String> {
86 self.0
87 .shift_remove(key)
88 }
89
90 pub fn len(&self) -> usize {
92 self.0
93 .len()
94 }
95
96 pub fn is_empty(&self) -> bool {
98 self.0
99 .is_empty()
100 }
101}
102
103impl From<IndexMap<String, String>> for EslHeaders {
104 fn from(map: IndexMap<String, String>) -> Self {
105 Self(map)
106 }
107}
108
109fn strip_brackets(s: &str) -> &str {
112 if let Some(inner) = s.strip_prefix('[') {
113 if let Some(inner) = inner.strip_suffix(']') {
114 return inner;
115 }
116 }
117 s
118}
119
120impl EslHeaders {
121 pub fn parse_uri_info(value: &str) -> Result<UriInfo, UriInfoError> {
154 let value = strip_brackets(value);
155 match EslArray::parse(value) {
156 Ok(array) => UriInfo::from_entries(
157 array
158 .items()
159 .iter()
160 .map(String::as_str),
161 ),
162 Err(EslArrayError::MissingPrefix) => UriInfo::parse(value),
163 Err(other) => Err(UriInfoError::MissingAngleBrackets(format!(
167 "ARRAY:: parse failed: {other}"
168 ))),
169 }
170 }
171
172 pub fn parse_history_info(value: &str) -> Result<HistoryInfo, HistoryInfoError> {
183 let value = strip_brackets(value);
184 match EslArray::parse(value) {
185 Ok(array) => HistoryInfo::from_entries(
186 array
187 .items()
188 .iter()
189 .map(String::as_str),
190 ),
191 Err(EslArrayError::MissingPrefix) => HistoryInfo::parse(value),
192 Err(_) => Err(HistoryInfoError::Empty),
193 }
194 }
195}
196
197impl SipHeaderLookup for EslHeaders {
198 fn sip_header_str(&self, name: &str) -> Option<&str> {
199 self.0
200 .get(name)
201 .map(|s| s.as_str())
202 }
203
204 fn call_info(&self) -> Result<Option<UriInfo>, UriInfoError> {
205 match self.sip_header(SipHeader::CallInfo) {
206 Some(s) => Self::parse_uri_info(s).map(Some),
207 None => Ok(None),
208 }
209 }
210
211 fn history_info(&self) -> Result<Option<HistoryInfo>, HistoryInfoError> {
212 match self.sip_header(SipHeader::HistoryInfo) {
213 Some(s) => Self::parse_history_info(s).map(Some),
214 None => Ok(None),
215 }
216 }
217
218 fn alert_info(&self) -> Result<Option<UriInfo>, UriInfoError> {
219 match self.sip_header(SipHeader::AlertInfo) {
220 Some(s) => Self::parse_uri_info(s).map(Some),
221 None => Ok(None),
222 }
223 }
224}
225
226impl HeaderLookup for EslHeaders {
227 fn header_str(&self, name: &str) -> Option<&str> {
228 self.0
229 .get(name)
230 .map(|s| s.as_str())
231 }
232
233 fn variable_str(&self, name: &str) -> Option<&str> {
234 self.0
235 .get(&format!("variable_{name}"))
236 .map(|s| s.as_str())
237 }
238}
239
240#[cfg(test)]
241mod tests {
242 use super::*;
243 use crate::headers::EventHeader;
244
245 #[test]
246 fn header_str_passthrough() {
247 let mut h = EslHeaders::new();
248 h.insert("Unique-ID", "abc-123");
249 assert_eq!(h.header_str("Unique-ID"), Some("abc-123"));
250 }
251
252 #[test]
253 fn variable_str_prepends_variable_prefix() {
254 let mut h = EslHeaders::new();
255 h.insert("variable_sip_call_id", "call-1");
256 assert_eq!(h.variable_str("sip_call_id"), Some("call-1"));
257 assert_eq!(h.variable_str("missing"), None);
258 }
259
260 #[test]
261 fn call_info_single_value_rfc() {
262 let mut h = EslHeaders::new();
263 h.insert(
264 "Call-Info",
265 "<sip:alice@example.com>;purpose=emergency-CallId",
266 );
267 let ci = h
268 .call_info()
269 .unwrap()
270 .expect("present");
271 assert_eq!(
272 ci.entries()
273 .len(),
274 1
275 );
276 assert_eq!(ci.entries()[0].purpose(), Some("emergency-CallId"));
277 }
278
279 #[test]
280 fn call_info_array_encoding() {
281 let mut h = EslHeaders::new();
282 h.insert(
283 "Call-Info",
284 "ARRAY::<sip:a@example.com>;purpose=icon|:<sip:b@example.com>;purpose=info",
285 );
286 let ci = h
287 .call_info()
288 .unwrap()
289 .expect("present");
290 assert_eq!(
291 ci.entries()
292 .len(),
293 2
294 );
295 assert_eq!(ci.entries()[0].purpose(), Some("icon"));
296 assert_eq!(ci.entries()[1].purpose(), Some("info"));
297 }
298
299 #[test]
300 fn call_info_bracket_wrapped() {
301 let mut h = EslHeaders::new();
302 h.insert(
303 "Call-Info",
304 "[<sip:alice@example.com>;purpose=emergency-CallId]",
305 );
306 let ci = h
307 .call_info()
308 .unwrap()
309 .expect("present");
310 assert_eq!(
311 ci.entries()
312 .len(),
313 1
314 );
315 }
316
317 #[test]
318 fn call_info_absent_is_ok_none() {
319 let h = EslHeaders::new();
320 assert!(h
321 .call_info()
322 .unwrap()
323 .is_none());
324 }
325
326 #[test]
327 fn history_info_array_encoding() {
328 let mut h = EslHeaders::new();
329 h.insert(
330 "History-Info",
331 "ARRAY::<sip:a@example.com>;index=1|:<sip:b@example.com>;index=1.1",
332 );
333 let hi = h
334 .history_info()
335 .unwrap()
336 .expect("present");
337 assert_eq!(
338 hi.entries()
339 .len(),
340 2
341 );
342 }
343
344 #[test]
345 fn header_lookup_typed_accessors() {
346 let mut h = EslHeaders::new();
347 h.insert(EventHeader::UniqueId.as_str(), "uuid-1");
348 h.insert(EventHeader::ChannelName.as_str(), "sofia/a/b");
349 assert_eq!(h.unique_id(), Some("uuid-1"));
350 assert_eq!(h.channel_name(), Some("sofia/a/b"));
351 }
352
353 #[test]
354 fn parse_uri_info_array_form() {
355 let value = "ARRAY::<urn:emergency:uid:callid:bcf.example.test>;purpose=emergency-CallId\
356 |:<urn:emergency:uid:incidentid:bcf.example.test>;purpose=emergency-IncidentId\
357 |:<https://eido.example.test/v1/bcf.example.test/abc?test-call=true>;purpose=emergency-eido";
358 let info = EslHeaders::parse_uri_info(value).expect("parse ARRAY form");
359 let entries = info.entries();
360 assert_eq!(entries.len(), 3);
361 assert_eq!(entries[0].purpose(), Some("emergency-CallId"));
362 assert_eq!(entries[1].purpose(), Some("emergency-IncidentId"));
363 assert_eq!(entries[2].purpose(), Some("emergency-eido"));
364 }
365
366 #[test]
367 fn parse_uri_info_single_entry() {
368 let value = "<urn:emergency:uid:callid:test>;purpose=emergency-CallId";
369 let info = EslHeaders::parse_uri_info(value).expect("parse single entry");
370 assert_eq!(
371 info.entries()
372 .len(),
373 1
374 );
375 assert_eq!(info.entries()[0].purpose(), Some("emergency-CallId"));
376 }
377
378 #[test]
379 fn parse_uri_info_empty_value() {
380 let result = EslHeaders::parse_uri_info("");
382 assert!(result.is_err());
383 }
384
385 #[test]
386 fn parse_uri_info_malformed_no_panic() {
387 let info = EslHeaders::parse_uri_info("sip:bare@example.test").expect("lenient parse");
389 assert_eq!(
390 info.entries()
391 .len(),
392 1
393 );
394 }
395
396 #[test]
397 fn parse_uri_info_bracket_wrapped() {
398 let value = "[<urn:emergency:uid:callid:test>;purpose=emergency-CallId]";
399 let info = EslHeaders::parse_uri_info(value).expect("parse bracket-wrapped");
400 assert_eq!(
401 info.entries()
402 .len(),
403 1
404 );
405 assert_eq!(info.entries()[0].purpose(), Some("emergency-CallId"));
406 }
407
408 #[test]
409 fn parse_history_info_array_form() {
410 let value = "ARRAY::<sip:a@example.com>;index=1|:<sip:b@example.com>;index=1.1";
411 let info = EslHeaders::parse_history_info(value).expect("parse ARRAY form");
412 assert_eq!(
413 info.entries()
414 .len(),
415 2
416 );
417 }
418}