1use crate::headers::{normalize_header_key, EventHeader};
4use crate::lookup::HeaderLookup;
5use crate::variables::EslArray;
6use percent_encoding::{percent_encode, NON_ALPHANUMERIC};
7use std::collections::HashMap;
8use std::fmt;
9use std::str::FromStr;
10
11#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
13#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
14#[non_exhaustive]
15pub enum EventFormat {
16 Plain,
18 Json,
20 Xml,
22}
23
24impl EventFormat {
25 pub fn from_content_type(ct: &str) -> Result<Self, ParseEventFormatError> {
30 match ct {
31 "text/event-json" => Ok(Self::Json),
32 "text/event-xml" => Ok(Self::Xml),
33 "text/event-plain" => Ok(Self::Plain),
34 _ => Err(ParseEventFormatError(ct.to_string())),
35 }
36 }
37}
38
39impl fmt::Display for EventFormat {
40 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
41 match self {
42 EventFormat::Plain => write!(f, "plain"),
43 EventFormat::Json => write!(f, "json"),
44 EventFormat::Xml => write!(f, "xml"),
45 }
46 }
47}
48
49impl FromStr for EventFormat {
50 type Err = ParseEventFormatError;
51
52 fn from_str(s: &str) -> Result<Self, Self::Err> {
53 if s.eq_ignore_ascii_case("plain") {
54 Ok(Self::Plain)
55 } else if s.eq_ignore_ascii_case("json") {
56 Ok(Self::Json)
57 } else if s.eq_ignore_ascii_case("xml") {
58 Ok(Self::Xml)
59 } else {
60 Err(ParseEventFormatError(s.to_string()))
61 }
62 }
63}
64
65#[derive(Debug, Clone, PartialEq, Eq)]
67pub struct ParseEventFormatError(pub String);
68
69impl fmt::Display for ParseEventFormatError {
70 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
71 write!(f, "unknown event format: {}", self.0)
72 }
73}
74
75impl std::error::Error for ParseEventFormatError {}
76
77macro_rules! esl_event_types {
79 (
80 $(
81 $(#[$attr:meta])*
82 $variant:ident => $wire:literal
83 ),+ $(,)?
84 ;
85 $(
87 $(#[$extra_attr:meta])*
88 $extra_variant:ident => $extra_wire:literal
89 ),* $(,)?
90 ) => {
91 #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
96 #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
97 #[non_exhaustive]
98 #[allow(missing_docs)]
99 pub enum EslEventType {
100 $(
101 $(#[$attr])*
102 $variant,
103 )+
104 $(
105 $(#[$extra_attr])*
106 $extra_variant,
107 )*
108 }
109
110 impl fmt::Display for EslEventType {
111 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
112 f.write_str(self.as_str())
113 }
114 }
115
116 impl EslEventType {
117 pub const fn as_str(&self) -> &'static str {
119 match self {
120 $( EslEventType::$variant => $wire, )+
121 $( EslEventType::$extra_variant => $extra_wire, )*
122 }
123 }
124
125 pub fn parse_event_type(s: &str) -> Option<Self> {
127 match s {
128 $( $wire => Some(EslEventType::$variant), )+
129 $( $extra_wire => Some(EslEventType::$extra_variant), )*
130 _ => None,
131 }
132 }
133 }
134
135 impl FromStr for EslEventType {
136 type Err = ParseEventTypeError;
137
138 fn from_str(s: &str) -> Result<Self, Self::Err> {
139 Self::parse_event_type(s).ok_or_else(|| ParseEventTypeError(s.to_string()))
140 }
141 }
142 };
143}
144
145esl_event_types! {
146 Custom => "CUSTOM",
147 Clone => "CLONE",
148 ChannelCreate => "CHANNEL_CREATE",
149 ChannelDestroy => "CHANNEL_DESTROY",
150 ChannelState => "CHANNEL_STATE",
151 ChannelCallstate => "CHANNEL_CALLSTATE",
152 ChannelAnswer => "CHANNEL_ANSWER",
153 ChannelHangup => "CHANNEL_HANGUP",
154 ChannelHangupComplete => "CHANNEL_HANGUP_COMPLETE",
155 ChannelExecute => "CHANNEL_EXECUTE",
156 ChannelExecuteComplete => "CHANNEL_EXECUTE_COMPLETE",
157 ChannelHold => "CHANNEL_HOLD",
158 ChannelUnhold => "CHANNEL_UNHOLD",
159 ChannelBridge => "CHANNEL_BRIDGE",
160 ChannelUnbridge => "CHANNEL_UNBRIDGE",
161 ChannelProgress => "CHANNEL_PROGRESS",
162 ChannelProgressMedia => "CHANNEL_PROGRESS_MEDIA",
163 ChannelOutgoing => "CHANNEL_OUTGOING",
164 ChannelPark => "CHANNEL_PARK",
165 ChannelUnpark => "CHANNEL_UNPARK",
166 ChannelApplication => "CHANNEL_APPLICATION",
167 ChannelOriginate => "CHANNEL_ORIGINATE",
168 ChannelUuid => "CHANNEL_UUID",
169 Api => "API",
170 Log => "LOG",
171 InboundChan => "INBOUND_CHAN",
172 OutboundChan => "OUTBOUND_CHAN",
173 Startup => "STARTUP",
174 Shutdown => "SHUTDOWN",
175 Publish => "PUBLISH",
176 Unpublish => "UNPUBLISH",
177 Talk => "TALK",
178 Notalk => "NOTALK",
179 SessionCrash => "SESSION_CRASH",
180 ModuleLoad => "MODULE_LOAD",
181 ModuleUnload => "MODULE_UNLOAD",
182 Dtmf => "DTMF",
183 Message => "MESSAGE",
184 PresenceIn => "PRESENCE_IN",
185 NotifyIn => "NOTIFY_IN",
186 PresenceOut => "PRESENCE_OUT",
187 PresenceProbe => "PRESENCE_PROBE",
188 MessageWaiting => "MESSAGE_WAITING",
189 MessageQuery => "MESSAGE_QUERY",
190 Roster => "ROSTER",
191 Codec => "CODEC",
192 BackgroundJob => "BACKGROUND_JOB",
193 DetectedSpeech => "DETECTED_SPEECH",
194 DetectedTone => "DETECTED_TONE",
195 PrivateCommand => "PRIVATE_COMMAND",
196 Heartbeat => "HEARTBEAT",
197 Trap => "TRAP",
198 AddSchedule => "ADD_SCHEDULE",
199 DelSchedule => "DEL_SCHEDULE",
200 ExeSchedule => "EXE_SCHEDULE",
201 ReSchedule => "RE_SCHEDULE",
202 ReloadXml => "RELOADXML",
203 Notify => "NOTIFY",
204 PhoneFeature => "PHONE_FEATURE",
205 PhoneFeatureSubscribe => "PHONE_FEATURE_SUBSCRIBE",
206 SendMessage => "SEND_MESSAGE",
207 RecvMessage => "RECV_MESSAGE",
208 RequestParams => "REQUEST_PARAMS",
209 ChannelData => "CHANNEL_DATA",
210 General => "GENERAL",
211 Command => "COMMAND",
212 SessionHeartbeat => "SESSION_HEARTBEAT",
213 ClientDisconnected => "CLIENT_DISCONNECTED",
214 ServerDisconnected => "SERVER_DISCONNECTED",
215 SendInfo => "SEND_INFO",
216 RecvInfo => "RECV_INFO",
217 RecvRtcpMessage => "RECV_RTCP_MESSAGE",
218 SendRtcpMessage => "SEND_RTCP_MESSAGE",
219 CallSecure => "CALL_SECURE",
220 Nat => "NAT",
221 RecordStart => "RECORD_START",
222 RecordStop => "RECORD_STOP",
223 PlaybackStart => "PLAYBACK_START",
224 PlaybackStop => "PLAYBACK_STOP",
225 CallUpdate => "CALL_UPDATE",
226 Failure => "FAILURE",
227 SocketData => "SOCKET_DATA",
228 MediaBugStart => "MEDIA_BUG_START",
229 MediaBugStop => "MEDIA_BUG_STOP",
230 ConferenceDataQuery => "CONFERENCE_DATA_QUERY",
231 ConferenceData => "CONFERENCE_DATA",
232 CallSetupReq => "CALL_SETUP_REQ",
233 CallSetupResult => "CALL_SETUP_RESULT",
234 CallDetail => "CALL_DETAIL",
235 DeviceState => "DEVICE_STATE",
236 Text => "TEXT",
237 ShutdownRequested => "SHUTDOWN_REQUESTED",
238 All => "ALL";
240 StartRecording => "START_RECORDING",
244}
245
246impl EslEventType {
255 pub const CHANNEL_EVENTS: &[EslEventType] = &[
266 EslEventType::ChannelCreate,
267 EslEventType::ChannelDestroy,
268 EslEventType::ChannelState,
269 EslEventType::ChannelCallstate,
270 EslEventType::ChannelAnswer,
271 EslEventType::ChannelHangup,
272 EslEventType::ChannelHangupComplete,
273 EslEventType::ChannelExecute,
274 EslEventType::ChannelExecuteComplete,
275 EslEventType::ChannelHold,
276 EslEventType::ChannelUnhold,
277 EslEventType::ChannelBridge,
278 EslEventType::ChannelUnbridge,
279 EslEventType::ChannelProgress,
280 EslEventType::ChannelProgressMedia,
281 EslEventType::ChannelOutgoing,
282 EslEventType::ChannelPark,
283 EslEventType::ChannelUnpark,
284 EslEventType::ChannelApplication,
285 EslEventType::ChannelOriginate,
286 EslEventType::ChannelUuid,
287 EslEventType::ChannelData,
288 ];
289
290 pub const IN_CALL_EVENTS: &[EslEventType] = &[
301 EslEventType::Dtmf,
302 EslEventType::Talk,
303 EslEventType::Notalk,
304 EslEventType::CallSecure,
305 EslEventType::CallUpdate,
306 EslEventType::RecvRtcpMessage,
307 EslEventType::SendRtcpMessage,
308 ];
309
310 pub const MEDIA_EVENTS: &[EslEventType] = &[
321 EslEventType::PlaybackStart,
322 EslEventType::PlaybackStop,
323 EslEventType::RecordStart,
324 EslEventType::RecordStop,
325 EslEventType::StartRecording,
326 EslEventType::MediaBugStart,
327 EslEventType::MediaBugStop,
328 EslEventType::DetectedSpeech,
329 EslEventType::DetectedTone,
330 ];
331
332 pub const PRESENCE_EVENTS: &[EslEventType] = &[
343 EslEventType::PresenceIn,
344 EslEventType::PresenceOut,
345 EslEventType::PresenceProbe,
346 EslEventType::MessageWaiting,
347 EslEventType::MessageQuery,
348 EslEventType::Roster,
349 ];
350
351 pub const SYSTEM_EVENTS: &[EslEventType] = &[
362 EslEventType::Startup,
363 EslEventType::Shutdown,
364 EslEventType::ShutdownRequested,
365 EslEventType::Heartbeat,
366 EslEventType::SessionHeartbeat,
367 EslEventType::SessionCrash,
368 EslEventType::ModuleLoad,
369 EslEventType::ModuleUnload,
370 EslEventType::ReloadXml,
371 ];
372
373 pub const CONFERENCE_EVENTS: &[EslEventType] = &[
380 EslEventType::ConferenceDataQuery,
381 EslEventType::ConferenceData,
382 ];
383}
384
385#[derive(Debug, Clone, PartialEq, Eq)]
387pub struct ParseEventTypeError(pub String);
388
389impl fmt::Display for ParseEventTypeError {
390 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
391 write!(f, "unknown event type: {}", self.0)
392 }
393}
394
395impl std::error::Error for ParseEventTypeError {}
396
397#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
399#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
400#[non_exhaustive]
401pub enum EslEventPriority {
402 Normal,
404 Low,
406 High,
408}
409
410impl fmt::Display for EslEventPriority {
411 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
412 match self {
413 EslEventPriority::Normal => write!(f, "NORMAL"),
414 EslEventPriority::Low => write!(f, "LOW"),
415 EslEventPriority::High => write!(f, "HIGH"),
416 }
417 }
418}
419
420#[derive(Debug, Clone, PartialEq, Eq)]
422pub struct ParsePriorityError(pub String);
423
424impl fmt::Display for ParsePriorityError {
425 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
426 write!(f, "unknown priority: {}", self.0)
427 }
428}
429
430impl std::error::Error for ParsePriorityError {}
431
432impl FromStr for EslEventPriority {
433 type Err = ParsePriorityError;
434
435 fn from_str(s: &str) -> Result<Self, Self::Err> {
436 match s {
437 "NORMAL" => Ok(EslEventPriority::Normal),
438 "LOW" => Ok(EslEventPriority::Low),
439 "HIGH" => Ok(EslEventPriority::High),
440 _ => Err(ParsePriorityError(s.to_string())),
441 }
442 }
443}
444
445#[derive(Debug, Clone, Eq)]
447#[cfg_attr(feature = "serde", derive(serde::Serialize))]
448pub struct EslEvent {
449 event_type: Option<EslEventType>,
450 headers: HashMap<String, String>,
451 #[cfg_attr(feature = "serde", serde(skip))]
452 original_keys: HashMap<String, String>,
453 body: Option<String>,
454}
455
456impl EslEvent {
457 pub fn new() -> Self {
459 Self {
460 event_type: None,
461 headers: HashMap::new(),
462 original_keys: HashMap::new(),
463 body: None,
464 }
465 }
466
467 pub fn with_type(event_type: EslEventType) -> Self {
469 Self {
470 event_type: Some(event_type),
471 headers: HashMap::new(),
472 original_keys: HashMap::new(),
473 body: None,
474 }
475 }
476
477 pub fn event_type(&self) -> Option<EslEventType> {
479 self.event_type
480 }
481
482 pub fn set_event_type(&mut self, event_type: Option<EslEventType>) {
484 self.event_type = event_type;
485 }
486
487 pub fn header(&self, name: EventHeader) -> Option<&str> {
491 self.headers
492 .get(name.as_str())
493 .map(|s| s.as_str())
494 }
495
496 pub fn header_str(&self, name: &str) -> Option<&str> {
504 self.headers
505 .get(name)
506 .or_else(|| {
507 self.original_keys
508 .get(name)
509 .and_then(|normalized| {
510 self.headers
511 .get(normalized)
512 })
513 })
514 .map(|s| s.as_str())
515 }
516
517 pub fn variable_str(&self, name: &str) -> Option<&str> {
522 let key = format!("variable_{}", name);
523 self.header_str(&key)
524 }
525
526 pub fn headers(&self) -> &HashMap<String, String> {
528 &self.headers
529 }
530
531 pub fn set_header(&mut self, name: impl Into<String>, value: impl Into<String>) {
533 let original = name.into();
534 let normalized = normalize_header_key(&original);
535 if original != normalized {
536 self.original_keys
537 .insert(original, normalized.clone());
538 }
539 self.headers
540 .insert(normalized, value.into());
541 }
542
543 pub fn remove_header(&mut self, name: impl AsRef<str>) -> Option<String> {
547 let name = name.as_ref();
548 if let Some(value) = self
549 .headers
550 .remove(name)
551 {
552 return Some(value);
553 }
554 if let Some(normalized) = self
555 .original_keys
556 .remove(name)
557 {
558 return self
559 .headers
560 .remove(&normalized);
561 }
562 None
563 }
564
565 pub fn body(&self) -> Option<&str> {
567 self.body
568 .as_deref()
569 }
570
571 pub fn set_body(&mut self, body: impl Into<String>) {
573 self.body = Some(body.into());
574 }
575
576 pub fn set_priority(&mut self, priority: EslEventPriority) {
581 self.set_header(EventHeader::Priority.as_str(), priority.to_string());
582 }
583
584 pub fn push_header(&mut self, name: &str, value: &str) {
598 self.stack_header(name, value, EslArray::push);
599 }
600
601 pub fn unshift_header(&mut self, name: &str, value: &str) {
613 self.stack_header(name, value, EslArray::unshift);
614 }
615
616 fn stack_header(&mut self, name: &str, value: &str, op: fn(&mut EslArray, String)) {
617 match self
618 .headers
619 .get(name)
620 {
621 None => {
622 self.set_header(name, value);
623 }
624 Some(existing) => {
625 let mut arr = match EslArray::parse(existing) {
626 Some(arr) => arr,
627 None => EslArray::new(vec![existing.clone()]),
628 };
629 op(&mut arr, value.into());
630 self.set_header(name, arr.to_string());
631 }
632 }
633 }
634
635 pub fn is_event_type(&self, event_type: EslEventType) -> bool {
637 self.event_type == Some(event_type)
638 }
639
640 pub fn to_plain_format(&self) -> String {
650 use std::fmt::Write;
651 let mut result = String::new();
652
653 let event_name_key = EventHeader::EventName.as_str();
654 if let Some(event_name) = self
655 .headers
656 .get(event_name_key)
657 {
658 let _ = writeln!(
659 result,
660 "{}: {}",
661 event_name_key,
662 percent_encode(event_name.as_bytes(), NON_ALPHANUMERIC)
663 );
664 }
665
666 let mut sorted_headers: Vec<_> = self
667 .headers
668 .iter()
669 .filter(|(k, _)| k.as_str() != event_name_key && k.as_str() != "Content-Length")
670 .collect();
671 sorted_headers.sort_by_key(|(k, _)| k.as_str());
672
673 for (key, value) in sorted_headers {
674 let _ = writeln!(
675 result,
676 "{}: {}",
677 key,
678 percent_encode(value.as_bytes(), NON_ALPHANUMERIC)
679 );
680 }
681
682 if let Some(body) = &self.body {
683 let _ = writeln!(result, "Content-Length: {}", body.len());
684 result.push('\n');
685 result.push_str(body);
686 } else {
687 result.push('\n');
688 }
689
690 result
691 }
692}
693
694impl Default for EslEvent {
695 fn default() -> Self {
696 Self::new()
697 }
698}
699
700impl HeaderLookup for EslEvent {
701 fn header_str(&self, name: &str) -> Option<&str> {
702 EslEvent::header_str(self, name)
703 }
704
705 fn variable_str(&self, name: &str) -> Option<&str> {
706 let key = format!("variable_{}", name);
707 self.header_str(&key)
708 }
709}
710
711impl PartialEq for EslEvent {
712 fn eq(&self, other: &Self) -> bool {
713 self.event_type == other.event_type
714 && self.headers == other.headers
715 && self.body == other.body
716 }
717}
718
719#[cfg(feature = "serde")]
720impl<'de> serde::Deserialize<'de> for EslEvent {
721 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
722 where
723 D: serde::Deserializer<'de>,
724 {
725 #[derive(serde::Deserialize)]
726 struct Raw {
727 event_type: Option<EslEventType>,
728 headers: HashMap<String, String>,
729 body: Option<String>,
730 }
731 let raw = Raw::deserialize(deserializer)?;
732 let mut event = EslEvent::new();
733 event.event_type = raw.event_type;
734 event.body = raw.body;
735 for (k, v) in raw.headers {
736 event.set_header(k, v);
737 }
738 Ok(event)
739 }
740}
741
742#[cfg(test)]
743mod tests {
744 use super::*;
745
746 #[test]
747 fn test_notify_in_parse() {
748 assert_eq!(
749 EslEventType::parse_event_type("NOTIFY_IN"),
750 Some(EslEventType::NotifyIn)
751 );
752 assert_eq!(EslEventType::parse_event_type("notify_in"), None);
753 }
754
755 #[test]
756 fn test_notify_in_display() {
757 assert_eq!(EslEventType::NotifyIn.to_string(), "NOTIFY_IN");
758 }
759
760 #[test]
761 fn test_notify_in_distinct_from_notify() {
762 assert_ne!(EslEventType::Notify, EslEventType::NotifyIn);
763 assert_ne!(
764 EslEventType::Notify.to_string(),
765 EslEventType::NotifyIn.to_string()
766 );
767 }
768
769 #[test]
770 fn test_wire_names_match_c_esl() {
771 assert_eq!(
772 EslEventType::ChannelOutgoing.to_string(),
773 "CHANNEL_OUTGOING"
774 );
775 assert_eq!(EslEventType::Api.to_string(), "API");
776 assert_eq!(EslEventType::ReloadXml.to_string(), "RELOADXML");
777 assert_eq!(EslEventType::PresenceIn.to_string(), "PRESENCE_IN");
778 assert_eq!(EslEventType::Roster.to_string(), "ROSTER");
779 assert_eq!(EslEventType::Text.to_string(), "TEXT");
780 assert_eq!(EslEventType::ReSchedule.to_string(), "RE_SCHEDULE");
781
782 assert_eq!(
783 EslEventType::parse_event_type("CHANNEL_OUTGOING"),
784 Some(EslEventType::ChannelOutgoing)
785 );
786 assert_eq!(
787 EslEventType::parse_event_type("API"),
788 Some(EslEventType::Api)
789 );
790 assert_eq!(
791 EslEventType::parse_event_type("RELOADXML"),
792 Some(EslEventType::ReloadXml)
793 );
794 assert_eq!(
795 EslEventType::parse_event_type("PRESENCE_IN"),
796 Some(EslEventType::PresenceIn)
797 );
798 }
799
800 #[test]
801 fn test_event_type_from_str() {
802 assert_eq!(
803 "CHANNEL_ANSWER".parse::<EslEventType>(),
804 Ok(EslEventType::ChannelAnswer)
805 );
806 assert!("channel_answer"
807 .parse::<EslEventType>()
808 .is_err());
809 assert!("UNKNOWN_EVENT"
810 .parse::<EslEventType>()
811 .is_err());
812 }
813
814 #[test]
815 fn test_remove_header() {
816 let mut event = EslEvent::new();
817 event.set_header("Foo", "bar");
818 event.set_header("Baz", "qux");
819
820 let removed = event.remove_header("Foo");
821 assert_eq!(removed, Some("bar".to_string()));
822 assert!(event
823 .header_str("Foo")
824 .is_none());
825 assert_eq!(event.header_str("Baz"), Some("qux"));
826
827 let removed_again = event.remove_header("Foo");
828 assert_eq!(removed_again, None);
829 }
830
831 #[test]
832 fn test_to_plain_format_basic() {
833 let mut event = EslEvent::with_type(EslEventType::Heartbeat);
834 event.set_header("Event-Name", "HEARTBEAT");
835 event.set_header("Core-UUID", "abc-123");
836
837 let plain = event.to_plain_format();
838
839 assert!(plain.starts_with("Event-Name: "));
840 assert!(plain.contains("Core-UUID: "));
841 assert!(plain.ends_with("\n\n"));
842 }
843
844 #[test]
845 fn test_to_plain_format_percent_encoding() {
846 let mut event = EslEvent::with_type(EslEventType::Heartbeat);
847 event.set_header("Event-Name", "HEARTBEAT");
848 event.set_header("Up-Time", "0 years, 0 days");
849
850 let plain = event.to_plain_format();
851
852 assert!(!plain.contains("0 years, 0 days"));
853 assert!(plain.contains("Up-Time: "));
854 assert!(plain.contains("%20"));
855 }
856
857 #[test]
858 fn test_to_plain_format_with_body() {
859 let mut event = EslEvent::with_type(EslEventType::BackgroundJob);
860 event.set_header("Event-Name", "BACKGROUND_JOB");
861 event.set_header("Job-UUID", "def-456");
862 event.set_body("+OK result\n".to_string());
863
864 let plain = event.to_plain_format();
865
866 assert!(plain.contains("Content-Length: 11\n"));
867 assert!(plain.ends_with("\n\n+OK result\n"));
868 }
869
870 #[test]
871 fn test_set_priority_normal() {
872 let mut event = EslEvent::new();
873 event.set_priority(EslEventPriority::Normal);
874 assert_eq!(
875 event
876 .priority()
877 .unwrap(),
878 Some(EslEventPriority::Normal)
879 );
880 assert_eq!(event.header(EventHeader::Priority), Some("NORMAL"));
881 }
882
883 #[test]
884 fn test_set_priority_high() {
885 let mut event = EslEvent::new();
886 event.set_priority(EslEventPriority::High);
887 assert_eq!(
888 event
889 .priority()
890 .unwrap(),
891 Some(EslEventPriority::High)
892 );
893 assert_eq!(event.header(EventHeader::Priority), Some("HIGH"));
894 }
895
896 #[test]
897 fn test_priority_display() {
898 assert_eq!(EslEventPriority::Normal.to_string(), "NORMAL");
899 assert_eq!(EslEventPriority::Low.to_string(), "LOW");
900 assert_eq!(EslEventPriority::High.to_string(), "HIGH");
901 }
902
903 #[test]
904 fn test_priority_from_str() {
905 assert_eq!(
906 "NORMAL".parse::<EslEventPriority>(),
907 Ok(EslEventPriority::Normal)
908 );
909 assert_eq!("LOW".parse::<EslEventPriority>(), Ok(EslEventPriority::Low));
910 assert_eq!(
911 "HIGH".parse::<EslEventPriority>(),
912 Ok(EslEventPriority::High)
913 );
914 assert!("INVALID"
915 .parse::<EslEventPriority>()
916 .is_err());
917 }
918
919 #[test]
920 fn test_priority_from_str_rejects_wrong_case() {
921 assert!("normal"
922 .parse::<EslEventPriority>()
923 .is_err());
924 assert!("Low"
925 .parse::<EslEventPriority>()
926 .is_err());
927 assert!("hIgH"
928 .parse::<EslEventPriority>()
929 .is_err());
930 }
931
932 #[test]
933 fn test_push_header_new() {
934 let mut event = EslEvent::new();
935 event.push_header("X-Test", "first");
936 assert_eq!(event.header_str("X-Test"), Some("first"));
937 }
938
939 #[test]
940 fn test_push_header_existing_plain() {
941 let mut event = EslEvent::new();
942 event.set_header("X-Test", "first");
943 event.push_header("X-Test", "second");
944 assert_eq!(event.header_str("X-Test"), Some("ARRAY::first|:second"));
945 }
946
947 #[test]
948 fn test_push_header_existing_array() {
949 let mut event = EslEvent::new();
950 event.set_header("X-Test", "ARRAY::a|:b");
951 event.push_header("X-Test", "c");
952 assert_eq!(event.header_str("X-Test"), Some("ARRAY::a|:b|:c"));
953 }
954
955 #[test]
956 fn test_unshift_header_new() {
957 let mut event = EslEvent::new();
958 event.unshift_header("X-Test", "only");
959 assert_eq!(event.header_str("X-Test"), Some("only"));
960 }
961
962 #[test]
963 fn test_unshift_header_existing_array() {
964 let mut event = EslEvent::new();
965 event.set_header("X-Test", "ARRAY::b|:c");
966 event.unshift_header("X-Test", "a");
967 assert_eq!(event.header_str("X-Test"), Some("ARRAY::a|:b|:c"));
968 }
969
970 #[test]
971 fn test_sendevent_with_priority_wire_format() {
972 let mut event = EslEvent::with_type(EslEventType::Custom);
973 event.set_header("Event-Name", "CUSTOM");
974 event.set_header("Event-Subclass", "test::priority");
975 event.set_priority(EslEventPriority::High);
976
977 let plain = event.to_plain_format();
978 assert!(plain.contains("priority: HIGH\n"));
979 }
980
981 #[test]
982 fn test_convenience_accessors() {
983 let mut event = EslEvent::new();
984 event.set_header("Channel-Name", "sofia/internal/1000@example.com");
985 event.set_header("Caller-Caller-ID-Number", "1000");
986 event.set_header("Caller-Caller-ID-Name", "Alice");
987 event.set_header("Hangup-Cause", "NORMAL_CLEARING");
988 event.set_header("Event-Subclass", "sofia::register");
989 event.set_header("variable_sip_from_display", "Bob");
990
991 assert_eq!(
992 event.channel_name(),
993 Some("sofia/internal/1000@example.com")
994 );
995 assert_eq!(event.caller_id_number(), Some("1000"));
996 assert_eq!(event.caller_id_name(), Some("Alice"));
997 assert_eq!(
998 event
999 .hangup_cause()
1000 .unwrap(),
1001 Some(crate::channel::HangupCause::NormalClearing)
1002 );
1003 assert_eq!(event.event_subclass(), Some("sofia::register"));
1004 assert_eq!(event.variable_str("sip_from_display"), Some("Bob"));
1005 assert_eq!(event.variable_str("nonexistent"), None);
1006 }
1007
1008 #[test]
1009 fn test_event_format_from_str() {
1010 assert_eq!("plain".parse::<EventFormat>(), Ok(EventFormat::Plain));
1011 assert_eq!("json".parse::<EventFormat>(), Ok(EventFormat::Json));
1012 assert_eq!("xml".parse::<EventFormat>(), Ok(EventFormat::Xml));
1013 assert!("foo"
1014 .parse::<EventFormat>()
1015 .is_err());
1016 }
1017
1018 #[test]
1019 fn test_event_format_from_str_case_insensitive() {
1020 assert_eq!("PLAIN".parse::<EventFormat>(), Ok(EventFormat::Plain));
1021 assert_eq!("Json".parse::<EventFormat>(), Ok(EventFormat::Json));
1022 assert_eq!("XML".parse::<EventFormat>(), Ok(EventFormat::Xml));
1023 assert_eq!("Xml".parse::<EventFormat>(), Ok(EventFormat::Xml));
1024 }
1025
1026 #[test]
1027 fn test_event_format_from_content_type() {
1028 assert_eq!(
1029 EventFormat::from_content_type("text/event-json"),
1030 Ok(EventFormat::Json)
1031 );
1032 assert_eq!(
1033 EventFormat::from_content_type("text/event-xml"),
1034 Ok(EventFormat::Xml)
1035 );
1036 assert_eq!(
1037 EventFormat::from_content_type("text/event-plain"),
1038 Ok(EventFormat::Plain)
1039 );
1040 assert!(EventFormat::from_content_type("unknown").is_err());
1041 }
1042
1043 #[test]
1046 fn test_event_channel_state_accessor() {
1047 use crate::channel::ChannelState;
1048 let mut event = EslEvent::new();
1049 event.set_header("Channel-State", "CS_EXECUTE");
1050 assert_eq!(
1051 event
1052 .channel_state()
1053 .unwrap(),
1054 Some(ChannelState::CsExecute)
1055 );
1056 }
1057
1058 #[test]
1059 fn test_event_channel_state_number_accessor() {
1060 use crate::channel::ChannelState;
1061 let mut event = EslEvent::new();
1062 event.set_header("Channel-State-Number", "4");
1063 assert_eq!(
1064 event
1065 .channel_state_number()
1066 .unwrap(),
1067 Some(ChannelState::CsExecute)
1068 );
1069 }
1070
1071 #[test]
1072 fn test_event_call_state_accessor() {
1073 use crate::channel::CallState;
1074 let mut event = EslEvent::new();
1075 event.set_header("Channel-Call-State", "ACTIVE");
1076 assert_eq!(
1077 event
1078 .call_state()
1079 .unwrap(),
1080 Some(CallState::Active)
1081 );
1082 }
1083
1084 #[test]
1085 fn test_event_answer_state_accessor() {
1086 use crate::channel::AnswerState;
1087 let mut event = EslEvent::new();
1088 event.set_header("Answer-State", "answered");
1089 assert_eq!(
1090 event
1091 .answer_state()
1092 .unwrap(),
1093 Some(AnswerState::Answered)
1094 );
1095 }
1096
1097 #[test]
1098 fn test_event_call_direction_accessor() {
1099 use crate::channel::CallDirection;
1100 let mut event = EslEvent::new();
1101 event.set_header("Call-Direction", "inbound");
1102 assert_eq!(
1103 event
1104 .call_direction()
1105 .unwrap(),
1106 Some(CallDirection::Inbound)
1107 );
1108 }
1109
1110 #[test]
1111 fn test_event_typed_accessors_missing_headers() {
1112 let event = EslEvent::new();
1113 assert_eq!(
1114 event
1115 .channel_state()
1116 .unwrap(),
1117 None
1118 );
1119 assert_eq!(
1120 event
1121 .channel_state_number()
1122 .unwrap(),
1123 None
1124 );
1125 assert_eq!(
1126 event
1127 .call_state()
1128 .unwrap(),
1129 None
1130 );
1131 assert_eq!(
1132 event
1133 .answer_state()
1134 .unwrap(),
1135 None
1136 );
1137 assert_eq!(
1138 event
1139 .call_direction()
1140 .unwrap(),
1141 None
1142 );
1143 }
1144
1145 #[test]
1148 fn test_sip_p_asserted_identity_comma_separated() {
1149 let mut event = EslEvent::new();
1150 event.set_header(
1153 "variable_sip_P-Asserted-Identity",
1154 "<sip:alice@atlanta.example.com>, <tel:+15551234567>",
1155 );
1156
1157 assert_eq!(
1158 event.variable_str("sip_P-Asserted-Identity"),
1159 Some("<sip:alice@atlanta.example.com>, <tel:+15551234567>")
1160 );
1161 }
1162
1163 #[test]
1164 fn test_sip_p_asserted_identity_array_format() {
1165 let mut event = EslEvent::new();
1166 event.push_header(
1168 "variable_sip_P-Asserted-Identity",
1169 "<sip:alice@atlanta.example.com>",
1170 );
1171 event.push_header("variable_sip_P-Asserted-Identity", "<tel:+15551234567>");
1172
1173 let raw = event
1174 .header_str("variable_sip_P-Asserted-Identity")
1175 .unwrap();
1176 assert_eq!(
1177 raw,
1178 "ARRAY::<sip:alice@atlanta.example.com>|:<tel:+15551234567>"
1179 );
1180
1181 let arr = crate::variables::EslArray::parse(raw).unwrap();
1182 assert_eq!(arr.len(), 2);
1183 assert_eq!(arr.items()[0], "<sip:alice@atlanta.example.com>");
1184 assert_eq!(arr.items()[1], "<tel:+15551234567>");
1185 }
1186
1187 #[test]
1188 fn test_sip_header_with_colons_in_uri() {
1189 let mut event = EslEvent::new();
1190 event.push_header(
1192 "variable_sip_h_Diversion",
1193 "<sip:+15551234567@gw.example.com;reason=unconditional>",
1194 );
1195 event.push_header(
1196 "variable_sip_h_Diversion",
1197 "<sips:+15559876543@secure.example.com;reason=no-answer;counter=3>",
1198 );
1199
1200 let raw = event
1201 .header_str("variable_sip_h_Diversion")
1202 .unwrap();
1203 let arr = crate::variables::EslArray::parse(raw).unwrap();
1204 assert_eq!(arr.len(), 2);
1205 assert_eq!(
1206 arr.items()[0],
1207 "<sip:+15551234567@gw.example.com;reason=unconditional>"
1208 );
1209 assert_eq!(
1210 arr.items()[1],
1211 "<sips:+15559876543@secure.example.com;reason=no-answer;counter=3>"
1212 );
1213 }
1214
1215 #[test]
1216 fn test_sip_p_asserted_identity_plain_format_round_trip() {
1217 let mut event = EslEvent::with_type(EslEventType::ChannelCreate);
1218 event.set_header("Event-Name", "CHANNEL_CREATE");
1219 event.set_header(
1220 "variable_sip_P-Asserted-Identity",
1221 "<sip:alice@atlanta.example.com>, <tel:+15551234567>",
1222 );
1223
1224 let plain = event.to_plain_format();
1225 assert!(plain.contains("variable_sip_P-Asserted-Identity:"));
1227 assert!(!plain.contains("<sip:alice"));
1229 }
1230
1231 #[test]
1236 fn set_header_normalizes_known_enum_variant() {
1237 let mut event = EslEvent::new();
1238 event.set_header("unique-id", "abc-123");
1239 assert_eq!(event.header(EventHeader::UniqueId), Some("abc-123"));
1240 }
1241
1242 #[test]
1243 fn set_header_normalizes_codec_header() {
1244 let mut event = EslEvent::new();
1245 event.set_header("channel-read-codec-bit-rate", "128000");
1246 assert_eq!(
1247 event.header(EventHeader::ChannelReadCodecBitRate),
1248 Some("128000")
1249 );
1250 }
1251
1252 #[test]
1253 fn header_str_finds_by_original_key() {
1254 let mut event = EslEvent::new();
1255 event.set_header("unique-id", "abc-123");
1256 assert_eq!(event.header_str("unique-id"), Some("abc-123"));
1258 assert_eq!(event.header_str("Unique-ID"), Some("abc-123"));
1260 }
1261
1262 #[test]
1263 fn header_str_finds_unknown_dash_header_by_original() {
1264 let mut event = EslEvent::new();
1265 event.set_header("x-custom-header", "val");
1266 assert_eq!(event.header_str("X-Custom-Header"), Some("val"));
1268 assert_eq!(event.header_str("x-custom-header"), Some("val"));
1270 }
1271
1272 #[test]
1273 fn set_header_underscore_passthrough_preserves_sip_h() {
1274 let mut event = EslEvent::new();
1275 event.set_header("variable_sip_h_X-My-CUSTOM-Header", "val");
1276 assert_eq!(
1277 event.header_str("variable_sip_h_X-My-CUSTOM-Header"),
1278 Some("val")
1279 );
1280 }
1281
1282 #[test]
1283 fn set_header_different_casing_overwrites() {
1284 let mut event = EslEvent::new();
1285 event.set_header("Unique-ID", "first");
1286 event.set_header("unique-id", "second");
1287 assert_eq!(event.header(EventHeader::UniqueId), Some("second"));
1289 }
1290
1291 #[test]
1292 fn remove_header_by_original_key() {
1293 let mut event = EslEvent::new();
1294 event.set_header("unique-id", "abc-123");
1295 let removed = event.remove_header("unique-id");
1296 assert_eq!(removed, Some("abc-123".to_string()));
1297 assert_eq!(event.header(EventHeader::UniqueId), None);
1298 }
1299
1300 #[test]
1301 fn remove_header_by_canonical_key() {
1302 let mut event = EslEvent::new();
1303 event.set_header("unique-id", "abc-123");
1304 let removed = event.remove_header("Unique-ID");
1305 assert_eq!(removed, Some("abc-123".to_string()));
1306 assert_eq!(event.header_str("unique-id"), None);
1307 }
1308
1309 #[test]
1310 fn serde_round_trip_preserves_canonical_lookups() {
1311 let mut event = EslEvent::new();
1312 event.set_header("unique-id", "abc-123");
1313 event.set_header("channel-read-codec-bit-rate", "128000");
1314 let json = serde_json::to_string(&event).unwrap();
1315 let deserialized: EslEvent = serde_json::from_str(&json).unwrap();
1316 assert_eq!(deserialized.header(EventHeader::UniqueId), Some("abc-123"));
1317 assert_eq!(
1318 deserialized.header(EventHeader::ChannelReadCodecBitRate),
1319 Some("128000")
1320 );
1321 }
1322
1323 #[test]
1324 fn serde_deserialize_normalizes_external_json() {
1325 let json = r#"{"event_type":null,"headers":{"unique-id":"abc-123","channel-read-codec-bit-rate":"128000"},"body":null}"#;
1326 let event: EslEvent = serde_json::from_str(json).unwrap();
1327 assert_eq!(event.header(EventHeader::UniqueId), Some("abc-123"));
1328 assert_eq!(
1329 event.header(EventHeader::ChannelReadCodecBitRate),
1330 Some("128000")
1331 );
1332 assert_eq!(event.header_str("unique-id"), Some("abc-123"));
1333 }
1334
1335 #[test]
1336 fn test_event_typed_accessors_invalid_values() {
1337 let mut event = EslEvent::new();
1338 event.set_header("Channel-State", "BOGUS");
1339 event.set_header("Channel-State-Number", "999");
1340 event.set_header("Channel-Call-State", "BOGUS");
1341 event.set_header("Answer-State", "bogus");
1342 event.set_header("Call-Direction", "bogus");
1343 assert!(event
1344 .channel_state()
1345 .is_err());
1346 assert!(event
1347 .channel_state_number()
1348 .is_err());
1349 assert!(event
1350 .call_state()
1351 .is_err());
1352 assert!(event
1353 .answer_state()
1354 .is_err());
1355 assert!(event
1356 .call_direction()
1357 .is_err());
1358 }
1359}