1use crate::headers::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, PartialEq, Eq, Serialize, Deserialize)]
445pub struct EslEvent {
446 event_type: Option<EslEventType>,
447 headers: HashMap<String, String>,
448 body: Option<String>,
449}
450
451impl EslEvent {
452 pub fn new() -> Self {
454 Self {
455 event_type: None,
456 headers: HashMap::new(),
457 body: None,
458 }
459 }
460
461 pub fn with_type(event_type: EslEventType) -> Self {
463 Self {
464 event_type: Some(event_type),
465 headers: HashMap::new(),
466 body: None,
467 }
468 }
469
470 pub fn event_type(&self) -> Option<EslEventType> {
472 self.event_type
473 }
474
475 pub fn set_event_type(&mut self, event_type: Option<EslEventType>) {
477 self.event_type = event_type;
478 }
479
480 pub fn header(&self, name: EventHeader) -> Option<&str> {
484 self.headers
485 .get(name.as_str())
486 .map(|s| s.as_str())
487 }
488
489 pub fn header_str(&self, name: &str) -> Option<&str> {
496 self.headers
497 .get(name)
498 .map(|s| s.as_str())
499 }
500
501 pub fn variable_str(&self, name: &str) -> Option<&str> {
506 let key = format!("variable_{}", name);
507 self.header_str(&key)
508 }
509
510 pub fn headers(&self) -> &HashMap<String, String> {
512 &self.headers
513 }
514
515 pub fn set_header(&mut self, name: impl Into<String>, value: impl Into<String>) {
517 self.headers
518 .insert(name.into(), value.into());
519 }
520
521 pub fn remove_header(&mut self, name: impl AsRef<str>) -> Option<String> {
523 self.headers
524 .remove(name.as_ref())
525 }
526
527 pub fn body(&self) -> Option<&str> {
529 self.body
530 .as_deref()
531 }
532
533 pub fn set_body(&mut self, body: impl Into<String>) {
535 self.body = Some(body.into());
536 }
537
538 pub fn set_priority(&mut self, priority: EslEventPriority) {
543 self.set_header(EventHeader::Priority.as_str(), priority.to_string());
544 }
545
546 pub fn push_header(&mut self, name: &str, value: &str) {
560 self.stack_header(name, value, EslArray::push);
561 }
562
563 pub fn unshift_header(&mut self, name: &str, value: &str) {
575 self.stack_header(name, value, EslArray::unshift);
576 }
577
578 fn stack_header(&mut self, name: &str, value: &str, op: fn(&mut EslArray, String)) {
579 match self
580 .headers
581 .get(name)
582 {
583 None => {
584 self.set_header(name, value);
585 }
586 Some(existing) => {
587 let mut arr = match EslArray::parse(existing) {
588 Some(arr) => arr,
589 None => EslArray::new(vec![existing.clone()]),
590 };
591 op(&mut arr, value.into());
592 self.set_header(name, arr.to_string());
593 }
594 }
595 }
596
597 pub fn is_event_type(&self, event_type: EslEventType) -> bool {
599 self.event_type == Some(event_type)
600 }
601
602 pub fn to_plain_format(&self) -> String {
612 use std::fmt::Write;
613 let mut result = String::new();
614
615 let event_name_key = EventHeader::EventName.as_str();
616 if let Some(event_name) = self
617 .headers
618 .get(event_name_key)
619 {
620 let _ = writeln!(
621 result,
622 "{}: {}",
623 event_name_key,
624 percent_encode(event_name.as_bytes(), NON_ALPHANUMERIC)
625 );
626 }
627
628 let mut sorted_headers: Vec<_> = self
629 .headers
630 .iter()
631 .filter(|(k, _)| k.as_str() != event_name_key && k.as_str() != "Content-Length")
632 .collect();
633 sorted_headers.sort_by_key(|(k, _)| k.as_str());
634
635 for (key, value) in sorted_headers {
636 let _ = writeln!(
637 result,
638 "{}: {}",
639 key,
640 percent_encode(value.as_bytes(), NON_ALPHANUMERIC)
641 );
642 }
643
644 if let Some(body) = &self.body {
645 let _ = writeln!(result, "Content-Length: {}", body.len());
646 result.push('\n');
647 result.push_str(body);
648 } else {
649 result.push('\n');
650 }
651
652 result
653 }
654}
655
656impl Default for EslEvent {
657 fn default() -> Self {
658 Self::new()
659 }
660}
661
662impl HeaderLookup for EslEvent {
663 fn header_str(&self, name: &str) -> Option<&str> {
664 self.headers
665 .get(name)
666 .map(|s| s.as_str())
667 }
668
669 fn variable_str(&self, name: &str) -> Option<&str> {
670 let key = format!("variable_{}", name);
671 self.header_str(&key)
672 }
673}
674
675#[cfg(test)]
676mod tests {
677 use super::*;
678
679 #[test]
680 fn test_notify_in_parse() {
681 assert_eq!(
682 EslEventType::parse_event_type("NOTIFY_IN"),
683 Some(EslEventType::NotifyIn)
684 );
685 assert_eq!(EslEventType::parse_event_type("notify_in"), None);
686 }
687
688 #[test]
689 fn test_notify_in_display() {
690 assert_eq!(EslEventType::NotifyIn.to_string(), "NOTIFY_IN");
691 }
692
693 #[test]
694 fn test_notify_in_distinct_from_notify() {
695 assert_ne!(EslEventType::Notify, EslEventType::NotifyIn);
696 assert_ne!(
697 EslEventType::Notify.to_string(),
698 EslEventType::NotifyIn.to_string()
699 );
700 }
701
702 #[test]
703 fn test_wire_names_match_c_esl() {
704 assert_eq!(
705 EslEventType::ChannelOutgoing.to_string(),
706 "CHANNEL_OUTGOING"
707 );
708 assert_eq!(EslEventType::Api.to_string(), "API");
709 assert_eq!(EslEventType::ReloadXml.to_string(), "RELOADXML");
710 assert_eq!(EslEventType::PresenceIn.to_string(), "PRESENCE_IN");
711 assert_eq!(EslEventType::Roster.to_string(), "ROSTER");
712 assert_eq!(EslEventType::Text.to_string(), "TEXT");
713 assert_eq!(EslEventType::ReSchedule.to_string(), "RE_SCHEDULE");
714
715 assert_eq!(
716 EslEventType::parse_event_type("CHANNEL_OUTGOING"),
717 Some(EslEventType::ChannelOutgoing)
718 );
719 assert_eq!(
720 EslEventType::parse_event_type("API"),
721 Some(EslEventType::Api)
722 );
723 assert_eq!(
724 EslEventType::parse_event_type("RELOADXML"),
725 Some(EslEventType::ReloadXml)
726 );
727 assert_eq!(
728 EslEventType::parse_event_type("PRESENCE_IN"),
729 Some(EslEventType::PresenceIn)
730 );
731 }
732
733 #[test]
734 fn test_event_type_from_str() {
735 assert_eq!(
736 "CHANNEL_ANSWER".parse::<EslEventType>(),
737 Ok(EslEventType::ChannelAnswer)
738 );
739 assert!("channel_answer"
740 .parse::<EslEventType>()
741 .is_err());
742 assert!("UNKNOWN_EVENT"
743 .parse::<EslEventType>()
744 .is_err());
745 }
746
747 #[test]
748 fn test_remove_header() {
749 let mut event = EslEvent::new();
750 event.set_header("Foo", "bar");
751 event.set_header("Baz", "qux");
752
753 let removed = event.remove_header("Foo");
754 assert_eq!(removed, Some("bar".to_string()));
755 assert!(event
756 .header_str("Foo")
757 .is_none());
758 assert_eq!(event.header_str("Baz"), Some("qux"));
759
760 let removed_again = event.remove_header("Foo");
761 assert_eq!(removed_again, None);
762 }
763
764 #[test]
765 fn test_to_plain_format_basic() {
766 let mut event = EslEvent::with_type(EslEventType::Heartbeat);
767 event.set_header("Event-Name", "HEARTBEAT");
768 event.set_header("Core-UUID", "abc-123");
769
770 let plain = event.to_plain_format();
771
772 assert!(plain.starts_with("Event-Name: "));
773 assert!(plain.contains("Core-UUID: "));
774 assert!(plain.ends_with("\n\n"));
775 }
776
777 #[test]
778 fn test_to_plain_format_percent_encoding() {
779 let mut event = EslEvent::with_type(EslEventType::Heartbeat);
780 event.set_header("Event-Name", "HEARTBEAT");
781 event.set_header("Up-Time", "0 years, 0 days");
782
783 let plain = event.to_plain_format();
784
785 assert!(!plain.contains("0 years, 0 days"));
786 assert!(plain.contains("Up-Time: "));
787 assert!(plain.contains("%20"));
788 }
789
790 #[test]
791 fn test_to_plain_format_with_body() {
792 let mut event = EslEvent::with_type(EslEventType::BackgroundJob);
793 event.set_header("Event-Name", "BACKGROUND_JOB");
794 event.set_header("Job-UUID", "def-456");
795 event.set_body("+OK result\n".to_string());
796
797 let plain = event.to_plain_format();
798
799 assert!(plain.contains("Content-Length: 11\n"));
800 assert!(plain.ends_with("\n\n+OK result\n"));
801 }
802
803 #[test]
804 fn test_set_priority_normal() {
805 let mut event = EslEvent::new();
806 event.set_priority(EslEventPriority::Normal);
807 assert_eq!(
808 event
809 .priority()
810 .unwrap(),
811 Some(EslEventPriority::Normal)
812 );
813 assert_eq!(event.header(EventHeader::Priority), Some("NORMAL"));
814 }
815
816 #[test]
817 fn test_set_priority_high() {
818 let mut event = EslEvent::new();
819 event.set_priority(EslEventPriority::High);
820 assert_eq!(
821 event
822 .priority()
823 .unwrap(),
824 Some(EslEventPriority::High)
825 );
826 assert_eq!(event.header(EventHeader::Priority), Some("HIGH"));
827 }
828
829 #[test]
830 fn test_priority_display() {
831 assert_eq!(EslEventPriority::Normal.to_string(), "NORMAL");
832 assert_eq!(EslEventPriority::Low.to_string(), "LOW");
833 assert_eq!(EslEventPriority::High.to_string(), "HIGH");
834 }
835
836 #[test]
837 fn test_priority_from_str() {
838 assert_eq!(
839 "NORMAL".parse::<EslEventPriority>(),
840 Ok(EslEventPriority::Normal)
841 );
842 assert_eq!("LOW".parse::<EslEventPriority>(), Ok(EslEventPriority::Low));
843 assert_eq!(
844 "HIGH".parse::<EslEventPriority>(),
845 Ok(EslEventPriority::High)
846 );
847 assert!("INVALID"
848 .parse::<EslEventPriority>()
849 .is_err());
850 }
851
852 #[test]
853 fn test_priority_from_str_rejects_wrong_case() {
854 assert!("normal"
855 .parse::<EslEventPriority>()
856 .is_err());
857 assert!("Low"
858 .parse::<EslEventPriority>()
859 .is_err());
860 assert!("hIgH"
861 .parse::<EslEventPriority>()
862 .is_err());
863 }
864
865 #[test]
866 fn test_push_header_new() {
867 let mut event = EslEvent::new();
868 event.push_header("X-Test", "first");
869 assert_eq!(event.header_str("X-Test"), Some("first"));
870 }
871
872 #[test]
873 fn test_push_header_existing_plain() {
874 let mut event = EslEvent::new();
875 event.set_header("X-Test", "first");
876 event.push_header("X-Test", "second");
877 assert_eq!(event.header_str("X-Test"), Some("ARRAY::first|:second"));
878 }
879
880 #[test]
881 fn test_push_header_existing_array() {
882 let mut event = EslEvent::new();
883 event.set_header("X-Test", "ARRAY::a|:b");
884 event.push_header("X-Test", "c");
885 assert_eq!(event.header_str("X-Test"), Some("ARRAY::a|:b|:c"));
886 }
887
888 #[test]
889 fn test_unshift_header_new() {
890 let mut event = EslEvent::new();
891 event.unshift_header("X-Test", "only");
892 assert_eq!(event.header_str("X-Test"), Some("only"));
893 }
894
895 #[test]
896 fn test_unshift_header_existing_array() {
897 let mut event = EslEvent::new();
898 event.set_header("X-Test", "ARRAY::b|:c");
899 event.unshift_header("X-Test", "a");
900 assert_eq!(event.header_str("X-Test"), Some("ARRAY::a|:b|:c"));
901 }
902
903 #[test]
904 fn test_sendevent_with_priority_wire_format() {
905 let mut event = EslEvent::with_type(EslEventType::Custom);
906 event.set_header("Event-Name", "CUSTOM");
907 event.set_header("Event-Subclass", "test::priority");
908 event.set_priority(EslEventPriority::High);
909
910 let plain = event.to_plain_format();
911 assert!(plain.contains("priority: HIGH\n"));
912 }
913
914 #[test]
915 fn test_convenience_accessors() {
916 let mut event = EslEvent::new();
917 event.set_header("Channel-Name", "sofia/internal/1000@example.com");
918 event.set_header("Caller-Caller-ID-Number", "1000");
919 event.set_header("Caller-Caller-ID-Name", "Alice");
920 event.set_header("Hangup-Cause", "NORMAL_CLEARING");
921 event.set_header("Event-Subclass", "sofia::register");
922 event.set_header("variable_sip_from_display", "Bob");
923
924 assert_eq!(
925 event.channel_name(),
926 Some("sofia/internal/1000@example.com")
927 );
928 assert_eq!(event.caller_id_number(), Some("1000"));
929 assert_eq!(event.caller_id_name(), Some("Alice"));
930 assert_eq!(
931 event
932 .hangup_cause()
933 .unwrap(),
934 Some(crate::channel::HangupCause::NormalClearing)
935 );
936 assert_eq!(event.event_subclass(), Some("sofia::register"));
937 assert_eq!(event.variable_str("sip_from_display"), Some("Bob"));
938 assert_eq!(event.variable_str("nonexistent"), None);
939 }
940
941 #[test]
942 fn test_event_format_from_str() {
943 assert_eq!("plain".parse::<EventFormat>(), Ok(EventFormat::Plain));
944 assert_eq!("json".parse::<EventFormat>(), Ok(EventFormat::Json));
945 assert_eq!("xml".parse::<EventFormat>(), Ok(EventFormat::Xml));
946 assert!("foo"
947 .parse::<EventFormat>()
948 .is_err());
949 }
950
951 #[test]
952 fn test_event_format_from_str_case_insensitive() {
953 assert_eq!("PLAIN".parse::<EventFormat>(), Ok(EventFormat::Plain));
954 assert_eq!("Json".parse::<EventFormat>(), Ok(EventFormat::Json));
955 assert_eq!("XML".parse::<EventFormat>(), Ok(EventFormat::Xml));
956 assert_eq!("Xml".parse::<EventFormat>(), Ok(EventFormat::Xml));
957 }
958
959 #[test]
960 fn test_event_format_from_content_type() {
961 assert_eq!(
962 EventFormat::from_content_type("text/event-json"),
963 Ok(EventFormat::Json)
964 );
965 assert_eq!(
966 EventFormat::from_content_type("text/event-xml"),
967 Ok(EventFormat::Xml)
968 );
969 assert_eq!(
970 EventFormat::from_content_type("text/event-plain"),
971 Ok(EventFormat::Plain)
972 );
973 assert!(EventFormat::from_content_type("unknown").is_err());
974 }
975
976 #[test]
979 fn test_event_channel_state_accessor() {
980 use crate::channel::ChannelState;
981 let mut event = EslEvent::new();
982 event.set_header("Channel-State", "CS_EXECUTE");
983 assert_eq!(
984 event
985 .channel_state()
986 .unwrap(),
987 Some(ChannelState::CsExecute)
988 );
989 }
990
991 #[test]
992 fn test_event_channel_state_number_accessor() {
993 use crate::channel::ChannelState;
994 let mut event = EslEvent::new();
995 event.set_header("Channel-State-Number", "4");
996 assert_eq!(
997 event
998 .channel_state_number()
999 .unwrap(),
1000 Some(ChannelState::CsExecute)
1001 );
1002 }
1003
1004 #[test]
1005 fn test_event_call_state_accessor() {
1006 use crate::channel::CallState;
1007 let mut event = EslEvent::new();
1008 event.set_header("Channel-Call-State", "ACTIVE");
1009 assert_eq!(
1010 event
1011 .call_state()
1012 .unwrap(),
1013 Some(CallState::Active)
1014 );
1015 }
1016
1017 #[test]
1018 fn test_event_answer_state_accessor() {
1019 use crate::channel::AnswerState;
1020 let mut event = EslEvent::new();
1021 event.set_header("Answer-State", "answered");
1022 assert_eq!(
1023 event
1024 .answer_state()
1025 .unwrap(),
1026 Some(AnswerState::Answered)
1027 );
1028 }
1029
1030 #[test]
1031 fn test_event_call_direction_accessor() {
1032 use crate::channel::CallDirection;
1033 let mut event = EslEvent::new();
1034 event.set_header("Call-Direction", "inbound");
1035 assert_eq!(
1036 event
1037 .call_direction()
1038 .unwrap(),
1039 Some(CallDirection::Inbound)
1040 );
1041 }
1042
1043 #[test]
1044 fn test_event_typed_accessors_missing_headers() {
1045 let event = EslEvent::new();
1046 assert_eq!(
1047 event
1048 .channel_state()
1049 .unwrap(),
1050 None
1051 );
1052 assert_eq!(
1053 event
1054 .channel_state_number()
1055 .unwrap(),
1056 None
1057 );
1058 assert_eq!(
1059 event
1060 .call_state()
1061 .unwrap(),
1062 None
1063 );
1064 assert_eq!(
1065 event
1066 .answer_state()
1067 .unwrap(),
1068 None
1069 );
1070 assert_eq!(
1071 event
1072 .call_direction()
1073 .unwrap(),
1074 None
1075 );
1076 }
1077
1078 #[test]
1081 fn test_sip_p_asserted_identity_comma_separated() {
1082 let mut event = EslEvent::new();
1083 event.set_header(
1086 "variable_sip_P-Asserted-Identity",
1087 "<sip:alice@atlanta.example.com>, <tel:+15551234567>",
1088 );
1089
1090 assert_eq!(
1091 event.variable_str("sip_P-Asserted-Identity"),
1092 Some("<sip:alice@atlanta.example.com>, <tel:+15551234567>")
1093 );
1094 }
1095
1096 #[test]
1097 fn test_sip_p_asserted_identity_array_format() {
1098 let mut event = EslEvent::new();
1099 event.push_header(
1101 "variable_sip_P-Asserted-Identity",
1102 "<sip:alice@atlanta.example.com>",
1103 );
1104 event.push_header("variable_sip_P-Asserted-Identity", "<tel:+15551234567>");
1105
1106 let raw = event
1107 .header_str("variable_sip_P-Asserted-Identity")
1108 .unwrap();
1109 assert_eq!(
1110 raw,
1111 "ARRAY::<sip:alice@atlanta.example.com>|:<tel:+15551234567>"
1112 );
1113
1114 let arr = crate::variables::EslArray::parse(raw).unwrap();
1115 assert_eq!(arr.len(), 2);
1116 assert_eq!(arr.items()[0], "<sip:alice@atlanta.example.com>");
1117 assert_eq!(arr.items()[1], "<tel:+15551234567>");
1118 }
1119
1120 #[test]
1121 fn test_sip_header_with_colons_in_uri() {
1122 let mut event = EslEvent::new();
1123 event.push_header(
1125 "variable_sip_h_Diversion",
1126 "<sip:+15551234567@gw.example.com;reason=unconditional>",
1127 );
1128 event.push_header(
1129 "variable_sip_h_Diversion",
1130 "<sips:+15559876543@secure.example.com;reason=no-answer;counter=3>",
1131 );
1132
1133 let raw = event
1134 .header_str("variable_sip_h_Diversion")
1135 .unwrap();
1136 let arr = crate::variables::EslArray::parse(raw).unwrap();
1137 assert_eq!(arr.len(), 2);
1138 assert_eq!(
1139 arr.items()[0],
1140 "<sip:+15551234567@gw.example.com;reason=unconditional>"
1141 );
1142 assert_eq!(
1143 arr.items()[1],
1144 "<sips:+15559876543@secure.example.com;reason=no-answer;counter=3>"
1145 );
1146 }
1147
1148 #[test]
1149 fn test_sip_p_asserted_identity_plain_format_round_trip() {
1150 let mut event = EslEvent::with_type(EslEventType::ChannelCreate);
1151 event.set_header("Event-Name", "CHANNEL_CREATE");
1152 event.set_header(
1153 "variable_sip_P-Asserted-Identity",
1154 "<sip:alice@atlanta.example.com>, <tel:+15551234567>",
1155 );
1156
1157 let plain = event.to_plain_format();
1158 assert!(plain.contains("variable_sip_P-Asserted-Identity:"));
1160 assert!(!plain.contains("<sip:alice"));
1162 }
1163
1164 #[test]
1165 fn test_event_typed_accessors_invalid_values() {
1166 let mut event = EslEvent::new();
1167 event.set_header("Channel-State", "BOGUS");
1168 event.set_header("Channel-State-Number", "999");
1169 event.set_header("Channel-Call-State", "BOGUS");
1170 event.set_header("Answer-State", "bogus");
1171 event.set_header("Call-Direction", "bogus");
1172 assert!(event
1173 .channel_state()
1174 .is_err());
1175 assert!(event
1176 .channel_state_number()
1177 .is_err());
1178 assert!(event
1179 .call_state()
1180 .is_err());
1181 assert!(event
1182 .answer_state()
1183 .is_err());
1184 assert!(event
1185 .call_direction()
1186 .is_err());
1187 }
1188}