1use crate::headers::{normalize_header_key, EventHeader};
4use crate::lookup::HeaderLookup;
5use crate::sofia::SofiaEventSubclass;
6use crate::variables::EslArray;
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 custom_subclasses: Vec<String>,
446 filters: Vec<(String, String)>,
447}
448
449fn validate_custom_subclass(s: &str) -> Result<(), EventSubscriptionError> {
451 if s.is_empty() {
452 return Err(EventSubscriptionError(
453 "custom subclass cannot be empty".into(),
454 ));
455 }
456 if s.contains('\n') || s.contains('\r') {
457 return Err(EventSubscriptionError(format!(
458 "custom subclass contains newline: {:?}",
459 s
460 )));
461 }
462 if s.contains(' ') {
463 return Err(EventSubscriptionError(format!(
464 "custom subclass contains space: {:?}",
465 s
466 )));
467 }
468 Ok(())
469}
470
471fn validate_filter_field(field: &str, label: &str) -> Result<(), EventSubscriptionError> {
473 if field.contains('\n') || field.contains('\r') {
474 return Err(EventSubscriptionError(format!(
475 "filter {} contains newline: {:?}",
476 label, field
477 )));
478 }
479 Ok(())
480}
481
482impl EventSubscription {
483 pub fn new(format: EventFormat) -> Self {
485 Self {
486 format,
487 events: Vec::new(),
488 custom_subclasses: Vec::new(),
489 filters: Vec::new(),
490 }
491 }
492
493 pub fn all(format: EventFormat) -> Self {
495 Self {
496 format,
497 events: vec![EslEventType::All],
498 custom_subclasses: Vec::new(),
499 filters: Vec::new(),
500 }
501 }
502
503 pub fn event(mut self, event: EslEventType) -> Self {
505 self.events
506 .push(event);
507 self
508 }
509
510 pub fn events<T: IntoIterator<Item = impl std::borrow::Borrow<EslEventType>>>(
512 mut self,
513 events: T,
514 ) -> Self {
515 self.events
516 .extend(
517 events
518 .into_iter()
519 .map(|e| *e.borrow()),
520 );
521 self
522 }
523
524 pub fn custom_subclass(
528 mut self,
529 subclass: impl Into<String>,
530 ) -> Result<Self, EventSubscriptionError> {
531 let s = subclass.into();
532 validate_custom_subclass(&s)?;
533 self.custom_subclasses
534 .push(s);
535 Ok(self)
536 }
537
538 pub fn custom_subclasses(
542 mut self,
543 subclasses: impl IntoIterator<Item = impl Into<String>>,
544 ) -> Result<Self, EventSubscriptionError> {
545 for s in subclasses {
546 let s = s.into();
547 validate_custom_subclass(&s)?;
548 self.custom_subclasses
549 .push(s);
550 }
551 Ok(self)
552 }
553
554 pub fn sofia_event(mut self, subclass: SofiaEventSubclass) -> Self {
559 self.custom_subclasses
560 .push(
561 subclass
562 .as_str()
563 .to_string(),
564 );
565 self
566 }
567
568 pub fn sofia_events(
570 mut self,
571 subclasses: impl IntoIterator<Item = impl std::borrow::Borrow<SofiaEventSubclass>>,
572 ) -> Self {
573 self.custom_subclasses
574 .extend(
575 subclasses
576 .into_iter()
577 .map(|s| {
578 s.borrow()
579 .as_str()
580 .to_string()
581 }),
582 );
583 self
584 }
585
586 pub fn filter(
590 self,
591 header: crate::headers::EventHeader,
592 value: impl Into<String>,
593 ) -> Result<Self, EventSubscriptionError> {
594 let v = value.into();
595 validate_filter_field(&v, "value")?;
596 let mut s = self;
597 s.filters
598 .push((
599 header
600 .as_str()
601 .to_string(),
602 v,
603 ));
604 Ok(s)
605 }
606
607 pub fn filter_raw(
611 self,
612 header: impl Into<String>,
613 value: impl Into<String>,
614 ) -> Result<Self, EventSubscriptionError> {
615 let h = header.into();
616 let v = value.into();
617 validate_filter_field(&h, "header")?;
618 validate_filter_field(&v, "value")?;
619 let mut s = self;
620 s.filters
621 .push((h, v));
622 Ok(s)
623 }
624
625 pub fn with_format(mut self, format: EventFormat) -> Self {
627 self.format = format;
628 self
629 }
630
631 pub fn format(&self) -> EventFormat {
633 self.format
634 }
635
636 pub fn format_mut(&mut self) -> &mut EventFormat {
638 &mut self.format
639 }
640
641 pub fn event_types(&self) -> &[EslEventType] {
643 &self.events
644 }
645
646 pub fn event_types_mut(&mut self) -> &mut Vec<EslEventType> {
648 &mut self.events
649 }
650
651 pub fn custom_subclass_list(&self) -> &[String] {
653 &self.custom_subclasses
654 }
655
656 pub fn custom_subclasses_mut(&mut self) -> &mut Vec<String> {
658 &mut self.custom_subclasses
659 }
660
661 pub fn filters(&self) -> &[(String, String)] {
663 &self.filters
664 }
665
666 pub fn filters_mut(&mut self) -> &mut Vec<(String, String)> {
668 &mut self.filters
669 }
670
671 pub fn is_all(&self) -> bool {
673 self.events
674 .contains(&EslEventType::All)
675 }
676
677 pub fn is_empty(&self) -> bool {
679 self.events
680 .is_empty()
681 && self
682 .custom_subclasses
683 .is_empty()
684 }
685
686 pub fn to_event_string(&self) -> Option<String> {
693 if self
694 .events
695 .contains(&EslEventType::All)
696 {
697 return Some("ALL".to_string());
698 }
699
700 let mut parts: Vec<&str> = self
701 .events
702 .iter()
703 .map(|e| e.as_str())
704 .collect();
705
706 if !self
707 .custom_subclasses
708 .is_empty()
709 {
710 if !self
711 .events
712 .contains(&EslEventType::Custom)
713 {
714 parts.push("CUSTOM");
715 }
716 for sc in &self.custom_subclasses {
717 parts.push(sc.as_str());
718 }
719 }
720
721 if parts.is_empty() {
722 None
723 } else {
724 Some(parts.join(" "))
725 }
726 }
727}
728
729#[cfg(feature = "serde")]
730mod event_subscription_serde {
731 use super::*;
732 use serde::{Deserialize, Serialize};
733
734 #[derive(Serialize, Deserialize)]
735 struct EventSubscriptionRaw {
736 format: EventFormat,
737 #[serde(default)]
738 events: Vec<EslEventType>,
739 #[serde(default)]
740 custom_subclasses: Vec<String>,
741 #[serde(default)]
742 filters: Vec<(String, String)>,
743 }
744
745 impl TryFrom<EventSubscriptionRaw> for EventSubscription {
746 type Error = EventSubscriptionError;
747
748 fn try_from(raw: EventSubscriptionRaw) -> Result<Self, Self::Error> {
749 for sc in &raw.custom_subclasses {
750 validate_custom_subclass(sc)?;
751 }
752 for (h, v) in &raw.filters {
753 validate_filter_field(h, "header")?;
754 validate_filter_field(v, "value")?;
755 }
756 Ok(EventSubscription {
757 format: raw.format,
758 events: raw.events,
759 custom_subclasses: raw.custom_subclasses,
760 filters: raw.filters,
761 })
762 }
763 }
764
765 impl Serialize for EventSubscription {
766 fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
767 let raw = EventSubscriptionRaw {
768 format: self.format,
769 events: self
770 .events
771 .clone(),
772 custom_subclasses: self
773 .custom_subclasses
774 .clone(),
775 filters: self
776 .filters
777 .clone(),
778 };
779 raw.serialize(serializer)
780 }
781 }
782
783 impl<'de> Deserialize<'de> for EventSubscription {
784 fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
785 let raw = EventSubscriptionRaw::deserialize(deserializer)?;
786 EventSubscription::try_from(raw).map_err(serde::de::Error::custom)
787 }
788 }
789}
790
791#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
793#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
794#[non_exhaustive]
795pub enum EslEventPriority {
796 Normal,
798 Low,
800 High,
802}
803
804impl fmt::Display for EslEventPriority {
805 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
806 match self {
807 EslEventPriority::Normal => write!(f, "NORMAL"),
808 EslEventPriority::Low => write!(f, "LOW"),
809 EslEventPriority::High => write!(f, "HIGH"),
810 }
811 }
812}
813
814#[derive(Debug, Clone, PartialEq, Eq)]
816pub struct ParsePriorityError(pub String);
817
818impl fmt::Display for ParsePriorityError {
819 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
820 write!(f, "unknown priority: {}", self.0)
821 }
822}
823
824impl std::error::Error for ParsePriorityError {}
825
826impl FromStr for EslEventPriority {
827 type Err = ParsePriorityError;
828
829 fn from_str(s: &str) -> Result<Self, Self::Err> {
830 match s {
831 "NORMAL" => Ok(EslEventPriority::Normal),
832 "LOW" => Ok(EslEventPriority::Low),
833 "HIGH" => Ok(EslEventPriority::High),
834 _ => Err(ParsePriorityError(s.to_string())),
835 }
836 }
837}
838
839#[derive(Debug, Clone, Eq)]
841#[cfg_attr(feature = "serde", derive(serde::Serialize))]
842pub struct EslEvent {
843 event_type: Option<EslEventType>,
844 headers: IndexMap<String, String>,
845 #[cfg_attr(feature = "serde", serde(skip))]
846 original_keys: IndexMap<String, String>,
847 body: Option<String>,
848}
849
850impl EslEvent {
851 pub fn new() -> Self {
853 Self {
854 event_type: None,
855 headers: IndexMap::new(),
856 original_keys: IndexMap::new(),
857 body: None,
858 }
859 }
860
861 pub fn with_type(event_type: EslEventType) -> Self {
863 Self {
864 event_type: Some(event_type),
865 headers: IndexMap::new(),
866 original_keys: IndexMap::new(),
867 body: None,
868 }
869 }
870
871 pub fn event_type(&self) -> Option<EslEventType> {
873 self.event_type
874 }
875
876 pub fn set_event_type(&mut self, event_type: Option<EslEventType>) {
878 self.event_type = event_type;
879 }
880
881 pub fn header(&self, name: EventHeader) -> Option<&str> {
885 self.headers
886 .get(name.as_str())
887 .map(|s| s.as_str())
888 }
889
890 pub fn header_str(&self, name: &str) -> Option<&str> {
898 self.headers
899 .get(name)
900 .or_else(|| {
901 self.original_keys
902 .get(name)
903 .and_then(|normalized| {
904 self.headers
905 .get(normalized)
906 })
907 })
908 .map(|s| s.as_str())
909 }
910
911 pub fn variable_str(&self, name: &str) -> Option<&str> {
916 let key = format!("variable_{}", name);
917 self.header_str(&key)
918 }
919
920 pub fn headers(&self) -> &IndexMap<String, String> {
922 &self.headers
923 }
924
925 pub fn set_header(&mut self, name: impl Into<String>, value: impl Into<String>) {
927 let original = name.into();
928 let normalized = normalize_header_key(&original);
929 if original != normalized {
930 self.original_keys
931 .insert(original, normalized.clone());
932 }
933 self.headers
934 .insert(normalized, value.into());
935 }
936
937 pub fn remove_header(&mut self, name: impl AsRef<str>) -> Option<String> {
941 let name = name.as_ref();
942 if let Some(value) = self
943 .headers
944 .shift_remove(name)
945 {
946 return Some(value);
947 }
948 if let Some(normalized) = self
949 .original_keys
950 .shift_remove(name)
951 {
952 return self
953 .headers
954 .shift_remove(&normalized);
955 }
956 None
957 }
958
959 pub fn body(&self) -> Option<&str> {
961 self.body
962 .as_deref()
963 }
964
965 pub fn set_body(&mut self, body: impl Into<String>) {
967 self.body = Some(body.into());
968 }
969
970 pub fn set_priority(&mut self, priority: EslEventPriority) {
975 self.set_header(EventHeader::Priority.as_str(), priority.to_string());
976 }
977
978 pub fn push_header(&mut self, name: &str, value: &str) {
992 self.stack_header(name, value, EslArray::push);
993 }
994
995 pub fn unshift_header(&mut self, name: &str, value: &str) {
1007 self.stack_header(name, value, EslArray::unshift);
1008 }
1009
1010 fn stack_header(&mut self, name: &str, value: &str, op: fn(&mut EslArray, String)) {
1011 match self
1012 .headers
1013 .get(name)
1014 {
1015 None => {
1016 self.set_header(name, value);
1017 }
1018 Some(existing) => {
1019 let mut arr = match EslArray::parse(existing) {
1020 Some(arr) => arr,
1021 None => EslArray::new(vec![existing.clone()]),
1022 };
1023 op(&mut arr, value.into());
1024 self.set_header(name, arr.to_string());
1025 }
1026 }
1027 }
1028
1029 pub fn is_event_type(&self, event_type: EslEventType) -> bool {
1031 self.event_type == Some(event_type)
1032 }
1033
1034 pub fn to_plain_format(&self) -> String {
1044 use std::fmt::Write;
1045 let mut result = String::new();
1046
1047 for (key, value) in &self.headers {
1048 if key == "Content-Length" {
1049 continue;
1050 }
1051 let _ = writeln!(
1052 result,
1053 "{}: {}",
1054 key,
1055 percent_encode(value.as_bytes(), NON_ALPHANUMERIC)
1056 );
1057 }
1058
1059 if let Some(body) = &self.body {
1060 let _ = writeln!(result, "Content-Length: {}", body.len());
1061 result.push('\n');
1062 result.push_str(body);
1063 } else {
1064 result.push('\n');
1065 }
1066
1067 result
1068 }
1069}
1070
1071impl Default for EslEvent {
1072 fn default() -> Self {
1073 Self::new()
1074 }
1075}
1076
1077impl HeaderLookup for EslEvent {
1078 fn header_str(&self, name: &str) -> Option<&str> {
1079 EslEvent::header_str(self, name)
1080 }
1081
1082 fn variable_str(&self, name: &str) -> Option<&str> {
1083 let key = format!("variable_{}", name);
1084 self.header_str(&key)
1085 }
1086}
1087
1088impl PartialEq for EslEvent {
1089 fn eq(&self, other: &Self) -> bool {
1090 self.event_type == other.event_type
1091 && self.headers == other.headers
1092 && self.body == other.body
1093 }
1094}
1095
1096impl std::hash::Hash for EslEvent {
1097 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
1098 self.event_type
1099 .hash(state);
1100 for (k, v) in &self.headers {
1101 k.hash(state);
1102 v.hash(state);
1103 }
1104 self.body
1105 .hash(state);
1106 }
1107}
1108
1109#[cfg(feature = "serde")]
1110impl<'de> serde::Deserialize<'de> for EslEvent {
1111 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
1112 where
1113 D: serde::Deserializer<'de>,
1114 {
1115 #[derive(serde::Deserialize)]
1116 struct Raw {
1117 event_type: Option<EslEventType>,
1118 headers: IndexMap<String, String>,
1119 body: Option<String>,
1120 }
1121 let raw = Raw::deserialize(deserializer)?;
1122 let mut event = EslEvent::new();
1123 event.event_type = raw.event_type;
1124 event.body = raw.body;
1125 for (k, v) in raw.headers {
1126 event.set_header(k, v);
1127 }
1128 Ok(event)
1129 }
1130}
1131
1132#[cfg(test)]
1133mod tests {
1134 use super::*;
1135
1136 #[test]
1137 fn headers_preserve_insertion_order() {
1138 let mut event = EslEvent::new();
1139 event.set_header("Zebra", "last");
1140 event.set_header("Alpha", "first");
1141 event.set_header("Middle", "mid");
1142 let keys: Vec<&str> = event
1143 .headers()
1144 .keys()
1145 .map(|s| s.as_str())
1146 .collect();
1147 assert_eq!(keys, vec!["Zebra", "Alpha", "Middle"]);
1148 }
1149
1150 #[test]
1151 fn test_notify_in_parse() {
1152 assert_eq!(
1153 EslEventType::parse_event_type("NOTIFY_IN"),
1154 Some(EslEventType::NotifyIn)
1155 );
1156 assert_eq!(EslEventType::parse_event_type("notify_in"), None);
1157 }
1158
1159 #[test]
1160 fn test_notify_in_display() {
1161 assert_eq!(EslEventType::NotifyIn.to_string(), "NOTIFY_IN");
1162 }
1163
1164 #[test]
1165 fn test_notify_in_distinct_from_notify() {
1166 assert_ne!(EslEventType::Notify, EslEventType::NotifyIn);
1167 assert_ne!(
1168 EslEventType::Notify.to_string(),
1169 EslEventType::NotifyIn.to_string()
1170 );
1171 }
1172
1173 #[test]
1174 fn test_wire_names_match_c_esl() {
1175 assert_eq!(
1176 EslEventType::ChannelOutgoing.to_string(),
1177 "CHANNEL_OUTGOING"
1178 );
1179 assert_eq!(EslEventType::Api.to_string(), "API");
1180 assert_eq!(EslEventType::ReloadXml.to_string(), "RELOADXML");
1181 assert_eq!(EslEventType::PresenceIn.to_string(), "PRESENCE_IN");
1182 assert_eq!(EslEventType::Roster.to_string(), "ROSTER");
1183 assert_eq!(EslEventType::Text.to_string(), "TEXT");
1184 assert_eq!(EslEventType::ReSchedule.to_string(), "RE_SCHEDULE");
1185
1186 assert_eq!(
1187 EslEventType::parse_event_type("CHANNEL_OUTGOING"),
1188 Some(EslEventType::ChannelOutgoing)
1189 );
1190 assert_eq!(
1191 EslEventType::parse_event_type("API"),
1192 Some(EslEventType::Api)
1193 );
1194 assert_eq!(
1195 EslEventType::parse_event_type("RELOADXML"),
1196 Some(EslEventType::ReloadXml)
1197 );
1198 assert_eq!(
1199 EslEventType::parse_event_type("PRESENCE_IN"),
1200 Some(EslEventType::PresenceIn)
1201 );
1202 }
1203
1204 #[test]
1205 fn test_event_type_from_str() {
1206 assert_eq!(
1207 "CHANNEL_ANSWER".parse::<EslEventType>(),
1208 Ok(EslEventType::ChannelAnswer)
1209 );
1210 assert!("channel_answer"
1211 .parse::<EslEventType>()
1212 .is_err());
1213 assert!("UNKNOWN_EVENT"
1214 .parse::<EslEventType>()
1215 .is_err());
1216 }
1217
1218 #[test]
1219 fn test_remove_header() {
1220 let mut event = EslEvent::new();
1221 event.set_header("Foo", "bar");
1222 event.set_header("Baz", "qux");
1223
1224 let removed = event.remove_header("Foo");
1225 assert_eq!(removed, Some("bar".to_string()));
1226 assert!(event
1227 .header_str("Foo")
1228 .is_none());
1229 assert_eq!(event.header_str("Baz"), Some("qux"));
1230
1231 let removed_again = event.remove_header("Foo");
1232 assert_eq!(removed_again, None);
1233 }
1234
1235 #[test]
1236 fn test_to_plain_format_basic() {
1237 let mut event = EslEvent::with_type(EslEventType::Heartbeat);
1238 event.set_header("Event-Name", "HEARTBEAT");
1239 event.set_header("Core-UUID", "abc-123");
1240
1241 let plain = event.to_plain_format();
1242
1243 assert!(plain.starts_with("Event-Name: "));
1244 assert!(plain.contains("Core-UUID: "));
1245 assert!(plain.ends_with("\n\n"));
1246 }
1247
1248 #[test]
1249 fn test_to_plain_format_percent_encoding() {
1250 let mut event = EslEvent::with_type(EslEventType::Heartbeat);
1251 event.set_header("Event-Name", "HEARTBEAT");
1252 event.set_header("Up-Time", "0 years, 0 days");
1253
1254 let plain = event.to_plain_format();
1255
1256 assert!(!plain.contains("0 years, 0 days"));
1257 assert!(plain.contains("Up-Time: "));
1258 assert!(plain.contains("%20"));
1259 }
1260
1261 #[test]
1262 fn test_to_plain_format_with_body() {
1263 let mut event = EslEvent::with_type(EslEventType::BackgroundJob);
1264 event.set_header("Event-Name", "BACKGROUND_JOB");
1265 event.set_header("Job-UUID", "def-456");
1266 event.set_body("+OK result\n".to_string());
1267
1268 let plain = event.to_plain_format();
1269
1270 assert!(plain.contains("Content-Length: 11\n"));
1271 assert!(plain.ends_with("\n\n+OK result\n"));
1272 }
1273
1274 #[test]
1275 fn test_to_plain_format_preserves_insertion_order() {
1276 let mut event = EslEvent::with_type(EslEventType::Heartbeat);
1277 event.set_header("Event-Name", "HEARTBEAT");
1278 event.set_header("Core-UUID", "abc-123");
1279 event.set_header("FreeSWITCH-Hostname", "fs01");
1280 event.set_header("Up-Time", "0 years, 1 day");
1281
1282 let plain = event.to_plain_format();
1283 let lines: Vec<&str> = plain
1284 .lines()
1285 .collect();
1286 assert!(lines[0].starts_with("Event-Name: "));
1287 assert!(lines[1].starts_with("Core-UUID: "));
1288 assert!(lines[2].starts_with("FreeSWITCH-Hostname: "));
1289 assert!(lines[3].starts_with("Up-Time: "));
1290 }
1291
1292 #[test]
1293 fn test_to_plain_format_round_trip() {
1294 let mut original = EslEvent::with_type(EslEventType::ChannelCreate);
1295 original.set_header("Event-Name", "CHANNEL_CREATE");
1296 original.set_header("Core-UUID", "abc-123");
1297 original.set_header("Channel-Name", "sofia/internal/1000@example.com");
1298 original.set_header("Caller-Caller-ID-Name", "Jérôme Poulin");
1299 original.set_body("some body content");
1300
1301 let plain = original.to_plain_format();
1302
1303 let (header_section, inner_body) = if let Some(pos) = plain.find("\n\n") {
1305 (&plain[..pos], Some(&plain[pos + 2..]))
1306 } else {
1307 (plain.as_str(), None)
1308 };
1309
1310 let mut parsed = EslEvent::new();
1311 for line in header_section.lines() {
1312 let line = line.trim();
1313 if line.is_empty() {
1314 continue;
1315 }
1316 if let Some(colon_pos) = line.find(':') {
1317 let key = line[..colon_pos].trim();
1318 if key == "Content-Length" {
1319 continue;
1320 }
1321 let raw_value = line[colon_pos + 1..].trim();
1322 let value = percent_encoding::percent_decode_str(raw_value)
1323 .decode_utf8()
1324 .unwrap()
1325 .into_owned();
1326 parsed.set_header(key, value);
1327 }
1328 }
1329 if let Some(ib) = inner_body {
1330 if !ib.is_empty() {
1331 parsed.set_body(ib);
1332 }
1333 }
1334
1335 assert_eq!(original.headers(), parsed.headers());
1336 assert_eq!(original.body(), parsed.body());
1337 }
1338
1339 #[test]
1340 fn test_set_priority_normal() {
1341 let mut event = EslEvent::new();
1342 event.set_priority(EslEventPriority::Normal);
1343 assert_eq!(
1344 event
1345 .priority()
1346 .unwrap(),
1347 Some(EslEventPriority::Normal)
1348 );
1349 assert_eq!(event.header(EventHeader::Priority), Some("NORMAL"));
1350 }
1351
1352 #[test]
1353 fn test_set_priority_high() {
1354 let mut event = EslEvent::new();
1355 event.set_priority(EslEventPriority::High);
1356 assert_eq!(
1357 event
1358 .priority()
1359 .unwrap(),
1360 Some(EslEventPriority::High)
1361 );
1362 assert_eq!(event.header(EventHeader::Priority), Some("HIGH"));
1363 }
1364
1365 #[test]
1366 fn test_priority_display() {
1367 assert_eq!(EslEventPriority::Normal.to_string(), "NORMAL");
1368 assert_eq!(EslEventPriority::Low.to_string(), "LOW");
1369 assert_eq!(EslEventPriority::High.to_string(), "HIGH");
1370 }
1371
1372 #[test]
1373 fn test_priority_from_str() {
1374 assert_eq!(
1375 "NORMAL".parse::<EslEventPriority>(),
1376 Ok(EslEventPriority::Normal)
1377 );
1378 assert_eq!("LOW".parse::<EslEventPriority>(), Ok(EslEventPriority::Low));
1379 assert_eq!(
1380 "HIGH".parse::<EslEventPriority>(),
1381 Ok(EslEventPriority::High)
1382 );
1383 assert!("INVALID"
1384 .parse::<EslEventPriority>()
1385 .is_err());
1386 }
1387
1388 #[test]
1389 fn test_priority_from_str_rejects_wrong_case() {
1390 assert!("normal"
1391 .parse::<EslEventPriority>()
1392 .is_err());
1393 assert!("Low"
1394 .parse::<EslEventPriority>()
1395 .is_err());
1396 assert!("hIgH"
1397 .parse::<EslEventPriority>()
1398 .is_err());
1399 }
1400
1401 #[test]
1402 fn test_push_header_new() {
1403 let mut event = EslEvent::new();
1404 event.push_header("X-Test", "first");
1405 assert_eq!(event.header_str("X-Test"), Some("first"));
1406 }
1407
1408 #[test]
1409 fn test_push_header_existing_plain() {
1410 let mut event = EslEvent::new();
1411 event.set_header("X-Test", "first");
1412 event.push_header("X-Test", "second");
1413 assert_eq!(event.header_str("X-Test"), Some("ARRAY::first|:second"));
1414 }
1415
1416 #[test]
1417 fn test_push_header_existing_array() {
1418 let mut event = EslEvent::new();
1419 event.set_header("X-Test", "ARRAY::a|:b");
1420 event.push_header("X-Test", "c");
1421 assert_eq!(event.header_str("X-Test"), Some("ARRAY::a|:b|:c"));
1422 }
1423
1424 #[test]
1425 fn test_unshift_header_new() {
1426 let mut event = EslEvent::new();
1427 event.unshift_header("X-Test", "only");
1428 assert_eq!(event.header_str("X-Test"), Some("only"));
1429 }
1430
1431 #[test]
1432 fn test_unshift_header_existing_array() {
1433 let mut event = EslEvent::new();
1434 event.set_header("X-Test", "ARRAY::b|:c");
1435 event.unshift_header("X-Test", "a");
1436 assert_eq!(event.header_str("X-Test"), Some("ARRAY::a|:b|:c"));
1437 }
1438
1439 #[test]
1440 fn test_sendevent_with_priority_wire_format() {
1441 let mut event = EslEvent::with_type(EslEventType::Custom);
1442 event.set_header("Event-Name", "CUSTOM");
1443 event.set_header("Event-Subclass", "test::priority");
1444 event.set_priority(EslEventPriority::High);
1445
1446 let plain = event.to_plain_format();
1447 assert!(plain.contains("priority: HIGH\n"));
1448 }
1449
1450 #[test]
1451 fn test_convenience_accessors() {
1452 let mut event = EslEvent::new();
1453 event.set_header("Channel-Name", "sofia/internal/1000@example.com");
1454 event.set_header("Caller-Caller-ID-Number", "1000");
1455 event.set_header("Caller-Caller-ID-Name", "Alice");
1456 event.set_header("Hangup-Cause", "NORMAL_CLEARING");
1457 event.set_header("Event-Subclass", "sofia::register");
1458 event.set_header("variable_sip_from_display", "Bob");
1459
1460 assert_eq!(
1461 event.channel_name(),
1462 Some("sofia/internal/1000@example.com")
1463 );
1464 assert_eq!(event.caller_id_number(), Some("1000"));
1465 assert_eq!(event.caller_id_name(), Some("Alice"));
1466 assert_eq!(
1467 event
1468 .hangup_cause()
1469 .unwrap(),
1470 Some(crate::channel::HangupCause::NormalClearing)
1471 );
1472 assert_eq!(event.event_subclass(), Some("sofia::register"));
1473 assert_eq!(event.variable_str("sip_from_display"), Some("Bob"));
1474 assert_eq!(event.variable_str("nonexistent"), None);
1475 }
1476
1477 #[test]
1478 fn test_event_format_from_str() {
1479 assert_eq!("plain".parse::<EventFormat>(), Ok(EventFormat::Plain));
1480 assert_eq!("json".parse::<EventFormat>(), Ok(EventFormat::Json));
1481 assert_eq!("xml".parse::<EventFormat>(), Ok(EventFormat::Xml));
1482 assert!("foo"
1483 .parse::<EventFormat>()
1484 .is_err());
1485 }
1486
1487 #[test]
1488 fn test_event_format_from_str_case_insensitive() {
1489 assert_eq!("PLAIN".parse::<EventFormat>(), Ok(EventFormat::Plain));
1490 assert_eq!("Json".parse::<EventFormat>(), Ok(EventFormat::Json));
1491 assert_eq!("XML".parse::<EventFormat>(), Ok(EventFormat::Xml));
1492 assert_eq!("Xml".parse::<EventFormat>(), Ok(EventFormat::Xml));
1493 }
1494
1495 #[test]
1496 fn test_event_format_from_content_type() {
1497 assert_eq!(
1498 EventFormat::from_content_type("text/event-json"),
1499 Ok(EventFormat::Json)
1500 );
1501 assert_eq!(
1502 EventFormat::from_content_type("text/event-xml"),
1503 Ok(EventFormat::Xml)
1504 );
1505 assert_eq!(
1506 EventFormat::from_content_type("text/event-plain"),
1507 Ok(EventFormat::Plain)
1508 );
1509 assert!(EventFormat::from_content_type("unknown").is_err());
1510 }
1511
1512 #[test]
1515 fn test_event_channel_state_accessor() {
1516 use crate::channel::ChannelState;
1517 let mut event = EslEvent::new();
1518 event.set_header("Channel-State", "CS_EXECUTE");
1519 assert_eq!(
1520 event
1521 .channel_state()
1522 .unwrap(),
1523 Some(ChannelState::CsExecute)
1524 );
1525 }
1526
1527 #[test]
1528 fn test_event_channel_state_number_accessor() {
1529 use crate::channel::ChannelState;
1530 let mut event = EslEvent::new();
1531 event.set_header("Channel-State-Number", "4");
1532 assert_eq!(
1533 event
1534 .channel_state_number()
1535 .unwrap(),
1536 Some(ChannelState::CsExecute)
1537 );
1538 }
1539
1540 #[test]
1541 fn test_event_call_state_accessor() {
1542 use crate::channel::CallState;
1543 let mut event = EslEvent::new();
1544 event.set_header("Channel-Call-State", "ACTIVE");
1545 assert_eq!(
1546 event
1547 .call_state()
1548 .unwrap(),
1549 Some(CallState::Active)
1550 );
1551 }
1552
1553 #[test]
1554 fn test_event_answer_state_accessor() {
1555 use crate::channel::AnswerState;
1556 let mut event = EslEvent::new();
1557 event.set_header("Answer-State", "answered");
1558 assert_eq!(
1559 event
1560 .answer_state()
1561 .unwrap(),
1562 Some(AnswerState::Answered)
1563 );
1564 }
1565
1566 #[test]
1567 fn test_event_call_direction_accessor() {
1568 use crate::channel::CallDirection;
1569 let mut event = EslEvent::new();
1570 event.set_header("Call-Direction", "inbound");
1571 assert_eq!(
1572 event
1573 .call_direction()
1574 .unwrap(),
1575 Some(CallDirection::Inbound)
1576 );
1577 }
1578
1579 #[test]
1580 fn test_event_typed_accessors_missing_headers() {
1581 let event = EslEvent::new();
1582 assert_eq!(
1583 event
1584 .channel_state()
1585 .unwrap(),
1586 None
1587 );
1588 assert_eq!(
1589 event
1590 .channel_state_number()
1591 .unwrap(),
1592 None
1593 );
1594 assert_eq!(
1595 event
1596 .call_state()
1597 .unwrap(),
1598 None
1599 );
1600 assert_eq!(
1601 event
1602 .answer_state()
1603 .unwrap(),
1604 None
1605 );
1606 assert_eq!(
1607 event
1608 .call_direction()
1609 .unwrap(),
1610 None
1611 );
1612 }
1613
1614 #[test]
1617 fn test_sip_p_asserted_identity_comma_separated() {
1618 let mut event = EslEvent::new();
1619 event.set_header(
1622 "variable_sip_P-Asserted-Identity",
1623 "<sip:alice@atlanta.example.com>, <tel:+15551234567>",
1624 );
1625
1626 assert_eq!(
1627 event.variable_str("sip_P-Asserted-Identity"),
1628 Some("<sip:alice@atlanta.example.com>, <tel:+15551234567>")
1629 );
1630 }
1631
1632 #[test]
1633 fn test_sip_p_asserted_identity_array_format() {
1634 let mut event = EslEvent::new();
1635 event.push_header(
1637 "variable_sip_P-Asserted-Identity",
1638 "<sip:alice@atlanta.example.com>",
1639 );
1640 event.push_header("variable_sip_P-Asserted-Identity", "<tel:+15551234567>");
1641
1642 let raw = event
1643 .header_str("variable_sip_P-Asserted-Identity")
1644 .unwrap();
1645 assert_eq!(
1646 raw,
1647 "ARRAY::<sip:alice@atlanta.example.com>|:<tel:+15551234567>"
1648 );
1649
1650 let arr = crate::variables::EslArray::parse(raw).unwrap();
1651 assert_eq!(arr.len(), 2);
1652 assert_eq!(arr.items()[0], "<sip:alice@atlanta.example.com>");
1653 assert_eq!(arr.items()[1], "<tel:+15551234567>");
1654 }
1655
1656 #[test]
1657 fn test_sip_header_with_colons_in_uri() {
1658 let mut event = EslEvent::new();
1659 event.push_header(
1661 "variable_sip_h_Diversion",
1662 "<sip:+15551234567@gw.example.com;reason=unconditional>",
1663 );
1664 event.push_header(
1665 "variable_sip_h_Diversion",
1666 "<sips:+15559876543@secure.example.com;reason=no-answer;counter=3>",
1667 );
1668
1669 let raw = event
1670 .header_str("variable_sip_h_Diversion")
1671 .unwrap();
1672 let arr = crate::variables::EslArray::parse(raw).unwrap();
1673 assert_eq!(arr.len(), 2);
1674 assert_eq!(
1675 arr.items()[0],
1676 "<sip:+15551234567@gw.example.com;reason=unconditional>"
1677 );
1678 assert_eq!(
1679 arr.items()[1],
1680 "<sips:+15559876543@secure.example.com;reason=no-answer;counter=3>"
1681 );
1682 }
1683
1684 #[test]
1685 fn test_sip_p_asserted_identity_plain_format_round_trip() {
1686 let mut event = EslEvent::with_type(EslEventType::ChannelCreate);
1687 event.set_header("Event-Name", "CHANNEL_CREATE");
1688 event.set_header(
1689 "variable_sip_P-Asserted-Identity",
1690 "<sip:alice@atlanta.example.com>, <tel:+15551234567>",
1691 );
1692
1693 let plain = event.to_plain_format();
1694 assert!(plain.contains("variable_sip_P-Asserted-Identity:"));
1696 assert!(!plain.contains("<sip:alice"));
1698 }
1699
1700 #[test]
1705 fn set_header_normalizes_known_enum_variant() {
1706 let mut event = EslEvent::new();
1707 event.set_header("unique-id", "abc-123");
1708 assert_eq!(event.header(EventHeader::UniqueId), Some("abc-123"));
1709 }
1710
1711 #[test]
1712 fn set_header_normalizes_codec_header() {
1713 let mut event = EslEvent::new();
1714 event.set_header("channel-read-codec-bit-rate", "128000");
1715 assert_eq!(
1716 event.header(EventHeader::ChannelReadCodecBitRate),
1717 Some("128000")
1718 );
1719 }
1720
1721 #[test]
1722 fn header_str_finds_by_original_key() {
1723 let mut event = EslEvent::new();
1724 event.set_header("unique-id", "abc-123");
1725 assert_eq!(event.header_str("unique-id"), Some("abc-123"));
1727 assert_eq!(event.header_str("Unique-ID"), Some("abc-123"));
1729 }
1730
1731 #[test]
1732 fn header_str_finds_unknown_dash_header_by_original() {
1733 let mut event = EslEvent::new();
1734 event.set_header("x-custom-header", "val");
1735 assert_eq!(event.header_str("X-Custom-Header"), Some("val"));
1737 assert_eq!(event.header_str("x-custom-header"), Some("val"));
1739 }
1740
1741 #[test]
1742 fn set_header_underscore_passthrough_preserves_sip_h() {
1743 let mut event = EslEvent::new();
1744 event.set_header("variable_sip_h_X-My-CUSTOM-Header", "val");
1745 assert_eq!(
1746 event.header_str("variable_sip_h_X-My-CUSTOM-Header"),
1747 Some("val")
1748 );
1749 }
1750
1751 #[test]
1752 fn set_header_different_casing_overwrites() {
1753 let mut event = EslEvent::new();
1754 event.set_header("Unique-ID", "first");
1755 event.set_header("unique-id", "second");
1756 assert_eq!(event.header(EventHeader::UniqueId), Some("second"));
1758 }
1759
1760 #[test]
1761 fn remove_header_by_original_key() {
1762 let mut event = EslEvent::new();
1763 event.set_header("unique-id", "abc-123");
1764 let removed = event.remove_header("unique-id");
1765 assert_eq!(removed, Some("abc-123".to_string()));
1766 assert_eq!(event.header(EventHeader::UniqueId), None);
1767 }
1768
1769 #[test]
1770 fn remove_header_by_canonical_key() {
1771 let mut event = EslEvent::new();
1772 event.set_header("unique-id", "abc-123");
1773 let removed = event.remove_header("Unique-ID");
1774 assert_eq!(removed, Some("abc-123".to_string()));
1775 assert_eq!(event.header_str("unique-id"), None);
1776 }
1777
1778 #[test]
1779 fn serde_round_trip_preserves_canonical_lookups() {
1780 let mut event = EslEvent::new();
1781 event.set_header("unique-id", "abc-123");
1782 event.set_header("channel-read-codec-bit-rate", "128000");
1783 let json = serde_json::to_string(&event).unwrap();
1784 let deserialized: EslEvent = serde_json::from_str(&json).unwrap();
1785 assert_eq!(deserialized.header(EventHeader::UniqueId), Some("abc-123"));
1786 assert_eq!(
1787 deserialized.header(EventHeader::ChannelReadCodecBitRate),
1788 Some("128000")
1789 );
1790 }
1791
1792 #[test]
1793 fn serde_deserialize_normalizes_external_json() {
1794 let json = r#"{"event_type":null,"headers":{"unique-id":"abc-123","channel-read-codec-bit-rate":"128000"},"body":null}"#;
1795 let event: EslEvent = serde_json::from_str(json).unwrap();
1796 assert_eq!(event.header(EventHeader::UniqueId), Some("abc-123"));
1797 assert_eq!(
1798 event.header(EventHeader::ChannelReadCodecBitRate),
1799 Some("128000")
1800 );
1801 assert_eq!(event.header_str("unique-id"), Some("abc-123"));
1802 }
1803
1804 #[test]
1805 fn test_event_typed_accessors_invalid_values() {
1806 let mut event = EslEvent::new();
1807 event.set_header("Channel-State", "BOGUS");
1808 event.set_header("Channel-State-Number", "999");
1809 event.set_header("Channel-Call-State", "BOGUS");
1810 event.set_header("Answer-State", "bogus");
1811 event.set_header("Call-Direction", "bogus");
1812 assert!(event
1813 .channel_state()
1814 .is_err());
1815 assert!(event
1816 .channel_state_number()
1817 .is_err());
1818 assert!(event
1819 .call_state()
1820 .is_err());
1821 assert!(event
1822 .answer_state()
1823 .is_err());
1824 assert!(event
1825 .call_direction()
1826 .is_err());
1827 }
1828
1829 #[test]
1832 fn new_creates_empty() {
1833 let sub = EventSubscription::new(EventFormat::Plain);
1834 assert!(sub.is_empty());
1835 assert!(!sub.is_all());
1836 assert_eq!(sub.format(), EventFormat::Plain);
1837 assert!(sub
1838 .event_types()
1839 .is_empty());
1840 assert!(sub
1841 .custom_subclass_list()
1842 .is_empty());
1843 assert!(sub
1844 .filters()
1845 .is_empty());
1846 }
1847
1848 #[test]
1849 fn all_creates_all() {
1850 let sub = EventSubscription::all(EventFormat::Json);
1851 assert!(sub.is_all());
1852 assert!(!sub.is_empty());
1853 assert_eq!(sub.to_event_string(), Some("ALL".to_string()));
1854 }
1855
1856 #[test]
1857 fn event_string_typed_only() {
1858 let sub = EventSubscription::new(EventFormat::Plain)
1859 .event(EslEventType::ChannelCreate)
1860 .event(EslEventType::ChannelAnswer);
1861 assert_eq!(
1862 sub.to_event_string(),
1863 Some("CHANNEL_CREATE CHANNEL_ANSWER".to_string())
1864 );
1865 }
1866
1867 #[test]
1868 fn event_string_custom_only() {
1869 let sub = EventSubscription::new(EventFormat::Plain)
1870 .custom_subclass("sofia::register")
1871 .unwrap()
1872 .custom_subclass("sofia::unregister")
1873 .unwrap();
1874 assert_eq!(
1875 sub.to_event_string(),
1876 Some("CUSTOM sofia::register sofia::unregister".to_string())
1877 );
1878 }
1879
1880 #[test]
1881 fn event_string_mixed() {
1882 let sub = EventSubscription::new(EventFormat::Plain)
1883 .event(EslEventType::Heartbeat)
1884 .custom_subclass("sofia::register")
1885 .unwrap();
1886 assert_eq!(
1887 sub.to_event_string(),
1888 Some("HEARTBEAT CUSTOM sofia::register".to_string())
1889 );
1890 }
1891
1892 #[test]
1893 fn event_string_custom_not_duplicated() {
1894 let sub = EventSubscription::new(EventFormat::Plain)
1895 .event(EslEventType::Custom)
1896 .custom_subclass("sofia::register")
1897 .unwrap();
1898 assert_eq!(
1900 sub.to_event_string(),
1901 Some("CUSTOM sofia::register".to_string())
1902 );
1903 }
1904
1905 #[test]
1906 fn event_string_empty_is_none() {
1907 let sub = EventSubscription::new(EventFormat::Plain);
1908 assert_eq!(sub.to_event_string(), None);
1909 }
1910
1911 #[test]
1912 fn filters_preserve_order() {
1913 let sub = EventSubscription::new(EventFormat::Plain)
1914 .filter(EventHeader::CallDirection, "inbound")
1915 .unwrap()
1916 .filter_raw("X-Custom", "value1")
1917 .unwrap()
1918 .filter(EventHeader::ChannelState, "CS_EXECUTE")
1919 .unwrap();
1920 assert_eq!(
1921 sub.filters(),
1922 &[
1923 ("Call-Direction".to_string(), "inbound".to_string()),
1924 ("X-Custom".to_string(), "value1".to_string()),
1925 ("Channel-State".to_string(), "CS_EXECUTE".to_string()),
1926 ]
1927 );
1928 }
1929
1930 #[test]
1931 fn builder_chain() {
1932 let sub = EventSubscription::new(EventFormat::Plain)
1933 .events(EslEventType::CHANNEL_EVENTS)
1934 .event(EslEventType::Heartbeat)
1935 .custom_subclass("sofia::register")
1936 .unwrap()
1937 .filter(EventHeader::CallDirection, "inbound")
1938 .unwrap()
1939 .with_format(EventFormat::Json);
1940
1941 assert_eq!(sub.format(), EventFormat::Json);
1942 assert!(!sub.is_empty());
1943 assert!(!sub.is_all());
1944 assert!(sub
1945 .event_types()
1946 .contains(&EslEventType::ChannelCreate));
1947 assert!(sub
1948 .event_types()
1949 .contains(&EslEventType::Heartbeat));
1950 assert_eq!(sub.custom_subclass_list(), &["sofia::register"]);
1951 assert_eq!(
1952 sub.filters()
1953 .len(),
1954 1
1955 );
1956 }
1957
1958 #[test]
1959 fn serde_round_trip_subscription() {
1960 let sub = EventSubscription::new(EventFormat::Plain)
1961 .event(EslEventType::ChannelCreate)
1962 .event(EslEventType::Heartbeat)
1963 .custom_subclass("sofia::register")
1964 .unwrap()
1965 .filter(EventHeader::CallDirection, "inbound")
1966 .unwrap();
1967
1968 let json = serde_json::to_string(&sub).unwrap();
1969 let deserialized: EventSubscription = serde_json::from_str(&json).unwrap();
1970 assert_eq!(sub, deserialized);
1971 }
1972
1973 #[test]
1974 fn serde_rejects_invalid_subclass() {
1975 let json =
1976 r#"{"format":"Plain","events":[],"custom_subclasses":["bad subclass"],"filters":[]}"#;
1977 let result: Result<EventSubscription, _> = serde_json::from_str(json);
1978 assert!(result.is_err());
1979 let err = result
1980 .unwrap_err()
1981 .to_string();
1982 assert!(err.contains("space"), "error should mention space: {err}");
1983 }
1984
1985 #[test]
1986 fn serde_rejects_newline_in_filter() {
1987 let json = r#"{"format":"Plain","events":[],"custom_subclasses":[],"filters":[["Header","val\n"]]}"#;
1988 let result: Result<EventSubscription, _> = serde_json::from_str(json);
1989 assert!(result.is_err());
1990 let err = result
1991 .unwrap_err()
1992 .to_string();
1993 assert!(
1994 err.contains("newline"),
1995 "error should mention newline: {err}"
1996 );
1997 }
1998
1999 #[test]
2000 fn custom_subclass_rejects_space() {
2001 let result = EventSubscription::new(EventFormat::Plain).custom_subclass("bad subclass");
2002 assert!(result.is_err());
2003 }
2004
2005 #[test]
2006 fn custom_subclass_rejects_newline() {
2007 let result = EventSubscription::new(EventFormat::Plain).custom_subclass("bad\nsubclass");
2008 assert!(result.is_err());
2009 }
2010
2011 #[test]
2012 fn custom_subclass_rejects_empty() {
2013 let result = EventSubscription::new(EventFormat::Plain).custom_subclass("");
2014 assert!(result.is_err());
2015 }
2016
2017 #[test]
2018 fn filter_raw_rejects_newline_in_header() {
2019 let result = EventSubscription::new(EventFormat::Plain).filter_raw("Bad\nHeader", "value");
2020 assert!(result.is_err());
2021 }
2022
2023 #[test]
2024 fn filter_raw_rejects_newline_in_value() {
2025 let result = EventSubscription::new(EventFormat::Plain).filter_raw("Header", "bad\nvalue");
2026 assert!(result.is_err());
2027 }
2028
2029 #[test]
2030 fn filter_typed_rejects_newline_in_value() {
2031 let result = EventSubscription::new(EventFormat::Plain)
2032 .filter(EventHeader::CallDirection, "bad\nvalue");
2033 assert!(result.is_err());
2034 }
2035
2036 #[test]
2037 fn sofia_event_single() {
2038 let sub =
2039 EventSubscription::new(EventFormat::Plain).sofia_event(SofiaEventSubclass::Register);
2040 assert_eq!(
2041 sub.to_event_string(),
2042 Some("CUSTOM sofia::register".to_string())
2043 );
2044 }
2045
2046 #[test]
2047 fn sofia_events_group() {
2048 let sub = EventSubscription::new(EventFormat::Plain)
2049 .sofia_events(SofiaEventSubclass::GATEWAY_EVENTS);
2050 let event_str = sub
2051 .to_event_string()
2052 .unwrap();
2053 assert!(event_str.starts_with("CUSTOM"));
2054 assert!(event_str.contains("sofia::gateway_state"));
2055 assert!(event_str.contains("sofia::gateway_add"));
2056 assert!(event_str.contains("sofia::gateway_delete"));
2057 assert!(event_str.contains("sofia::gateway_invalid_digest_req"));
2058 }
2059
2060 #[test]
2061 fn sofia_event_mixed_with_typed_events() {
2062 let sub = EventSubscription::new(EventFormat::Plain)
2063 .event(EslEventType::Heartbeat)
2064 .sofia_event(SofiaEventSubclass::GatewayState);
2065 assert_eq!(
2066 sub.to_event_string(),
2067 Some("HEARTBEAT CUSTOM sofia::gateway_state".to_string())
2068 );
2069 }
2070}