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