1mod event_type;
4mod format;
5mod subscription;
6
7pub use event_type::{EslEventType, ParseEventTypeError};
8pub use format::{EventFormat, ParseEventFormatError};
9pub use subscription::{EventSubscription, EventSubscriptionError};
10
11use crate::headers::{case_alias_key, normalize_header_key, EventHeader};
12use crate::lookup::HeaderLookup;
13use crate::lossy_values::LossyValues;
14use crate::variables::{EslArray, EslArrayError};
15use indexmap::IndexMap;
16use percent_encoding::{percent_encode, NON_ALPHANUMERIC};
17use std::fmt;
18
19wire_enum! {
20 #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
22 pub enum EslEventPriority {
23 Normal => "NORMAL",
25 Low => "LOW",
27 High => "HIGH",
29 }
30 error ParsePriorityError("priority");
31 tests: esl_event_priority_wire_tests;
32}
33
34#[derive(Debug, Clone, Eq)]
36#[cfg_attr(feature = "serde", derive(serde::Serialize))]
37pub struct EslEvent {
38 headers: IndexMap<String, String>,
39 #[cfg_attr(feature = "serde", serde(skip))]
49 original_keys: IndexMap<String, String>,
50 body: Option<String>,
51 #[cfg_attr(
54 feature = "serde",
55 serde(default, skip_serializing_if = "Option::is_none")
56 )]
57 raw_body: Option<Vec<u8>>,
58 #[cfg_attr(
59 feature = "serde",
60 serde(default, skip_serializing_if = "LossyValues::is_empty")
61 )]
62 lossy_values: LossyValues,
63}
64
65impl EslEvent {
66 pub fn new() -> Self {
68 Self {
69 headers: IndexMap::new(),
70 original_keys: IndexMap::new(),
71 body: None,
72 raw_body: None,
73 lossy_values: LossyValues::default(),
74 }
75 }
76
77 pub fn with_type(event_type: EslEventType) -> Self {
82 let mut event = Self::new();
83 event.set_header(EventHeader::EventName.as_str(), event_type.as_str());
84 event
85 }
86
87 pub fn event_type(&self) -> Option<EslEventType> {
94 self.header(EventHeader::EventName)
95 .and_then(EslEventType::parse_event_type)
96 }
97
98 pub fn header(&self, name: EventHeader) -> Option<&str> {
102 self.headers
103 .get(name.as_str())
104 .map(|s| s.as_str())
105 }
106
107 pub fn header_str(&self, name: &str) -> Option<&str> {
115 self.headers
116 .get(name)
117 .or_else(|| {
118 self.original_keys
119 .get(name)
120 .and_then(|normalized| {
121 self.headers
122 .get(normalized)
123 })
124 })
125 .map(|s| s.as_str())
126 }
127
128 pub fn variable_str(&self, name: &str) -> Option<&str> {
133 let key = format!("variable_{}", name);
134 self.header_str(&key)
135 }
136
137 pub fn headers(&self) -> &IndexMap<String, String> {
139 &self.headers
140 }
141
142 pub fn set_header(&mut self, name: impl Into<String>, value: impl Into<String>) {
144 let original = name.into();
145 let normalized = normalize_header_key(&original);
146 if case_alias_key(&original).is_some() && original != normalized {
147 self.original_keys
148 .insert(original, normalized.clone());
149 }
150 self.headers
151 .insert(normalized, value.into());
152 }
153
154 pub fn remove_header(&mut self, name: impl AsRef<str>) -> Option<String> {
158 let name = name.as_ref();
159 if let Some(value) = self
160 .headers
161 .shift_remove(name)
162 {
163 return Some(value);
164 }
165 if let Some(normalized) = self
166 .original_keys
167 .shift_remove(name)
168 {
169 return self
170 .headers
171 .shift_remove(&normalized);
172 }
173 None
174 }
175
176 pub fn body(&self) -> Option<&str> {
178 self.body
179 .as_deref()
180 }
181
182 pub fn set_body(&mut self, body: impl Into<String>) {
184 self.body = Some(body.into());
185 }
186
187 pub fn raw_body(&self) -> Option<&[u8]> {
196 self.raw_body
197 .as_deref()
198 }
199
200 #[doc(hidden)]
204 pub fn set_raw_body(&mut self, bytes: Vec<u8>) {
205 self.raw_body = Some(bytes);
206 }
207
208 pub fn lossy_values(&self) -> &LossyValues {
216 &self.lossy_values
217 }
218
219 #[doc(hidden)]
223 pub fn set_lossy_values(&mut self, v: LossyValues) {
224 self.lossy_values = v;
225 }
226
227 pub fn set_priority(&mut self, priority: EslEventPriority) {
232 self.set_header(EventHeader::Priority.as_str(), priority.to_string());
233 }
234
235 pub fn push_header(&mut self, name: &str, value: &str) -> Result<(), EslArrayError> {
252 self.stack_header(name, value, EslArray::push)
253 }
254
255 pub fn unshift_header(&mut self, name: &str, value: &str) -> Result<(), EslArrayError> {
268 self.stack_header(name, value, EslArray::unshift)
269 }
270
271 fn stack_header(
272 &mut self,
273 name: &str,
274 value: &str,
275 op: fn(&mut EslArray, String),
276 ) -> Result<(), EslArrayError> {
277 match self
278 .headers
279 .get(name)
280 {
281 None => {
282 self.set_header(name, value);
283 }
284 Some(existing) => {
285 let arr = match EslArray::parse(existing) {
286 Ok(arr) => arr,
287 Err(EslArrayError::MissingPrefix) => EslArray::new(vec![existing.clone()]),
288 Err(e) => return Err(e),
289 };
290 if arr.len() >= crate::variables::MAX_ARRAY_ITEMS {
291 return Err(EslArrayError::TooManyItems {
292 count: arr.len(),
293 max: crate::variables::MAX_ARRAY_ITEMS,
294 });
295 }
296 let mut arr = arr;
297 op(&mut arr, value.into());
298 self.set_header(name, arr.to_string());
299 }
300 }
301 Ok(())
302 }
303
304 pub fn is_event_type(&self, event_type: EslEventType) -> bool {
306 self.event_type() == Some(event_type)
307 }
308
309 pub fn to_plain_format(&self) -> String {
319 use fmt::Write;
320 let mut result = String::new();
321
322 for (key, value) in &self.headers {
323 if key == "Content-Length" {
324 continue;
325 }
326 writeln!(
327 result,
328 "{}: {}",
329 key,
330 percent_encode(value.as_bytes(), NON_ALPHANUMERIC)
331 )
332 .expect("writing to String is infallible");
333 }
334
335 if let Some(body) = &self.body {
336 writeln!(result, "Content-Length: {}", body.len())
337 .expect("writing to String is infallible");
338 result.push('\n');
339 result.push_str(body);
340 } else {
341 result.push('\n');
342 }
343
344 result
345 }
346}
347
348impl Default for EslEvent {
349 fn default() -> Self {
350 Self::new()
351 }
352}
353
354impl HeaderLookup for EslEvent {
355 fn header_str(&self, name: &str) -> Option<&str> {
356 EslEvent::header_str(self, name)
357 }
358
359 fn variable_str(&self, name: &str) -> Option<&str> {
360 let key = format!("variable_{}", name);
361 self.header_str(&key)
362 }
363}
364
365impl sip_header::SipHeaderLookup for EslEvent {
366 fn sip_header_str(&self, name: &str) -> Option<&str> {
367 EslEvent::header_str(self, name)
368 }
369
370 fn call_info(&self) -> Result<Option<sip_header::UriInfo>, sip_header::UriInfoError> {
371 match self.sip_header(sip_header::SipHeader::CallInfo) {
372 Some(s) => crate::variables::EslHeaders::parse_uri_info(s).map(Some),
373 None => Ok(None),
374 }
375 }
376
377 fn history_info(
378 &self,
379 ) -> Result<Option<sip_header::HistoryInfo>, sip_header::HistoryInfoError> {
380 match self.sip_header(sip_header::SipHeader::HistoryInfo) {
381 Some(s) => crate::variables::EslHeaders::parse_history_info(s).map(Some),
382 None => Ok(None),
383 }
384 }
385
386 fn alert_info(&self) -> Result<Option<sip_header::UriInfo>, sip_header::UriInfoError> {
387 match self.sip_header(sip_header::SipHeader::AlertInfo) {
388 Some(s) => crate::variables::EslHeaders::parse_uri_info(s).map(Some),
389 None => Ok(None),
390 }
391 }
392}
393
394impl PartialEq for EslEvent {
395 fn eq(&self, other: &Self) -> bool {
396 self.headers == other.headers && self.body == other.body
397 }
398}
399
400impl std::hash::Hash for EslEvent {
401 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
402 for (k, v) in &self.headers {
403 k.hash(state);
404 v.hash(state);
405 }
406 self.body
407 .hash(state);
408 }
409}
410
411#[cfg(feature = "serde")]
412impl<'de> serde::Deserialize<'de> for EslEvent {
413 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
414 where
415 D: serde::Deserializer<'de>,
416 {
417 #[derive(serde::Deserialize)]
421 struct Raw {
422 #[serde(default)]
423 #[allow(dead_code)]
424 event_type: Option<EslEventType>,
425 headers: IndexMap<String, String>,
426 body: Option<String>,
427 #[serde(default)]
428 raw_body: Option<Vec<u8>>,
429 #[serde(default)]
430 lossy_values: LossyValues,
431 }
432 let raw = Raw::deserialize(deserializer)?;
433 let mut event = EslEvent::new();
434 event.body = raw.body;
435 event.raw_body = raw.raw_body;
436 event.lossy_values = raw.lossy_values;
437 for (k, v) in raw.headers {
438 event.set_header(k, v);
439 }
440 Ok(event)
441 }
442}
443
444#[cfg(test)]
445mod tests {
446 use super::*;
447
448 #[test]
449 fn headers_preserve_insertion_order() {
450 let mut event = EslEvent::new();
451 event.set_header("Zebra", "last");
452 event.set_header("Alpha", "first");
453 event.set_header("Middle", "mid");
454 let keys: Vec<&str> = event
455 .headers()
456 .keys()
457 .map(|s| s.as_str())
458 .collect();
459 assert_eq!(keys, vec!["Zebra", "Alpha", "Middle"]);
460 }
461
462 #[test]
463 fn test_remove_header() {
464 let mut event = EslEvent::new();
465 event.set_header("Foo", "bar");
466 event.set_header("Baz", "qux");
467
468 let removed = event.remove_header("Foo");
469 assert_eq!(removed, Some("bar".to_string()));
470 assert!(event
471 .header_str("Foo")
472 .is_none());
473 assert_eq!(event.header_str("Baz"), Some("qux"));
474
475 let removed_again = event.remove_header("Foo");
476 assert_eq!(removed_again, None);
477 }
478
479 #[test]
480 fn test_to_plain_format_basic() {
481 let mut event = EslEvent::with_type(EslEventType::Heartbeat);
482 event.set_header("Event-Name", "HEARTBEAT");
483 event.set_header("Core-UUID", "abc-123");
484
485 let plain = event.to_plain_format();
486
487 assert!(plain.starts_with("Event-Name: "));
488 assert!(plain.contains("Core-UUID: "));
489 assert!(plain.ends_with("\n\n"));
490 }
491
492 #[test]
493 fn test_to_plain_format_percent_encoding() {
494 let mut event = EslEvent::with_type(EslEventType::Heartbeat);
495 event.set_header("Event-Name", "HEARTBEAT");
496 event.set_header("Up-Time", "0 years, 0 days");
497
498 let plain = event.to_plain_format();
499
500 assert!(!plain.contains("0 years, 0 days"));
501 assert!(plain.contains("Up-Time: "));
502 assert!(plain.contains("%20"));
503 }
504
505 #[test]
506 fn test_to_plain_format_with_body() {
507 let mut event = EslEvent::with_type(EslEventType::BackgroundJob);
508 event.set_header("Event-Name", "BACKGROUND_JOB");
509 event.set_header("Job-UUID", "def-456");
510 event.set_body("+OK result\n".to_string());
511
512 let plain = event.to_plain_format();
513
514 assert!(plain.contains("Content-Length: 11\n"));
515 assert!(plain.ends_with("\n\n+OK result\n"));
516 }
517
518 #[test]
519 fn test_to_plain_format_preserves_insertion_order() {
520 let mut event = EslEvent::with_type(EslEventType::Heartbeat);
521 event.set_header("Event-Name", "HEARTBEAT");
522 event.set_header("Core-UUID", "abc-123");
523 event.set_header("FreeSWITCH-Hostname", "fs01");
524 event.set_header("Up-Time", "0 years, 1 day");
525
526 let plain = event.to_plain_format();
527 let lines: Vec<&str> = plain
528 .lines()
529 .collect();
530 assert!(lines[0].starts_with("Event-Name: "));
531 assert!(lines[1].starts_with("Core-UUID: "));
532 assert!(lines[2].starts_with("FreeSWITCH-Hostname: "));
533 assert!(lines[3].starts_with("Up-Time: "));
534 }
535
536 #[test]
537 fn test_to_plain_format_round_trip() {
538 let mut original = EslEvent::with_type(EslEventType::ChannelCreate);
539 original.set_header("Event-Name", "CHANNEL_CREATE");
540 original.set_header("Core-UUID", "abc-123");
541 original.set_header("Channel-Name", "sofia/internal/1000@example.com");
542 original.set_header("Caller-Caller-ID-Name", "Jérôme Poulin");
543 original.set_body("some body content");
544
545 let plain = original.to_plain_format();
546
547 let (header_section, inner_body) = if let Some(pos) = plain.find("\n\n") {
549 (&plain[..pos], Some(&plain[pos + 2..]))
550 } else {
551 (plain.as_str(), None)
552 };
553
554 let mut parsed = EslEvent::new();
555 for line in header_section.lines() {
556 let line = line.trim();
557 if line.is_empty() {
558 continue;
559 }
560 if let Some(colon_pos) = line.find(':') {
561 let key = line[..colon_pos].trim();
562 if key == "Content-Length" {
563 continue;
564 }
565 let raw_value = line[colon_pos + 1..].trim();
566 let value = percent_encoding::percent_decode_str(raw_value)
567 .decode_utf8()
568 .unwrap()
569 .into_owned();
570 parsed.set_header(key, value);
571 }
572 }
573 if let Some(ib) = inner_body {
574 if !ib.is_empty() {
575 parsed.set_body(ib);
576 }
577 }
578
579 assert_eq!(original.headers(), parsed.headers());
580 assert_eq!(original.body(), parsed.body());
581 }
582
583 #[test]
584 fn test_set_priority_normal() {
585 let mut event = EslEvent::new();
586 event.set_priority(EslEventPriority::Normal);
587 assert_eq!(
588 event
589 .priority()
590 .unwrap(),
591 Some(EslEventPriority::Normal)
592 );
593 assert_eq!(event.header(EventHeader::Priority), Some("NORMAL"));
594 }
595
596 #[test]
597 fn test_set_priority_high() {
598 let mut event = EslEvent::new();
599 event.set_priority(EslEventPriority::High);
600 assert_eq!(
601 event
602 .priority()
603 .unwrap(),
604 Some(EslEventPriority::High)
605 );
606 assert_eq!(event.header(EventHeader::Priority), Some("HIGH"));
607 }
608
609 #[test]
610 fn test_priority_display() {
611 assert_eq!(EslEventPriority::Normal.to_string(), "NORMAL");
612 assert_eq!(EslEventPriority::Low.to_string(), "LOW");
613 assert_eq!(EslEventPriority::High.to_string(), "HIGH");
614 }
615
616 #[test]
617 fn test_priority_from_str() {
618 assert_eq!(
619 "NORMAL".parse::<EslEventPriority>(),
620 Ok(EslEventPriority::Normal)
621 );
622 assert_eq!("LOW".parse::<EslEventPriority>(), Ok(EslEventPriority::Low));
623 assert_eq!(
624 "HIGH".parse::<EslEventPriority>(),
625 Ok(EslEventPriority::High)
626 );
627 assert!("INVALID"
628 .parse::<EslEventPriority>()
629 .is_err());
630 }
631
632 #[test]
633 fn test_priority_from_str_rejects_wrong_case() {
634 assert!("normal"
635 .parse::<EslEventPriority>()
636 .is_err());
637 assert!("Low"
638 .parse::<EslEventPriority>()
639 .is_err());
640 assert!("hIgH"
641 .parse::<EslEventPriority>()
642 .is_err());
643 }
644
645 #[test]
646 fn test_push_header_new() {
647 let mut event = EslEvent::new();
648 event
649 .push_header("X-Test", "first")
650 .unwrap();
651 assert_eq!(event.header_str("X-Test"), Some("first"));
652 }
653
654 #[test]
655 fn test_push_header_existing_plain() {
656 let mut event = EslEvent::new();
657 event.set_header("X-Test", "first");
658 event
659 .push_header("X-Test", "second")
660 .unwrap();
661 assert_eq!(event.header_str("X-Test"), Some("ARRAY::first|:second"));
662 }
663
664 #[test]
665 fn test_push_header_existing_array() {
666 let mut event = EslEvent::new();
667 event.set_header("X-Test", "ARRAY::a|:b");
668 event
669 .push_header("X-Test", "c")
670 .unwrap();
671 assert_eq!(event.header_str("X-Test"), Some("ARRAY::a|:b|:c"));
672 }
673
674 #[test]
675 fn test_push_header_at_capacity() {
676 use crate::variables::MAX_ARRAY_ITEMS;
677 let mut event = EslEvent::new();
678 let items: Vec<&str> = (0..MAX_ARRAY_ITEMS)
679 .map(|_| "x")
680 .collect();
681 event.set_header("X-Test", format!("ARRAY::{}", items.join("|:")).as_str());
682 assert!(matches!(
683 event.push_header("X-Test", "overflow"),
684 Err(EslArrayError::TooManyItems { .. })
685 ));
686 }
687
688 #[test]
689 fn test_unshift_header_new() {
690 let mut event = EslEvent::new();
691 event
692 .unshift_header("X-Test", "only")
693 .unwrap();
694 assert_eq!(event.header_str("X-Test"), Some("only"));
695 }
696
697 #[test]
698 fn test_unshift_header_existing_array() {
699 let mut event = EslEvent::new();
700 event.set_header("X-Test", "ARRAY::b|:c");
701 event
702 .unshift_header("X-Test", "a")
703 .unwrap();
704 assert_eq!(event.header_str("X-Test"), Some("ARRAY::a|:b|:c"));
705 }
706
707 #[test]
708 fn test_sendevent_with_priority_wire_format() {
709 let mut event = EslEvent::with_type(EslEventType::Custom);
710 event.set_header("Event-Name", "CUSTOM");
711 event.set_header("Event-Subclass", "test::priority");
712 event.set_priority(EslEventPriority::High);
713
714 let plain = event.to_plain_format();
715 assert!(plain.contains("priority: HIGH\n"));
716 }
717
718 #[test]
719 fn test_convenience_accessors() {
720 let mut event = EslEvent::new();
721 event.set_header("Channel-Name", "sofia/internal/1000@example.com");
722 event.set_header("Caller-Caller-ID-Number", "1000");
723 event.set_header("Caller-Caller-ID-Name", "Alice");
724 event.set_header("Hangup-Cause", "NORMAL_CLEARING");
725 event.set_header("Event-Subclass", "sofia::register");
726 event.set_header("variable_sip_from_display", "Bob");
727
728 assert_eq!(
729 event.channel_name(),
730 Some("sofia/internal/1000@example.com")
731 );
732 assert_eq!(event.caller_id_number(), Some("1000"));
733 assert_eq!(event.caller_id_name(), Some("Alice"));
734 assert_eq!(
735 event
736 .hangup_cause()
737 .unwrap(),
738 Some(crate::channel::HangupCause::NormalClearing)
739 );
740 assert_eq!(event.event_subclass(), Some("sofia::register"));
741 assert_eq!(event.variable_str("sip_from_display"), Some("Bob"));
742 assert_eq!(event.variable_str("nonexistent"), None);
743 }
744
745 #[test]
748 fn test_event_channel_state_accessor() {
749 use crate::channel::ChannelState;
750 let mut event = EslEvent::new();
751 event.set_header("Channel-State", "CS_EXECUTE");
752 assert_eq!(
753 event
754 .channel_state()
755 .unwrap(),
756 Some(ChannelState::CsExecute)
757 );
758 }
759
760 #[test]
761 fn test_event_channel_state_number_accessor() {
762 use crate::channel::ChannelState;
763 let mut event = EslEvent::new();
764 event.set_header("Channel-State-Number", "4");
765 assert_eq!(
766 event
767 .channel_state_number()
768 .unwrap(),
769 Some(ChannelState::CsExecute)
770 );
771 }
772
773 #[test]
774 fn test_event_call_state_accessor() {
775 use crate::channel::CallState;
776 let mut event = EslEvent::new();
777 event.set_header("Channel-Call-State", "ACTIVE");
778 assert_eq!(
779 event
780 .call_state()
781 .unwrap(),
782 Some(CallState::Active)
783 );
784 }
785
786 #[test]
787 fn test_event_answer_state_accessor() {
788 use crate::channel::AnswerState;
789 let mut event = EslEvent::new();
790 event.set_header("Answer-State", "answered");
791 assert_eq!(
792 event
793 .answer_state()
794 .unwrap(),
795 Some(AnswerState::Answered)
796 );
797 }
798
799 #[test]
800 fn test_event_call_direction_accessor() {
801 use crate::channel::CallDirection;
802 let mut event = EslEvent::new();
803 event.set_header("Call-Direction", "inbound");
804 assert_eq!(
805 event
806 .call_direction()
807 .unwrap(),
808 Some(CallDirection::Inbound)
809 );
810 }
811
812 #[test]
813 fn test_event_typed_accessors_missing_headers() {
814 let event = EslEvent::new();
815 assert_eq!(
816 event
817 .channel_state()
818 .unwrap(),
819 None
820 );
821 assert_eq!(
822 event
823 .channel_state_number()
824 .unwrap(),
825 None
826 );
827 assert_eq!(
828 event
829 .call_state()
830 .unwrap(),
831 None
832 );
833 assert_eq!(
834 event
835 .answer_state()
836 .unwrap(),
837 None
838 );
839 assert_eq!(
840 event
841 .call_direction()
842 .unwrap(),
843 None
844 );
845 }
846
847 #[test]
850 fn test_sip_p_asserted_identity_comma_separated() {
851 let mut event = EslEvent::new();
852 event.set_header(
855 "variable_sip_P-Asserted-Identity",
856 "<sip:alice@atlanta.example.com>, <tel:+15551234567>",
857 );
858
859 assert_eq!(
860 event.variable_str("sip_P-Asserted-Identity"),
861 Some("<sip:alice@atlanta.example.com>, <tel:+15551234567>")
862 );
863 }
864
865 #[test]
866 fn test_sip_p_asserted_identity_array_format() {
867 let mut event = EslEvent::new();
868 event
870 .push_header(
871 "variable_sip_P-Asserted-Identity",
872 "<sip:alice@atlanta.example.com>",
873 )
874 .unwrap();
875 event
876 .push_header("variable_sip_P-Asserted-Identity", "<tel:+15551234567>")
877 .unwrap();
878
879 let raw = event
880 .header_str("variable_sip_P-Asserted-Identity")
881 .unwrap();
882 assert_eq!(
883 raw,
884 "ARRAY::<sip:alice@atlanta.example.com>|:<tel:+15551234567>"
885 );
886
887 let arr = crate::variables::EslArray::parse(raw).unwrap();
888 assert_eq!(arr.len(), 2);
889 assert_eq!(arr.items()[0], "<sip:alice@atlanta.example.com>");
890 assert_eq!(arr.items()[1], "<tel:+15551234567>");
891 }
892
893 #[test]
894 fn test_sip_header_with_colons_in_uri() {
895 let mut event = EslEvent::new();
896 event
898 .push_header(
899 "variable_sip_h_Diversion",
900 "<sip:+15551234567@gw.example.com;reason=unconditional>",
901 )
902 .unwrap();
903 event
904 .push_header(
905 "variable_sip_h_Diversion",
906 "<sips:+15559876543@secure.example.com;reason=no-answer;counter=3>",
907 )
908 .unwrap();
909
910 let raw = event
911 .header_str("variable_sip_h_Diversion")
912 .unwrap();
913 let arr = crate::variables::EslArray::parse(raw).unwrap();
914 assert_eq!(arr.len(), 2);
915 assert_eq!(
916 arr.items()[0],
917 "<sip:+15551234567@gw.example.com;reason=unconditional>"
918 );
919 assert_eq!(
920 arr.items()[1],
921 "<sips:+15559876543@secure.example.com;reason=no-answer;counter=3>"
922 );
923 }
924
925 #[test]
926 fn test_sip_p_asserted_identity_plain_format_round_trip() {
927 let mut event = EslEvent::with_type(EslEventType::ChannelCreate);
928 event.set_header("Event-Name", "CHANNEL_CREATE");
929 event.set_header(
930 "variable_sip_P-Asserted-Identity",
931 "<sip:alice@atlanta.example.com>, <tel:+15551234567>",
932 );
933
934 let plain = event.to_plain_format();
935 assert!(plain.contains("variable_sip_P-Asserted-Identity:"));
937 assert!(!plain.contains("<sip:alice"));
939 }
940
941 #[test]
946 fn set_header_normalizes_known_enum_variant() {
947 let mut event = EslEvent::new();
948 event.set_header("unique-id", "abc-123");
949 assert_eq!(event.header(EventHeader::UniqueId), Some("abc-123"));
950 }
951
952 #[test]
953 fn set_header_normalizes_codec_header() {
954 let mut event = EslEvent::new();
955 event.set_header("channel-read-codec-bit-rate", "128000");
956 assert_eq!(
957 event.header(EventHeader::ChannelReadCodecBitRate),
958 Some("128000")
959 );
960 }
961
962 #[test]
963 fn header_str_finds_by_original_key() {
964 let mut event = EslEvent::new();
965 event.set_header("unique-id", "abc-123");
966 assert_eq!(event.header_str("unique-id"), Some("abc-123"));
968 assert_eq!(event.header_str("Unique-ID"), Some("abc-123"));
970 }
971
972 #[test]
973 fn header_str_finds_unknown_dash_header_by_original() {
974 let mut event = EslEvent::new();
975 event.set_header("x-custom-header", "val");
976 assert_eq!(event.header_str("X-Custom-Header"), Some("val"));
978 assert_eq!(event.header_str("x-custom-header"), Some("val"));
980 }
981
982 #[test]
983 fn set_header_underscore_passthrough_preserves_sip_h() {
984 let mut event = EslEvent::new();
985 event.set_header("variable_sip_h_X-My-CUSTOM-Header", "val");
986 assert_eq!(
987 event.header_str("variable_sip_h_X-My-CUSTOM-Header"),
988 Some("val")
989 );
990 }
991
992 #[test]
993 fn set_header_different_casing_overwrites() {
994 let mut event = EslEvent::new();
995 event.set_header("Unique-ID", "first");
996 event.set_header("unique-id", "second");
997 assert_eq!(event.header(EventHeader::UniqueId), Some("second"));
999 }
1000
1001 #[test]
1002 fn remove_header_by_original_key() {
1003 let mut event = EslEvent::new();
1004 event.set_header("unique-id", "abc-123");
1005 let removed = event.remove_header("unique-id");
1006 assert_eq!(removed, Some("abc-123".to_string()));
1007 assert_eq!(event.header(EventHeader::UniqueId), None);
1008 }
1009
1010 #[test]
1011 fn remove_header_by_canonical_key() {
1012 let mut event = EslEvent::new();
1013 event.set_header("unique-id", "abc-123");
1014 let removed = event.remove_header("Unique-ID");
1015 assert_eq!(removed, Some("abc-123".to_string()));
1016 assert_eq!(event.header_str("unique-id"), None);
1017 }
1018
1019 #[test]
1020 fn serde_round_trip_preserves_canonical_lookups() {
1021 let mut event = EslEvent::new();
1022 event.set_header("unique-id", "abc-123");
1023 event.set_header("channel-read-codec-bit-rate", "128000");
1024 let json = serde_json::to_string(&event).unwrap();
1025 let deserialized: EslEvent = serde_json::from_str(&json).unwrap();
1026 assert_eq!(deserialized.header(EventHeader::UniqueId), Some("abc-123"));
1027 assert_eq!(
1028 deserialized.header(EventHeader::ChannelReadCodecBitRate),
1029 Some("128000")
1030 );
1031 }
1032
1033 #[test]
1034 fn serde_deserialize_normalizes_external_json() {
1035 let json = r#"{"event_type":null,"headers":{"unique-id":"abc-123","channel-read-codec-bit-rate":"128000"},"body":null}"#;
1036 let event: EslEvent = serde_json::from_str(json).unwrap();
1037 assert_eq!(event.header(EventHeader::UniqueId), Some("abc-123"));
1038 assert_eq!(
1039 event.header(EventHeader::ChannelReadCodecBitRate),
1040 Some("128000")
1041 );
1042 assert_eq!(event.header_str("unique-id"), Some("abc-123"));
1043 }
1044
1045 #[test]
1046 fn original_keys_rebuilt_after_serde_roundtrip() {
1047 let external_json = r#"{
1065 "event_type": null,
1066 "headers": {
1067 "Channel-Write-Codec-Name": "opus",
1068 "channel-write-codec-bit-rate": "64000",
1069 "Custom-X-Header": "preserved"
1070 },
1071 "body": null
1072 }"#;
1073 let parsed: EslEvent = serde_json::from_str(external_json).unwrap();
1074
1075 assert_eq!(
1078 parsed.header(EventHeader::ChannelWriteCodecName),
1079 Some("opus")
1080 );
1081 assert_eq!(
1082 parsed.header(EventHeader::ChannelWriteCodecBitRate),
1083 Some("64000")
1084 );
1085
1086 assert_eq!(
1089 parsed.header_str("channel-write-codec-bit-rate"),
1090 Some("64000")
1091 );
1092 assert_eq!(
1094 parsed.header_str("Channel-Write-Codec-Bit-Rate"),
1095 Some("64000")
1096 );
1097
1098 assert_eq!(parsed.header_str("Custom-X-Header"), Some("preserved"));
1101
1102 let json = serde_json::to_string(&parsed).unwrap();
1105 let re_parsed: EslEvent = serde_json::from_str(&json).unwrap();
1106 assert_eq!(
1107 re_parsed.header(EventHeader::ChannelWriteCodecBitRate),
1108 Some("64000")
1109 );
1110 }
1111
1112 #[test]
1113 fn test_event_typed_accessors_invalid_values() {
1114 let mut event = EslEvent::new();
1115 event.set_header("Channel-State", "BOGUS");
1116 event.set_header("Channel-State-Number", "999");
1117 event.set_header("Channel-Call-State", "BOGUS");
1118 event.set_header("Answer-State", "bogus");
1119 event.set_header("Call-Direction", "bogus");
1120 assert!(event
1121 .channel_state()
1122 .is_err());
1123 assert!(event
1124 .channel_state_number()
1125 .is_err());
1126 assert!(event
1127 .call_state()
1128 .is_err());
1129 assert!(event
1130 .answer_state()
1131 .is_err());
1132 assert!(event
1133 .call_direction()
1134 .is_err());
1135 }
1136
1137 #[test]
1140 fn esl_event_call_info_array_encoding() {
1141 use sip_header::SipHeaderLookup;
1142
1143 let mut event = EslEvent::new();
1144 event.set_header(
1145 "Call-Info".to_string(),
1146 "ARRAY::<urn:emergency:uid:callid:abc>;purpose=emergency-CallId\
1147 |:<urn:emergency:uid:incidentid:def>;purpose=emergency-IncidentId"
1148 .to_string(),
1149 );
1150 let ci = event
1151 .call_info()
1152 .expect("should parse")
1153 .expect("should be present");
1154 assert_eq!(
1155 ci.entries()
1156 .len(),
1157 2,
1158 "ARRAY:: entries should expand"
1159 );
1160 }
1161
1162 #[test]
1163 fn esl_event_call_info_plain_value_unchanged() {
1164 use sip_header::SipHeaderLookup;
1165
1166 let mut event = EslEvent::new();
1167 event.set_header(
1168 "Call-Info".to_string(),
1169 "<sip:pbx.example.com>;purpose=icon".to_string(),
1170 );
1171 let ci = event
1172 .call_info()
1173 .expect("plain value should parse")
1174 .expect("should be present");
1175 assert_eq!(
1176 ci.entries()
1177 .len(),
1178 1
1179 );
1180 }
1181
1182 #[test]
1183 fn esl_event_history_info_array_encoding() {
1184 use sip_header::SipHeaderLookup;
1185
1186 let mut event = EslEvent::new();
1187 event.set_header(
1188 "History-Info".to_string(),
1189 "ARRAY::<sip:user@pbx.example.com>;index=1\
1190 |:<sip:forward@pbx.example.com?Reason=unconditional>;index=1.1"
1191 .to_string(),
1192 );
1193 let hi = event
1194 .history_info()
1195 .expect("should parse")
1196 .expect("should be present");
1197 assert_eq!(
1198 hi.entries()
1199 .len(),
1200 2,
1201 "ARRAY:: entries should expand"
1202 );
1203 }
1204}