1use crate::headers::{normalize_header_key, EventHeader};
4use crate::lookup::HeaderLookup;
5use crate::sofia::SofiaEventSubclass;
6use crate::variables::{EslArray, EslArrayError};
7use indexmap::IndexMap;
8use percent_encoding::{percent_encode, NON_ALPHANUMERIC};
9use std::fmt;
10use std::str::FromStr;
11
12#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
14#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
15#[non_exhaustive]
16pub enum EventFormat {
17 Plain,
19 Json,
21 Xml,
23}
24
25impl EventFormat {
26 pub fn from_content_type(ct: &str) -> Result<Self, ParseEventFormatError> {
31 match ct {
32 "text/event-json" => Ok(Self::Json),
33 "text/event-xml" => Ok(Self::Xml),
34 "text/event-plain" => Ok(Self::Plain),
35 _ => Err(ParseEventFormatError(ct.to_string())),
36 }
37 }
38}
39
40impl fmt::Display for EventFormat {
41 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
42 match self {
43 EventFormat::Plain => write!(f, "plain"),
44 EventFormat::Json => write!(f, "json"),
45 EventFormat::Xml => write!(f, "xml"),
46 }
47 }
48}
49
50impl FromStr for EventFormat {
51 type Err = ParseEventFormatError;
52
53 fn from_str(s: &str) -> Result<Self, Self::Err> {
54 if s.eq_ignore_ascii_case("plain") {
55 Ok(Self::Plain)
56 } else if s.eq_ignore_ascii_case("json") {
57 Ok(Self::Json)
58 } else if s.eq_ignore_ascii_case("xml") {
59 Ok(Self::Xml)
60 } else {
61 Err(ParseEventFormatError(s.to_string()))
62 }
63 }
64}
65
66#[derive(Debug, Clone, PartialEq, Eq)]
68pub struct ParseEventFormatError(pub String);
69
70impl fmt::Display for ParseEventFormatError {
71 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
72 write!(f, "unknown event format: {}", self.0)
73 }
74}
75
76impl std::error::Error for ParseEventFormatError {}
77
78macro_rules! esl_event_types {
80 (
81 $(
82 $(#[$attr:meta])*
83 $variant:ident => $wire:literal
84 ),+ $(,)?
85 ;
86 $(
88 $(#[$extra_attr:meta])*
89 $extra_variant:ident => $extra_wire:literal
90 ),* $(,)?
91 ) => {
92 #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
97 #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
98 #[non_exhaustive]
99 #[allow(missing_docs)]
100 pub enum EslEventType {
101 $(
102 $(#[$attr])*
103 $variant,
104 )+
105 $(
106 $(#[$extra_attr])*
107 $extra_variant,
108 )*
109 }
110
111 impl fmt::Display for EslEventType {
112 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
113 f.write_str(self.as_str())
114 }
115 }
116
117 impl EslEventType {
118 pub const fn as_str(&self) -> &'static str {
120 match self {
121 $( EslEventType::$variant => $wire, )+
122 $( EslEventType::$extra_variant => $extra_wire, )*
123 }
124 }
125
126 pub fn parse_event_type(s: &str) -> Option<Self> {
128 match s {
129 $( $wire => Some(EslEventType::$variant), )+
130 $( $extra_wire => Some(EslEventType::$extra_variant), )*
131 _ => None,
132 }
133 }
134 }
135
136 impl FromStr for EslEventType {
137 type Err = ParseEventTypeError;
138
139 fn from_str(s: &str) -> Result<Self, Self::Err> {
140 Self::parse_event_type(s).ok_or_else(|| ParseEventTypeError(s.to_string()))
141 }
142 }
143 };
144}
145
146esl_event_types! {
147 Custom => "CUSTOM",
148 Clone => "CLONE",
149 ChannelCreate => "CHANNEL_CREATE",
150 ChannelDestroy => "CHANNEL_DESTROY",
151 ChannelState => "CHANNEL_STATE",
152 ChannelCallstate => "CHANNEL_CALLSTATE",
153 ChannelAnswer => "CHANNEL_ANSWER",
154 ChannelHangup => "CHANNEL_HANGUP",
155 ChannelHangupComplete => "CHANNEL_HANGUP_COMPLETE",
156 ChannelExecute => "CHANNEL_EXECUTE",
157 ChannelExecuteComplete => "CHANNEL_EXECUTE_COMPLETE",
158 ChannelHold => "CHANNEL_HOLD",
159 ChannelUnhold => "CHANNEL_UNHOLD",
160 ChannelBridge => "CHANNEL_BRIDGE",
161 ChannelUnbridge => "CHANNEL_UNBRIDGE",
162 ChannelProgress => "CHANNEL_PROGRESS",
163 ChannelProgressMedia => "CHANNEL_PROGRESS_MEDIA",
164 ChannelOutgoing => "CHANNEL_OUTGOING",
165 ChannelPark => "CHANNEL_PARK",
166 ChannelUnpark => "CHANNEL_UNPARK",
167 ChannelApplication => "CHANNEL_APPLICATION",
168 ChannelOriginate => "CHANNEL_ORIGINATE",
169 ChannelUuid => "CHANNEL_UUID",
170 Api => "API",
171 Log => "LOG",
172 InboundChan => "INBOUND_CHAN",
173 OutboundChan => "OUTBOUND_CHAN",
174 Startup => "STARTUP",
175 Shutdown => "SHUTDOWN",
176 Publish => "PUBLISH",
177 Unpublish => "UNPUBLISH",
178 Talk => "TALK",
179 Notalk => "NOTALK",
180 SessionCrash => "SESSION_CRASH",
181 ModuleLoad => "MODULE_LOAD",
182 ModuleUnload => "MODULE_UNLOAD",
183 Dtmf => "DTMF",
184 Message => "MESSAGE",
185 PresenceIn => "PRESENCE_IN",
186 NotifyIn => "NOTIFY_IN",
187 PresenceOut => "PRESENCE_OUT",
188 PresenceProbe => "PRESENCE_PROBE",
189 MessageWaiting => "MESSAGE_WAITING",
190 MessageQuery => "MESSAGE_QUERY",
191 Roster => "ROSTER",
192 Codec => "CODEC",
193 BackgroundJob => "BACKGROUND_JOB",
194 DetectedSpeech => "DETECTED_SPEECH",
195 DetectedTone => "DETECTED_TONE",
196 PrivateCommand => "PRIVATE_COMMAND",
197 Heartbeat => "HEARTBEAT",
198 Trap => "TRAP",
199 AddSchedule => "ADD_SCHEDULE",
200 DelSchedule => "DEL_SCHEDULE",
201 ExeSchedule => "EXE_SCHEDULE",
202 ReSchedule => "RE_SCHEDULE",
203 ReloadXml => "RELOADXML",
204 Notify => "NOTIFY",
205 PhoneFeature => "PHONE_FEATURE",
206 PhoneFeatureSubscribe => "PHONE_FEATURE_SUBSCRIBE",
207 SendMessage => "SEND_MESSAGE",
208 RecvMessage => "RECV_MESSAGE",
209 RequestParams => "REQUEST_PARAMS",
210 ChannelData => "CHANNEL_DATA",
211 General => "GENERAL",
212 Command => "COMMAND",
213 SessionHeartbeat => "SESSION_HEARTBEAT",
214 ClientDisconnected => "CLIENT_DISCONNECTED",
215 ServerDisconnected => "SERVER_DISCONNECTED",
216 SendInfo => "SEND_INFO",
217 RecvInfo => "RECV_INFO",
218 RecvRtcpMessage => "RECV_RTCP_MESSAGE",
219 SendRtcpMessage => "SEND_RTCP_MESSAGE",
220 CallSecure => "CALL_SECURE",
221 Nat => "NAT",
222 RecordStart => "RECORD_START",
223 RecordStop => "RECORD_STOP",
224 PlaybackStart => "PLAYBACK_START",
225 PlaybackStop => "PLAYBACK_STOP",
226 CallUpdate => "CALL_UPDATE",
227 Failure => "FAILURE",
228 SocketData => "SOCKET_DATA",
229 MediaBugStart => "MEDIA_BUG_START",
230 MediaBugStop => "MEDIA_BUG_STOP",
231 ConferenceDataQuery => "CONFERENCE_DATA_QUERY",
232 ConferenceData => "CONFERENCE_DATA",
233 CallSetupReq => "CALL_SETUP_REQ",
234 CallSetupResult => "CALL_SETUP_RESULT",
235 CallDetail => "CALL_DETAIL",
236 DeviceState => "DEVICE_STATE",
237 Text => "TEXT",
238 ShutdownRequested => "SHUTDOWN_REQUESTED",
239 All => "ALL";
241 StartRecording => "START_RECORDING",
245}
246
247impl EslEventType {
256 pub const CHANNEL_EVENTS: &[EslEventType] = &[
267 EslEventType::ChannelCreate,
268 EslEventType::ChannelDestroy,
269 EslEventType::ChannelState,
270 EslEventType::ChannelCallstate,
271 EslEventType::ChannelAnswer,
272 EslEventType::ChannelHangup,
273 EslEventType::ChannelHangupComplete,
274 EslEventType::ChannelExecute,
275 EslEventType::ChannelExecuteComplete,
276 EslEventType::ChannelHold,
277 EslEventType::ChannelUnhold,
278 EslEventType::ChannelBridge,
279 EslEventType::ChannelUnbridge,
280 EslEventType::ChannelProgress,
281 EslEventType::ChannelProgressMedia,
282 EslEventType::ChannelOutgoing,
283 EslEventType::ChannelPark,
284 EslEventType::ChannelUnpark,
285 EslEventType::ChannelApplication,
286 EslEventType::ChannelOriginate,
287 EslEventType::ChannelUuid,
288 EslEventType::ChannelData,
289 ];
290
291 pub const IN_CALL_EVENTS: &[EslEventType] = &[
302 EslEventType::Dtmf,
303 EslEventType::Talk,
304 EslEventType::Notalk,
305 EslEventType::CallSecure,
306 EslEventType::CallUpdate,
307 EslEventType::RecvRtcpMessage,
308 EslEventType::SendRtcpMessage,
309 ];
310
311 pub const MEDIA_EVENTS: &[EslEventType] = &[
322 EslEventType::PlaybackStart,
323 EslEventType::PlaybackStop,
324 EslEventType::RecordStart,
325 EslEventType::RecordStop,
326 EslEventType::StartRecording,
327 EslEventType::MediaBugStart,
328 EslEventType::MediaBugStop,
329 EslEventType::DetectedSpeech,
330 EslEventType::DetectedTone,
331 ];
332
333 pub const PRESENCE_EVENTS: &[EslEventType] = &[
344 EslEventType::PresenceIn,
345 EslEventType::PresenceOut,
346 EslEventType::PresenceProbe,
347 EslEventType::MessageWaiting,
348 EslEventType::MessageQuery,
349 EslEventType::Roster,
350 ];
351
352 pub const SYSTEM_EVENTS: &[EslEventType] = &[
363 EslEventType::Startup,
364 EslEventType::Shutdown,
365 EslEventType::ShutdownRequested,
366 EslEventType::Heartbeat,
367 EslEventType::SessionHeartbeat,
368 EslEventType::SessionCrash,
369 EslEventType::ModuleLoad,
370 EslEventType::ModuleUnload,
371 EslEventType::ReloadXml,
372 ];
373
374 pub const CONFERENCE_EVENTS: &[EslEventType] = &[
381 EslEventType::ConferenceDataQuery,
382 EslEventType::ConferenceData,
383 ];
384}
385
386#[derive(Debug, Clone, PartialEq, Eq)]
388pub struct ParseEventTypeError(pub String);
389
390impl fmt::Display for ParseEventTypeError {
391 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
392 write!(f, "unknown event type: {}", self.0)
393 }
394}
395
396impl std::error::Error for ParseEventTypeError {}
397
398#[derive(Debug, Clone, PartialEq, Eq)]
403pub struct EventSubscriptionError(pub String);
404
405impl fmt::Display for EventSubscriptionError {
406 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
407 write!(f, "invalid event subscription: {}", self.0)
408 }
409}
410
411impl std::error::Error for EventSubscriptionError {}
412
413#[derive(Debug, Clone, PartialEq, Eq)]
441#[non_exhaustive]
442pub struct EventSubscription {
443 format: EventFormat,
444 events: Vec<EslEventType>,
445 raw_events: Vec<String>,
446 custom_subclasses: Vec<String>,
447 filters: Vec<(String, String)>,
448}
449
450fn validate_raw_event(s: &str) -> Result<(), EventSubscriptionError> {
455 if s.is_empty() {
456 return Err(EventSubscriptionError("raw event cannot be empty".into()));
457 }
458 if s.contains('\n') || s.contains('\r') {
459 return Err(EventSubscriptionError(format!(
460 "raw event contains newline: {:?}",
461 s
462 )));
463 }
464 if s.contains(' ') {
465 return Err(EventSubscriptionError(format!(
466 "raw event contains space: {:?}",
467 s
468 )));
469 }
470 Ok(())
471}
472
473fn validate_custom_subclass(s: &str) -> Result<(), EventSubscriptionError> {
475 if s.is_empty() {
476 return Err(EventSubscriptionError(
477 "custom subclass cannot be empty".into(),
478 ));
479 }
480 if s.contains('\n') || s.contains('\r') {
481 return Err(EventSubscriptionError(format!(
482 "custom subclass contains newline: {:?}",
483 s
484 )));
485 }
486 if s.contains(' ') {
487 return Err(EventSubscriptionError(format!(
488 "custom subclass contains space: {:?}",
489 s
490 )));
491 }
492 Ok(())
493}
494
495fn validate_filter_field(field: &str, label: &str) -> Result<(), EventSubscriptionError> {
497 if field.contains('\n') || field.contains('\r') {
498 return Err(EventSubscriptionError(format!(
499 "filter {} contains newline: {:?}",
500 label, field
501 )));
502 }
503 Ok(())
504}
505
506impl EventSubscription {
507 pub fn new(format: EventFormat) -> Self {
509 Self {
510 format,
511 events: Vec::new(),
512 raw_events: Vec::new(),
513 custom_subclasses: Vec::new(),
514 filters: Vec::new(),
515 }
516 }
517
518 pub fn all(format: EventFormat) -> Self {
520 Self {
521 format,
522 events: vec![EslEventType::All],
523 raw_events: Vec::new(),
524 custom_subclasses: Vec::new(),
525 filters: Vec::new(),
526 }
527 }
528
529 pub fn event(mut self, event: EslEventType) -> Self {
531 self.events
532 .push(event);
533 self
534 }
535
536 pub fn events<T: IntoIterator<Item = impl std::borrow::Borrow<EslEventType>>>(
538 mut self,
539 events: T,
540 ) -> Self {
541 self.events
542 .extend(
543 events
544 .into_iter()
545 .map(|e| *e.borrow()),
546 );
547 self
548 }
549
550 pub fn event_raw(mut self, event: impl Into<String>) -> Result<Self, EventSubscriptionError> {
559 let s = event.into();
560 validate_raw_event(&s)?;
561 self.raw_events
562 .push(s);
563 Ok(self)
564 }
565
566 pub fn events_raw<I, S>(mut self, events: I) -> Result<Self, EventSubscriptionError>
570 where
571 I: IntoIterator<Item = S>,
572 S: Into<String>,
573 {
574 for e in events {
575 let s = e.into();
576 validate_raw_event(&s)?;
577 self.raw_events
578 .push(s);
579 }
580 Ok(self)
581 }
582
583 pub fn custom_subclass(
587 mut self,
588 subclass: impl Into<String>,
589 ) -> Result<Self, EventSubscriptionError> {
590 let s = subclass.into();
591 validate_custom_subclass(&s)?;
592 self.custom_subclasses
593 .push(s);
594 Ok(self)
595 }
596
597 pub fn custom_subclasses(
601 mut self,
602 subclasses: impl IntoIterator<Item = impl Into<String>>,
603 ) -> Result<Self, EventSubscriptionError> {
604 for s in subclasses {
605 let s = s.into();
606 validate_custom_subclass(&s)?;
607 self.custom_subclasses
608 .push(s);
609 }
610 Ok(self)
611 }
612
613 pub fn sofia_event(mut self, subclass: SofiaEventSubclass) -> Self {
618 self.custom_subclasses
619 .push(
620 subclass
621 .as_str()
622 .to_string(),
623 );
624 self
625 }
626
627 pub fn sofia_events(
629 mut self,
630 subclasses: impl IntoIterator<Item = impl std::borrow::Borrow<SofiaEventSubclass>>,
631 ) -> Self {
632 self.custom_subclasses
633 .extend(
634 subclasses
635 .into_iter()
636 .map(|s| {
637 s.borrow()
638 .as_str()
639 .to_string()
640 }),
641 );
642 self
643 }
644
645 pub fn filter(
649 self,
650 header: crate::headers::EventHeader,
651 value: impl Into<String>,
652 ) -> Result<Self, EventSubscriptionError> {
653 let v = value.into();
654 validate_filter_field(&v, "value")?;
655 let mut s = self;
656 s.filters
657 .push((
658 header
659 .as_str()
660 .to_string(),
661 v,
662 ));
663 Ok(s)
664 }
665
666 pub fn filter_raw(
670 self,
671 header: impl Into<String>,
672 value: impl Into<String>,
673 ) -> Result<Self, EventSubscriptionError> {
674 let h = header.into();
675 let v = value.into();
676 validate_filter_field(&h, "header")?;
677 validate_filter_field(&v, "value")?;
678 let mut s = self;
679 s.filters
680 .push((h, v));
681 Ok(s)
682 }
683
684 pub fn with_format(mut self, format: EventFormat) -> Self {
686 self.format = format;
687 self
688 }
689
690 pub fn format(&self) -> EventFormat {
692 self.format
693 }
694
695 pub fn format_mut(&mut self) -> &mut EventFormat {
697 &mut self.format
698 }
699
700 pub fn event_types(&self) -> &[EslEventType] {
702 &self.events
703 }
704
705 pub fn event_types_mut(&mut self) -> &mut Vec<EslEventType> {
707 &mut self.events
708 }
709
710 pub fn event_types_raw(&self) -> &[String] {
712 &self.raw_events
713 }
714
715 pub fn event_types_raw_mut(&mut self) -> &mut Vec<String> {
721 &mut self.raw_events
722 }
723
724 pub fn custom_subclass_list(&self) -> &[String] {
726 &self.custom_subclasses
727 }
728
729 pub fn custom_subclasses_mut(&mut self) -> &mut Vec<String> {
731 &mut self.custom_subclasses
732 }
733
734 pub fn filters(&self) -> &[(String, String)] {
736 &self.filters
737 }
738
739 pub fn filters_mut(&mut self) -> &mut Vec<(String, String)> {
741 &mut self.filters
742 }
743
744 pub fn is_all(&self) -> bool {
746 self.events
747 .contains(&EslEventType::All)
748 }
749
750 pub fn is_empty(&self) -> bool {
753 self.events
754 .is_empty()
755 && self
756 .raw_events
757 .is_empty()
758 && self
759 .custom_subclasses
760 .is_empty()
761 }
762
763 pub fn to_event_string(&self) -> Option<String> {
770 if self
771 .events
772 .contains(&EslEventType::All)
773 {
774 return Some("ALL".to_string());
775 }
776
777 let mut parts: Vec<&str> = self
778 .events
779 .iter()
780 .map(|e| e.as_str())
781 .collect();
782
783 parts.extend(
784 self.raw_events
785 .iter()
786 .map(|s| s.as_str()),
787 );
788
789 if !self
790 .custom_subclasses
791 .is_empty()
792 {
793 if !self
794 .events
795 .contains(&EslEventType::Custom)
796 {
797 parts.push("CUSTOM");
798 }
799 for sc in &self.custom_subclasses {
800 parts.push(sc.as_str());
801 }
802 }
803
804 if parts.is_empty() {
805 None
806 } else {
807 Some(parts.join(" "))
808 }
809 }
810}
811
812#[cfg(feature = "serde")]
813mod event_subscription_serde {
814 use super::*;
815 use serde::{Deserialize, Serialize};
816
817 #[derive(Serialize, Deserialize)]
818 struct EventSubscriptionRaw {
819 format: EventFormat,
820 #[serde(default)]
821 events: Vec<EslEventType>,
822 #[serde(default, skip_serializing_if = "Vec::is_empty")]
823 raw_events: Vec<String>,
824 #[serde(default)]
825 custom_subclasses: Vec<String>,
826 #[serde(default)]
827 filters: Vec<(String, String)>,
828 }
829
830 impl TryFrom<EventSubscriptionRaw> for EventSubscription {
831 type Error = EventSubscriptionError;
832
833 fn try_from(raw: EventSubscriptionRaw) -> Result<Self, Self::Error> {
834 for re in &raw.raw_events {
835 validate_raw_event(re)?;
836 }
837 for sc in &raw.custom_subclasses {
838 validate_custom_subclass(sc)?;
839 }
840 for (h, v) in &raw.filters {
841 validate_filter_field(h, "header")?;
842 validate_filter_field(v, "value")?;
843 }
844 Ok(EventSubscription {
845 format: raw.format,
846 events: raw.events,
847 raw_events: raw.raw_events,
848 custom_subclasses: raw.custom_subclasses,
849 filters: raw.filters,
850 })
851 }
852 }
853
854 impl Serialize for EventSubscription {
855 fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
856 let raw = EventSubscriptionRaw {
857 format: self.format,
858 events: self
859 .events
860 .clone(),
861 raw_events: self
862 .raw_events
863 .clone(),
864 custom_subclasses: self
865 .custom_subclasses
866 .clone(),
867 filters: self
868 .filters
869 .clone(),
870 };
871 raw.serialize(serializer)
872 }
873 }
874
875 impl<'de> Deserialize<'de> for EventSubscription {
876 fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
877 let raw = EventSubscriptionRaw::deserialize(deserializer)?;
878 EventSubscription::try_from(raw).map_err(serde::de::Error::custom)
879 }
880 }
881}
882
883#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
885#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
886#[non_exhaustive]
887pub enum EslEventPriority {
888 Normal,
890 Low,
892 High,
894}
895
896impl fmt::Display for EslEventPriority {
897 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
898 match self {
899 EslEventPriority::Normal => write!(f, "NORMAL"),
900 EslEventPriority::Low => write!(f, "LOW"),
901 EslEventPriority::High => write!(f, "HIGH"),
902 }
903 }
904}
905
906#[derive(Debug, Clone, PartialEq, Eq)]
908pub struct ParsePriorityError(pub String);
909
910impl fmt::Display for ParsePriorityError {
911 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
912 write!(f, "unknown priority: {}", self.0)
913 }
914}
915
916impl std::error::Error for ParsePriorityError {}
917
918impl FromStr for EslEventPriority {
919 type Err = ParsePriorityError;
920
921 fn from_str(s: &str) -> Result<Self, Self::Err> {
922 match s {
923 "NORMAL" => Ok(EslEventPriority::Normal),
924 "LOW" => Ok(EslEventPriority::Low),
925 "HIGH" => Ok(EslEventPriority::High),
926 _ => Err(ParsePriorityError(s.to_string())),
927 }
928 }
929}
930
931#[derive(Debug, Clone, Eq)]
933#[cfg_attr(feature = "serde", derive(serde::Serialize))]
934pub struct EslEvent {
935 event_type: Option<EslEventType>,
936 headers: IndexMap<String, String>,
937 #[cfg_attr(feature = "serde", serde(skip))]
947 original_keys: IndexMap<String, String>,
948 body: Option<String>,
949}
950
951impl EslEvent {
952 pub fn new() -> Self {
954 Self {
955 event_type: None,
956 headers: IndexMap::new(),
957 original_keys: IndexMap::new(),
958 body: None,
959 }
960 }
961
962 pub fn with_type(event_type: EslEventType) -> Self {
964 Self {
965 event_type: Some(event_type),
966 headers: IndexMap::new(),
967 original_keys: IndexMap::new(),
968 body: None,
969 }
970 }
971
972 pub fn event_type(&self) -> Option<EslEventType> {
974 self.event_type
975 }
976
977 pub fn set_event_type(&mut self, event_type: Option<EslEventType>) {
979 self.event_type = event_type;
980 }
981
982 pub fn header(&self, name: EventHeader) -> Option<&str> {
986 self.headers
987 .get(name.as_str())
988 .map(|s| s.as_str())
989 }
990
991 pub fn header_str(&self, name: &str) -> Option<&str> {
999 self.headers
1000 .get(name)
1001 .or_else(|| {
1002 self.original_keys
1003 .get(name)
1004 .and_then(|normalized| {
1005 self.headers
1006 .get(normalized)
1007 })
1008 })
1009 .map(|s| s.as_str())
1010 }
1011
1012 pub fn variable_str(&self, name: &str) -> Option<&str> {
1017 let key = format!("variable_{}", name);
1018 self.header_str(&key)
1019 }
1020
1021 pub fn headers(&self) -> &IndexMap<String, String> {
1023 &self.headers
1024 }
1025
1026 pub fn set_header(&mut self, name: impl Into<String>, value: impl Into<String>) {
1028 let original = name.into();
1029 let normalized = normalize_header_key(&original);
1030 if original != normalized {
1031 self.original_keys
1032 .insert(original, normalized.clone());
1033 }
1034 self.headers
1035 .insert(normalized, value.into());
1036 }
1037
1038 pub fn remove_header(&mut self, name: impl AsRef<str>) -> Option<String> {
1042 let name = name.as_ref();
1043 if let Some(value) = self
1044 .headers
1045 .shift_remove(name)
1046 {
1047 return Some(value);
1048 }
1049 if let Some(normalized) = self
1050 .original_keys
1051 .shift_remove(name)
1052 {
1053 return self
1054 .headers
1055 .shift_remove(&normalized);
1056 }
1057 None
1058 }
1059
1060 pub fn body(&self) -> Option<&str> {
1062 self.body
1063 .as_deref()
1064 }
1065
1066 pub fn set_body(&mut self, body: impl Into<String>) {
1068 self.body = Some(body.into());
1069 }
1070
1071 pub fn set_priority(&mut self, priority: EslEventPriority) {
1076 self.set_header(EventHeader::Priority.as_str(), priority.to_string());
1077 }
1078
1079 pub fn push_header(&mut self, name: &str, value: &str) -> Result<(), EslArrayError> {
1096 self.stack_header(name, value, EslArray::push)
1097 }
1098
1099 pub fn unshift_header(&mut self, name: &str, value: &str) -> Result<(), EslArrayError> {
1112 self.stack_header(name, value, EslArray::unshift)
1113 }
1114
1115 fn stack_header(
1116 &mut self,
1117 name: &str,
1118 value: &str,
1119 op: fn(&mut EslArray, String),
1120 ) -> Result<(), EslArrayError> {
1121 match self
1122 .headers
1123 .get(name)
1124 {
1125 None => {
1126 self.set_header(name, value);
1127 }
1128 Some(existing) => {
1129 let arr = match EslArray::parse(existing) {
1130 Ok(arr) => arr,
1131 Err(EslArrayError::MissingPrefix) => EslArray::new(vec![existing.clone()]),
1132 Err(e) => return Err(e),
1133 };
1134 if arr.len() >= crate::variables::MAX_ARRAY_ITEMS {
1135 return Err(EslArrayError::TooManyItems {
1136 count: arr.len(),
1137 max: crate::variables::MAX_ARRAY_ITEMS,
1138 });
1139 }
1140 let mut arr = arr;
1141 op(&mut arr, value.into());
1142 self.set_header(name, arr.to_string());
1143 }
1144 }
1145 Ok(())
1146 }
1147
1148 pub fn is_event_type(&self, event_type: EslEventType) -> bool {
1150 self.event_type == Some(event_type)
1151 }
1152
1153 pub fn to_plain_format(&self) -> String {
1163 use std::fmt::Write;
1164 let mut result = String::new();
1165
1166 for (key, value) in &self.headers {
1167 if key == "Content-Length" {
1168 continue;
1169 }
1170 let _ = writeln!(
1171 result,
1172 "{}: {}",
1173 key,
1174 percent_encode(value.as_bytes(), NON_ALPHANUMERIC)
1175 );
1176 }
1177
1178 if let Some(body) = &self.body {
1179 let _ = writeln!(result, "Content-Length: {}", body.len());
1180 result.push('\n');
1181 result.push_str(body);
1182 } else {
1183 result.push('\n');
1184 }
1185
1186 result
1187 }
1188}
1189
1190impl Default for EslEvent {
1191 fn default() -> Self {
1192 Self::new()
1193 }
1194}
1195
1196impl HeaderLookup for EslEvent {
1197 fn header_str(&self, name: &str) -> Option<&str> {
1198 EslEvent::header_str(self, name)
1199 }
1200
1201 fn variable_str(&self, name: &str) -> Option<&str> {
1202 let key = format!("variable_{}", name);
1203 self.header_str(&key)
1204 }
1205}
1206
1207impl sip_header::SipHeaderLookup for EslEvent {
1208 fn sip_header_str(&self, name: &str) -> Option<&str> {
1209 EslEvent::header_str(self, name)
1210 }
1211}
1212
1213impl PartialEq for EslEvent {
1214 fn eq(&self, other: &Self) -> bool {
1215 self.event_type == other.event_type
1216 && self.headers == other.headers
1217 && self.body == other.body
1218 }
1219}
1220
1221impl std::hash::Hash for EslEvent {
1222 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
1223 self.event_type
1224 .hash(state);
1225 for (k, v) in &self.headers {
1226 k.hash(state);
1227 v.hash(state);
1228 }
1229 self.body
1230 .hash(state);
1231 }
1232}
1233
1234#[cfg(feature = "serde")]
1235impl<'de> serde::Deserialize<'de> for EslEvent {
1236 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
1237 where
1238 D: serde::Deserializer<'de>,
1239 {
1240 #[derive(serde::Deserialize)]
1241 struct Raw {
1242 event_type: Option<EslEventType>,
1243 headers: IndexMap<String, String>,
1244 body: Option<String>,
1245 }
1246 let raw = Raw::deserialize(deserializer)?;
1247 let mut event = EslEvent::new();
1248 event.event_type = raw.event_type;
1249 event.body = raw.body;
1250 for (k, v) in raw.headers {
1251 event.set_header(k, v);
1252 }
1253 Ok(event)
1254 }
1255}
1256
1257#[cfg(test)]
1258mod tests {
1259 use super::*;
1260
1261 #[test]
1262 fn headers_preserve_insertion_order() {
1263 let mut event = EslEvent::new();
1264 event.set_header("Zebra", "last");
1265 event.set_header("Alpha", "first");
1266 event.set_header("Middle", "mid");
1267 let keys: Vec<&str> = event
1268 .headers()
1269 .keys()
1270 .map(|s| s.as_str())
1271 .collect();
1272 assert_eq!(keys, vec!["Zebra", "Alpha", "Middle"]);
1273 }
1274
1275 #[test]
1276 fn test_notify_in_parse() {
1277 assert_eq!(
1278 EslEventType::parse_event_type("NOTIFY_IN"),
1279 Some(EslEventType::NotifyIn)
1280 );
1281 assert_eq!(EslEventType::parse_event_type("notify_in"), None);
1282 }
1283
1284 #[test]
1285 fn test_notify_in_display() {
1286 assert_eq!(EslEventType::NotifyIn.to_string(), "NOTIFY_IN");
1287 }
1288
1289 #[test]
1290 fn test_notify_in_distinct_from_notify() {
1291 assert_ne!(EslEventType::Notify, EslEventType::NotifyIn);
1292 assert_ne!(
1293 EslEventType::Notify.to_string(),
1294 EslEventType::NotifyIn.to_string()
1295 );
1296 }
1297
1298 #[test]
1299 fn test_wire_names_match_c_esl() {
1300 assert_eq!(
1301 EslEventType::ChannelOutgoing.to_string(),
1302 "CHANNEL_OUTGOING"
1303 );
1304 assert_eq!(EslEventType::Api.to_string(), "API");
1305 assert_eq!(EslEventType::ReloadXml.to_string(), "RELOADXML");
1306 assert_eq!(EslEventType::PresenceIn.to_string(), "PRESENCE_IN");
1307 assert_eq!(EslEventType::Roster.to_string(), "ROSTER");
1308 assert_eq!(EslEventType::Text.to_string(), "TEXT");
1309 assert_eq!(EslEventType::ReSchedule.to_string(), "RE_SCHEDULE");
1310
1311 assert_eq!(
1312 EslEventType::parse_event_type("CHANNEL_OUTGOING"),
1313 Some(EslEventType::ChannelOutgoing)
1314 );
1315 assert_eq!(
1316 EslEventType::parse_event_type("API"),
1317 Some(EslEventType::Api)
1318 );
1319 assert_eq!(
1320 EslEventType::parse_event_type("RELOADXML"),
1321 Some(EslEventType::ReloadXml)
1322 );
1323 assert_eq!(
1324 EslEventType::parse_event_type("PRESENCE_IN"),
1325 Some(EslEventType::PresenceIn)
1326 );
1327 }
1328
1329 #[test]
1330 fn test_event_type_from_str() {
1331 assert_eq!(
1332 "CHANNEL_ANSWER".parse::<EslEventType>(),
1333 Ok(EslEventType::ChannelAnswer)
1334 );
1335 assert!("channel_answer"
1336 .parse::<EslEventType>()
1337 .is_err());
1338 assert!("UNKNOWN_EVENT"
1339 .parse::<EslEventType>()
1340 .is_err());
1341 }
1342
1343 #[test]
1344 fn test_remove_header() {
1345 let mut event = EslEvent::new();
1346 event.set_header("Foo", "bar");
1347 event.set_header("Baz", "qux");
1348
1349 let removed = event.remove_header("Foo");
1350 assert_eq!(removed, Some("bar".to_string()));
1351 assert!(event
1352 .header_str("Foo")
1353 .is_none());
1354 assert_eq!(event.header_str("Baz"), Some("qux"));
1355
1356 let removed_again = event.remove_header("Foo");
1357 assert_eq!(removed_again, None);
1358 }
1359
1360 #[test]
1361 fn test_to_plain_format_basic() {
1362 let mut event = EslEvent::with_type(EslEventType::Heartbeat);
1363 event.set_header("Event-Name", "HEARTBEAT");
1364 event.set_header("Core-UUID", "abc-123");
1365
1366 let plain = event.to_plain_format();
1367
1368 assert!(plain.starts_with("Event-Name: "));
1369 assert!(plain.contains("Core-UUID: "));
1370 assert!(plain.ends_with("\n\n"));
1371 }
1372
1373 #[test]
1374 fn test_to_plain_format_percent_encoding() {
1375 let mut event = EslEvent::with_type(EslEventType::Heartbeat);
1376 event.set_header("Event-Name", "HEARTBEAT");
1377 event.set_header("Up-Time", "0 years, 0 days");
1378
1379 let plain = event.to_plain_format();
1380
1381 assert!(!plain.contains("0 years, 0 days"));
1382 assert!(plain.contains("Up-Time: "));
1383 assert!(plain.contains("%20"));
1384 }
1385
1386 #[test]
1387 fn test_to_plain_format_with_body() {
1388 let mut event = EslEvent::with_type(EslEventType::BackgroundJob);
1389 event.set_header("Event-Name", "BACKGROUND_JOB");
1390 event.set_header("Job-UUID", "def-456");
1391 event.set_body("+OK result\n".to_string());
1392
1393 let plain = event.to_plain_format();
1394
1395 assert!(plain.contains("Content-Length: 11\n"));
1396 assert!(plain.ends_with("\n\n+OK result\n"));
1397 }
1398
1399 #[test]
1400 fn test_to_plain_format_preserves_insertion_order() {
1401 let mut event = EslEvent::with_type(EslEventType::Heartbeat);
1402 event.set_header("Event-Name", "HEARTBEAT");
1403 event.set_header("Core-UUID", "abc-123");
1404 event.set_header("FreeSWITCH-Hostname", "fs01");
1405 event.set_header("Up-Time", "0 years, 1 day");
1406
1407 let plain = event.to_plain_format();
1408 let lines: Vec<&str> = plain
1409 .lines()
1410 .collect();
1411 assert!(lines[0].starts_with("Event-Name: "));
1412 assert!(lines[1].starts_with("Core-UUID: "));
1413 assert!(lines[2].starts_with("FreeSWITCH-Hostname: "));
1414 assert!(lines[3].starts_with("Up-Time: "));
1415 }
1416
1417 #[test]
1418 fn test_to_plain_format_round_trip() {
1419 let mut original = EslEvent::with_type(EslEventType::ChannelCreate);
1420 original.set_header("Event-Name", "CHANNEL_CREATE");
1421 original.set_header("Core-UUID", "abc-123");
1422 original.set_header("Channel-Name", "sofia/internal/1000@example.com");
1423 original.set_header("Caller-Caller-ID-Name", "Jérôme Poulin");
1424 original.set_body("some body content");
1425
1426 let plain = original.to_plain_format();
1427
1428 let (header_section, inner_body) = if let Some(pos) = plain.find("\n\n") {
1430 (&plain[..pos], Some(&plain[pos + 2..]))
1431 } else {
1432 (plain.as_str(), None)
1433 };
1434
1435 let mut parsed = EslEvent::new();
1436 for line in header_section.lines() {
1437 let line = line.trim();
1438 if line.is_empty() {
1439 continue;
1440 }
1441 if let Some(colon_pos) = line.find(':') {
1442 let key = line[..colon_pos].trim();
1443 if key == "Content-Length" {
1444 continue;
1445 }
1446 let raw_value = line[colon_pos + 1..].trim();
1447 let value = percent_encoding::percent_decode_str(raw_value)
1448 .decode_utf8()
1449 .unwrap()
1450 .into_owned();
1451 parsed.set_header(key, value);
1452 }
1453 }
1454 if let Some(ib) = inner_body {
1455 if !ib.is_empty() {
1456 parsed.set_body(ib);
1457 }
1458 }
1459
1460 assert_eq!(original.headers(), parsed.headers());
1461 assert_eq!(original.body(), parsed.body());
1462 }
1463
1464 #[test]
1465 fn test_set_priority_normal() {
1466 let mut event = EslEvent::new();
1467 event.set_priority(EslEventPriority::Normal);
1468 assert_eq!(
1469 event
1470 .priority()
1471 .unwrap(),
1472 Some(EslEventPriority::Normal)
1473 );
1474 assert_eq!(event.header(EventHeader::Priority), Some("NORMAL"));
1475 }
1476
1477 #[test]
1478 fn test_set_priority_high() {
1479 let mut event = EslEvent::new();
1480 event.set_priority(EslEventPriority::High);
1481 assert_eq!(
1482 event
1483 .priority()
1484 .unwrap(),
1485 Some(EslEventPriority::High)
1486 );
1487 assert_eq!(event.header(EventHeader::Priority), Some("HIGH"));
1488 }
1489
1490 #[test]
1491 fn test_priority_display() {
1492 assert_eq!(EslEventPriority::Normal.to_string(), "NORMAL");
1493 assert_eq!(EslEventPriority::Low.to_string(), "LOW");
1494 assert_eq!(EslEventPriority::High.to_string(), "HIGH");
1495 }
1496
1497 #[test]
1498 fn test_priority_from_str() {
1499 assert_eq!(
1500 "NORMAL".parse::<EslEventPriority>(),
1501 Ok(EslEventPriority::Normal)
1502 );
1503 assert_eq!("LOW".parse::<EslEventPriority>(), Ok(EslEventPriority::Low));
1504 assert_eq!(
1505 "HIGH".parse::<EslEventPriority>(),
1506 Ok(EslEventPriority::High)
1507 );
1508 assert!("INVALID"
1509 .parse::<EslEventPriority>()
1510 .is_err());
1511 }
1512
1513 #[test]
1514 fn test_priority_from_str_rejects_wrong_case() {
1515 assert!("normal"
1516 .parse::<EslEventPriority>()
1517 .is_err());
1518 assert!("Low"
1519 .parse::<EslEventPriority>()
1520 .is_err());
1521 assert!("hIgH"
1522 .parse::<EslEventPriority>()
1523 .is_err());
1524 }
1525
1526 #[test]
1527 fn test_push_header_new() {
1528 let mut event = EslEvent::new();
1529 event
1530 .push_header("X-Test", "first")
1531 .unwrap();
1532 assert_eq!(event.header_str("X-Test"), Some("first"));
1533 }
1534
1535 #[test]
1536 fn test_push_header_existing_plain() {
1537 let mut event = EslEvent::new();
1538 event.set_header("X-Test", "first");
1539 event
1540 .push_header("X-Test", "second")
1541 .unwrap();
1542 assert_eq!(event.header_str("X-Test"), Some("ARRAY::first|:second"));
1543 }
1544
1545 #[test]
1546 fn test_push_header_existing_array() {
1547 let mut event = EslEvent::new();
1548 event.set_header("X-Test", "ARRAY::a|:b");
1549 event
1550 .push_header("X-Test", "c")
1551 .unwrap();
1552 assert_eq!(event.header_str("X-Test"), Some("ARRAY::a|:b|:c"));
1553 }
1554
1555 #[test]
1556 fn test_push_header_at_capacity() {
1557 use crate::variables::MAX_ARRAY_ITEMS;
1558 let mut event = EslEvent::new();
1559 let items: Vec<&str> = (0..MAX_ARRAY_ITEMS)
1560 .map(|_| "x")
1561 .collect();
1562 event.set_header("X-Test", format!("ARRAY::{}", items.join("|:")).as_str());
1563 assert!(matches!(
1564 event.push_header("X-Test", "overflow"),
1565 Err(EslArrayError::TooManyItems { .. })
1566 ));
1567 }
1568
1569 #[test]
1570 fn test_unshift_header_new() {
1571 let mut event = EslEvent::new();
1572 event
1573 .unshift_header("X-Test", "only")
1574 .unwrap();
1575 assert_eq!(event.header_str("X-Test"), Some("only"));
1576 }
1577
1578 #[test]
1579 fn test_unshift_header_existing_array() {
1580 let mut event = EslEvent::new();
1581 event.set_header("X-Test", "ARRAY::b|:c");
1582 event
1583 .unshift_header("X-Test", "a")
1584 .unwrap();
1585 assert_eq!(event.header_str("X-Test"), Some("ARRAY::a|:b|:c"));
1586 }
1587
1588 #[test]
1589 fn test_sendevent_with_priority_wire_format() {
1590 let mut event = EslEvent::with_type(EslEventType::Custom);
1591 event.set_header("Event-Name", "CUSTOM");
1592 event.set_header("Event-Subclass", "test::priority");
1593 event.set_priority(EslEventPriority::High);
1594
1595 let plain = event.to_plain_format();
1596 assert!(plain.contains("priority: HIGH\n"));
1597 }
1598
1599 #[test]
1600 fn test_convenience_accessors() {
1601 let mut event = EslEvent::new();
1602 event.set_header("Channel-Name", "sofia/internal/1000@example.com");
1603 event.set_header("Caller-Caller-ID-Number", "1000");
1604 event.set_header("Caller-Caller-ID-Name", "Alice");
1605 event.set_header("Hangup-Cause", "NORMAL_CLEARING");
1606 event.set_header("Event-Subclass", "sofia::register");
1607 event.set_header("variable_sip_from_display", "Bob");
1608
1609 assert_eq!(
1610 event.channel_name(),
1611 Some("sofia/internal/1000@example.com")
1612 );
1613 assert_eq!(event.caller_id_number(), Some("1000"));
1614 assert_eq!(event.caller_id_name(), Some("Alice"));
1615 assert_eq!(
1616 event
1617 .hangup_cause()
1618 .unwrap(),
1619 Some(crate::channel::HangupCause::NormalClearing)
1620 );
1621 assert_eq!(event.event_subclass(), Some("sofia::register"));
1622 assert_eq!(event.variable_str("sip_from_display"), Some("Bob"));
1623 assert_eq!(event.variable_str("nonexistent"), None);
1624 }
1625
1626 #[test]
1627 fn test_event_format_from_str() {
1628 assert_eq!("plain".parse::<EventFormat>(), Ok(EventFormat::Plain));
1629 assert_eq!("json".parse::<EventFormat>(), Ok(EventFormat::Json));
1630 assert_eq!("xml".parse::<EventFormat>(), Ok(EventFormat::Xml));
1631 assert!("foo"
1632 .parse::<EventFormat>()
1633 .is_err());
1634 }
1635
1636 #[test]
1637 fn test_event_format_from_str_case_insensitive() {
1638 assert_eq!("PLAIN".parse::<EventFormat>(), Ok(EventFormat::Plain));
1639 assert_eq!("Json".parse::<EventFormat>(), Ok(EventFormat::Json));
1640 assert_eq!("XML".parse::<EventFormat>(), Ok(EventFormat::Xml));
1641 assert_eq!("Xml".parse::<EventFormat>(), Ok(EventFormat::Xml));
1642 }
1643
1644 #[test]
1645 fn test_event_format_from_content_type() {
1646 assert_eq!(
1647 EventFormat::from_content_type("text/event-json"),
1648 Ok(EventFormat::Json)
1649 );
1650 assert_eq!(
1651 EventFormat::from_content_type("text/event-xml"),
1652 Ok(EventFormat::Xml)
1653 );
1654 assert_eq!(
1655 EventFormat::from_content_type("text/event-plain"),
1656 Ok(EventFormat::Plain)
1657 );
1658 assert!(EventFormat::from_content_type("unknown").is_err());
1659 }
1660
1661 #[test]
1664 fn test_event_channel_state_accessor() {
1665 use crate::channel::ChannelState;
1666 let mut event = EslEvent::new();
1667 event.set_header("Channel-State", "CS_EXECUTE");
1668 assert_eq!(
1669 event
1670 .channel_state()
1671 .unwrap(),
1672 Some(ChannelState::CsExecute)
1673 );
1674 }
1675
1676 #[test]
1677 fn test_event_channel_state_number_accessor() {
1678 use crate::channel::ChannelState;
1679 let mut event = EslEvent::new();
1680 event.set_header("Channel-State-Number", "4");
1681 assert_eq!(
1682 event
1683 .channel_state_number()
1684 .unwrap(),
1685 Some(ChannelState::CsExecute)
1686 );
1687 }
1688
1689 #[test]
1690 fn test_event_call_state_accessor() {
1691 use crate::channel::CallState;
1692 let mut event = EslEvent::new();
1693 event.set_header("Channel-Call-State", "ACTIVE");
1694 assert_eq!(
1695 event
1696 .call_state()
1697 .unwrap(),
1698 Some(CallState::Active)
1699 );
1700 }
1701
1702 #[test]
1703 fn test_event_answer_state_accessor() {
1704 use crate::channel::AnswerState;
1705 let mut event = EslEvent::new();
1706 event.set_header("Answer-State", "answered");
1707 assert_eq!(
1708 event
1709 .answer_state()
1710 .unwrap(),
1711 Some(AnswerState::Answered)
1712 );
1713 }
1714
1715 #[test]
1716 fn test_event_call_direction_accessor() {
1717 use crate::channel::CallDirection;
1718 let mut event = EslEvent::new();
1719 event.set_header("Call-Direction", "inbound");
1720 assert_eq!(
1721 event
1722 .call_direction()
1723 .unwrap(),
1724 Some(CallDirection::Inbound)
1725 );
1726 }
1727
1728 #[test]
1729 fn test_event_typed_accessors_missing_headers() {
1730 let event = EslEvent::new();
1731 assert_eq!(
1732 event
1733 .channel_state()
1734 .unwrap(),
1735 None
1736 );
1737 assert_eq!(
1738 event
1739 .channel_state_number()
1740 .unwrap(),
1741 None
1742 );
1743 assert_eq!(
1744 event
1745 .call_state()
1746 .unwrap(),
1747 None
1748 );
1749 assert_eq!(
1750 event
1751 .answer_state()
1752 .unwrap(),
1753 None
1754 );
1755 assert_eq!(
1756 event
1757 .call_direction()
1758 .unwrap(),
1759 None
1760 );
1761 }
1762
1763 #[test]
1766 fn test_sip_p_asserted_identity_comma_separated() {
1767 let mut event = EslEvent::new();
1768 event.set_header(
1771 "variable_sip_P-Asserted-Identity",
1772 "<sip:alice@atlanta.example.com>, <tel:+15551234567>",
1773 );
1774
1775 assert_eq!(
1776 event.variable_str("sip_P-Asserted-Identity"),
1777 Some("<sip:alice@atlanta.example.com>, <tel:+15551234567>")
1778 );
1779 }
1780
1781 #[test]
1782 fn test_sip_p_asserted_identity_array_format() {
1783 let mut event = EslEvent::new();
1784 event
1786 .push_header(
1787 "variable_sip_P-Asserted-Identity",
1788 "<sip:alice@atlanta.example.com>",
1789 )
1790 .unwrap();
1791 event
1792 .push_header("variable_sip_P-Asserted-Identity", "<tel:+15551234567>")
1793 .unwrap();
1794
1795 let raw = event
1796 .header_str("variable_sip_P-Asserted-Identity")
1797 .unwrap();
1798 assert_eq!(
1799 raw,
1800 "ARRAY::<sip:alice@atlanta.example.com>|:<tel:+15551234567>"
1801 );
1802
1803 let arr = crate::variables::EslArray::parse(raw).unwrap();
1804 assert_eq!(arr.len(), 2);
1805 assert_eq!(arr.items()[0], "<sip:alice@atlanta.example.com>");
1806 assert_eq!(arr.items()[1], "<tel:+15551234567>");
1807 }
1808
1809 #[test]
1810 fn test_sip_header_with_colons_in_uri() {
1811 let mut event = EslEvent::new();
1812 event
1814 .push_header(
1815 "variable_sip_h_Diversion",
1816 "<sip:+15551234567@gw.example.com;reason=unconditional>",
1817 )
1818 .unwrap();
1819 event
1820 .push_header(
1821 "variable_sip_h_Diversion",
1822 "<sips:+15559876543@secure.example.com;reason=no-answer;counter=3>",
1823 )
1824 .unwrap();
1825
1826 let raw = event
1827 .header_str("variable_sip_h_Diversion")
1828 .unwrap();
1829 let arr = crate::variables::EslArray::parse(raw).unwrap();
1830 assert_eq!(arr.len(), 2);
1831 assert_eq!(
1832 arr.items()[0],
1833 "<sip:+15551234567@gw.example.com;reason=unconditional>"
1834 );
1835 assert_eq!(
1836 arr.items()[1],
1837 "<sips:+15559876543@secure.example.com;reason=no-answer;counter=3>"
1838 );
1839 }
1840
1841 #[test]
1842 fn test_sip_p_asserted_identity_plain_format_round_trip() {
1843 let mut event = EslEvent::with_type(EslEventType::ChannelCreate);
1844 event.set_header("Event-Name", "CHANNEL_CREATE");
1845 event.set_header(
1846 "variable_sip_P-Asserted-Identity",
1847 "<sip:alice@atlanta.example.com>, <tel:+15551234567>",
1848 );
1849
1850 let plain = event.to_plain_format();
1851 assert!(plain.contains("variable_sip_P-Asserted-Identity:"));
1853 assert!(!plain.contains("<sip:alice"));
1855 }
1856
1857 #[test]
1862 fn set_header_normalizes_known_enum_variant() {
1863 let mut event = EslEvent::new();
1864 event.set_header("unique-id", "abc-123");
1865 assert_eq!(event.header(EventHeader::UniqueId), Some("abc-123"));
1866 }
1867
1868 #[test]
1869 fn set_header_normalizes_codec_header() {
1870 let mut event = EslEvent::new();
1871 event.set_header("channel-read-codec-bit-rate", "128000");
1872 assert_eq!(
1873 event.header(EventHeader::ChannelReadCodecBitRate),
1874 Some("128000")
1875 );
1876 }
1877
1878 #[test]
1879 fn header_str_finds_by_original_key() {
1880 let mut event = EslEvent::new();
1881 event.set_header("unique-id", "abc-123");
1882 assert_eq!(event.header_str("unique-id"), Some("abc-123"));
1884 assert_eq!(event.header_str("Unique-ID"), Some("abc-123"));
1886 }
1887
1888 #[test]
1889 fn header_str_finds_unknown_dash_header_by_original() {
1890 let mut event = EslEvent::new();
1891 event.set_header("x-custom-header", "val");
1892 assert_eq!(event.header_str("X-Custom-Header"), Some("val"));
1894 assert_eq!(event.header_str("x-custom-header"), Some("val"));
1896 }
1897
1898 #[test]
1899 fn set_header_underscore_passthrough_preserves_sip_h() {
1900 let mut event = EslEvent::new();
1901 event.set_header("variable_sip_h_X-My-CUSTOM-Header", "val");
1902 assert_eq!(
1903 event.header_str("variable_sip_h_X-My-CUSTOM-Header"),
1904 Some("val")
1905 );
1906 }
1907
1908 #[test]
1909 fn set_header_different_casing_overwrites() {
1910 let mut event = EslEvent::new();
1911 event.set_header("Unique-ID", "first");
1912 event.set_header("unique-id", "second");
1913 assert_eq!(event.header(EventHeader::UniqueId), Some("second"));
1915 }
1916
1917 #[test]
1918 fn remove_header_by_original_key() {
1919 let mut event = EslEvent::new();
1920 event.set_header("unique-id", "abc-123");
1921 let removed = event.remove_header("unique-id");
1922 assert_eq!(removed, Some("abc-123".to_string()));
1923 assert_eq!(event.header(EventHeader::UniqueId), None);
1924 }
1925
1926 #[test]
1927 fn remove_header_by_canonical_key() {
1928 let mut event = EslEvent::new();
1929 event.set_header("unique-id", "abc-123");
1930 let removed = event.remove_header("Unique-ID");
1931 assert_eq!(removed, Some("abc-123".to_string()));
1932 assert_eq!(event.header_str("unique-id"), None);
1933 }
1934
1935 #[test]
1936 fn serde_round_trip_preserves_canonical_lookups() {
1937 let mut event = EslEvent::new();
1938 event.set_header("unique-id", "abc-123");
1939 event.set_header("channel-read-codec-bit-rate", "128000");
1940 let json = serde_json::to_string(&event).unwrap();
1941 let deserialized: EslEvent = serde_json::from_str(&json).unwrap();
1942 assert_eq!(deserialized.header(EventHeader::UniqueId), Some("abc-123"));
1943 assert_eq!(
1944 deserialized.header(EventHeader::ChannelReadCodecBitRate),
1945 Some("128000")
1946 );
1947 }
1948
1949 #[test]
1950 fn serde_deserialize_normalizes_external_json() {
1951 let json = r#"{"event_type":null,"headers":{"unique-id":"abc-123","channel-read-codec-bit-rate":"128000"},"body":null}"#;
1952 let event: EslEvent = serde_json::from_str(json).unwrap();
1953 assert_eq!(event.header(EventHeader::UniqueId), Some("abc-123"));
1954 assert_eq!(
1955 event.header(EventHeader::ChannelReadCodecBitRate),
1956 Some("128000")
1957 );
1958 assert_eq!(event.header_str("unique-id"), Some("abc-123"));
1959 }
1960
1961 #[test]
1962 fn original_keys_rebuilt_after_serde_roundtrip() {
1963 let external_json = r#"{
1981 "event_type": null,
1982 "headers": {
1983 "Channel-Write-Codec-Name": "opus",
1984 "channel-write-codec-bit-rate": "64000",
1985 "Custom-X-Header": "preserved"
1986 },
1987 "body": null
1988 }"#;
1989 let parsed: EslEvent = serde_json::from_str(external_json).unwrap();
1990
1991 assert_eq!(
1994 parsed.header(EventHeader::ChannelWriteCodecName),
1995 Some("opus")
1996 );
1997 assert_eq!(
1998 parsed.header(EventHeader::ChannelWriteCodecBitRate),
1999 Some("64000")
2000 );
2001
2002 assert_eq!(
2005 parsed.header_str("channel-write-codec-bit-rate"),
2006 Some("64000")
2007 );
2008 assert_eq!(
2010 parsed.header_str("Channel-Write-Codec-Bit-Rate"),
2011 Some("64000")
2012 );
2013
2014 assert_eq!(parsed.header_str("Custom-X-Header"), Some("preserved"));
2017
2018 let json = serde_json::to_string(&parsed).unwrap();
2021 let re_parsed: EslEvent = serde_json::from_str(&json).unwrap();
2022 assert_eq!(
2023 re_parsed.header(EventHeader::ChannelWriteCodecBitRate),
2024 Some("64000")
2025 );
2026 }
2027
2028 #[test]
2029 fn test_event_typed_accessors_invalid_values() {
2030 let mut event = EslEvent::new();
2031 event.set_header("Channel-State", "BOGUS");
2032 event.set_header("Channel-State-Number", "999");
2033 event.set_header("Channel-Call-State", "BOGUS");
2034 event.set_header("Answer-State", "bogus");
2035 event.set_header("Call-Direction", "bogus");
2036 assert!(event
2037 .channel_state()
2038 .is_err());
2039 assert!(event
2040 .channel_state_number()
2041 .is_err());
2042 assert!(event
2043 .call_state()
2044 .is_err());
2045 assert!(event
2046 .answer_state()
2047 .is_err());
2048 assert!(event
2049 .call_direction()
2050 .is_err());
2051 }
2052
2053 #[test]
2056 fn new_creates_empty() {
2057 let sub = EventSubscription::new(EventFormat::Plain);
2058 assert!(sub.is_empty());
2059 assert!(!sub.is_all());
2060 assert_eq!(sub.format(), EventFormat::Plain);
2061 assert!(sub
2062 .event_types()
2063 .is_empty());
2064 assert!(sub
2065 .custom_subclass_list()
2066 .is_empty());
2067 assert!(sub
2068 .filters()
2069 .is_empty());
2070 }
2071
2072 #[test]
2073 fn all_creates_all() {
2074 let sub = EventSubscription::all(EventFormat::Json);
2075 assert!(sub.is_all());
2076 assert!(!sub.is_empty());
2077 assert_eq!(sub.to_event_string(), Some("ALL".to_string()));
2078 }
2079
2080 #[test]
2081 fn event_string_typed_only() {
2082 let sub = EventSubscription::new(EventFormat::Plain)
2083 .event(EslEventType::ChannelCreate)
2084 .event(EslEventType::ChannelAnswer);
2085 assert_eq!(
2086 sub.to_event_string(),
2087 Some("CHANNEL_CREATE CHANNEL_ANSWER".to_string())
2088 );
2089 }
2090
2091 #[test]
2092 fn event_string_custom_only() {
2093 let sub = EventSubscription::new(EventFormat::Plain)
2094 .custom_subclass("sofia::register")
2095 .unwrap()
2096 .custom_subclass("sofia::unregister")
2097 .unwrap();
2098 assert_eq!(
2099 sub.to_event_string(),
2100 Some("CUSTOM sofia::register sofia::unregister".to_string())
2101 );
2102 }
2103
2104 #[test]
2105 fn event_string_mixed() {
2106 let sub = EventSubscription::new(EventFormat::Plain)
2107 .event(EslEventType::Heartbeat)
2108 .custom_subclass("sofia::register")
2109 .unwrap();
2110 assert_eq!(
2111 sub.to_event_string(),
2112 Some("HEARTBEAT CUSTOM sofia::register".to_string())
2113 );
2114 }
2115
2116 #[test]
2117 fn event_string_custom_not_duplicated() {
2118 let sub = EventSubscription::new(EventFormat::Plain)
2119 .event(EslEventType::Custom)
2120 .custom_subclass("sofia::register")
2121 .unwrap();
2122 assert_eq!(
2124 sub.to_event_string(),
2125 Some("CUSTOM sofia::register".to_string())
2126 );
2127 }
2128
2129 #[test]
2130 fn event_string_empty_is_none() {
2131 let sub = EventSubscription::new(EventFormat::Plain);
2132 assert_eq!(sub.to_event_string(), None);
2133 }
2134
2135 #[test]
2136 fn filters_preserve_order() {
2137 let sub = EventSubscription::new(EventFormat::Plain)
2138 .filter(EventHeader::CallDirection, "inbound")
2139 .unwrap()
2140 .filter_raw("X-Custom", "value1")
2141 .unwrap()
2142 .filter(EventHeader::ChannelState, "CS_EXECUTE")
2143 .unwrap();
2144 assert_eq!(
2145 sub.filters(),
2146 &[
2147 ("Call-Direction".to_string(), "inbound".to_string()),
2148 ("X-Custom".to_string(), "value1".to_string()),
2149 ("Channel-State".to_string(), "CS_EXECUTE".to_string()),
2150 ]
2151 );
2152 }
2153
2154 #[test]
2155 fn builder_chain() {
2156 let sub = EventSubscription::new(EventFormat::Plain)
2157 .events(EslEventType::CHANNEL_EVENTS)
2158 .event(EslEventType::Heartbeat)
2159 .custom_subclass("sofia::register")
2160 .unwrap()
2161 .filter(EventHeader::CallDirection, "inbound")
2162 .unwrap()
2163 .with_format(EventFormat::Json);
2164
2165 assert_eq!(sub.format(), EventFormat::Json);
2166 assert!(!sub.is_empty());
2167 assert!(!sub.is_all());
2168 assert!(sub
2169 .event_types()
2170 .contains(&EslEventType::ChannelCreate));
2171 assert!(sub
2172 .event_types()
2173 .contains(&EslEventType::Heartbeat));
2174 assert_eq!(sub.custom_subclass_list(), &["sofia::register"]);
2175 assert_eq!(
2176 sub.filters()
2177 .len(),
2178 1
2179 );
2180 }
2181
2182 #[test]
2183 fn serde_round_trip_subscription() {
2184 let sub = EventSubscription::new(EventFormat::Plain)
2185 .event(EslEventType::ChannelCreate)
2186 .event(EslEventType::Heartbeat)
2187 .custom_subclass("sofia::register")
2188 .unwrap()
2189 .filter(EventHeader::CallDirection, "inbound")
2190 .unwrap();
2191
2192 let json = serde_json::to_string(&sub).unwrap();
2193 let deserialized: EventSubscription = serde_json::from_str(&json).unwrap();
2194 assert_eq!(sub, deserialized);
2195 }
2196
2197 #[test]
2198 fn serde_rejects_invalid_subclass() {
2199 let json =
2200 r#"{"format":"Plain","events":[],"custom_subclasses":["bad subclass"],"filters":[]}"#;
2201 let result: Result<EventSubscription, _> = serde_json::from_str(json);
2202 assert!(result.is_err());
2203 let err = result
2204 .unwrap_err()
2205 .to_string();
2206 assert!(err.contains("space"), "error should mention space: {err}");
2207 }
2208
2209 #[test]
2210 fn serde_rejects_newline_in_filter() {
2211 let json = r#"{"format":"Plain","events":[],"custom_subclasses":[],"filters":[["Header","val\n"]]}"#;
2212 let result: Result<EventSubscription, _> = serde_json::from_str(json);
2213 assert!(result.is_err());
2214 let err = result
2215 .unwrap_err()
2216 .to_string();
2217 assert!(
2218 err.contains("newline"),
2219 "error should mention newline: {err}"
2220 );
2221 }
2222
2223 #[test]
2224 fn custom_subclass_rejects_space() {
2225 let result = EventSubscription::new(EventFormat::Plain).custom_subclass("bad subclass");
2226 assert!(result.is_err());
2227 }
2228
2229 #[test]
2230 fn custom_subclass_rejects_newline() {
2231 let result = EventSubscription::new(EventFormat::Plain).custom_subclass("bad\nsubclass");
2232 assert!(result.is_err());
2233 }
2234
2235 #[test]
2236 fn custom_subclass_rejects_empty() {
2237 let result = EventSubscription::new(EventFormat::Plain).custom_subclass("");
2238 assert!(result.is_err());
2239 }
2240
2241 #[test]
2242 fn filter_raw_rejects_newline_in_header() {
2243 let result = EventSubscription::new(EventFormat::Plain).filter_raw("Bad\nHeader", "value");
2244 assert!(result.is_err());
2245 }
2246
2247 #[test]
2248 fn filter_raw_rejects_newline_in_value() {
2249 let result = EventSubscription::new(EventFormat::Plain).filter_raw("Header", "bad\nvalue");
2250 assert!(result.is_err());
2251 }
2252
2253 #[test]
2254 fn filter_typed_rejects_newline_in_value() {
2255 let result = EventSubscription::new(EventFormat::Plain)
2256 .filter(EventHeader::CallDirection, "bad\nvalue");
2257 assert!(result.is_err());
2258 }
2259
2260 #[test]
2261 fn sofia_event_single() {
2262 let sub =
2263 EventSubscription::new(EventFormat::Plain).sofia_event(SofiaEventSubclass::Register);
2264 assert_eq!(
2265 sub.to_event_string(),
2266 Some("CUSTOM sofia::register".to_string())
2267 );
2268 }
2269
2270 #[test]
2271 fn sofia_events_group() {
2272 let sub = EventSubscription::new(EventFormat::Plain)
2273 .sofia_events(SofiaEventSubclass::GATEWAY_EVENTS);
2274 let event_str = sub
2275 .to_event_string()
2276 .unwrap();
2277 assert!(event_str.starts_with("CUSTOM"));
2278 assert!(event_str.contains("sofia::gateway_state"));
2279 assert!(event_str.contains("sofia::gateway_add"));
2280 assert!(event_str.contains("sofia::gateway_delete"));
2281 assert!(event_str.contains("sofia::gateway_invalid_digest_req"));
2282 }
2283
2284 #[test]
2285 fn event_raw_wire_string() {
2286 let sub = EventSubscription::new(EventFormat::Plain)
2287 .event(EslEventType::Heartbeat)
2288 .event_raw("NEW_EVENT_NOT_IN_ENUM")
2289 .unwrap();
2290 assert_eq!(
2291 sub.to_event_string(),
2292 Some("HEARTBEAT NEW_EVENT_NOT_IN_ENUM".to_string())
2293 );
2294 }
2295
2296 #[test]
2297 fn events_raw_wire_string() {
2298 let sub = EventSubscription::new(EventFormat::Plain)
2299 .events_raw(["FUTURE_A", "FUTURE_B"])
2300 .unwrap();
2301 assert_eq!(sub.to_event_string(), Some("FUTURE_A FUTURE_B".to_string()));
2302 }
2303
2304 #[test]
2305 fn event_raw_with_custom_subclass() {
2306 let sub = EventSubscription::new(EventFormat::Plain)
2307 .event_raw("NEW_EVENT")
2308 .unwrap()
2309 .custom_subclass("sofia::register")
2310 .unwrap();
2311 assert_eq!(
2312 sub.to_event_string(),
2313 Some("NEW_EVENT CUSTOM sofia::register".to_string())
2314 );
2315 }
2316
2317 #[test]
2318 fn event_raw_rejects_newline() {
2319 assert!(EventSubscription::new(EventFormat::Plain)
2320 .event_raw("bad\nevent")
2321 .is_err());
2322 }
2323
2324 #[test]
2325 fn event_raw_rejects_space() {
2326 assert!(EventSubscription::new(EventFormat::Plain)
2327 .event_raw("bad event")
2328 .is_err());
2329 }
2330
2331 #[test]
2332 fn event_raw_rejects_empty() {
2333 assert!(EventSubscription::new(EventFormat::Plain)
2334 .event_raw("")
2335 .is_err());
2336 }
2337
2338 #[test]
2339 fn events_raw_errors_on_first_invalid() {
2340 let result =
2341 EventSubscription::new(EventFormat::Plain).events_raw(["GOOD", "bad event", "OTHER"]);
2342 assert!(result.is_err());
2343 }
2344
2345 #[test]
2346 fn event_types_raw_mut_mutable() {
2347 let mut sub = EventSubscription::new(EventFormat::Plain);
2348 sub.event_types_raw_mut()
2349 .push("DIRECT_PUSH".to_string());
2350 assert_eq!(sub.event_types_raw(), &["DIRECT_PUSH".to_string()]);
2351 }
2352
2353 #[test]
2354 fn is_empty_sees_raw_events() {
2355 let sub = EventSubscription::new(EventFormat::Plain)
2356 .event_raw("ONLY_RAW")
2357 .unwrap();
2358 assert!(!sub.is_empty());
2359 }
2360
2361 #[test]
2362 fn serde_round_trip_with_raw_events() {
2363 let sub = EventSubscription::new(EventFormat::Plain)
2364 .event(EslEventType::ChannelCreate)
2365 .event_raw("FUTURE_EVENT")
2366 .unwrap()
2367 .custom_subclass("sofia::register")
2368 .unwrap();
2369
2370 let json = serde_json::to_string(&sub).unwrap();
2371 let deserialized: EventSubscription = serde_json::from_str(&json).unwrap();
2372 assert_eq!(sub, deserialized);
2373 }
2374
2375 #[test]
2376 fn serde_rejects_invalid_raw_event() {
2377 let json = r#"{"format":"Plain","events":[],"raw_events":["bad event"],"custom_subclasses":[],"filters":[]}"#;
2378 let result: Result<EventSubscription, _> = serde_json::from_str(json);
2379 assert!(result.is_err());
2380 }
2381
2382 #[test]
2383 fn serde_missing_raw_events_field_defaults_to_empty() {
2384 let json =
2387 r#"{"format":"Plain","events":["Heartbeat"],"custom_subclasses":[],"filters":[]}"#;
2388 let sub: EventSubscription = serde_json::from_str(json).unwrap();
2389 assert!(sub
2390 .event_types_raw()
2391 .is_empty());
2392 }
2393
2394 #[test]
2395 fn sofia_event_mixed_with_typed_events() {
2396 let sub = EventSubscription::new(EventFormat::Plain)
2397 .event(EslEventType::Heartbeat)
2398 .sofia_event(SofiaEventSubclass::GatewayState);
2399 assert_eq!(
2400 sub.to_event_string(),
2401 Some("HEARTBEAT CUSTOM sofia::gateway_state".to_string())
2402 );
2403 }
2404}