1mod event_type;
4mod format;
5mod subscription;
6
7pub use event_type::{EslEventType, ParseEventTypeError};
8pub use format::{EventFormat, ParseEventFormatError};
9pub use subscription::{
10 order_event_tokens, swallowed_event_types, EventSubscription, EventSubscriptionError,
11};
12
13use crate::headers::EventHeader;
14use crate::lookup::{variable_key, HeaderLookup};
15use crate::lossy_values::LossyValues;
16use crate::variables::{EslArray, EslArrayError, EslHeaders};
17use indexmap::IndexMap;
18use percent_encoding::{percent_encode, NON_ALPHANUMERIC};
19use std::fmt;
20
21wire_enum! {
22 #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
24 pub enum EslEventPriority {
25 Normal => "NORMAL",
27 Low => "LOW",
29 High => "HIGH",
31 }
32 error ParsePriorityError("priority");
33 tests: esl_event_priority_wire_tests;
34}
35
36#[derive(Debug, Clone, Eq)]
38#[cfg_attr(feature = "serde", derive(serde::Serialize))]
39pub struct EslEvent {
40 headers: EslHeaders,
41 body: Option<String>,
42 #[cfg_attr(
45 feature = "serde",
46 serde(default, skip_serializing_if = "Option::is_none")
47 )]
48 raw_body: Option<Vec<u8>>,
49 #[cfg_attr(
50 feature = "serde",
51 serde(default, skip_serializing_if = "LossyValues::is_empty")
52 )]
53 lossy_values: LossyValues,
54}
55
56impl EslEvent {
57 pub fn new() -> Self {
59 Self {
60 headers: EslHeaders::new(),
61 body: None,
62 raw_body: None,
63 lossy_values: LossyValues::default(),
64 }
65 }
66
67 pub fn with_type(event_type: EslEventType) -> Self {
72 let mut event = Self::new();
73 event.set_header(EventHeader::EventName.as_str(), event_type.as_str());
74 event
75 }
76
77 pub fn event_type(&self) -> Option<EslEventType> {
84 self.header(EventHeader::EventName)
85 .and_then(EslEventType::parse_event_type)
86 }
87
88 pub fn header(&self, name: EventHeader) -> Option<&str> {
92 HeaderLookup::header(self, name)
93 }
94
95 pub fn header_str(&self, name: &str) -> Option<&str> {
103 self.headers
104 .header_str(name)
105 }
106
107 pub fn variable_str(&self, name: &str) -> Option<&str> {
112 self.header_str(&variable_key(name))
113 }
114
115 pub fn headers(&self) -> &IndexMap<String, String> {
117 self.headers
118 .as_map()
119 }
120
121 pub fn set_header(&mut self, name: impl Into<String>, value: impl Into<String>) {
123 self.headers
124 .insert(name, value);
125 }
126
127 pub fn remove_header(&mut self, name: impl AsRef<str>) -> Option<String> {
131 self.headers
132 .remove(name.as_ref())
133 }
134
135 pub fn body(&self) -> Option<&str> {
137 self.body
138 .as_deref()
139 }
140
141 pub fn set_body(&mut self, body: impl Into<String>) {
143 self.body = Some(body.into());
144 }
145
146 pub fn raw_body(&self) -> Option<&[u8]> {
157 self.raw_body
158 .as_deref()
159 }
160
161 #[doc(hidden)]
165 pub fn set_raw_body(&mut self, bytes: Vec<u8>) {
166 self.raw_body = Some(bytes);
167 }
168
169 pub fn lossy_values(&self) -> &LossyValues {
177 &self.lossy_values
178 }
179
180 #[doc(hidden)]
184 pub fn set_lossy_values(&mut self, v: LossyValues) {
185 self.lossy_values = v;
186 }
187
188 pub fn set_priority(&mut self, priority: EslEventPriority) {
193 self.set_header(EventHeader::Priority.as_str(), priority.to_string());
194 }
195
196 pub fn push_header(&mut self, name: &str, value: &str) -> Result<(), EslArrayError> {
213 self.stack_header(name, value, EslArray::push)
214 }
215
216 pub fn unshift_header(&mut self, name: &str, value: &str) -> Result<(), EslArrayError> {
229 self.stack_header(name, value, EslArray::unshift)
230 }
231
232 fn stack_header(
233 &mut self,
234 name: &str,
235 value: &str,
236 op: fn(&mut EslArray, String),
237 ) -> Result<(), EslArrayError> {
238 match self
239 .header_str(name)
240 .map(str::to_string)
241 {
242 None => {
243 self.set_header(name, value);
244 }
245 Some(existing) => {
246 let arr = match EslArray::parse(&existing) {
247 Ok(arr) => arr,
248 Err(EslArrayError::MissingPrefix) => EslArray::new(vec![existing]),
249 Err(e) => return Err(e),
250 };
251 if arr.len() >= crate::variables::MAX_ARRAY_ITEMS {
252 return Err(EslArrayError::TooManyItems {
253 count: arr.len(),
254 max: crate::variables::MAX_ARRAY_ITEMS,
255 });
256 }
257 let mut arr = arr;
258 op(&mut arr, value.into());
259 self.set_header(name, arr.to_string());
260 }
261 }
262 Ok(())
263 }
264
265 pub fn is_event_type(&self, event_type: EslEventType) -> bool {
267 self.event_type() == Some(event_type)
268 }
269
270 pub fn to_plain_format(&self) -> String {
280 use fmt::Write;
281 let mut result = String::new();
282
283 for (key, value) in self.headers() {
284 if key == "Content-Length" {
285 continue;
286 }
287 writeln!(
288 result,
289 "{}: {}",
290 key,
291 percent_encode(value.as_bytes(), NON_ALPHANUMERIC)
292 )
293 .expect("writing to String is infallible");
294 }
295
296 if let Some(body) = &self.body {
297 writeln!(result, "Content-Length: {}", body.len())
298 .expect("writing to String is infallible");
299 result.push('\n');
300 result.push_str(body);
301 } else {
302 result.push('\n');
303 }
304
305 result
306 }
307}
308
309impl Default for EslEvent {
310 fn default() -> Self {
311 Self::new()
312 }
313}
314
315impl HeaderLookup for EslEvent {
316 fn header_str(&self, name: &str) -> Option<&str> {
317 EslEvent::header_str(self, name)
318 }
319
320 fn variable_str(&self, name: &str) -> Option<&str> {
321 EslEvent::variable_str(self, name)
322 }
323}
324
325impl sip_header::SipHeaderLookup for EslEvent {
326 fn sip_header_str(&self, name: &str) -> Option<&str> {
327 EslEvent::header_str(self, name)
328 }
329
330 crate::esl_sip_header_overrides!();
331}
332
333impl PartialEq for EslEvent {
334 fn eq(&self, other: &Self) -> bool {
335 self.headers == other.headers && self.body == other.body
336 }
337}
338
339impl std::hash::Hash for EslEvent {
340 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
341 for (k, v) in self.headers() {
342 k.hash(state);
343 v.hash(state);
344 }
345 self.body
346 .hash(state);
347 }
348}
349
350#[cfg(feature = "serde")]
351impl<'de> serde::Deserialize<'de> for EslEvent {
352 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
353 where
354 D: serde::Deserializer<'de>,
355 {
356 #[derive(serde::Deserialize)]
357 struct Raw {
358 headers: EslHeaders,
359 body: Option<String>,
360 #[serde(default)]
361 raw_body: Option<Vec<u8>>,
362 #[serde(default)]
363 lossy_values: LossyValues,
364 }
365 let raw = Raw::deserialize(deserializer)?;
366 Ok(EslEvent {
367 headers: raw.headers,
368 body: raw.body,
369 raw_body: raw.raw_body,
370 lossy_values: raw.lossy_values,
371 })
372 }
373}
374
375#[cfg(test)]
376mod tests {
377 use super::*;
378
379 #[test]
380 fn headers_preserve_insertion_order() {
381 let mut event = EslEvent::new();
382 event.set_header("Zebra", "last");
383 event.set_header("Alpha", "first");
384 event.set_header("Middle", "mid");
385 let keys: Vec<&str> = event
386 .headers()
387 .keys()
388 .map(|s| s.as_str())
389 .collect();
390 assert_eq!(keys, vec!["Zebra", "Alpha", "Middle"]);
391 }
392
393 #[test]
394 fn test_remove_header() {
395 let mut event = EslEvent::new();
396 event.set_header("Foo", "bar");
397 event.set_header("Baz", "qux");
398
399 let removed = event.remove_header("Foo");
400 assert_eq!(removed, Some("bar".to_string()));
401 assert!(event
402 .header_str("Foo")
403 .is_none());
404 assert_eq!(event.header_str("Baz"), Some("qux"));
405
406 let removed_again = event.remove_header("Foo");
407 assert_eq!(removed_again, None);
408 }
409
410 #[test]
411 fn test_to_plain_format_basic() {
412 let mut event = EslEvent::with_type(EslEventType::Heartbeat);
413 event.set_header("Event-Name", "HEARTBEAT");
414 event.set_header("Core-UUID", "abc-123");
415
416 let plain = event.to_plain_format();
417
418 assert!(plain.starts_with("Event-Name: "));
419 assert!(plain.contains("Core-UUID: "));
420 assert!(plain.ends_with("\n\n"));
421 }
422
423 #[test]
424 fn test_to_plain_format_percent_encoding() {
425 let mut event = EslEvent::with_type(EslEventType::Heartbeat);
426 event.set_header("Event-Name", "HEARTBEAT");
427 event.set_header("Up-Time", "0 years, 0 days");
428
429 let plain = event.to_plain_format();
430
431 assert!(!plain.contains("0 years, 0 days"));
432 assert!(plain.contains("Up-Time: "));
433 assert!(plain.contains("%20"));
434 }
435
436 #[test]
437 fn test_to_plain_format_with_body() {
438 let mut event = EslEvent::with_type(EslEventType::BackgroundJob);
439 event.set_header("Event-Name", "BACKGROUND_JOB");
440 event.set_header("Job-UUID", "def-456");
441 event.set_body("+OK result\n".to_string());
442
443 let plain = event.to_plain_format();
444
445 assert!(plain.contains("Content-Length: 11\n"));
446 assert!(plain.ends_with("\n\n+OK result\n"));
447 }
448
449 #[test]
450 fn test_to_plain_format_preserves_insertion_order() {
451 let mut event = EslEvent::with_type(EslEventType::Heartbeat);
452 event.set_header("Event-Name", "HEARTBEAT");
453 event.set_header("Core-UUID", "abc-123");
454 event.set_header("FreeSWITCH-Hostname", "fs01");
455 event.set_header("Up-Time", "0 years, 1 day");
456
457 let plain = event.to_plain_format();
458 let lines: Vec<&str> = plain
459 .lines()
460 .collect();
461 assert!(lines[0].starts_with("Event-Name: "));
462 assert!(lines[1].starts_with("Core-UUID: "));
463 assert!(lines[2].starts_with("FreeSWITCH-Hostname: "));
464 assert!(lines[3].starts_with("Up-Time: "));
465 }
466
467 #[test]
468 fn test_set_priority_normal() {
469 let mut event = EslEvent::new();
470 event.set_priority(EslEventPriority::Normal);
471 assert_eq!(
472 event
473 .priority()
474 .unwrap(),
475 Some(EslEventPriority::Normal)
476 );
477 assert_eq!(event.header(EventHeader::Priority), Some("NORMAL"));
478 }
479
480 #[test]
481 fn test_set_priority_high() {
482 let mut event = EslEvent::new();
483 event.set_priority(EslEventPriority::High);
484 assert_eq!(
485 event
486 .priority()
487 .unwrap(),
488 Some(EslEventPriority::High)
489 );
490 assert_eq!(event.header(EventHeader::Priority), Some("HIGH"));
491 }
492
493 #[test]
494 fn test_push_header_new() {
495 let mut event = EslEvent::new();
496 event
497 .push_header("X-Test", "first")
498 .unwrap();
499 assert_eq!(event.header_str("X-Test"), Some("first"));
500 }
501
502 #[test]
503 fn test_push_header_existing_plain() {
504 let mut event = EslEvent::new();
505 event.set_header("X-Test", "first");
506 event
507 .push_header("X-Test", "second")
508 .unwrap();
509 assert_eq!(event.header_str("X-Test"), Some("ARRAY::first|:second"));
510 }
511
512 #[test]
513 fn test_push_header_existing_array() {
514 let mut event = EslEvent::new();
515 event.set_header("X-Test", "ARRAY::a|:b");
516 event
517 .push_header("X-Test", "c")
518 .unwrap();
519 assert_eq!(event.header_str("X-Test"), Some("ARRAY::a|:b|:c"));
520 }
521
522 #[test]
523 fn test_push_header_at_capacity() {
524 use crate::variables::MAX_ARRAY_ITEMS;
525 let mut event = EslEvent::new();
526 let items: Vec<&str> = (0..MAX_ARRAY_ITEMS)
527 .map(|_| "x")
528 .collect();
529 event.set_header("X-Test", format!("ARRAY::{}", items.join("|:")).as_str());
530 assert!(matches!(
531 event.push_header("X-Test", "overflow"),
532 Err(EslArrayError::TooManyItems { .. })
533 ));
534 }
535
536 #[test]
537 fn test_unshift_header_new() {
538 let mut event = EslEvent::new();
539 event
540 .unshift_header("X-Test", "only")
541 .unwrap();
542 assert_eq!(event.header_str("X-Test"), Some("only"));
543 }
544
545 #[test]
546 fn test_unshift_header_existing_array() {
547 let mut event = EslEvent::new();
548 event.set_header("X-Test", "ARRAY::b|:c");
549 event
550 .unshift_header("X-Test", "a")
551 .unwrap();
552 assert_eq!(event.header_str("X-Test"), Some("ARRAY::a|:b|:c"));
553 }
554
555 #[test]
556 fn test_sendevent_with_priority_wire_format() {
557 let mut event = EslEvent::with_type(EslEventType::Custom);
558 event.set_header("Event-Name", "CUSTOM");
559 event.set_header("Event-Subclass", "test::priority");
560 event.set_priority(EslEventPriority::High);
561
562 let plain = event.to_plain_format();
563 assert!(plain.contains("priority: HIGH\n"));
564 }
565
566 #[test]
567 fn test_convenience_accessors() {
568 let mut event = EslEvent::new();
569 event.set_header("Channel-Name", "sofia/internal/1000@example.com");
570 event.set_header("Caller-Caller-ID-Number", "1000");
571 event.set_header("Caller-Caller-ID-Name", "Alice");
572 event.set_header("Hangup-Cause", "NORMAL_CLEARING");
573 event.set_header("Event-Subclass", "sofia::register");
574 event.set_header("variable_sip_from_display", "Bob");
575
576 assert_eq!(
577 event.channel_name(),
578 Some("sofia/internal/1000@example.com")
579 );
580 assert_eq!(event.caller_id_number(), Some("1000"));
581 assert_eq!(event.caller_id_name(), Some("Alice"));
582 assert_eq!(
583 event
584 .hangup_cause()
585 .unwrap(),
586 Some(crate::channel::HangupCause::NormalClearing)
587 );
588 assert_eq!(event.event_subclass(), Some("sofia::register"));
589 assert_eq!(event.variable_str("sip_from_display"), Some("Bob"));
590 assert_eq!(event.variable_str("nonexistent"), None);
591 }
592
593 #[test]
596 fn test_event_channel_state_accessor() {
597 use crate::channel::ChannelState;
598 let mut event = EslEvent::new();
599 event.set_header("Channel-State", "CS_EXECUTE");
600 assert_eq!(
601 event
602 .channel_state()
603 .unwrap(),
604 Some(ChannelState::CsExecute)
605 );
606 }
607
608 #[test]
609 fn test_event_channel_state_number_accessor() {
610 use crate::channel::ChannelState;
611 let mut event = EslEvent::new();
612 event.set_header("Channel-State-Number", "4");
613 assert_eq!(
614 event
615 .channel_state_number()
616 .unwrap(),
617 Some(ChannelState::CsExecute)
618 );
619 }
620
621 #[test]
622 fn test_event_call_state_accessor() {
623 use crate::channel::CallState;
624 let mut event = EslEvent::new();
625 event.set_header("Channel-Call-State", "ACTIVE");
626 assert_eq!(
627 event
628 .call_state()
629 .unwrap(),
630 Some(CallState::Active)
631 );
632 }
633
634 #[test]
635 fn test_event_answer_state_accessor() {
636 use crate::channel::AnswerState;
637 let mut event = EslEvent::new();
638 event.set_header("Answer-State", "answered");
639 assert_eq!(
640 event
641 .answer_state()
642 .unwrap(),
643 Some(AnswerState::Answered)
644 );
645 }
646
647 #[test]
648 fn test_event_call_direction_accessor() {
649 use crate::channel::CallDirection;
650 let mut event = EslEvent::new();
651 event.set_header("Call-Direction", "inbound");
652 assert_eq!(
653 event
654 .call_direction()
655 .unwrap(),
656 Some(CallDirection::Inbound)
657 );
658 }
659
660 #[test]
661 fn test_event_typed_accessors_missing_headers() {
662 let event = EslEvent::new();
663 assert_eq!(
664 event
665 .channel_state()
666 .unwrap(),
667 None
668 );
669 assert_eq!(
670 event
671 .channel_state_number()
672 .unwrap(),
673 None
674 );
675 assert_eq!(
676 event
677 .call_state()
678 .unwrap(),
679 None
680 );
681 assert_eq!(
682 event
683 .answer_state()
684 .unwrap(),
685 None
686 );
687 assert_eq!(
688 event
689 .call_direction()
690 .unwrap(),
691 None
692 );
693 }
694
695 #[test]
698 fn test_sip_p_asserted_identity_comma_separated() {
699 let mut event = EslEvent::new();
700 event.set_header(
703 "variable_sip_P-Asserted-Identity",
704 "<sip:alice@atlanta.example.com>, <tel:+15551234567>",
705 );
706
707 assert_eq!(
708 event.variable_str("sip_P-Asserted-Identity"),
709 Some("<sip:alice@atlanta.example.com>, <tel:+15551234567>")
710 );
711 }
712
713 #[test]
714 fn test_sip_p_asserted_identity_array_format() {
715 let mut event = EslEvent::new();
716 event
718 .push_header(
719 "variable_sip_P-Asserted-Identity",
720 "<sip:alice@atlanta.example.com>",
721 )
722 .unwrap();
723 event
724 .push_header("variable_sip_P-Asserted-Identity", "<tel:+15551234567>")
725 .unwrap();
726
727 let raw = event
728 .header_str("variable_sip_P-Asserted-Identity")
729 .unwrap();
730 assert_eq!(
731 raw,
732 "ARRAY::<sip:alice@atlanta.example.com>|:<tel:+15551234567>"
733 );
734
735 let arr = crate::variables::EslArray::parse(raw).unwrap();
736 assert_eq!(arr.len(), 2);
737 assert_eq!(arr.items()[0], "<sip:alice@atlanta.example.com>");
738 assert_eq!(arr.items()[1], "<tel:+15551234567>");
739 }
740
741 #[test]
742 fn test_sip_header_with_colons_in_uri() {
743 let mut event = EslEvent::new();
744 event
746 .push_header(
747 "variable_sip_h_Diversion",
748 "<sip:+15551234567@gw.example.com;reason=unconditional>",
749 )
750 .unwrap();
751 event
752 .push_header(
753 "variable_sip_h_Diversion",
754 "<sips:+15559876543@secure.example.com;reason=no-answer;counter=3>",
755 )
756 .unwrap();
757
758 let raw = event
759 .header_str("variable_sip_h_Diversion")
760 .unwrap();
761 let arr = crate::variables::EslArray::parse(raw).unwrap();
762 assert_eq!(arr.len(), 2);
763 assert_eq!(
764 arr.items()[0],
765 "<sip:+15551234567@gw.example.com;reason=unconditional>"
766 );
767 assert_eq!(
768 arr.items()[1],
769 "<sips:+15559876543@secure.example.com;reason=no-answer;counter=3>"
770 );
771 }
772
773 #[test]
774 fn test_sip_p_asserted_identity_plain_format_round_trip() {
775 let mut event = EslEvent::with_type(EslEventType::ChannelCreate);
776 event.set_header("Event-Name", "CHANNEL_CREATE");
777 event.set_header(
778 "variable_sip_P-Asserted-Identity",
779 "<sip:alice@atlanta.example.com>, <tel:+15551234567>",
780 );
781
782 let plain = event.to_plain_format();
783 assert!(plain.contains("variable_sip_P-Asserted-Identity:"));
785 assert!(!plain.contains("<sip:alice"));
787 }
788
789 #[test]
792 fn set_header_normalizes_known_enum_variant() {
793 let mut event = EslEvent::new();
794 event.set_header("unique-id", "abc-123");
795 assert_eq!(event.header(EventHeader::UniqueId), Some("abc-123"));
796 }
797
798 #[test]
799 fn set_header_keeps_both_profile_name_spellings() {
800 let mut event = EslEvent::new();
801 event.set_header("profile-name", "internal");
802 event.set_header("profile_name", "external");
803 assert_eq!(event.header(EventHeader::ProfileName), Some("internal"));
804 assert_eq!(
805 event.header(EventHeader::ProfileNameSnake),
806 Some("external")
807 );
808 assert_eq!(event.profile_name(), Some("internal"));
809 }
810
811 #[test]
812 fn set_header_normalizes_codec_header() {
813 let mut event = EslEvent::new();
814 event.set_header("channel-read-codec-bit-rate", "128000");
815 assert_eq!(
816 event.header(EventHeader::ChannelReadCodecBitRate),
817 Some("128000")
818 );
819 }
820
821 #[test]
822 fn header_str_finds_by_original_key() {
823 let mut event = EslEvent::new();
824 event.set_header("unique-id", "abc-123");
825 assert_eq!(event.header_str("unique-id"), Some("abc-123"));
826 assert_eq!(event.header_str("Unique-ID"), Some("abc-123"));
827 }
828
829 #[test]
830 fn header_str_finds_unknown_dash_header_by_original() {
831 let mut event = EslEvent::new();
832 event.set_header("x-custom-header", "val");
833 assert_eq!(event.header_str("X-Custom-Header"), Some("val"));
834 assert_eq!(event.header_str("x-custom-header"), Some("val"));
835 }
836
837 #[test]
838 fn set_header_underscore_passthrough_preserves_sip_h() {
839 let mut event = EslEvent::new();
840 event.set_header("variable_sip_h_X-My-CUSTOM-Header", "val");
841 assert_eq!(
842 event.header_str("variable_sip_h_X-My-CUSTOM-Header"),
843 Some("val")
844 );
845 }
846
847 #[test]
848 fn set_header_different_casing_overwrites() {
849 let mut event = EslEvent::new();
850 event.set_header("Unique-ID", "first");
851 event.set_header("unique-id", "second");
852 assert_eq!(event.header(EventHeader::UniqueId), Some("second"));
854 }
855
856 #[test]
857 fn remove_header_by_original_key() {
858 let mut event = EslEvent::new();
859 event.set_header("unique-id", "abc-123");
860 let removed = event.remove_header("unique-id");
861 assert_eq!(removed, Some("abc-123".to_string()));
862 assert_eq!(event.header(EventHeader::UniqueId), None);
863 }
864
865 #[test]
866 fn remove_header_by_canonical_key() {
867 let mut event = EslEvent::new();
868 event.set_header("unique-id", "abc-123");
869 let removed = event.remove_header("Unique-ID");
870 assert_eq!(removed, Some("abc-123".to_string()));
871 assert_eq!(event.header_str("unique-id"), None);
872 }
873
874 #[test]
875 fn serde_round_trip_preserves_canonical_lookups() {
876 let mut event = EslEvent::new();
877 event.set_header("unique-id", "abc-123");
878 event.set_header("channel-read-codec-bit-rate", "128000");
879 let json = serde_json::to_string(&event).unwrap();
880 let deserialized: EslEvent = serde_json::from_str(&json).unwrap();
881 assert_eq!(deserialized.header(EventHeader::UniqueId), Some("abc-123"));
882 assert_eq!(
883 deserialized.header(EventHeader::ChannelReadCodecBitRate),
884 Some("128000")
885 );
886 }
887
888 #[test]
889 fn serde_deserialize_normalizes_external_json() {
890 let json = r#"{"event_type":null,"headers":{"unique-id":"abc-123","channel-read-codec-bit-rate":"128000"},"body":null}"#;
891 let event: EslEvent = serde_json::from_str(json).unwrap();
892 assert_eq!(event.header(EventHeader::UniqueId), Some("abc-123"));
893 assert_eq!(
894 event.header(EventHeader::ChannelReadCodecBitRate),
895 Some("128000")
896 );
897 assert_eq!(event.header_str("unique-id"), Some("abc-123"));
898 }
899
900 #[test]
901 fn both_spellings_resolve_after_serde_roundtrip() {
902 let external_json = r#"{
906 "event_type": null,
907 "headers": {
908 "Channel-Write-Codec-Name": "opus",
909 "channel-write-codec-bit-rate": "64000",
910 "Custom-X-Header": "preserved"
911 },
912 "body": null
913 }"#;
914 let parsed: EslEvent = serde_json::from_str(external_json).unwrap();
915
916 assert_eq!(
917 parsed.header(EventHeader::ChannelWriteCodecName),
918 Some("opus")
919 );
920 assert_eq!(
921 parsed.header(EventHeader::ChannelWriteCodecBitRate),
922 Some("64000")
923 );
924
925 assert_eq!(
926 parsed.header_str("channel-write-codec-bit-rate"),
927 Some("64000")
928 );
929 assert_eq!(
930 parsed.header_str("Channel-Write-Codec-Bit-Rate"),
931 Some("64000")
932 );
933 assert_eq!(parsed.header_str("Custom-X-Header"), Some("preserved"));
934
935 let json = serde_json::to_string(&parsed).unwrap();
936 let re_parsed: EslEvent = serde_json::from_str(&json).unwrap();
937 assert_eq!(
938 re_parsed.header(EventHeader::ChannelWriteCodecBitRate),
939 Some("64000")
940 );
941 }
942
943 #[test]
944 fn test_event_typed_accessors_invalid_values() {
945 let mut event = EslEvent::new();
946 event.set_header("Channel-State", "BOGUS");
947 event.set_header("Channel-State-Number", "999");
948 event.set_header("Channel-Call-State", "BOGUS");
949 event.set_header("Answer-State", "bogus");
950 event.set_header("Call-Direction", "bogus");
951 assert!(event
952 .channel_state()
953 .is_err());
954 assert!(event
955 .channel_state_number()
956 .is_err());
957 assert!(event
958 .call_state()
959 .is_err());
960 assert!(event
961 .answer_state()
962 .is_err());
963 assert!(event
964 .call_direction()
965 .is_err());
966 }
967
968 #[test]
971 fn esl_event_call_info_array_encoding() {
972 use sip_header::SipHeaderLookup;
973
974 let mut event = EslEvent::new();
975 event.set_header(
976 "Call-Info".to_string(),
977 "ARRAY::<urn:emergency:uid:callid:abc>;purpose=emergency-CallId\
978 |:<urn:emergency:uid:incidentid:def>;purpose=emergency-IncidentId"
979 .to_string(),
980 );
981 let ci = event
982 .call_info()
983 .expect("should parse")
984 .expect("should be present");
985 assert_eq!(
986 ci.entries()
987 .len(),
988 2,
989 "ARRAY:: entries should expand"
990 );
991 }
992
993 #[test]
994 fn esl_event_call_info_plain_value_unchanged() {
995 use sip_header::SipHeaderLookup;
996
997 let mut event = EslEvent::new();
998 event.set_header(
999 "Call-Info".to_string(),
1000 "<sip:pbx.example.com>;purpose=icon".to_string(),
1001 );
1002 let ci = event
1003 .call_info()
1004 .expect("plain value should parse")
1005 .expect("should be present");
1006 assert_eq!(
1007 ci.entries()
1008 .len(),
1009 1
1010 );
1011 }
1012
1013 #[test]
1014 fn esl_event_history_info_array_encoding() {
1015 use sip_header::SipHeaderLookup;
1016
1017 let mut event = EslEvent::new();
1018 event.set_header(
1019 "History-Info".to_string(),
1020 "ARRAY::<sip:user@pbx.example.com>;index=1\
1021 |:<sip:forward@pbx.example.com?Reason=unconditional>;index=1.1"
1022 .to_string(),
1023 );
1024 let hi = event
1025 .history_info()
1026 .expect("should parse")
1027 .expect("should be present");
1028 assert_eq!(
1029 hi.entries()
1030 .len(),
1031 2,
1032 "ARRAY:: entries should expand"
1033 );
1034 }
1035}