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