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