1use crate::headers::{normalize_header_key, EventHeader};
4use crate::lookup::HeaderLookup;
5use crate::variables::EslArray;
6use indexmap::IndexMap;
7use percent_encoding::{percent_encode, NON_ALPHANUMERIC};
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: IndexMap<String, String>,
451 #[cfg_attr(feature = "serde", serde(skip))]
452 original_keys: IndexMap<String, String>,
453 body: Option<String>,
454}
455
456impl EslEvent {
457 pub fn new() -> Self {
459 Self {
460 event_type: None,
461 headers: IndexMap::new(),
462 original_keys: IndexMap::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: IndexMap::new(),
472 original_keys: IndexMap::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) -> &IndexMap<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 .shift_remove(name)
551 {
552 return Some(value);
553 }
554 if let Some(normalized) = self
555 .original_keys
556 .shift_remove(name)
557 {
558 return self
559 .headers
560 .shift_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 for (key, value) in &self.headers {
654 if key == "Content-Length" {
655 continue;
656 }
657 let _ = writeln!(
658 result,
659 "{}: {}",
660 key,
661 percent_encode(value.as_bytes(), NON_ALPHANUMERIC)
662 );
663 }
664
665 if let Some(body) = &self.body {
666 let _ = writeln!(result, "Content-Length: {}", body.len());
667 result.push('\n');
668 result.push_str(body);
669 } else {
670 result.push('\n');
671 }
672
673 result
674 }
675}
676
677impl Default for EslEvent {
678 fn default() -> Self {
679 Self::new()
680 }
681}
682
683impl HeaderLookup for EslEvent {
684 fn header_str(&self, name: &str) -> Option<&str> {
685 EslEvent::header_str(self, name)
686 }
687
688 fn variable_str(&self, name: &str) -> Option<&str> {
689 let key = format!("variable_{}", name);
690 self.header_str(&key)
691 }
692}
693
694impl PartialEq for EslEvent {
695 fn eq(&self, other: &Self) -> bool {
696 self.event_type == other.event_type
697 && self.headers == other.headers
698 && self.body == other.body
699 }
700}
701
702impl std::hash::Hash for EslEvent {
703 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
704 self.event_type
705 .hash(state);
706 for (k, v) in &self.headers {
707 k.hash(state);
708 v.hash(state);
709 }
710 self.body
711 .hash(state);
712 }
713}
714
715#[cfg(feature = "serde")]
716impl<'de> serde::Deserialize<'de> for EslEvent {
717 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
718 where
719 D: serde::Deserializer<'de>,
720 {
721 #[derive(serde::Deserialize)]
722 struct Raw {
723 event_type: Option<EslEventType>,
724 headers: IndexMap<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 headers_preserve_insertion_order() {
744 let mut event = EslEvent::new();
745 event.set_header("Zebra", "last");
746 event.set_header("Alpha", "first");
747 event.set_header("Middle", "mid");
748 let keys: Vec<&str> = event
749 .headers()
750 .keys()
751 .map(|s| s.as_str())
752 .collect();
753 assert_eq!(keys, vec!["Zebra", "Alpha", "Middle"]);
754 }
755
756 #[test]
757 fn test_notify_in_parse() {
758 assert_eq!(
759 EslEventType::parse_event_type("NOTIFY_IN"),
760 Some(EslEventType::NotifyIn)
761 );
762 assert_eq!(EslEventType::parse_event_type("notify_in"), None);
763 }
764
765 #[test]
766 fn test_notify_in_display() {
767 assert_eq!(EslEventType::NotifyIn.to_string(), "NOTIFY_IN");
768 }
769
770 #[test]
771 fn test_notify_in_distinct_from_notify() {
772 assert_ne!(EslEventType::Notify, EslEventType::NotifyIn);
773 assert_ne!(
774 EslEventType::Notify.to_string(),
775 EslEventType::NotifyIn.to_string()
776 );
777 }
778
779 #[test]
780 fn test_wire_names_match_c_esl() {
781 assert_eq!(
782 EslEventType::ChannelOutgoing.to_string(),
783 "CHANNEL_OUTGOING"
784 );
785 assert_eq!(EslEventType::Api.to_string(), "API");
786 assert_eq!(EslEventType::ReloadXml.to_string(), "RELOADXML");
787 assert_eq!(EslEventType::PresenceIn.to_string(), "PRESENCE_IN");
788 assert_eq!(EslEventType::Roster.to_string(), "ROSTER");
789 assert_eq!(EslEventType::Text.to_string(), "TEXT");
790 assert_eq!(EslEventType::ReSchedule.to_string(), "RE_SCHEDULE");
791
792 assert_eq!(
793 EslEventType::parse_event_type("CHANNEL_OUTGOING"),
794 Some(EslEventType::ChannelOutgoing)
795 );
796 assert_eq!(
797 EslEventType::parse_event_type("API"),
798 Some(EslEventType::Api)
799 );
800 assert_eq!(
801 EslEventType::parse_event_type("RELOADXML"),
802 Some(EslEventType::ReloadXml)
803 );
804 assert_eq!(
805 EslEventType::parse_event_type("PRESENCE_IN"),
806 Some(EslEventType::PresenceIn)
807 );
808 }
809
810 #[test]
811 fn test_event_type_from_str() {
812 assert_eq!(
813 "CHANNEL_ANSWER".parse::<EslEventType>(),
814 Ok(EslEventType::ChannelAnswer)
815 );
816 assert!("channel_answer"
817 .parse::<EslEventType>()
818 .is_err());
819 assert!("UNKNOWN_EVENT"
820 .parse::<EslEventType>()
821 .is_err());
822 }
823
824 #[test]
825 fn test_remove_header() {
826 let mut event = EslEvent::new();
827 event.set_header("Foo", "bar");
828 event.set_header("Baz", "qux");
829
830 let removed = event.remove_header("Foo");
831 assert_eq!(removed, Some("bar".to_string()));
832 assert!(event
833 .header_str("Foo")
834 .is_none());
835 assert_eq!(event.header_str("Baz"), Some("qux"));
836
837 let removed_again = event.remove_header("Foo");
838 assert_eq!(removed_again, None);
839 }
840
841 #[test]
842 fn test_to_plain_format_basic() {
843 let mut event = EslEvent::with_type(EslEventType::Heartbeat);
844 event.set_header("Event-Name", "HEARTBEAT");
845 event.set_header("Core-UUID", "abc-123");
846
847 let plain = event.to_plain_format();
848
849 assert!(plain.starts_with("Event-Name: "));
850 assert!(plain.contains("Core-UUID: "));
851 assert!(plain.ends_with("\n\n"));
852 }
853
854 #[test]
855 fn test_to_plain_format_percent_encoding() {
856 let mut event = EslEvent::with_type(EslEventType::Heartbeat);
857 event.set_header("Event-Name", "HEARTBEAT");
858 event.set_header("Up-Time", "0 years, 0 days");
859
860 let plain = event.to_plain_format();
861
862 assert!(!plain.contains("0 years, 0 days"));
863 assert!(plain.contains("Up-Time: "));
864 assert!(plain.contains("%20"));
865 }
866
867 #[test]
868 fn test_to_plain_format_with_body() {
869 let mut event = EslEvent::with_type(EslEventType::BackgroundJob);
870 event.set_header("Event-Name", "BACKGROUND_JOB");
871 event.set_header("Job-UUID", "def-456");
872 event.set_body("+OK result\n".to_string());
873
874 let plain = event.to_plain_format();
875
876 assert!(plain.contains("Content-Length: 11\n"));
877 assert!(plain.ends_with("\n\n+OK result\n"));
878 }
879
880 #[test]
881 fn test_to_plain_format_preserves_insertion_order() {
882 let mut event = EslEvent::with_type(EslEventType::Heartbeat);
883 event.set_header("Event-Name", "HEARTBEAT");
884 event.set_header("Core-UUID", "abc-123");
885 event.set_header("FreeSWITCH-Hostname", "fs01");
886 event.set_header("Up-Time", "0 years, 1 day");
887
888 let plain = event.to_plain_format();
889 let lines: Vec<&str> = plain
890 .lines()
891 .collect();
892 assert!(lines[0].starts_with("Event-Name: "));
893 assert!(lines[1].starts_with("Core-UUID: "));
894 assert!(lines[2].starts_with("FreeSWITCH-Hostname: "));
895 assert!(lines[3].starts_with("Up-Time: "));
896 }
897
898 #[test]
899 fn test_to_plain_format_round_trip() {
900 let mut original = EslEvent::with_type(EslEventType::ChannelCreate);
901 original.set_header("Event-Name", "CHANNEL_CREATE");
902 original.set_header("Core-UUID", "abc-123");
903 original.set_header("Channel-Name", "sofia/internal/1000@example.com");
904 original.set_header("Caller-Caller-ID-Name", "Jérôme Poulin");
905 original.set_body("some body content");
906
907 let plain = original.to_plain_format();
908
909 let (header_section, inner_body) = if let Some(pos) = plain.find("\n\n") {
911 (&plain[..pos], Some(&plain[pos + 2..]))
912 } else {
913 (plain.as_str(), None)
914 };
915
916 let mut parsed = EslEvent::new();
917 for line in header_section.lines() {
918 let line = line.trim();
919 if line.is_empty() {
920 continue;
921 }
922 if let Some(colon_pos) = line.find(':') {
923 let key = line[..colon_pos].trim();
924 if key == "Content-Length" {
925 continue;
926 }
927 let raw_value = line[colon_pos + 1..].trim();
928 let value = percent_encoding::percent_decode_str(raw_value)
929 .decode_utf8()
930 .unwrap()
931 .into_owned();
932 parsed.set_header(key, value);
933 }
934 }
935 if let Some(ib) = inner_body {
936 if !ib.is_empty() {
937 parsed.set_body(ib);
938 }
939 }
940
941 assert_eq!(original.headers(), parsed.headers());
942 assert_eq!(original.body(), parsed.body());
943 }
944
945 #[test]
946 fn test_set_priority_normal() {
947 let mut event = EslEvent::new();
948 event.set_priority(EslEventPriority::Normal);
949 assert_eq!(
950 event
951 .priority()
952 .unwrap(),
953 Some(EslEventPriority::Normal)
954 );
955 assert_eq!(event.header(EventHeader::Priority), Some("NORMAL"));
956 }
957
958 #[test]
959 fn test_set_priority_high() {
960 let mut event = EslEvent::new();
961 event.set_priority(EslEventPriority::High);
962 assert_eq!(
963 event
964 .priority()
965 .unwrap(),
966 Some(EslEventPriority::High)
967 );
968 assert_eq!(event.header(EventHeader::Priority), Some("HIGH"));
969 }
970
971 #[test]
972 fn test_priority_display() {
973 assert_eq!(EslEventPriority::Normal.to_string(), "NORMAL");
974 assert_eq!(EslEventPriority::Low.to_string(), "LOW");
975 assert_eq!(EslEventPriority::High.to_string(), "HIGH");
976 }
977
978 #[test]
979 fn test_priority_from_str() {
980 assert_eq!(
981 "NORMAL".parse::<EslEventPriority>(),
982 Ok(EslEventPriority::Normal)
983 );
984 assert_eq!("LOW".parse::<EslEventPriority>(), Ok(EslEventPriority::Low));
985 assert_eq!(
986 "HIGH".parse::<EslEventPriority>(),
987 Ok(EslEventPriority::High)
988 );
989 assert!("INVALID"
990 .parse::<EslEventPriority>()
991 .is_err());
992 }
993
994 #[test]
995 fn test_priority_from_str_rejects_wrong_case() {
996 assert!("normal"
997 .parse::<EslEventPriority>()
998 .is_err());
999 assert!("Low"
1000 .parse::<EslEventPriority>()
1001 .is_err());
1002 assert!("hIgH"
1003 .parse::<EslEventPriority>()
1004 .is_err());
1005 }
1006
1007 #[test]
1008 fn test_push_header_new() {
1009 let mut event = EslEvent::new();
1010 event.push_header("X-Test", "first");
1011 assert_eq!(event.header_str("X-Test"), Some("first"));
1012 }
1013
1014 #[test]
1015 fn test_push_header_existing_plain() {
1016 let mut event = EslEvent::new();
1017 event.set_header("X-Test", "first");
1018 event.push_header("X-Test", "second");
1019 assert_eq!(event.header_str("X-Test"), Some("ARRAY::first|:second"));
1020 }
1021
1022 #[test]
1023 fn test_push_header_existing_array() {
1024 let mut event = EslEvent::new();
1025 event.set_header("X-Test", "ARRAY::a|:b");
1026 event.push_header("X-Test", "c");
1027 assert_eq!(event.header_str("X-Test"), Some("ARRAY::a|:b|:c"));
1028 }
1029
1030 #[test]
1031 fn test_unshift_header_new() {
1032 let mut event = EslEvent::new();
1033 event.unshift_header("X-Test", "only");
1034 assert_eq!(event.header_str("X-Test"), Some("only"));
1035 }
1036
1037 #[test]
1038 fn test_unshift_header_existing_array() {
1039 let mut event = EslEvent::new();
1040 event.set_header("X-Test", "ARRAY::b|:c");
1041 event.unshift_header("X-Test", "a");
1042 assert_eq!(event.header_str("X-Test"), Some("ARRAY::a|:b|:c"));
1043 }
1044
1045 #[test]
1046 fn test_sendevent_with_priority_wire_format() {
1047 let mut event = EslEvent::with_type(EslEventType::Custom);
1048 event.set_header("Event-Name", "CUSTOM");
1049 event.set_header("Event-Subclass", "test::priority");
1050 event.set_priority(EslEventPriority::High);
1051
1052 let plain = event.to_plain_format();
1053 assert!(plain.contains("priority: HIGH\n"));
1054 }
1055
1056 #[test]
1057 fn test_convenience_accessors() {
1058 let mut event = EslEvent::new();
1059 event.set_header("Channel-Name", "sofia/internal/1000@example.com");
1060 event.set_header("Caller-Caller-ID-Number", "1000");
1061 event.set_header("Caller-Caller-ID-Name", "Alice");
1062 event.set_header("Hangup-Cause", "NORMAL_CLEARING");
1063 event.set_header("Event-Subclass", "sofia::register");
1064 event.set_header("variable_sip_from_display", "Bob");
1065
1066 assert_eq!(
1067 event.channel_name(),
1068 Some("sofia/internal/1000@example.com")
1069 );
1070 assert_eq!(event.caller_id_number(), Some("1000"));
1071 assert_eq!(event.caller_id_name(), Some("Alice"));
1072 assert_eq!(
1073 event
1074 .hangup_cause()
1075 .unwrap(),
1076 Some(crate::channel::HangupCause::NormalClearing)
1077 );
1078 assert_eq!(event.event_subclass(), Some("sofia::register"));
1079 assert_eq!(event.variable_str("sip_from_display"), Some("Bob"));
1080 assert_eq!(event.variable_str("nonexistent"), None);
1081 }
1082
1083 #[test]
1084 fn test_event_format_from_str() {
1085 assert_eq!("plain".parse::<EventFormat>(), Ok(EventFormat::Plain));
1086 assert_eq!("json".parse::<EventFormat>(), Ok(EventFormat::Json));
1087 assert_eq!("xml".parse::<EventFormat>(), Ok(EventFormat::Xml));
1088 assert!("foo"
1089 .parse::<EventFormat>()
1090 .is_err());
1091 }
1092
1093 #[test]
1094 fn test_event_format_from_str_case_insensitive() {
1095 assert_eq!("PLAIN".parse::<EventFormat>(), Ok(EventFormat::Plain));
1096 assert_eq!("Json".parse::<EventFormat>(), Ok(EventFormat::Json));
1097 assert_eq!("XML".parse::<EventFormat>(), Ok(EventFormat::Xml));
1098 assert_eq!("Xml".parse::<EventFormat>(), Ok(EventFormat::Xml));
1099 }
1100
1101 #[test]
1102 fn test_event_format_from_content_type() {
1103 assert_eq!(
1104 EventFormat::from_content_type("text/event-json"),
1105 Ok(EventFormat::Json)
1106 );
1107 assert_eq!(
1108 EventFormat::from_content_type("text/event-xml"),
1109 Ok(EventFormat::Xml)
1110 );
1111 assert_eq!(
1112 EventFormat::from_content_type("text/event-plain"),
1113 Ok(EventFormat::Plain)
1114 );
1115 assert!(EventFormat::from_content_type("unknown").is_err());
1116 }
1117
1118 #[test]
1121 fn test_event_channel_state_accessor() {
1122 use crate::channel::ChannelState;
1123 let mut event = EslEvent::new();
1124 event.set_header("Channel-State", "CS_EXECUTE");
1125 assert_eq!(
1126 event
1127 .channel_state()
1128 .unwrap(),
1129 Some(ChannelState::CsExecute)
1130 );
1131 }
1132
1133 #[test]
1134 fn test_event_channel_state_number_accessor() {
1135 use crate::channel::ChannelState;
1136 let mut event = EslEvent::new();
1137 event.set_header("Channel-State-Number", "4");
1138 assert_eq!(
1139 event
1140 .channel_state_number()
1141 .unwrap(),
1142 Some(ChannelState::CsExecute)
1143 );
1144 }
1145
1146 #[test]
1147 fn test_event_call_state_accessor() {
1148 use crate::channel::CallState;
1149 let mut event = EslEvent::new();
1150 event.set_header("Channel-Call-State", "ACTIVE");
1151 assert_eq!(
1152 event
1153 .call_state()
1154 .unwrap(),
1155 Some(CallState::Active)
1156 );
1157 }
1158
1159 #[test]
1160 fn test_event_answer_state_accessor() {
1161 use crate::channel::AnswerState;
1162 let mut event = EslEvent::new();
1163 event.set_header("Answer-State", "answered");
1164 assert_eq!(
1165 event
1166 .answer_state()
1167 .unwrap(),
1168 Some(AnswerState::Answered)
1169 );
1170 }
1171
1172 #[test]
1173 fn test_event_call_direction_accessor() {
1174 use crate::channel::CallDirection;
1175 let mut event = EslEvent::new();
1176 event.set_header("Call-Direction", "inbound");
1177 assert_eq!(
1178 event
1179 .call_direction()
1180 .unwrap(),
1181 Some(CallDirection::Inbound)
1182 );
1183 }
1184
1185 #[test]
1186 fn test_event_typed_accessors_missing_headers() {
1187 let event = EslEvent::new();
1188 assert_eq!(
1189 event
1190 .channel_state()
1191 .unwrap(),
1192 None
1193 );
1194 assert_eq!(
1195 event
1196 .channel_state_number()
1197 .unwrap(),
1198 None
1199 );
1200 assert_eq!(
1201 event
1202 .call_state()
1203 .unwrap(),
1204 None
1205 );
1206 assert_eq!(
1207 event
1208 .answer_state()
1209 .unwrap(),
1210 None
1211 );
1212 assert_eq!(
1213 event
1214 .call_direction()
1215 .unwrap(),
1216 None
1217 );
1218 }
1219
1220 #[test]
1223 fn test_sip_p_asserted_identity_comma_separated() {
1224 let mut event = EslEvent::new();
1225 event.set_header(
1228 "variable_sip_P-Asserted-Identity",
1229 "<sip:alice@atlanta.example.com>, <tel:+15551234567>",
1230 );
1231
1232 assert_eq!(
1233 event.variable_str("sip_P-Asserted-Identity"),
1234 Some("<sip:alice@atlanta.example.com>, <tel:+15551234567>")
1235 );
1236 }
1237
1238 #[test]
1239 fn test_sip_p_asserted_identity_array_format() {
1240 let mut event = EslEvent::new();
1241 event.push_header(
1243 "variable_sip_P-Asserted-Identity",
1244 "<sip:alice@atlanta.example.com>",
1245 );
1246 event.push_header("variable_sip_P-Asserted-Identity", "<tel:+15551234567>");
1247
1248 let raw = event
1249 .header_str("variable_sip_P-Asserted-Identity")
1250 .unwrap();
1251 assert_eq!(
1252 raw,
1253 "ARRAY::<sip:alice@atlanta.example.com>|:<tel:+15551234567>"
1254 );
1255
1256 let arr = crate::variables::EslArray::parse(raw).unwrap();
1257 assert_eq!(arr.len(), 2);
1258 assert_eq!(arr.items()[0], "<sip:alice@atlanta.example.com>");
1259 assert_eq!(arr.items()[1], "<tel:+15551234567>");
1260 }
1261
1262 #[test]
1263 fn test_sip_header_with_colons_in_uri() {
1264 let mut event = EslEvent::new();
1265 event.push_header(
1267 "variable_sip_h_Diversion",
1268 "<sip:+15551234567@gw.example.com;reason=unconditional>",
1269 );
1270 event.push_header(
1271 "variable_sip_h_Diversion",
1272 "<sips:+15559876543@secure.example.com;reason=no-answer;counter=3>",
1273 );
1274
1275 let raw = event
1276 .header_str("variable_sip_h_Diversion")
1277 .unwrap();
1278 let arr = crate::variables::EslArray::parse(raw).unwrap();
1279 assert_eq!(arr.len(), 2);
1280 assert_eq!(
1281 arr.items()[0],
1282 "<sip:+15551234567@gw.example.com;reason=unconditional>"
1283 );
1284 assert_eq!(
1285 arr.items()[1],
1286 "<sips:+15559876543@secure.example.com;reason=no-answer;counter=3>"
1287 );
1288 }
1289
1290 #[test]
1291 fn test_sip_p_asserted_identity_plain_format_round_trip() {
1292 let mut event = EslEvent::with_type(EslEventType::ChannelCreate);
1293 event.set_header("Event-Name", "CHANNEL_CREATE");
1294 event.set_header(
1295 "variable_sip_P-Asserted-Identity",
1296 "<sip:alice@atlanta.example.com>, <tel:+15551234567>",
1297 );
1298
1299 let plain = event.to_plain_format();
1300 assert!(plain.contains("variable_sip_P-Asserted-Identity:"));
1302 assert!(!plain.contains("<sip:alice"));
1304 }
1305
1306 #[test]
1311 fn set_header_normalizes_known_enum_variant() {
1312 let mut event = EslEvent::new();
1313 event.set_header("unique-id", "abc-123");
1314 assert_eq!(event.header(EventHeader::UniqueId), Some("abc-123"));
1315 }
1316
1317 #[test]
1318 fn set_header_normalizes_codec_header() {
1319 let mut event = EslEvent::new();
1320 event.set_header("channel-read-codec-bit-rate", "128000");
1321 assert_eq!(
1322 event.header(EventHeader::ChannelReadCodecBitRate),
1323 Some("128000")
1324 );
1325 }
1326
1327 #[test]
1328 fn header_str_finds_by_original_key() {
1329 let mut event = EslEvent::new();
1330 event.set_header("unique-id", "abc-123");
1331 assert_eq!(event.header_str("unique-id"), Some("abc-123"));
1333 assert_eq!(event.header_str("Unique-ID"), Some("abc-123"));
1335 }
1336
1337 #[test]
1338 fn header_str_finds_unknown_dash_header_by_original() {
1339 let mut event = EslEvent::new();
1340 event.set_header("x-custom-header", "val");
1341 assert_eq!(event.header_str("X-Custom-Header"), Some("val"));
1343 assert_eq!(event.header_str("x-custom-header"), Some("val"));
1345 }
1346
1347 #[test]
1348 fn set_header_underscore_passthrough_preserves_sip_h() {
1349 let mut event = EslEvent::new();
1350 event.set_header("variable_sip_h_X-My-CUSTOM-Header", "val");
1351 assert_eq!(
1352 event.header_str("variable_sip_h_X-My-CUSTOM-Header"),
1353 Some("val")
1354 );
1355 }
1356
1357 #[test]
1358 fn set_header_different_casing_overwrites() {
1359 let mut event = EslEvent::new();
1360 event.set_header("Unique-ID", "first");
1361 event.set_header("unique-id", "second");
1362 assert_eq!(event.header(EventHeader::UniqueId), Some("second"));
1364 }
1365
1366 #[test]
1367 fn remove_header_by_original_key() {
1368 let mut event = EslEvent::new();
1369 event.set_header("unique-id", "abc-123");
1370 let removed = event.remove_header("unique-id");
1371 assert_eq!(removed, Some("abc-123".to_string()));
1372 assert_eq!(event.header(EventHeader::UniqueId), None);
1373 }
1374
1375 #[test]
1376 fn remove_header_by_canonical_key() {
1377 let mut event = EslEvent::new();
1378 event.set_header("unique-id", "abc-123");
1379 let removed = event.remove_header("Unique-ID");
1380 assert_eq!(removed, Some("abc-123".to_string()));
1381 assert_eq!(event.header_str("unique-id"), None);
1382 }
1383
1384 #[test]
1385 fn serde_round_trip_preserves_canonical_lookups() {
1386 let mut event = EslEvent::new();
1387 event.set_header("unique-id", "abc-123");
1388 event.set_header("channel-read-codec-bit-rate", "128000");
1389 let json = serde_json::to_string(&event).unwrap();
1390 let deserialized: EslEvent = serde_json::from_str(&json).unwrap();
1391 assert_eq!(deserialized.header(EventHeader::UniqueId), Some("abc-123"));
1392 assert_eq!(
1393 deserialized.header(EventHeader::ChannelReadCodecBitRate),
1394 Some("128000")
1395 );
1396 }
1397
1398 #[test]
1399 fn serde_deserialize_normalizes_external_json() {
1400 let json = r#"{"event_type":null,"headers":{"unique-id":"abc-123","channel-read-codec-bit-rate":"128000"},"body":null}"#;
1401 let event: EslEvent = serde_json::from_str(json).unwrap();
1402 assert_eq!(event.header(EventHeader::UniqueId), Some("abc-123"));
1403 assert_eq!(
1404 event.header(EventHeader::ChannelReadCodecBitRate),
1405 Some("128000")
1406 );
1407 assert_eq!(event.header_str("unique-id"), Some("abc-123"));
1408 }
1409
1410 #[test]
1411 fn test_event_typed_accessors_invalid_values() {
1412 let mut event = EslEvent::new();
1413 event.set_header("Channel-State", "BOGUS");
1414 event.set_header("Channel-State-Number", "999");
1415 event.set_header("Channel-Call-State", "BOGUS");
1416 event.set_header("Answer-State", "bogus");
1417 event.set_header("Call-Direction", "bogus");
1418 assert!(event
1419 .channel_state()
1420 .is_err());
1421 assert!(event
1422 .channel_state_number()
1423 .is_err());
1424 assert!(event
1425 .call_state()
1426 .is_err());
1427 assert!(event
1428 .answer_state()
1429 .is_err());
1430 assert!(event
1431 .call_direction()
1432 .is_err());
1433 }
1434}