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