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::Malformed(other.to_string())),
164 }
165 }
166
167 pub fn parse_history_info(value: &str) -> Result<HistoryInfo, HistoryInfoError> {
177 let value = strip_brackets(value);
178 match EslArray::parse(value) {
179 Ok(array) => HistoryInfo::from_entries(
180 array
181 .items()
182 .iter()
183 .map(String::as_str),
184 ),
185 Err(EslArrayError::MissingPrefix) => HistoryInfo::parse(value),
186 Err(other) => Err(HistoryInfoError::Malformed(other.to_string())),
187 }
188 }
189}
190
191impl SipHeaderLookup for EslHeaders {
192 fn sip_header_str(&self, name: &str) -> Option<&str> {
193 self.0
194 .get(name)
195 .map(|s| s.as_str())
196 }
197
198 fn call_info(&self) -> Result<Option<UriInfo>, UriInfoError> {
199 match self.sip_header(SipHeader::CallInfo) {
200 Some(s) => Self::parse_uri_info(s).map(Some),
201 None => Ok(None),
202 }
203 }
204
205 fn history_info(&self) -> Result<Option<HistoryInfo>, HistoryInfoError> {
206 match self.sip_header(SipHeader::HistoryInfo) {
207 Some(s) => Self::parse_history_info(s).map(Some),
208 None => Ok(None),
209 }
210 }
211
212 fn alert_info(&self) -> Result<Option<UriInfo>, UriInfoError> {
213 match self.sip_header(SipHeader::AlertInfo) {
214 Some(s) => Self::parse_uri_info(s).map(Some),
215 None => Ok(None),
216 }
217 }
218}
219
220impl HeaderLookup for EslHeaders {
221 fn header_str(&self, name: &str) -> Option<&str> {
222 self.0
223 .get(name)
224 .map(|s| s.as_str())
225 }
226
227 fn variable_str(&self, name: &str) -> Option<&str> {
228 self.0
229 .get(&format!("variable_{name}"))
230 .map(|s| s.as_str())
231 }
232}
233
234#[cfg(test)]
235mod tests {
236 use super::*;
237 use crate::headers::EventHeader;
238
239 #[test]
240 fn header_str_passthrough() {
241 let mut h = EslHeaders::new();
242 h.insert("Unique-ID", "abc-123");
243 assert_eq!(h.header_str("Unique-ID"), Some("abc-123"));
244 }
245
246 #[test]
247 fn variable_str_prepends_variable_prefix() {
248 let mut h = EslHeaders::new();
249 h.insert("variable_sip_call_id", "call-1");
250 assert_eq!(h.variable_str("sip_call_id"), Some("call-1"));
251 assert_eq!(h.variable_str("missing"), None);
252 }
253
254 #[test]
255 fn call_info_single_value_rfc() {
256 let mut h = EslHeaders::new();
257 h.insert(
258 "Call-Info",
259 "<sip:alice@example.com>;purpose=emergency-CallId",
260 );
261 let ci = h
262 .call_info()
263 .unwrap()
264 .expect("present");
265 assert_eq!(
266 ci.entries()
267 .len(),
268 1
269 );
270 assert_eq!(ci.entries()[0].purpose(), Some("emergency-CallId"));
271 }
272
273 #[test]
274 fn call_info_array_encoding() {
275 let mut h = EslHeaders::new();
276 h.insert(
277 "Call-Info",
278 "ARRAY::<sip:a@example.com>;purpose=icon|:<sip:b@example.com>;purpose=info",
279 );
280 let ci = h
281 .call_info()
282 .unwrap()
283 .expect("present");
284 assert_eq!(
285 ci.entries()
286 .len(),
287 2
288 );
289 assert_eq!(ci.entries()[0].purpose(), Some("icon"));
290 assert_eq!(ci.entries()[1].purpose(), Some("info"));
291 }
292
293 #[test]
294 fn call_info_bracket_wrapped() {
295 let mut h = EslHeaders::new();
296 h.insert(
297 "Call-Info",
298 "[<sip:alice@example.com>;purpose=emergency-CallId]",
299 );
300 let ci = h
301 .call_info()
302 .unwrap()
303 .expect("present");
304 assert_eq!(
305 ci.entries()
306 .len(),
307 1
308 );
309 }
310
311 #[test]
312 fn call_info_absent_is_ok_none() {
313 let h = EslHeaders::new();
314 assert!(h
315 .call_info()
316 .unwrap()
317 .is_none());
318 }
319
320 #[test]
321 fn history_info_array_encoding() {
322 let mut h = EslHeaders::new();
323 h.insert(
324 "History-Info",
325 "ARRAY::<sip:a@example.com>;index=1|:<sip:b@example.com>;index=1.1",
326 );
327 let hi = h
328 .history_info()
329 .unwrap()
330 .expect("present");
331 assert_eq!(
332 hi.entries()
333 .len(),
334 2
335 );
336 }
337
338 #[test]
339 fn header_lookup_typed_accessors() {
340 let mut h = EslHeaders::new();
341 h.insert(EventHeader::UniqueId.as_str(), "uuid-1");
342 h.insert(EventHeader::ChannelName.as_str(), "sofia/a/b");
343 assert_eq!(h.unique_id(), Some("uuid-1"));
344 assert_eq!(h.channel_name(), Some("sofia/a/b"));
345 }
346
347 #[test]
348 fn parse_uri_info_array_form() {
349 let value = "ARRAY::<urn:emergency:uid:callid:bcf.example.test>;purpose=emergency-CallId\
350 |:<urn:emergency:uid:incidentid:bcf.example.test>;purpose=emergency-IncidentId\
351 |:<https://eido.example.test/v1/bcf.example.test/abc?test-call=true>;purpose=emergency-eido";
352 let info = EslHeaders::parse_uri_info(value).expect("parse ARRAY form");
353 let entries = info.entries();
354 assert_eq!(entries.len(), 3);
355 assert_eq!(entries[0].purpose(), Some("emergency-CallId"));
356 assert_eq!(entries[1].purpose(), Some("emergency-IncidentId"));
357 assert_eq!(entries[2].purpose(), Some("emergency-eido"));
358 }
359
360 #[test]
361 fn parse_uri_info_single_entry() {
362 let value = "<urn:emergency:uid:callid:test>;purpose=emergency-CallId";
363 let info = EslHeaders::parse_uri_info(value).expect("parse single entry");
364 assert_eq!(
365 info.entries()
366 .len(),
367 1
368 );
369 assert_eq!(info.entries()[0].purpose(), Some("emergency-CallId"));
370 }
371
372 #[test]
373 fn parse_uri_info_empty_value() {
374 let result = EslHeaders::parse_uri_info("");
376 assert!(result.is_err());
377 }
378
379 #[test]
380 fn parse_uri_info_malformed_no_panic() {
381 let info = EslHeaders::parse_uri_info("sip:bare@example.test").expect("lenient parse");
383 assert_eq!(
384 info.entries()
385 .len(),
386 1
387 );
388 }
389
390 #[test]
391 fn parse_uri_info_bracket_wrapped() {
392 let value = "[<urn:emergency:uid:callid:test>;purpose=emergency-CallId]";
393 let info = EslHeaders::parse_uri_info(value).expect("parse bracket-wrapped");
394 assert_eq!(
395 info.entries()
396 .len(),
397 1
398 );
399 assert_eq!(info.entries()[0].purpose(), Some("emergency-CallId"));
400 }
401
402 #[test]
403 fn parse_uri_info_structural_failure_is_malformed() {
404 let value = format!(
405 "ARRAY::{}",
406 vec!["<sip:a@example.com>"; crate::variables::MAX_ARRAY_ITEMS + 1].join("|:")
407 );
408 let err = EslHeaders::parse_uri_info(&value).expect_err("over-limit array must fail");
409 assert!(matches!(err, UriInfoError::Malformed(_)), "got {err:?}");
410 }
411
412 #[test]
413 fn parse_history_info_structural_failure_is_malformed() {
414 let value = format!(
415 "ARRAY::{}",
416 vec!["<sip:a@example.com>;index=1"; crate::variables::MAX_ARRAY_ITEMS + 1].join("|:")
417 );
418 let err = EslHeaders::parse_history_info(&value).expect_err("over-limit array must fail");
419 assert!(matches!(err, HistoryInfoError::Malformed(_)), "got {err:?}");
420 }
421
422 #[test]
423 fn parse_history_info_array_form() {
424 let value = "ARRAY::<sip:a@example.com>;index=1|:<sip:b@example.com>;index=1.1";
425 let info = EslHeaders::parse_history_info(value).expect("parse ARRAY form");
426 assert_eq!(
427 info.entries()
428 .len(),
429 2
430 );
431 }
432}