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 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) -> Result<(), EslArrayError> {
995 self.stack_header(name, value, EslArray::push)
996 }
997
998 pub fn unshift_header(&mut self, name: &str, value: &str) -> Result<(), EslArrayError> {
1011 self.stack_header(name, value, EslArray::unshift)
1012 }
1013
1014 fn stack_header(
1015 &mut self,
1016 name: &str,
1017 value: &str,
1018 op: fn(&mut EslArray, String),
1019 ) -> Result<(), EslArrayError> {
1020 match self
1021 .headers
1022 .get(name)
1023 {
1024 None => {
1025 self.set_header(name, value);
1026 }
1027 Some(existing) => {
1028 let arr = match EslArray::parse(existing) {
1029 Ok(arr) => arr,
1030 Err(EslArrayError::MissingPrefix) => EslArray::new(vec![existing.clone()]),
1031 Err(e) => return Err(e),
1032 };
1033 if arr.len() >= crate::variables::MAX_ARRAY_ITEMS {
1034 return Err(EslArrayError::TooManyItems {
1035 count: arr.len(),
1036 max: crate::variables::MAX_ARRAY_ITEMS,
1037 });
1038 }
1039 let mut arr = arr;
1040 op(&mut arr, value.into());
1041 self.set_header(name, arr.to_string());
1042 }
1043 }
1044 Ok(())
1045 }
1046
1047 pub fn is_event_type(&self, event_type: EslEventType) -> bool {
1049 self.event_type == Some(event_type)
1050 }
1051
1052 pub fn to_plain_format(&self) -> String {
1062 use std::fmt::Write;
1063 let mut result = String::new();
1064
1065 for (key, value) in &self.headers {
1066 if key == "Content-Length" {
1067 continue;
1068 }
1069 let _ = writeln!(
1070 result,
1071 "{}: {}",
1072 key,
1073 percent_encode(value.as_bytes(), NON_ALPHANUMERIC)
1074 );
1075 }
1076
1077 if let Some(body) = &self.body {
1078 let _ = writeln!(result, "Content-Length: {}", body.len());
1079 result.push('\n');
1080 result.push_str(body);
1081 } else {
1082 result.push('\n');
1083 }
1084
1085 result
1086 }
1087}
1088
1089impl Default for EslEvent {
1090 fn default() -> Self {
1091 Self::new()
1092 }
1093}
1094
1095impl HeaderLookup for EslEvent {
1096 fn header_str(&self, name: &str) -> Option<&str> {
1097 EslEvent::header_str(self, name)
1098 }
1099
1100 fn variable_str(&self, name: &str) -> Option<&str> {
1101 let key = format!("variable_{}", name);
1102 self.header_str(&key)
1103 }
1104}
1105
1106impl PartialEq for EslEvent {
1107 fn eq(&self, other: &Self) -> bool {
1108 self.event_type == other.event_type
1109 && self.headers == other.headers
1110 && self.body == other.body
1111 }
1112}
1113
1114impl std::hash::Hash for EslEvent {
1115 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
1116 self.event_type
1117 .hash(state);
1118 for (k, v) in &self.headers {
1119 k.hash(state);
1120 v.hash(state);
1121 }
1122 self.body
1123 .hash(state);
1124 }
1125}
1126
1127#[cfg(feature = "serde")]
1128impl<'de> serde::Deserialize<'de> for EslEvent {
1129 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
1130 where
1131 D: serde::Deserializer<'de>,
1132 {
1133 #[derive(serde::Deserialize)]
1134 struct Raw {
1135 event_type: Option<EslEventType>,
1136 headers: IndexMap<String, String>,
1137 body: Option<String>,
1138 }
1139 let raw = Raw::deserialize(deserializer)?;
1140 let mut event = EslEvent::new();
1141 event.event_type = raw.event_type;
1142 event.body = raw.body;
1143 for (k, v) in raw.headers {
1144 event.set_header(k, v);
1145 }
1146 Ok(event)
1147 }
1148}
1149
1150#[cfg(test)]
1151mod tests {
1152 use super::*;
1153
1154 #[test]
1155 fn headers_preserve_insertion_order() {
1156 let mut event = EslEvent::new();
1157 event.set_header("Zebra", "last");
1158 event.set_header("Alpha", "first");
1159 event.set_header("Middle", "mid");
1160 let keys: Vec<&str> = event
1161 .headers()
1162 .keys()
1163 .map(|s| s.as_str())
1164 .collect();
1165 assert_eq!(keys, vec!["Zebra", "Alpha", "Middle"]);
1166 }
1167
1168 #[test]
1169 fn test_notify_in_parse() {
1170 assert_eq!(
1171 EslEventType::parse_event_type("NOTIFY_IN"),
1172 Some(EslEventType::NotifyIn)
1173 );
1174 assert_eq!(EslEventType::parse_event_type("notify_in"), None);
1175 }
1176
1177 #[test]
1178 fn test_notify_in_display() {
1179 assert_eq!(EslEventType::NotifyIn.to_string(), "NOTIFY_IN");
1180 }
1181
1182 #[test]
1183 fn test_notify_in_distinct_from_notify() {
1184 assert_ne!(EslEventType::Notify, EslEventType::NotifyIn);
1185 assert_ne!(
1186 EslEventType::Notify.to_string(),
1187 EslEventType::NotifyIn.to_string()
1188 );
1189 }
1190
1191 #[test]
1192 fn test_wire_names_match_c_esl() {
1193 assert_eq!(
1194 EslEventType::ChannelOutgoing.to_string(),
1195 "CHANNEL_OUTGOING"
1196 );
1197 assert_eq!(EslEventType::Api.to_string(), "API");
1198 assert_eq!(EslEventType::ReloadXml.to_string(), "RELOADXML");
1199 assert_eq!(EslEventType::PresenceIn.to_string(), "PRESENCE_IN");
1200 assert_eq!(EslEventType::Roster.to_string(), "ROSTER");
1201 assert_eq!(EslEventType::Text.to_string(), "TEXT");
1202 assert_eq!(EslEventType::ReSchedule.to_string(), "RE_SCHEDULE");
1203
1204 assert_eq!(
1205 EslEventType::parse_event_type("CHANNEL_OUTGOING"),
1206 Some(EslEventType::ChannelOutgoing)
1207 );
1208 assert_eq!(
1209 EslEventType::parse_event_type("API"),
1210 Some(EslEventType::Api)
1211 );
1212 assert_eq!(
1213 EslEventType::parse_event_type("RELOADXML"),
1214 Some(EslEventType::ReloadXml)
1215 );
1216 assert_eq!(
1217 EslEventType::parse_event_type("PRESENCE_IN"),
1218 Some(EslEventType::PresenceIn)
1219 );
1220 }
1221
1222 #[test]
1223 fn test_event_type_from_str() {
1224 assert_eq!(
1225 "CHANNEL_ANSWER".parse::<EslEventType>(),
1226 Ok(EslEventType::ChannelAnswer)
1227 );
1228 assert!("channel_answer"
1229 .parse::<EslEventType>()
1230 .is_err());
1231 assert!("UNKNOWN_EVENT"
1232 .parse::<EslEventType>()
1233 .is_err());
1234 }
1235
1236 #[test]
1237 fn test_remove_header() {
1238 let mut event = EslEvent::new();
1239 event.set_header("Foo", "bar");
1240 event.set_header("Baz", "qux");
1241
1242 let removed = event.remove_header("Foo");
1243 assert_eq!(removed, Some("bar".to_string()));
1244 assert!(event
1245 .header_str("Foo")
1246 .is_none());
1247 assert_eq!(event.header_str("Baz"), Some("qux"));
1248
1249 let removed_again = event.remove_header("Foo");
1250 assert_eq!(removed_again, None);
1251 }
1252
1253 #[test]
1254 fn test_to_plain_format_basic() {
1255 let mut event = EslEvent::with_type(EslEventType::Heartbeat);
1256 event.set_header("Event-Name", "HEARTBEAT");
1257 event.set_header("Core-UUID", "abc-123");
1258
1259 let plain = event.to_plain_format();
1260
1261 assert!(plain.starts_with("Event-Name: "));
1262 assert!(plain.contains("Core-UUID: "));
1263 assert!(plain.ends_with("\n\n"));
1264 }
1265
1266 #[test]
1267 fn test_to_plain_format_percent_encoding() {
1268 let mut event = EslEvent::with_type(EslEventType::Heartbeat);
1269 event.set_header("Event-Name", "HEARTBEAT");
1270 event.set_header("Up-Time", "0 years, 0 days");
1271
1272 let plain = event.to_plain_format();
1273
1274 assert!(!plain.contains("0 years, 0 days"));
1275 assert!(plain.contains("Up-Time: "));
1276 assert!(plain.contains("%20"));
1277 }
1278
1279 #[test]
1280 fn test_to_plain_format_with_body() {
1281 let mut event = EslEvent::with_type(EslEventType::BackgroundJob);
1282 event.set_header("Event-Name", "BACKGROUND_JOB");
1283 event.set_header("Job-UUID", "def-456");
1284 event.set_body("+OK result\n".to_string());
1285
1286 let plain = event.to_plain_format();
1287
1288 assert!(plain.contains("Content-Length: 11\n"));
1289 assert!(plain.ends_with("\n\n+OK result\n"));
1290 }
1291
1292 #[test]
1293 fn test_to_plain_format_preserves_insertion_order() {
1294 let mut event = EslEvent::with_type(EslEventType::Heartbeat);
1295 event.set_header("Event-Name", "HEARTBEAT");
1296 event.set_header("Core-UUID", "abc-123");
1297 event.set_header("FreeSWITCH-Hostname", "fs01");
1298 event.set_header("Up-Time", "0 years, 1 day");
1299
1300 let plain = event.to_plain_format();
1301 let lines: Vec<&str> = plain
1302 .lines()
1303 .collect();
1304 assert!(lines[0].starts_with("Event-Name: "));
1305 assert!(lines[1].starts_with("Core-UUID: "));
1306 assert!(lines[2].starts_with("FreeSWITCH-Hostname: "));
1307 assert!(lines[3].starts_with("Up-Time: "));
1308 }
1309
1310 #[test]
1311 fn test_to_plain_format_round_trip() {
1312 let mut original = EslEvent::with_type(EslEventType::ChannelCreate);
1313 original.set_header("Event-Name", "CHANNEL_CREATE");
1314 original.set_header("Core-UUID", "abc-123");
1315 original.set_header("Channel-Name", "sofia/internal/1000@example.com");
1316 original.set_header("Caller-Caller-ID-Name", "Jérôme Poulin");
1317 original.set_body("some body content");
1318
1319 let plain = original.to_plain_format();
1320
1321 let (header_section, inner_body) = if let Some(pos) = plain.find("\n\n") {
1323 (&plain[..pos], Some(&plain[pos + 2..]))
1324 } else {
1325 (plain.as_str(), None)
1326 };
1327
1328 let mut parsed = EslEvent::new();
1329 for line in header_section.lines() {
1330 let line = line.trim();
1331 if line.is_empty() {
1332 continue;
1333 }
1334 if let Some(colon_pos) = line.find(':') {
1335 let key = line[..colon_pos].trim();
1336 if key == "Content-Length" {
1337 continue;
1338 }
1339 let raw_value = line[colon_pos + 1..].trim();
1340 let value = percent_encoding::percent_decode_str(raw_value)
1341 .decode_utf8()
1342 .unwrap()
1343 .into_owned();
1344 parsed.set_header(key, value);
1345 }
1346 }
1347 if let Some(ib) = inner_body {
1348 if !ib.is_empty() {
1349 parsed.set_body(ib);
1350 }
1351 }
1352
1353 assert_eq!(original.headers(), parsed.headers());
1354 assert_eq!(original.body(), parsed.body());
1355 }
1356
1357 #[test]
1358 fn test_set_priority_normal() {
1359 let mut event = EslEvent::new();
1360 event.set_priority(EslEventPriority::Normal);
1361 assert_eq!(
1362 event
1363 .priority()
1364 .unwrap(),
1365 Some(EslEventPriority::Normal)
1366 );
1367 assert_eq!(event.header(EventHeader::Priority), Some("NORMAL"));
1368 }
1369
1370 #[test]
1371 fn test_set_priority_high() {
1372 let mut event = EslEvent::new();
1373 event.set_priority(EslEventPriority::High);
1374 assert_eq!(
1375 event
1376 .priority()
1377 .unwrap(),
1378 Some(EslEventPriority::High)
1379 );
1380 assert_eq!(event.header(EventHeader::Priority), Some("HIGH"));
1381 }
1382
1383 #[test]
1384 fn test_priority_display() {
1385 assert_eq!(EslEventPriority::Normal.to_string(), "NORMAL");
1386 assert_eq!(EslEventPriority::Low.to_string(), "LOW");
1387 assert_eq!(EslEventPriority::High.to_string(), "HIGH");
1388 }
1389
1390 #[test]
1391 fn test_priority_from_str() {
1392 assert_eq!(
1393 "NORMAL".parse::<EslEventPriority>(),
1394 Ok(EslEventPriority::Normal)
1395 );
1396 assert_eq!("LOW".parse::<EslEventPriority>(), Ok(EslEventPriority::Low));
1397 assert_eq!(
1398 "HIGH".parse::<EslEventPriority>(),
1399 Ok(EslEventPriority::High)
1400 );
1401 assert!("INVALID"
1402 .parse::<EslEventPriority>()
1403 .is_err());
1404 }
1405
1406 #[test]
1407 fn test_priority_from_str_rejects_wrong_case() {
1408 assert!("normal"
1409 .parse::<EslEventPriority>()
1410 .is_err());
1411 assert!("Low"
1412 .parse::<EslEventPriority>()
1413 .is_err());
1414 assert!("hIgH"
1415 .parse::<EslEventPriority>()
1416 .is_err());
1417 }
1418
1419 #[test]
1420 fn test_push_header_new() {
1421 let mut event = EslEvent::new();
1422 event
1423 .push_header("X-Test", "first")
1424 .unwrap();
1425 assert_eq!(event.header_str("X-Test"), Some("first"));
1426 }
1427
1428 #[test]
1429 fn test_push_header_existing_plain() {
1430 let mut event = EslEvent::new();
1431 event.set_header("X-Test", "first");
1432 event
1433 .push_header("X-Test", "second")
1434 .unwrap();
1435 assert_eq!(event.header_str("X-Test"), Some("ARRAY::first|:second"));
1436 }
1437
1438 #[test]
1439 fn test_push_header_existing_array() {
1440 let mut event = EslEvent::new();
1441 event.set_header("X-Test", "ARRAY::a|:b");
1442 event
1443 .push_header("X-Test", "c")
1444 .unwrap();
1445 assert_eq!(event.header_str("X-Test"), Some("ARRAY::a|:b|:c"));
1446 }
1447
1448 #[test]
1449 fn test_push_header_at_capacity() {
1450 use crate::variables::MAX_ARRAY_ITEMS;
1451 let mut event = EslEvent::new();
1452 let items: Vec<&str> = (0..MAX_ARRAY_ITEMS)
1453 .map(|_| "x")
1454 .collect();
1455 event.set_header("X-Test", format!("ARRAY::{}", items.join("|:")).as_str());
1456 assert!(matches!(
1457 event.push_header("X-Test", "overflow"),
1458 Err(EslArrayError::TooManyItems { .. })
1459 ));
1460 }
1461
1462 #[test]
1463 fn test_unshift_header_new() {
1464 let mut event = EslEvent::new();
1465 event
1466 .unshift_header("X-Test", "only")
1467 .unwrap();
1468 assert_eq!(event.header_str("X-Test"), Some("only"));
1469 }
1470
1471 #[test]
1472 fn test_unshift_header_existing_array() {
1473 let mut event = EslEvent::new();
1474 event.set_header("X-Test", "ARRAY::b|:c");
1475 event
1476 .unshift_header("X-Test", "a")
1477 .unwrap();
1478 assert_eq!(event.header_str("X-Test"), Some("ARRAY::a|:b|:c"));
1479 }
1480
1481 #[test]
1482 fn test_sendevent_with_priority_wire_format() {
1483 let mut event = EslEvent::with_type(EslEventType::Custom);
1484 event.set_header("Event-Name", "CUSTOM");
1485 event.set_header("Event-Subclass", "test::priority");
1486 event.set_priority(EslEventPriority::High);
1487
1488 let plain = event.to_plain_format();
1489 assert!(plain.contains("priority: HIGH\n"));
1490 }
1491
1492 #[test]
1493 fn test_convenience_accessors() {
1494 let mut event = EslEvent::new();
1495 event.set_header("Channel-Name", "sofia/internal/1000@example.com");
1496 event.set_header("Caller-Caller-ID-Number", "1000");
1497 event.set_header("Caller-Caller-ID-Name", "Alice");
1498 event.set_header("Hangup-Cause", "NORMAL_CLEARING");
1499 event.set_header("Event-Subclass", "sofia::register");
1500 event.set_header("variable_sip_from_display", "Bob");
1501
1502 assert_eq!(
1503 event.channel_name(),
1504 Some("sofia/internal/1000@example.com")
1505 );
1506 assert_eq!(event.caller_id_number(), Some("1000"));
1507 assert_eq!(event.caller_id_name(), Some("Alice"));
1508 assert_eq!(
1509 event
1510 .hangup_cause()
1511 .unwrap(),
1512 Some(crate::channel::HangupCause::NormalClearing)
1513 );
1514 assert_eq!(event.event_subclass(), Some("sofia::register"));
1515 assert_eq!(event.variable_str("sip_from_display"), Some("Bob"));
1516 assert_eq!(event.variable_str("nonexistent"), None);
1517 }
1518
1519 #[test]
1520 fn test_event_format_from_str() {
1521 assert_eq!("plain".parse::<EventFormat>(), Ok(EventFormat::Plain));
1522 assert_eq!("json".parse::<EventFormat>(), Ok(EventFormat::Json));
1523 assert_eq!("xml".parse::<EventFormat>(), Ok(EventFormat::Xml));
1524 assert!("foo"
1525 .parse::<EventFormat>()
1526 .is_err());
1527 }
1528
1529 #[test]
1530 fn test_event_format_from_str_case_insensitive() {
1531 assert_eq!("PLAIN".parse::<EventFormat>(), Ok(EventFormat::Plain));
1532 assert_eq!("Json".parse::<EventFormat>(), Ok(EventFormat::Json));
1533 assert_eq!("XML".parse::<EventFormat>(), Ok(EventFormat::Xml));
1534 assert_eq!("Xml".parse::<EventFormat>(), Ok(EventFormat::Xml));
1535 }
1536
1537 #[test]
1538 fn test_event_format_from_content_type() {
1539 assert_eq!(
1540 EventFormat::from_content_type("text/event-json"),
1541 Ok(EventFormat::Json)
1542 );
1543 assert_eq!(
1544 EventFormat::from_content_type("text/event-xml"),
1545 Ok(EventFormat::Xml)
1546 );
1547 assert_eq!(
1548 EventFormat::from_content_type("text/event-plain"),
1549 Ok(EventFormat::Plain)
1550 );
1551 assert!(EventFormat::from_content_type("unknown").is_err());
1552 }
1553
1554 #[test]
1557 fn test_event_channel_state_accessor() {
1558 use crate::channel::ChannelState;
1559 let mut event = EslEvent::new();
1560 event.set_header("Channel-State", "CS_EXECUTE");
1561 assert_eq!(
1562 event
1563 .channel_state()
1564 .unwrap(),
1565 Some(ChannelState::CsExecute)
1566 );
1567 }
1568
1569 #[test]
1570 fn test_event_channel_state_number_accessor() {
1571 use crate::channel::ChannelState;
1572 let mut event = EslEvent::new();
1573 event.set_header("Channel-State-Number", "4");
1574 assert_eq!(
1575 event
1576 .channel_state_number()
1577 .unwrap(),
1578 Some(ChannelState::CsExecute)
1579 );
1580 }
1581
1582 #[test]
1583 fn test_event_call_state_accessor() {
1584 use crate::channel::CallState;
1585 let mut event = EslEvent::new();
1586 event.set_header("Channel-Call-State", "ACTIVE");
1587 assert_eq!(
1588 event
1589 .call_state()
1590 .unwrap(),
1591 Some(CallState::Active)
1592 );
1593 }
1594
1595 #[test]
1596 fn test_event_answer_state_accessor() {
1597 use crate::channel::AnswerState;
1598 let mut event = EslEvent::new();
1599 event.set_header("Answer-State", "answered");
1600 assert_eq!(
1601 event
1602 .answer_state()
1603 .unwrap(),
1604 Some(AnswerState::Answered)
1605 );
1606 }
1607
1608 #[test]
1609 fn test_event_call_direction_accessor() {
1610 use crate::channel::CallDirection;
1611 let mut event = EslEvent::new();
1612 event.set_header("Call-Direction", "inbound");
1613 assert_eq!(
1614 event
1615 .call_direction()
1616 .unwrap(),
1617 Some(CallDirection::Inbound)
1618 );
1619 }
1620
1621 #[test]
1622 fn test_event_typed_accessors_missing_headers() {
1623 let event = EslEvent::new();
1624 assert_eq!(
1625 event
1626 .channel_state()
1627 .unwrap(),
1628 None
1629 );
1630 assert_eq!(
1631 event
1632 .channel_state_number()
1633 .unwrap(),
1634 None
1635 );
1636 assert_eq!(
1637 event
1638 .call_state()
1639 .unwrap(),
1640 None
1641 );
1642 assert_eq!(
1643 event
1644 .answer_state()
1645 .unwrap(),
1646 None
1647 );
1648 assert_eq!(
1649 event
1650 .call_direction()
1651 .unwrap(),
1652 None
1653 );
1654 }
1655
1656 #[test]
1659 fn test_sip_p_asserted_identity_comma_separated() {
1660 let mut event = EslEvent::new();
1661 event.set_header(
1664 "variable_sip_P-Asserted-Identity",
1665 "<sip:alice@atlanta.example.com>, <tel:+15551234567>",
1666 );
1667
1668 assert_eq!(
1669 event.variable_str("sip_P-Asserted-Identity"),
1670 Some("<sip:alice@atlanta.example.com>, <tel:+15551234567>")
1671 );
1672 }
1673
1674 #[test]
1675 fn test_sip_p_asserted_identity_array_format() {
1676 let mut event = EslEvent::new();
1677 event
1679 .push_header(
1680 "variable_sip_P-Asserted-Identity",
1681 "<sip:alice@atlanta.example.com>",
1682 )
1683 .unwrap();
1684 event
1685 .push_header("variable_sip_P-Asserted-Identity", "<tel:+15551234567>")
1686 .unwrap();
1687
1688 let raw = event
1689 .header_str("variable_sip_P-Asserted-Identity")
1690 .unwrap();
1691 assert_eq!(
1692 raw,
1693 "ARRAY::<sip:alice@atlanta.example.com>|:<tel:+15551234567>"
1694 );
1695
1696 let arr = crate::variables::EslArray::parse(raw).unwrap();
1697 assert_eq!(arr.len(), 2);
1698 assert_eq!(arr.items()[0], "<sip:alice@atlanta.example.com>");
1699 assert_eq!(arr.items()[1], "<tel:+15551234567>");
1700 }
1701
1702 #[test]
1703 fn test_sip_header_with_colons_in_uri() {
1704 let mut event = EslEvent::new();
1705 event
1707 .push_header(
1708 "variable_sip_h_Diversion",
1709 "<sip:+15551234567@gw.example.com;reason=unconditional>",
1710 )
1711 .unwrap();
1712 event
1713 .push_header(
1714 "variable_sip_h_Diversion",
1715 "<sips:+15559876543@secure.example.com;reason=no-answer;counter=3>",
1716 )
1717 .unwrap();
1718
1719 let raw = event
1720 .header_str("variable_sip_h_Diversion")
1721 .unwrap();
1722 let arr = crate::variables::EslArray::parse(raw).unwrap();
1723 assert_eq!(arr.len(), 2);
1724 assert_eq!(
1725 arr.items()[0],
1726 "<sip:+15551234567@gw.example.com;reason=unconditional>"
1727 );
1728 assert_eq!(
1729 arr.items()[1],
1730 "<sips:+15559876543@secure.example.com;reason=no-answer;counter=3>"
1731 );
1732 }
1733
1734 #[test]
1735 fn test_sip_p_asserted_identity_plain_format_round_trip() {
1736 let mut event = EslEvent::with_type(EslEventType::ChannelCreate);
1737 event.set_header("Event-Name", "CHANNEL_CREATE");
1738 event.set_header(
1739 "variable_sip_P-Asserted-Identity",
1740 "<sip:alice@atlanta.example.com>, <tel:+15551234567>",
1741 );
1742
1743 let plain = event.to_plain_format();
1744 assert!(plain.contains("variable_sip_P-Asserted-Identity:"));
1746 assert!(!plain.contains("<sip:alice"));
1748 }
1749
1750 #[test]
1755 fn set_header_normalizes_known_enum_variant() {
1756 let mut event = EslEvent::new();
1757 event.set_header("unique-id", "abc-123");
1758 assert_eq!(event.header(EventHeader::UniqueId), Some("abc-123"));
1759 }
1760
1761 #[test]
1762 fn set_header_normalizes_codec_header() {
1763 let mut event = EslEvent::new();
1764 event.set_header("channel-read-codec-bit-rate", "128000");
1765 assert_eq!(
1766 event.header(EventHeader::ChannelReadCodecBitRate),
1767 Some("128000")
1768 );
1769 }
1770
1771 #[test]
1772 fn header_str_finds_by_original_key() {
1773 let mut event = EslEvent::new();
1774 event.set_header("unique-id", "abc-123");
1775 assert_eq!(event.header_str("unique-id"), Some("abc-123"));
1777 assert_eq!(event.header_str("Unique-ID"), Some("abc-123"));
1779 }
1780
1781 #[test]
1782 fn header_str_finds_unknown_dash_header_by_original() {
1783 let mut event = EslEvent::new();
1784 event.set_header("x-custom-header", "val");
1785 assert_eq!(event.header_str("X-Custom-Header"), Some("val"));
1787 assert_eq!(event.header_str("x-custom-header"), Some("val"));
1789 }
1790
1791 #[test]
1792 fn set_header_underscore_passthrough_preserves_sip_h() {
1793 let mut event = EslEvent::new();
1794 event.set_header("variable_sip_h_X-My-CUSTOM-Header", "val");
1795 assert_eq!(
1796 event.header_str("variable_sip_h_X-My-CUSTOM-Header"),
1797 Some("val")
1798 );
1799 }
1800
1801 #[test]
1802 fn set_header_different_casing_overwrites() {
1803 let mut event = EslEvent::new();
1804 event.set_header("Unique-ID", "first");
1805 event.set_header("unique-id", "second");
1806 assert_eq!(event.header(EventHeader::UniqueId), Some("second"));
1808 }
1809
1810 #[test]
1811 fn remove_header_by_original_key() {
1812 let mut event = EslEvent::new();
1813 event.set_header("unique-id", "abc-123");
1814 let removed = event.remove_header("unique-id");
1815 assert_eq!(removed, Some("abc-123".to_string()));
1816 assert_eq!(event.header(EventHeader::UniqueId), None);
1817 }
1818
1819 #[test]
1820 fn remove_header_by_canonical_key() {
1821 let mut event = EslEvent::new();
1822 event.set_header("unique-id", "abc-123");
1823 let removed = event.remove_header("Unique-ID");
1824 assert_eq!(removed, Some("abc-123".to_string()));
1825 assert_eq!(event.header_str("unique-id"), None);
1826 }
1827
1828 #[test]
1829 fn serde_round_trip_preserves_canonical_lookups() {
1830 let mut event = EslEvent::new();
1831 event.set_header("unique-id", "abc-123");
1832 event.set_header("channel-read-codec-bit-rate", "128000");
1833 let json = serde_json::to_string(&event).unwrap();
1834 let deserialized: EslEvent = serde_json::from_str(&json).unwrap();
1835 assert_eq!(deserialized.header(EventHeader::UniqueId), Some("abc-123"));
1836 assert_eq!(
1837 deserialized.header(EventHeader::ChannelReadCodecBitRate),
1838 Some("128000")
1839 );
1840 }
1841
1842 #[test]
1843 fn serde_deserialize_normalizes_external_json() {
1844 let json = r#"{"event_type":null,"headers":{"unique-id":"abc-123","channel-read-codec-bit-rate":"128000"},"body":null}"#;
1845 let event: EslEvent = serde_json::from_str(json).unwrap();
1846 assert_eq!(event.header(EventHeader::UniqueId), Some("abc-123"));
1847 assert_eq!(
1848 event.header(EventHeader::ChannelReadCodecBitRate),
1849 Some("128000")
1850 );
1851 assert_eq!(event.header_str("unique-id"), Some("abc-123"));
1852 }
1853
1854 #[test]
1855 fn test_event_typed_accessors_invalid_values() {
1856 let mut event = EslEvent::new();
1857 event.set_header("Channel-State", "BOGUS");
1858 event.set_header("Channel-State-Number", "999");
1859 event.set_header("Channel-Call-State", "BOGUS");
1860 event.set_header("Answer-State", "bogus");
1861 event.set_header("Call-Direction", "bogus");
1862 assert!(event
1863 .channel_state()
1864 .is_err());
1865 assert!(event
1866 .channel_state_number()
1867 .is_err());
1868 assert!(event
1869 .call_state()
1870 .is_err());
1871 assert!(event
1872 .answer_state()
1873 .is_err());
1874 assert!(event
1875 .call_direction()
1876 .is_err());
1877 }
1878
1879 #[test]
1882 fn new_creates_empty() {
1883 let sub = EventSubscription::new(EventFormat::Plain);
1884 assert!(sub.is_empty());
1885 assert!(!sub.is_all());
1886 assert_eq!(sub.format(), EventFormat::Plain);
1887 assert!(sub
1888 .event_types()
1889 .is_empty());
1890 assert!(sub
1891 .custom_subclass_list()
1892 .is_empty());
1893 assert!(sub
1894 .filters()
1895 .is_empty());
1896 }
1897
1898 #[test]
1899 fn all_creates_all() {
1900 let sub = EventSubscription::all(EventFormat::Json);
1901 assert!(sub.is_all());
1902 assert!(!sub.is_empty());
1903 assert_eq!(sub.to_event_string(), Some("ALL".to_string()));
1904 }
1905
1906 #[test]
1907 fn event_string_typed_only() {
1908 let sub = EventSubscription::new(EventFormat::Plain)
1909 .event(EslEventType::ChannelCreate)
1910 .event(EslEventType::ChannelAnswer);
1911 assert_eq!(
1912 sub.to_event_string(),
1913 Some("CHANNEL_CREATE CHANNEL_ANSWER".to_string())
1914 );
1915 }
1916
1917 #[test]
1918 fn event_string_custom_only() {
1919 let sub = EventSubscription::new(EventFormat::Plain)
1920 .custom_subclass("sofia::register")
1921 .unwrap()
1922 .custom_subclass("sofia::unregister")
1923 .unwrap();
1924 assert_eq!(
1925 sub.to_event_string(),
1926 Some("CUSTOM sofia::register sofia::unregister".to_string())
1927 );
1928 }
1929
1930 #[test]
1931 fn event_string_mixed() {
1932 let sub = EventSubscription::new(EventFormat::Plain)
1933 .event(EslEventType::Heartbeat)
1934 .custom_subclass("sofia::register")
1935 .unwrap();
1936 assert_eq!(
1937 sub.to_event_string(),
1938 Some("HEARTBEAT CUSTOM sofia::register".to_string())
1939 );
1940 }
1941
1942 #[test]
1943 fn event_string_custom_not_duplicated() {
1944 let sub = EventSubscription::new(EventFormat::Plain)
1945 .event(EslEventType::Custom)
1946 .custom_subclass("sofia::register")
1947 .unwrap();
1948 assert_eq!(
1950 sub.to_event_string(),
1951 Some("CUSTOM sofia::register".to_string())
1952 );
1953 }
1954
1955 #[test]
1956 fn event_string_empty_is_none() {
1957 let sub = EventSubscription::new(EventFormat::Plain);
1958 assert_eq!(sub.to_event_string(), None);
1959 }
1960
1961 #[test]
1962 fn filters_preserve_order() {
1963 let sub = EventSubscription::new(EventFormat::Plain)
1964 .filter(EventHeader::CallDirection, "inbound")
1965 .unwrap()
1966 .filter_raw("X-Custom", "value1")
1967 .unwrap()
1968 .filter(EventHeader::ChannelState, "CS_EXECUTE")
1969 .unwrap();
1970 assert_eq!(
1971 sub.filters(),
1972 &[
1973 ("Call-Direction".to_string(), "inbound".to_string()),
1974 ("X-Custom".to_string(), "value1".to_string()),
1975 ("Channel-State".to_string(), "CS_EXECUTE".to_string()),
1976 ]
1977 );
1978 }
1979
1980 #[test]
1981 fn builder_chain() {
1982 let sub = EventSubscription::new(EventFormat::Plain)
1983 .events(EslEventType::CHANNEL_EVENTS)
1984 .event(EslEventType::Heartbeat)
1985 .custom_subclass("sofia::register")
1986 .unwrap()
1987 .filter(EventHeader::CallDirection, "inbound")
1988 .unwrap()
1989 .with_format(EventFormat::Json);
1990
1991 assert_eq!(sub.format(), EventFormat::Json);
1992 assert!(!sub.is_empty());
1993 assert!(!sub.is_all());
1994 assert!(sub
1995 .event_types()
1996 .contains(&EslEventType::ChannelCreate));
1997 assert!(sub
1998 .event_types()
1999 .contains(&EslEventType::Heartbeat));
2000 assert_eq!(sub.custom_subclass_list(), &["sofia::register"]);
2001 assert_eq!(
2002 sub.filters()
2003 .len(),
2004 1
2005 );
2006 }
2007
2008 #[test]
2009 fn serde_round_trip_subscription() {
2010 let sub = EventSubscription::new(EventFormat::Plain)
2011 .event(EslEventType::ChannelCreate)
2012 .event(EslEventType::Heartbeat)
2013 .custom_subclass("sofia::register")
2014 .unwrap()
2015 .filter(EventHeader::CallDirection, "inbound")
2016 .unwrap();
2017
2018 let json = serde_json::to_string(&sub).unwrap();
2019 let deserialized: EventSubscription = serde_json::from_str(&json).unwrap();
2020 assert_eq!(sub, deserialized);
2021 }
2022
2023 #[test]
2024 fn serde_rejects_invalid_subclass() {
2025 let json =
2026 r#"{"format":"Plain","events":[],"custom_subclasses":["bad subclass"],"filters":[]}"#;
2027 let result: Result<EventSubscription, _> = serde_json::from_str(json);
2028 assert!(result.is_err());
2029 let err = result
2030 .unwrap_err()
2031 .to_string();
2032 assert!(err.contains("space"), "error should mention space: {err}");
2033 }
2034
2035 #[test]
2036 fn serde_rejects_newline_in_filter() {
2037 let json = r#"{"format":"Plain","events":[],"custom_subclasses":[],"filters":[["Header","val\n"]]}"#;
2038 let result: Result<EventSubscription, _> = serde_json::from_str(json);
2039 assert!(result.is_err());
2040 let err = result
2041 .unwrap_err()
2042 .to_string();
2043 assert!(
2044 err.contains("newline"),
2045 "error should mention newline: {err}"
2046 );
2047 }
2048
2049 #[test]
2050 fn custom_subclass_rejects_space() {
2051 let result = EventSubscription::new(EventFormat::Plain).custom_subclass("bad subclass");
2052 assert!(result.is_err());
2053 }
2054
2055 #[test]
2056 fn custom_subclass_rejects_newline() {
2057 let result = EventSubscription::new(EventFormat::Plain).custom_subclass("bad\nsubclass");
2058 assert!(result.is_err());
2059 }
2060
2061 #[test]
2062 fn custom_subclass_rejects_empty() {
2063 let result = EventSubscription::new(EventFormat::Plain).custom_subclass("");
2064 assert!(result.is_err());
2065 }
2066
2067 #[test]
2068 fn filter_raw_rejects_newline_in_header() {
2069 let result = EventSubscription::new(EventFormat::Plain).filter_raw("Bad\nHeader", "value");
2070 assert!(result.is_err());
2071 }
2072
2073 #[test]
2074 fn filter_raw_rejects_newline_in_value() {
2075 let result = EventSubscription::new(EventFormat::Plain).filter_raw("Header", "bad\nvalue");
2076 assert!(result.is_err());
2077 }
2078
2079 #[test]
2080 fn filter_typed_rejects_newline_in_value() {
2081 let result = EventSubscription::new(EventFormat::Plain)
2082 .filter(EventHeader::CallDirection, "bad\nvalue");
2083 assert!(result.is_err());
2084 }
2085
2086 #[test]
2087 fn sofia_event_single() {
2088 let sub =
2089 EventSubscription::new(EventFormat::Plain).sofia_event(SofiaEventSubclass::Register);
2090 assert_eq!(
2091 sub.to_event_string(),
2092 Some("CUSTOM sofia::register".to_string())
2093 );
2094 }
2095
2096 #[test]
2097 fn sofia_events_group() {
2098 let sub = EventSubscription::new(EventFormat::Plain)
2099 .sofia_events(SofiaEventSubclass::GATEWAY_EVENTS);
2100 let event_str = sub
2101 .to_event_string()
2102 .unwrap();
2103 assert!(event_str.starts_with("CUSTOM"));
2104 assert!(event_str.contains("sofia::gateway_state"));
2105 assert!(event_str.contains("sofia::gateway_add"));
2106 assert!(event_str.contains("sofia::gateway_delete"));
2107 assert!(event_str.contains("sofia::gateway_invalid_digest_req"));
2108 }
2109
2110 #[test]
2111 fn sofia_event_mixed_with_typed_events() {
2112 let sub = EventSubscription::new(EventFormat::Plain)
2113 .event(EslEventType::Heartbeat)
2114 .sofia_event(SofiaEventSubclass::GatewayState);
2115 assert_eq!(
2116 sub.to_event_string(),
2117 Some("HEARTBEAT CUSTOM sofia::gateway_state".to_string())
2118 );
2119 }
2120}