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