1use crate::headers::{normalize_header_key, EventHeader};
4use crate::lookup::HeaderLookup;
5use crate::variables::EslArray;
6use indexmap::IndexMap;
7use percent_encoding::{percent_encode, NON_ALPHANUMERIC};
8use std::fmt;
9use std::str::FromStr;
10
11#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
13#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
14#[non_exhaustive]
15pub enum EventFormat {
16 Plain,
18 Json,
20 Xml,
22}
23
24impl EventFormat {
25 pub fn from_content_type(ct: &str) -> Result<Self, ParseEventFormatError> {
30 match ct {
31 "text/event-json" => Ok(Self::Json),
32 "text/event-xml" => Ok(Self::Xml),
33 "text/event-plain" => Ok(Self::Plain),
34 _ => Err(ParseEventFormatError(ct.to_string())),
35 }
36 }
37}
38
39impl fmt::Display for EventFormat {
40 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
41 match self {
42 EventFormat::Plain => write!(f, "plain"),
43 EventFormat::Json => write!(f, "json"),
44 EventFormat::Xml => write!(f, "xml"),
45 }
46 }
47}
48
49impl FromStr for EventFormat {
50 type Err = ParseEventFormatError;
51
52 fn from_str(s: &str) -> Result<Self, Self::Err> {
53 if s.eq_ignore_ascii_case("plain") {
54 Ok(Self::Plain)
55 } else if s.eq_ignore_ascii_case("json") {
56 Ok(Self::Json)
57 } else if s.eq_ignore_ascii_case("xml") {
58 Ok(Self::Xml)
59 } else {
60 Err(ParseEventFormatError(s.to_string()))
61 }
62 }
63}
64
65#[derive(Debug, Clone, PartialEq, Eq)]
67pub struct ParseEventFormatError(pub String);
68
69impl fmt::Display for ParseEventFormatError {
70 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
71 write!(f, "unknown event format: {}", self.0)
72 }
73}
74
75impl std::error::Error for ParseEventFormatError {}
76
77macro_rules! esl_event_types {
79 (
80 $(
81 $(#[$attr:meta])*
82 $variant:ident => $wire:literal
83 ),+ $(,)?
84 ;
85 $(
87 $(#[$extra_attr:meta])*
88 $extra_variant:ident => $extra_wire:literal
89 ),* $(,)?
90 ) => {
91 #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
96 #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
97 #[non_exhaustive]
98 #[allow(missing_docs)]
99 pub enum EslEventType {
100 $(
101 $(#[$attr])*
102 $variant,
103 )+
104 $(
105 $(#[$extra_attr])*
106 $extra_variant,
107 )*
108 }
109
110 impl fmt::Display for EslEventType {
111 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
112 f.write_str(self.as_str())
113 }
114 }
115
116 impl EslEventType {
117 pub const fn as_str(&self) -> &'static str {
119 match self {
120 $( EslEventType::$variant => $wire, )+
121 $( EslEventType::$extra_variant => $extra_wire, )*
122 }
123 }
124
125 pub fn parse_event_type(s: &str) -> Option<Self> {
127 match s {
128 $( $wire => Some(EslEventType::$variant), )+
129 $( $extra_wire => Some(EslEventType::$extra_variant), )*
130 _ => None,
131 }
132 }
133 }
134
135 impl FromStr for EslEventType {
136 type Err = ParseEventTypeError;
137
138 fn from_str(s: &str) -> Result<Self, Self::Err> {
139 Self::parse_event_type(s).ok_or_else(|| ParseEventTypeError(s.to_string()))
140 }
141 }
142 };
143}
144
145esl_event_types! {
146 Custom => "CUSTOM",
147 Clone => "CLONE",
148 ChannelCreate => "CHANNEL_CREATE",
149 ChannelDestroy => "CHANNEL_DESTROY",
150 ChannelState => "CHANNEL_STATE",
151 ChannelCallstate => "CHANNEL_CALLSTATE",
152 ChannelAnswer => "CHANNEL_ANSWER",
153 ChannelHangup => "CHANNEL_HANGUP",
154 ChannelHangupComplete => "CHANNEL_HANGUP_COMPLETE",
155 ChannelExecute => "CHANNEL_EXECUTE",
156 ChannelExecuteComplete => "CHANNEL_EXECUTE_COMPLETE",
157 ChannelHold => "CHANNEL_HOLD",
158 ChannelUnhold => "CHANNEL_UNHOLD",
159 ChannelBridge => "CHANNEL_BRIDGE",
160 ChannelUnbridge => "CHANNEL_UNBRIDGE",
161 ChannelProgress => "CHANNEL_PROGRESS",
162 ChannelProgressMedia => "CHANNEL_PROGRESS_MEDIA",
163 ChannelOutgoing => "CHANNEL_OUTGOING",
164 ChannelPark => "CHANNEL_PARK",
165 ChannelUnpark => "CHANNEL_UNPARK",
166 ChannelApplication => "CHANNEL_APPLICATION",
167 ChannelOriginate => "CHANNEL_ORIGINATE",
168 ChannelUuid => "CHANNEL_UUID",
169 Api => "API",
170 Log => "LOG",
171 InboundChan => "INBOUND_CHAN",
172 OutboundChan => "OUTBOUND_CHAN",
173 Startup => "STARTUP",
174 Shutdown => "SHUTDOWN",
175 Publish => "PUBLISH",
176 Unpublish => "UNPUBLISH",
177 Talk => "TALK",
178 Notalk => "NOTALK",
179 SessionCrash => "SESSION_CRASH",
180 ModuleLoad => "MODULE_LOAD",
181 ModuleUnload => "MODULE_UNLOAD",
182 Dtmf => "DTMF",
183 Message => "MESSAGE",
184 PresenceIn => "PRESENCE_IN",
185 NotifyIn => "NOTIFY_IN",
186 PresenceOut => "PRESENCE_OUT",
187 PresenceProbe => "PRESENCE_PROBE",
188 MessageWaiting => "MESSAGE_WAITING",
189 MessageQuery => "MESSAGE_QUERY",
190 Roster => "ROSTER",
191 Codec => "CODEC",
192 BackgroundJob => "BACKGROUND_JOB",
193 DetectedSpeech => "DETECTED_SPEECH",
194 DetectedTone => "DETECTED_TONE",
195 PrivateCommand => "PRIVATE_COMMAND",
196 Heartbeat => "HEARTBEAT",
197 Trap => "TRAP",
198 AddSchedule => "ADD_SCHEDULE",
199 DelSchedule => "DEL_SCHEDULE",
200 ExeSchedule => "EXE_SCHEDULE",
201 ReSchedule => "RE_SCHEDULE",
202 ReloadXml => "RELOADXML",
203 Notify => "NOTIFY",
204 PhoneFeature => "PHONE_FEATURE",
205 PhoneFeatureSubscribe => "PHONE_FEATURE_SUBSCRIBE",
206 SendMessage => "SEND_MESSAGE",
207 RecvMessage => "RECV_MESSAGE",
208 RequestParams => "REQUEST_PARAMS",
209 ChannelData => "CHANNEL_DATA",
210 General => "GENERAL",
211 Command => "COMMAND",
212 SessionHeartbeat => "SESSION_HEARTBEAT",
213 ClientDisconnected => "CLIENT_DISCONNECTED",
214 ServerDisconnected => "SERVER_DISCONNECTED",
215 SendInfo => "SEND_INFO",
216 RecvInfo => "RECV_INFO",
217 RecvRtcpMessage => "RECV_RTCP_MESSAGE",
218 SendRtcpMessage => "SEND_RTCP_MESSAGE",
219 CallSecure => "CALL_SECURE",
220 Nat => "NAT",
221 RecordStart => "RECORD_START",
222 RecordStop => "RECORD_STOP",
223 PlaybackStart => "PLAYBACK_START",
224 PlaybackStop => "PLAYBACK_STOP",
225 CallUpdate => "CALL_UPDATE",
226 Failure => "FAILURE",
227 SocketData => "SOCKET_DATA",
228 MediaBugStart => "MEDIA_BUG_START",
229 MediaBugStop => "MEDIA_BUG_STOP",
230 ConferenceDataQuery => "CONFERENCE_DATA_QUERY",
231 ConferenceData => "CONFERENCE_DATA",
232 CallSetupReq => "CALL_SETUP_REQ",
233 CallSetupResult => "CALL_SETUP_RESULT",
234 CallDetail => "CALL_DETAIL",
235 DeviceState => "DEVICE_STATE",
236 Text => "TEXT",
237 ShutdownRequested => "SHUTDOWN_REQUESTED",
238 All => "ALL";
240 StartRecording => "START_RECORDING",
244}
245
246impl EslEventType {
255 pub const CHANNEL_EVENTS: &[EslEventType] = &[
266 EslEventType::ChannelCreate,
267 EslEventType::ChannelDestroy,
268 EslEventType::ChannelState,
269 EslEventType::ChannelCallstate,
270 EslEventType::ChannelAnswer,
271 EslEventType::ChannelHangup,
272 EslEventType::ChannelHangupComplete,
273 EslEventType::ChannelExecute,
274 EslEventType::ChannelExecuteComplete,
275 EslEventType::ChannelHold,
276 EslEventType::ChannelUnhold,
277 EslEventType::ChannelBridge,
278 EslEventType::ChannelUnbridge,
279 EslEventType::ChannelProgress,
280 EslEventType::ChannelProgressMedia,
281 EslEventType::ChannelOutgoing,
282 EslEventType::ChannelPark,
283 EslEventType::ChannelUnpark,
284 EslEventType::ChannelApplication,
285 EslEventType::ChannelOriginate,
286 EslEventType::ChannelUuid,
287 EslEventType::ChannelData,
288 ];
289
290 pub const IN_CALL_EVENTS: &[EslEventType] = &[
301 EslEventType::Dtmf,
302 EslEventType::Talk,
303 EslEventType::Notalk,
304 EslEventType::CallSecure,
305 EslEventType::CallUpdate,
306 EslEventType::RecvRtcpMessage,
307 EslEventType::SendRtcpMessage,
308 ];
309
310 pub const MEDIA_EVENTS: &[EslEventType] = &[
321 EslEventType::PlaybackStart,
322 EslEventType::PlaybackStop,
323 EslEventType::RecordStart,
324 EslEventType::RecordStop,
325 EslEventType::StartRecording,
326 EslEventType::MediaBugStart,
327 EslEventType::MediaBugStop,
328 EslEventType::DetectedSpeech,
329 EslEventType::DetectedTone,
330 ];
331
332 pub const PRESENCE_EVENTS: &[EslEventType] = &[
343 EslEventType::PresenceIn,
344 EslEventType::PresenceOut,
345 EslEventType::PresenceProbe,
346 EslEventType::MessageWaiting,
347 EslEventType::MessageQuery,
348 EslEventType::Roster,
349 ];
350
351 pub const SYSTEM_EVENTS: &[EslEventType] = &[
362 EslEventType::Startup,
363 EslEventType::Shutdown,
364 EslEventType::ShutdownRequested,
365 EslEventType::Heartbeat,
366 EslEventType::SessionHeartbeat,
367 EslEventType::SessionCrash,
368 EslEventType::ModuleLoad,
369 EslEventType::ModuleUnload,
370 EslEventType::ReloadXml,
371 ];
372
373 pub const CONFERENCE_EVENTS: &[EslEventType] = &[
380 EslEventType::ConferenceDataQuery,
381 EslEventType::ConferenceData,
382 ];
383}
384
385#[derive(Debug, Clone, PartialEq, Eq)]
387pub struct ParseEventTypeError(pub String);
388
389impl fmt::Display for ParseEventTypeError {
390 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
391 write!(f, "unknown event type: {}", self.0)
392 }
393}
394
395impl std::error::Error for ParseEventTypeError {}
396
397#[derive(Debug, Clone, PartialEq, Eq)]
402pub struct EventSubscriptionError(pub String);
403
404impl fmt::Display for EventSubscriptionError {
405 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
406 write!(f, "invalid event subscription: {}", self.0)
407 }
408}
409
410impl std::error::Error for EventSubscriptionError {}
411
412#[derive(Debug, Clone, PartialEq, Eq)]
440#[non_exhaustive]
441pub struct EventSubscription {
442 format: EventFormat,
443 events: Vec<EslEventType>,
444 custom_subclasses: Vec<String>,
445 filters: Vec<(String, String)>,
446}
447
448fn validate_custom_subclass(s: &str) -> Result<(), EventSubscriptionError> {
450 if s.is_empty() {
451 return Err(EventSubscriptionError(
452 "custom subclass cannot be empty".into(),
453 ));
454 }
455 if s.contains('\n') || s.contains('\r') {
456 return Err(EventSubscriptionError(format!(
457 "custom subclass contains newline: {:?}",
458 s
459 )));
460 }
461 if s.contains(' ') {
462 return Err(EventSubscriptionError(format!(
463 "custom subclass contains space: {:?}",
464 s
465 )));
466 }
467 Ok(())
468}
469
470fn validate_filter_field(field: &str, label: &str) -> Result<(), EventSubscriptionError> {
472 if field.contains('\n') || field.contains('\r') {
473 return Err(EventSubscriptionError(format!(
474 "filter {} contains newline: {:?}",
475 label, field
476 )));
477 }
478 Ok(())
479}
480
481impl EventSubscription {
482 pub fn new(format: EventFormat) -> Self {
484 Self {
485 format,
486 events: Vec::new(),
487 custom_subclasses: Vec::new(),
488 filters: Vec::new(),
489 }
490 }
491
492 pub fn all(format: EventFormat) -> Self {
494 Self {
495 format,
496 events: vec![EslEventType::All],
497 custom_subclasses: Vec::new(),
498 filters: Vec::new(),
499 }
500 }
501
502 pub fn event(mut self, event: EslEventType) -> Self {
504 self.events
505 .push(event);
506 self
507 }
508
509 pub fn events<T: IntoIterator<Item = impl std::borrow::Borrow<EslEventType>>>(
511 mut self,
512 events: T,
513 ) -> Self {
514 self.events
515 .extend(
516 events
517 .into_iter()
518 .map(|e| *e.borrow()),
519 );
520 self
521 }
522
523 pub fn custom_subclass(
527 mut self,
528 subclass: impl Into<String>,
529 ) -> Result<Self, EventSubscriptionError> {
530 let s = subclass.into();
531 validate_custom_subclass(&s)?;
532 self.custom_subclasses
533 .push(s);
534 Ok(self)
535 }
536
537 pub fn custom_subclasses(
541 mut self,
542 subclasses: impl IntoIterator<Item = impl Into<String>>,
543 ) -> Result<Self, EventSubscriptionError> {
544 for s in subclasses {
545 let s = s.into();
546 validate_custom_subclass(&s)?;
547 self.custom_subclasses
548 .push(s);
549 }
550 Ok(self)
551 }
552
553 pub fn filter(
557 self,
558 header: crate::headers::EventHeader,
559 value: impl Into<String>,
560 ) -> Result<Self, EventSubscriptionError> {
561 let v = value.into();
562 validate_filter_field(&v, "value")?;
563 let mut s = self;
564 s.filters
565 .push((
566 header
567 .as_str()
568 .to_string(),
569 v,
570 ));
571 Ok(s)
572 }
573
574 pub fn filter_raw(
578 self,
579 header: impl Into<String>,
580 value: impl Into<String>,
581 ) -> Result<Self, EventSubscriptionError> {
582 let h = header.into();
583 let v = value.into();
584 validate_filter_field(&h, "header")?;
585 validate_filter_field(&v, "value")?;
586 let mut s = self;
587 s.filters
588 .push((h, v));
589 Ok(s)
590 }
591
592 pub fn with_format(mut self, format: EventFormat) -> Self {
594 self.format = format;
595 self
596 }
597
598 pub fn format(&self) -> EventFormat {
600 self.format
601 }
602
603 pub fn format_mut(&mut self) -> &mut EventFormat {
605 &mut self.format
606 }
607
608 pub fn event_types(&self) -> &[EslEventType] {
610 &self.events
611 }
612
613 pub fn event_types_mut(&mut self) -> &mut Vec<EslEventType> {
615 &mut self.events
616 }
617
618 pub fn custom_subclass_list(&self) -> &[String] {
620 &self.custom_subclasses
621 }
622
623 pub fn custom_subclasses_mut(&mut self) -> &mut Vec<String> {
625 &mut self.custom_subclasses
626 }
627
628 pub fn filters(&self) -> &[(String, String)] {
630 &self.filters
631 }
632
633 pub fn filters_mut(&mut self) -> &mut Vec<(String, String)> {
635 &mut self.filters
636 }
637
638 pub fn is_all(&self) -> bool {
640 self.events
641 .contains(&EslEventType::All)
642 }
643
644 pub fn is_empty(&self) -> bool {
646 self.events
647 .is_empty()
648 && self
649 .custom_subclasses
650 .is_empty()
651 }
652
653 pub fn to_event_string(&self) -> Option<String> {
660 if self
661 .events
662 .contains(&EslEventType::All)
663 {
664 return Some("ALL".to_string());
665 }
666
667 let mut parts: Vec<&str> = self
668 .events
669 .iter()
670 .map(|e| e.as_str())
671 .collect();
672
673 if !self
674 .custom_subclasses
675 .is_empty()
676 {
677 if !self
678 .events
679 .contains(&EslEventType::Custom)
680 {
681 parts.push("CUSTOM");
682 }
683 for sc in &self.custom_subclasses {
684 parts.push(sc.as_str());
685 }
686 }
687
688 if parts.is_empty() {
689 None
690 } else {
691 Some(parts.join(" "))
692 }
693 }
694}
695
696#[cfg(feature = "serde")]
697mod event_subscription_serde {
698 use super::*;
699 use serde::{Deserialize, Serialize};
700
701 #[derive(Serialize, Deserialize)]
702 struct EventSubscriptionRaw {
703 format: EventFormat,
704 #[serde(default)]
705 events: Vec<EslEventType>,
706 #[serde(default)]
707 custom_subclasses: Vec<String>,
708 #[serde(default)]
709 filters: Vec<(String, String)>,
710 }
711
712 impl TryFrom<EventSubscriptionRaw> for EventSubscription {
713 type Error = EventSubscriptionError;
714
715 fn try_from(raw: EventSubscriptionRaw) -> Result<Self, Self::Error> {
716 for sc in &raw.custom_subclasses {
717 validate_custom_subclass(sc)?;
718 }
719 for (h, v) in &raw.filters {
720 validate_filter_field(h, "header")?;
721 validate_filter_field(v, "value")?;
722 }
723 Ok(EventSubscription {
724 format: raw.format,
725 events: raw.events,
726 custom_subclasses: raw.custom_subclasses,
727 filters: raw.filters,
728 })
729 }
730 }
731
732 impl Serialize for EventSubscription {
733 fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
734 let raw = EventSubscriptionRaw {
735 format: self.format,
736 events: self
737 .events
738 .clone(),
739 custom_subclasses: self
740 .custom_subclasses
741 .clone(),
742 filters: self
743 .filters
744 .clone(),
745 };
746 raw.serialize(serializer)
747 }
748 }
749
750 impl<'de> Deserialize<'de> for EventSubscription {
751 fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
752 let raw = EventSubscriptionRaw::deserialize(deserializer)?;
753 EventSubscription::try_from(raw).map_err(serde::de::Error::custom)
754 }
755 }
756}
757
758#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
760#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
761#[non_exhaustive]
762pub enum EslEventPriority {
763 Normal,
765 Low,
767 High,
769}
770
771impl fmt::Display for EslEventPriority {
772 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
773 match self {
774 EslEventPriority::Normal => write!(f, "NORMAL"),
775 EslEventPriority::Low => write!(f, "LOW"),
776 EslEventPriority::High => write!(f, "HIGH"),
777 }
778 }
779}
780
781#[derive(Debug, Clone, PartialEq, Eq)]
783pub struct ParsePriorityError(pub String);
784
785impl fmt::Display for ParsePriorityError {
786 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
787 write!(f, "unknown priority: {}", self.0)
788 }
789}
790
791impl std::error::Error for ParsePriorityError {}
792
793impl FromStr for EslEventPriority {
794 type Err = ParsePriorityError;
795
796 fn from_str(s: &str) -> Result<Self, Self::Err> {
797 match s {
798 "NORMAL" => Ok(EslEventPriority::Normal),
799 "LOW" => Ok(EslEventPriority::Low),
800 "HIGH" => Ok(EslEventPriority::High),
801 _ => Err(ParsePriorityError(s.to_string())),
802 }
803 }
804}
805
806#[derive(Debug, Clone, Eq)]
808#[cfg_attr(feature = "serde", derive(serde::Serialize))]
809pub struct EslEvent {
810 event_type: Option<EslEventType>,
811 headers: IndexMap<String, String>,
812 #[cfg_attr(feature = "serde", serde(skip))]
813 original_keys: IndexMap<String, String>,
814 body: Option<String>,
815}
816
817impl EslEvent {
818 pub fn new() -> Self {
820 Self {
821 event_type: None,
822 headers: IndexMap::new(),
823 original_keys: IndexMap::new(),
824 body: None,
825 }
826 }
827
828 pub fn with_type(event_type: EslEventType) -> Self {
830 Self {
831 event_type: Some(event_type),
832 headers: IndexMap::new(),
833 original_keys: IndexMap::new(),
834 body: None,
835 }
836 }
837
838 pub fn event_type(&self) -> Option<EslEventType> {
840 self.event_type
841 }
842
843 pub fn set_event_type(&mut self, event_type: Option<EslEventType>) {
845 self.event_type = event_type;
846 }
847
848 pub fn header(&self, name: EventHeader) -> Option<&str> {
852 self.headers
853 .get(name.as_str())
854 .map(|s| s.as_str())
855 }
856
857 pub fn header_str(&self, name: &str) -> Option<&str> {
865 self.headers
866 .get(name)
867 .or_else(|| {
868 self.original_keys
869 .get(name)
870 .and_then(|normalized| {
871 self.headers
872 .get(normalized)
873 })
874 })
875 .map(|s| s.as_str())
876 }
877
878 pub fn variable_str(&self, name: &str) -> Option<&str> {
883 let key = format!("variable_{}", name);
884 self.header_str(&key)
885 }
886
887 pub fn headers(&self) -> &IndexMap<String, String> {
889 &self.headers
890 }
891
892 pub fn set_header(&mut self, name: impl Into<String>, value: impl Into<String>) {
894 let original = name.into();
895 let normalized = normalize_header_key(&original);
896 if original != normalized {
897 self.original_keys
898 .insert(original, normalized.clone());
899 }
900 self.headers
901 .insert(normalized, value.into());
902 }
903
904 pub fn remove_header(&mut self, name: impl AsRef<str>) -> Option<String> {
908 let name = name.as_ref();
909 if let Some(value) = self
910 .headers
911 .shift_remove(name)
912 {
913 return Some(value);
914 }
915 if let Some(normalized) = self
916 .original_keys
917 .shift_remove(name)
918 {
919 return self
920 .headers
921 .shift_remove(&normalized);
922 }
923 None
924 }
925
926 pub fn body(&self) -> Option<&str> {
928 self.body
929 .as_deref()
930 }
931
932 pub fn set_body(&mut self, body: impl Into<String>) {
934 self.body = Some(body.into());
935 }
936
937 pub fn set_priority(&mut self, priority: EslEventPriority) {
942 self.set_header(EventHeader::Priority.as_str(), priority.to_string());
943 }
944
945 pub fn push_header(&mut self, name: &str, value: &str) {
959 self.stack_header(name, value, EslArray::push);
960 }
961
962 pub fn unshift_header(&mut self, name: &str, value: &str) {
974 self.stack_header(name, value, EslArray::unshift);
975 }
976
977 fn stack_header(&mut self, name: &str, value: &str, op: fn(&mut EslArray, String)) {
978 match self
979 .headers
980 .get(name)
981 {
982 None => {
983 self.set_header(name, value);
984 }
985 Some(existing) => {
986 let mut arr = match EslArray::parse(existing) {
987 Some(arr) => arr,
988 None => EslArray::new(vec![existing.clone()]),
989 };
990 op(&mut arr, value.into());
991 self.set_header(name, arr.to_string());
992 }
993 }
994 }
995
996 pub fn is_event_type(&self, event_type: EslEventType) -> bool {
998 self.event_type == Some(event_type)
999 }
1000
1001 pub fn to_plain_format(&self) -> String {
1011 use std::fmt::Write;
1012 let mut result = String::new();
1013
1014 for (key, value) in &self.headers {
1015 if key == "Content-Length" {
1016 continue;
1017 }
1018 let _ = writeln!(
1019 result,
1020 "{}: {}",
1021 key,
1022 percent_encode(value.as_bytes(), NON_ALPHANUMERIC)
1023 );
1024 }
1025
1026 if let Some(body) = &self.body {
1027 let _ = writeln!(result, "Content-Length: {}", body.len());
1028 result.push('\n');
1029 result.push_str(body);
1030 } else {
1031 result.push('\n');
1032 }
1033
1034 result
1035 }
1036}
1037
1038impl Default for EslEvent {
1039 fn default() -> Self {
1040 Self::new()
1041 }
1042}
1043
1044impl HeaderLookup for EslEvent {
1045 fn header_str(&self, name: &str) -> Option<&str> {
1046 EslEvent::header_str(self, name)
1047 }
1048
1049 fn variable_str(&self, name: &str) -> Option<&str> {
1050 let key = format!("variable_{}", name);
1051 self.header_str(&key)
1052 }
1053}
1054
1055impl PartialEq for EslEvent {
1056 fn eq(&self, other: &Self) -> bool {
1057 self.event_type == other.event_type
1058 && self.headers == other.headers
1059 && self.body == other.body
1060 }
1061}
1062
1063impl std::hash::Hash for EslEvent {
1064 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
1065 self.event_type
1066 .hash(state);
1067 for (k, v) in &self.headers {
1068 k.hash(state);
1069 v.hash(state);
1070 }
1071 self.body
1072 .hash(state);
1073 }
1074}
1075
1076#[cfg(feature = "serde")]
1077impl<'de> serde::Deserialize<'de> for EslEvent {
1078 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
1079 where
1080 D: serde::Deserializer<'de>,
1081 {
1082 #[derive(serde::Deserialize)]
1083 struct Raw {
1084 event_type: Option<EslEventType>,
1085 headers: IndexMap<String, String>,
1086 body: Option<String>,
1087 }
1088 let raw = Raw::deserialize(deserializer)?;
1089 let mut event = EslEvent::new();
1090 event.event_type = raw.event_type;
1091 event.body = raw.body;
1092 for (k, v) in raw.headers {
1093 event.set_header(k, v);
1094 }
1095 Ok(event)
1096 }
1097}
1098
1099#[cfg(test)]
1100mod tests {
1101 use super::*;
1102
1103 #[test]
1104 fn headers_preserve_insertion_order() {
1105 let mut event = EslEvent::new();
1106 event.set_header("Zebra", "last");
1107 event.set_header("Alpha", "first");
1108 event.set_header("Middle", "mid");
1109 let keys: Vec<&str> = event
1110 .headers()
1111 .keys()
1112 .map(|s| s.as_str())
1113 .collect();
1114 assert_eq!(keys, vec!["Zebra", "Alpha", "Middle"]);
1115 }
1116
1117 #[test]
1118 fn test_notify_in_parse() {
1119 assert_eq!(
1120 EslEventType::parse_event_type("NOTIFY_IN"),
1121 Some(EslEventType::NotifyIn)
1122 );
1123 assert_eq!(EslEventType::parse_event_type("notify_in"), None);
1124 }
1125
1126 #[test]
1127 fn test_notify_in_display() {
1128 assert_eq!(EslEventType::NotifyIn.to_string(), "NOTIFY_IN");
1129 }
1130
1131 #[test]
1132 fn test_notify_in_distinct_from_notify() {
1133 assert_ne!(EslEventType::Notify, EslEventType::NotifyIn);
1134 assert_ne!(
1135 EslEventType::Notify.to_string(),
1136 EslEventType::NotifyIn.to_string()
1137 );
1138 }
1139
1140 #[test]
1141 fn test_wire_names_match_c_esl() {
1142 assert_eq!(
1143 EslEventType::ChannelOutgoing.to_string(),
1144 "CHANNEL_OUTGOING"
1145 );
1146 assert_eq!(EslEventType::Api.to_string(), "API");
1147 assert_eq!(EslEventType::ReloadXml.to_string(), "RELOADXML");
1148 assert_eq!(EslEventType::PresenceIn.to_string(), "PRESENCE_IN");
1149 assert_eq!(EslEventType::Roster.to_string(), "ROSTER");
1150 assert_eq!(EslEventType::Text.to_string(), "TEXT");
1151 assert_eq!(EslEventType::ReSchedule.to_string(), "RE_SCHEDULE");
1152
1153 assert_eq!(
1154 EslEventType::parse_event_type("CHANNEL_OUTGOING"),
1155 Some(EslEventType::ChannelOutgoing)
1156 );
1157 assert_eq!(
1158 EslEventType::parse_event_type("API"),
1159 Some(EslEventType::Api)
1160 );
1161 assert_eq!(
1162 EslEventType::parse_event_type("RELOADXML"),
1163 Some(EslEventType::ReloadXml)
1164 );
1165 assert_eq!(
1166 EslEventType::parse_event_type("PRESENCE_IN"),
1167 Some(EslEventType::PresenceIn)
1168 );
1169 }
1170
1171 #[test]
1172 fn test_event_type_from_str() {
1173 assert_eq!(
1174 "CHANNEL_ANSWER".parse::<EslEventType>(),
1175 Ok(EslEventType::ChannelAnswer)
1176 );
1177 assert!("channel_answer"
1178 .parse::<EslEventType>()
1179 .is_err());
1180 assert!("UNKNOWN_EVENT"
1181 .parse::<EslEventType>()
1182 .is_err());
1183 }
1184
1185 #[test]
1186 fn test_remove_header() {
1187 let mut event = EslEvent::new();
1188 event.set_header("Foo", "bar");
1189 event.set_header("Baz", "qux");
1190
1191 let removed = event.remove_header("Foo");
1192 assert_eq!(removed, Some("bar".to_string()));
1193 assert!(event
1194 .header_str("Foo")
1195 .is_none());
1196 assert_eq!(event.header_str("Baz"), Some("qux"));
1197
1198 let removed_again = event.remove_header("Foo");
1199 assert_eq!(removed_again, None);
1200 }
1201
1202 #[test]
1203 fn test_to_plain_format_basic() {
1204 let mut event = EslEvent::with_type(EslEventType::Heartbeat);
1205 event.set_header("Event-Name", "HEARTBEAT");
1206 event.set_header("Core-UUID", "abc-123");
1207
1208 let plain = event.to_plain_format();
1209
1210 assert!(plain.starts_with("Event-Name: "));
1211 assert!(plain.contains("Core-UUID: "));
1212 assert!(plain.ends_with("\n\n"));
1213 }
1214
1215 #[test]
1216 fn test_to_plain_format_percent_encoding() {
1217 let mut event = EslEvent::with_type(EslEventType::Heartbeat);
1218 event.set_header("Event-Name", "HEARTBEAT");
1219 event.set_header("Up-Time", "0 years, 0 days");
1220
1221 let plain = event.to_plain_format();
1222
1223 assert!(!plain.contains("0 years, 0 days"));
1224 assert!(plain.contains("Up-Time: "));
1225 assert!(plain.contains("%20"));
1226 }
1227
1228 #[test]
1229 fn test_to_plain_format_with_body() {
1230 let mut event = EslEvent::with_type(EslEventType::BackgroundJob);
1231 event.set_header("Event-Name", "BACKGROUND_JOB");
1232 event.set_header("Job-UUID", "def-456");
1233 event.set_body("+OK result\n".to_string());
1234
1235 let plain = event.to_plain_format();
1236
1237 assert!(plain.contains("Content-Length: 11\n"));
1238 assert!(plain.ends_with("\n\n+OK result\n"));
1239 }
1240
1241 #[test]
1242 fn test_to_plain_format_preserves_insertion_order() {
1243 let mut event = EslEvent::with_type(EslEventType::Heartbeat);
1244 event.set_header("Event-Name", "HEARTBEAT");
1245 event.set_header("Core-UUID", "abc-123");
1246 event.set_header("FreeSWITCH-Hostname", "fs01");
1247 event.set_header("Up-Time", "0 years, 1 day");
1248
1249 let plain = event.to_plain_format();
1250 let lines: Vec<&str> = plain
1251 .lines()
1252 .collect();
1253 assert!(lines[0].starts_with("Event-Name: "));
1254 assert!(lines[1].starts_with("Core-UUID: "));
1255 assert!(lines[2].starts_with("FreeSWITCH-Hostname: "));
1256 assert!(lines[3].starts_with("Up-Time: "));
1257 }
1258
1259 #[test]
1260 fn test_to_plain_format_round_trip() {
1261 let mut original = EslEvent::with_type(EslEventType::ChannelCreate);
1262 original.set_header("Event-Name", "CHANNEL_CREATE");
1263 original.set_header("Core-UUID", "abc-123");
1264 original.set_header("Channel-Name", "sofia/internal/1000@example.com");
1265 original.set_header("Caller-Caller-ID-Name", "Jérôme Poulin");
1266 original.set_body("some body content");
1267
1268 let plain = original.to_plain_format();
1269
1270 let (header_section, inner_body) = if let Some(pos) = plain.find("\n\n") {
1272 (&plain[..pos], Some(&plain[pos + 2..]))
1273 } else {
1274 (plain.as_str(), None)
1275 };
1276
1277 let mut parsed = EslEvent::new();
1278 for line in header_section.lines() {
1279 let line = line.trim();
1280 if line.is_empty() {
1281 continue;
1282 }
1283 if let Some(colon_pos) = line.find(':') {
1284 let key = line[..colon_pos].trim();
1285 if key == "Content-Length" {
1286 continue;
1287 }
1288 let raw_value = line[colon_pos + 1..].trim();
1289 let value = percent_encoding::percent_decode_str(raw_value)
1290 .decode_utf8()
1291 .unwrap()
1292 .into_owned();
1293 parsed.set_header(key, value);
1294 }
1295 }
1296 if let Some(ib) = inner_body {
1297 if !ib.is_empty() {
1298 parsed.set_body(ib);
1299 }
1300 }
1301
1302 assert_eq!(original.headers(), parsed.headers());
1303 assert_eq!(original.body(), parsed.body());
1304 }
1305
1306 #[test]
1307 fn test_set_priority_normal() {
1308 let mut event = EslEvent::new();
1309 event.set_priority(EslEventPriority::Normal);
1310 assert_eq!(
1311 event
1312 .priority()
1313 .unwrap(),
1314 Some(EslEventPriority::Normal)
1315 );
1316 assert_eq!(event.header(EventHeader::Priority), Some("NORMAL"));
1317 }
1318
1319 #[test]
1320 fn test_set_priority_high() {
1321 let mut event = EslEvent::new();
1322 event.set_priority(EslEventPriority::High);
1323 assert_eq!(
1324 event
1325 .priority()
1326 .unwrap(),
1327 Some(EslEventPriority::High)
1328 );
1329 assert_eq!(event.header(EventHeader::Priority), Some("HIGH"));
1330 }
1331
1332 #[test]
1333 fn test_priority_display() {
1334 assert_eq!(EslEventPriority::Normal.to_string(), "NORMAL");
1335 assert_eq!(EslEventPriority::Low.to_string(), "LOW");
1336 assert_eq!(EslEventPriority::High.to_string(), "HIGH");
1337 }
1338
1339 #[test]
1340 fn test_priority_from_str() {
1341 assert_eq!(
1342 "NORMAL".parse::<EslEventPriority>(),
1343 Ok(EslEventPriority::Normal)
1344 );
1345 assert_eq!("LOW".parse::<EslEventPriority>(), Ok(EslEventPriority::Low));
1346 assert_eq!(
1347 "HIGH".parse::<EslEventPriority>(),
1348 Ok(EslEventPriority::High)
1349 );
1350 assert!("INVALID"
1351 .parse::<EslEventPriority>()
1352 .is_err());
1353 }
1354
1355 #[test]
1356 fn test_priority_from_str_rejects_wrong_case() {
1357 assert!("normal"
1358 .parse::<EslEventPriority>()
1359 .is_err());
1360 assert!("Low"
1361 .parse::<EslEventPriority>()
1362 .is_err());
1363 assert!("hIgH"
1364 .parse::<EslEventPriority>()
1365 .is_err());
1366 }
1367
1368 #[test]
1369 fn test_push_header_new() {
1370 let mut event = EslEvent::new();
1371 event.push_header("X-Test", "first");
1372 assert_eq!(event.header_str("X-Test"), Some("first"));
1373 }
1374
1375 #[test]
1376 fn test_push_header_existing_plain() {
1377 let mut event = EslEvent::new();
1378 event.set_header("X-Test", "first");
1379 event.push_header("X-Test", "second");
1380 assert_eq!(event.header_str("X-Test"), Some("ARRAY::first|:second"));
1381 }
1382
1383 #[test]
1384 fn test_push_header_existing_array() {
1385 let mut event = EslEvent::new();
1386 event.set_header("X-Test", "ARRAY::a|:b");
1387 event.push_header("X-Test", "c");
1388 assert_eq!(event.header_str("X-Test"), Some("ARRAY::a|:b|:c"));
1389 }
1390
1391 #[test]
1392 fn test_unshift_header_new() {
1393 let mut event = EslEvent::new();
1394 event.unshift_header("X-Test", "only");
1395 assert_eq!(event.header_str("X-Test"), Some("only"));
1396 }
1397
1398 #[test]
1399 fn test_unshift_header_existing_array() {
1400 let mut event = EslEvent::new();
1401 event.set_header("X-Test", "ARRAY::b|:c");
1402 event.unshift_header("X-Test", "a");
1403 assert_eq!(event.header_str("X-Test"), Some("ARRAY::a|:b|:c"));
1404 }
1405
1406 #[test]
1407 fn test_sendevent_with_priority_wire_format() {
1408 let mut event = EslEvent::with_type(EslEventType::Custom);
1409 event.set_header("Event-Name", "CUSTOM");
1410 event.set_header("Event-Subclass", "test::priority");
1411 event.set_priority(EslEventPriority::High);
1412
1413 let plain = event.to_plain_format();
1414 assert!(plain.contains("priority: HIGH\n"));
1415 }
1416
1417 #[test]
1418 fn test_convenience_accessors() {
1419 let mut event = EslEvent::new();
1420 event.set_header("Channel-Name", "sofia/internal/1000@example.com");
1421 event.set_header("Caller-Caller-ID-Number", "1000");
1422 event.set_header("Caller-Caller-ID-Name", "Alice");
1423 event.set_header("Hangup-Cause", "NORMAL_CLEARING");
1424 event.set_header("Event-Subclass", "sofia::register");
1425 event.set_header("variable_sip_from_display", "Bob");
1426
1427 assert_eq!(
1428 event.channel_name(),
1429 Some("sofia/internal/1000@example.com")
1430 );
1431 assert_eq!(event.caller_id_number(), Some("1000"));
1432 assert_eq!(event.caller_id_name(), Some("Alice"));
1433 assert_eq!(
1434 event
1435 .hangup_cause()
1436 .unwrap(),
1437 Some(crate::channel::HangupCause::NormalClearing)
1438 );
1439 assert_eq!(event.event_subclass(), Some("sofia::register"));
1440 assert_eq!(event.variable_str("sip_from_display"), Some("Bob"));
1441 assert_eq!(event.variable_str("nonexistent"), None);
1442 }
1443
1444 #[test]
1445 fn test_event_format_from_str() {
1446 assert_eq!("plain".parse::<EventFormat>(), Ok(EventFormat::Plain));
1447 assert_eq!("json".parse::<EventFormat>(), Ok(EventFormat::Json));
1448 assert_eq!("xml".parse::<EventFormat>(), Ok(EventFormat::Xml));
1449 assert!("foo"
1450 .parse::<EventFormat>()
1451 .is_err());
1452 }
1453
1454 #[test]
1455 fn test_event_format_from_str_case_insensitive() {
1456 assert_eq!("PLAIN".parse::<EventFormat>(), Ok(EventFormat::Plain));
1457 assert_eq!("Json".parse::<EventFormat>(), Ok(EventFormat::Json));
1458 assert_eq!("XML".parse::<EventFormat>(), Ok(EventFormat::Xml));
1459 assert_eq!("Xml".parse::<EventFormat>(), Ok(EventFormat::Xml));
1460 }
1461
1462 #[test]
1463 fn test_event_format_from_content_type() {
1464 assert_eq!(
1465 EventFormat::from_content_type("text/event-json"),
1466 Ok(EventFormat::Json)
1467 );
1468 assert_eq!(
1469 EventFormat::from_content_type("text/event-xml"),
1470 Ok(EventFormat::Xml)
1471 );
1472 assert_eq!(
1473 EventFormat::from_content_type("text/event-plain"),
1474 Ok(EventFormat::Plain)
1475 );
1476 assert!(EventFormat::from_content_type("unknown").is_err());
1477 }
1478
1479 #[test]
1482 fn test_event_channel_state_accessor() {
1483 use crate::channel::ChannelState;
1484 let mut event = EslEvent::new();
1485 event.set_header("Channel-State", "CS_EXECUTE");
1486 assert_eq!(
1487 event
1488 .channel_state()
1489 .unwrap(),
1490 Some(ChannelState::CsExecute)
1491 );
1492 }
1493
1494 #[test]
1495 fn test_event_channel_state_number_accessor() {
1496 use crate::channel::ChannelState;
1497 let mut event = EslEvent::new();
1498 event.set_header("Channel-State-Number", "4");
1499 assert_eq!(
1500 event
1501 .channel_state_number()
1502 .unwrap(),
1503 Some(ChannelState::CsExecute)
1504 );
1505 }
1506
1507 #[test]
1508 fn test_event_call_state_accessor() {
1509 use crate::channel::CallState;
1510 let mut event = EslEvent::new();
1511 event.set_header("Channel-Call-State", "ACTIVE");
1512 assert_eq!(
1513 event
1514 .call_state()
1515 .unwrap(),
1516 Some(CallState::Active)
1517 );
1518 }
1519
1520 #[test]
1521 fn test_event_answer_state_accessor() {
1522 use crate::channel::AnswerState;
1523 let mut event = EslEvent::new();
1524 event.set_header("Answer-State", "answered");
1525 assert_eq!(
1526 event
1527 .answer_state()
1528 .unwrap(),
1529 Some(AnswerState::Answered)
1530 );
1531 }
1532
1533 #[test]
1534 fn test_event_call_direction_accessor() {
1535 use crate::channel::CallDirection;
1536 let mut event = EslEvent::new();
1537 event.set_header("Call-Direction", "inbound");
1538 assert_eq!(
1539 event
1540 .call_direction()
1541 .unwrap(),
1542 Some(CallDirection::Inbound)
1543 );
1544 }
1545
1546 #[test]
1547 fn test_event_typed_accessors_missing_headers() {
1548 let event = EslEvent::new();
1549 assert_eq!(
1550 event
1551 .channel_state()
1552 .unwrap(),
1553 None
1554 );
1555 assert_eq!(
1556 event
1557 .channel_state_number()
1558 .unwrap(),
1559 None
1560 );
1561 assert_eq!(
1562 event
1563 .call_state()
1564 .unwrap(),
1565 None
1566 );
1567 assert_eq!(
1568 event
1569 .answer_state()
1570 .unwrap(),
1571 None
1572 );
1573 assert_eq!(
1574 event
1575 .call_direction()
1576 .unwrap(),
1577 None
1578 );
1579 }
1580
1581 #[test]
1584 fn test_sip_p_asserted_identity_comma_separated() {
1585 let mut event = EslEvent::new();
1586 event.set_header(
1589 "variable_sip_P-Asserted-Identity",
1590 "<sip:alice@atlanta.example.com>, <tel:+15551234567>",
1591 );
1592
1593 assert_eq!(
1594 event.variable_str("sip_P-Asserted-Identity"),
1595 Some("<sip:alice@atlanta.example.com>, <tel:+15551234567>")
1596 );
1597 }
1598
1599 #[test]
1600 fn test_sip_p_asserted_identity_array_format() {
1601 let mut event = EslEvent::new();
1602 event.push_header(
1604 "variable_sip_P-Asserted-Identity",
1605 "<sip:alice@atlanta.example.com>",
1606 );
1607 event.push_header("variable_sip_P-Asserted-Identity", "<tel:+15551234567>");
1608
1609 let raw = event
1610 .header_str("variable_sip_P-Asserted-Identity")
1611 .unwrap();
1612 assert_eq!(
1613 raw,
1614 "ARRAY::<sip:alice@atlanta.example.com>|:<tel:+15551234567>"
1615 );
1616
1617 let arr = crate::variables::EslArray::parse(raw).unwrap();
1618 assert_eq!(arr.len(), 2);
1619 assert_eq!(arr.items()[0], "<sip:alice@atlanta.example.com>");
1620 assert_eq!(arr.items()[1], "<tel:+15551234567>");
1621 }
1622
1623 #[test]
1624 fn test_sip_header_with_colons_in_uri() {
1625 let mut event = EslEvent::new();
1626 event.push_header(
1628 "variable_sip_h_Diversion",
1629 "<sip:+15551234567@gw.example.com;reason=unconditional>",
1630 );
1631 event.push_header(
1632 "variable_sip_h_Diversion",
1633 "<sips:+15559876543@secure.example.com;reason=no-answer;counter=3>",
1634 );
1635
1636 let raw = event
1637 .header_str("variable_sip_h_Diversion")
1638 .unwrap();
1639 let arr = crate::variables::EslArray::parse(raw).unwrap();
1640 assert_eq!(arr.len(), 2);
1641 assert_eq!(
1642 arr.items()[0],
1643 "<sip:+15551234567@gw.example.com;reason=unconditional>"
1644 );
1645 assert_eq!(
1646 arr.items()[1],
1647 "<sips:+15559876543@secure.example.com;reason=no-answer;counter=3>"
1648 );
1649 }
1650
1651 #[test]
1652 fn test_sip_p_asserted_identity_plain_format_round_trip() {
1653 let mut event = EslEvent::with_type(EslEventType::ChannelCreate);
1654 event.set_header("Event-Name", "CHANNEL_CREATE");
1655 event.set_header(
1656 "variable_sip_P-Asserted-Identity",
1657 "<sip:alice@atlanta.example.com>, <tel:+15551234567>",
1658 );
1659
1660 let plain = event.to_plain_format();
1661 assert!(plain.contains("variable_sip_P-Asserted-Identity:"));
1663 assert!(!plain.contains("<sip:alice"));
1665 }
1666
1667 #[test]
1672 fn set_header_normalizes_known_enum_variant() {
1673 let mut event = EslEvent::new();
1674 event.set_header("unique-id", "abc-123");
1675 assert_eq!(event.header(EventHeader::UniqueId), Some("abc-123"));
1676 }
1677
1678 #[test]
1679 fn set_header_normalizes_codec_header() {
1680 let mut event = EslEvent::new();
1681 event.set_header("channel-read-codec-bit-rate", "128000");
1682 assert_eq!(
1683 event.header(EventHeader::ChannelReadCodecBitRate),
1684 Some("128000")
1685 );
1686 }
1687
1688 #[test]
1689 fn header_str_finds_by_original_key() {
1690 let mut event = EslEvent::new();
1691 event.set_header("unique-id", "abc-123");
1692 assert_eq!(event.header_str("unique-id"), Some("abc-123"));
1694 assert_eq!(event.header_str("Unique-ID"), Some("abc-123"));
1696 }
1697
1698 #[test]
1699 fn header_str_finds_unknown_dash_header_by_original() {
1700 let mut event = EslEvent::new();
1701 event.set_header("x-custom-header", "val");
1702 assert_eq!(event.header_str("X-Custom-Header"), Some("val"));
1704 assert_eq!(event.header_str("x-custom-header"), Some("val"));
1706 }
1707
1708 #[test]
1709 fn set_header_underscore_passthrough_preserves_sip_h() {
1710 let mut event = EslEvent::new();
1711 event.set_header("variable_sip_h_X-My-CUSTOM-Header", "val");
1712 assert_eq!(
1713 event.header_str("variable_sip_h_X-My-CUSTOM-Header"),
1714 Some("val")
1715 );
1716 }
1717
1718 #[test]
1719 fn set_header_different_casing_overwrites() {
1720 let mut event = EslEvent::new();
1721 event.set_header("Unique-ID", "first");
1722 event.set_header("unique-id", "second");
1723 assert_eq!(event.header(EventHeader::UniqueId), Some("second"));
1725 }
1726
1727 #[test]
1728 fn remove_header_by_original_key() {
1729 let mut event = EslEvent::new();
1730 event.set_header("unique-id", "abc-123");
1731 let removed = event.remove_header("unique-id");
1732 assert_eq!(removed, Some("abc-123".to_string()));
1733 assert_eq!(event.header(EventHeader::UniqueId), None);
1734 }
1735
1736 #[test]
1737 fn remove_header_by_canonical_key() {
1738 let mut event = EslEvent::new();
1739 event.set_header("unique-id", "abc-123");
1740 let removed = event.remove_header("Unique-ID");
1741 assert_eq!(removed, Some("abc-123".to_string()));
1742 assert_eq!(event.header_str("unique-id"), None);
1743 }
1744
1745 #[test]
1746 fn serde_round_trip_preserves_canonical_lookups() {
1747 let mut event = EslEvent::new();
1748 event.set_header("unique-id", "abc-123");
1749 event.set_header("channel-read-codec-bit-rate", "128000");
1750 let json = serde_json::to_string(&event).unwrap();
1751 let deserialized: EslEvent = serde_json::from_str(&json).unwrap();
1752 assert_eq!(deserialized.header(EventHeader::UniqueId), Some("abc-123"));
1753 assert_eq!(
1754 deserialized.header(EventHeader::ChannelReadCodecBitRate),
1755 Some("128000")
1756 );
1757 }
1758
1759 #[test]
1760 fn serde_deserialize_normalizes_external_json() {
1761 let json = r#"{"event_type":null,"headers":{"unique-id":"abc-123","channel-read-codec-bit-rate":"128000"},"body":null}"#;
1762 let event: EslEvent = serde_json::from_str(json).unwrap();
1763 assert_eq!(event.header(EventHeader::UniqueId), Some("abc-123"));
1764 assert_eq!(
1765 event.header(EventHeader::ChannelReadCodecBitRate),
1766 Some("128000")
1767 );
1768 assert_eq!(event.header_str("unique-id"), Some("abc-123"));
1769 }
1770
1771 #[test]
1772 fn test_event_typed_accessors_invalid_values() {
1773 let mut event = EslEvent::new();
1774 event.set_header("Channel-State", "BOGUS");
1775 event.set_header("Channel-State-Number", "999");
1776 event.set_header("Channel-Call-State", "BOGUS");
1777 event.set_header("Answer-State", "bogus");
1778 event.set_header("Call-Direction", "bogus");
1779 assert!(event
1780 .channel_state()
1781 .is_err());
1782 assert!(event
1783 .channel_state_number()
1784 .is_err());
1785 assert!(event
1786 .call_state()
1787 .is_err());
1788 assert!(event
1789 .answer_state()
1790 .is_err());
1791 assert!(event
1792 .call_direction()
1793 .is_err());
1794 }
1795
1796 #[test]
1799 fn new_creates_empty() {
1800 let sub = EventSubscription::new(EventFormat::Plain);
1801 assert!(sub.is_empty());
1802 assert!(!sub.is_all());
1803 assert_eq!(sub.format(), EventFormat::Plain);
1804 assert!(sub
1805 .event_types()
1806 .is_empty());
1807 assert!(sub
1808 .custom_subclass_list()
1809 .is_empty());
1810 assert!(sub
1811 .filters()
1812 .is_empty());
1813 }
1814
1815 #[test]
1816 fn all_creates_all() {
1817 let sub = EventSubscription::all(EventFormat::Json);
1818 assert!(sub.is_all());
1819 assert!(!sub.is_empty());
1820 assert_eq!(sub.to_event_string(), Some("ALL".to_string()));
1821 }
1822
1823 #[test]
1824 fn event_string_typed_only() {
1825 let sub = EventSubscription::new(EventFormat::Plain)
1826 .event(EslEventType::ChannelCreate)
1827 .event(EslEventType::ChannelAnswer);
1828 assert_eq!(
1829 sub.to_event_string(),
1830 Some("CHANNEL_CREATE CHANNEL_ANSWER".to_string())
1831 );
1832 }
1833
1834 #[test]
1835 fn event_string_custom_only() {
1836 let sub = EventSubscription::new(EventFormat::Plain)
1837 .custom_subclass("sofia::register")
1838 .unwrap()
1839 .custom_subclass("sofia::unregister")
1840 .unwrap();
1841 assert_eq!(
1842 sub.to_event_string(),
1843 Some("CUSTOM sofia::register sofia::unregister".to_string())
1844 );
1845 }
1846
1847 #[test]
1848 fn event_string_mixed() {
1849 let sub = EventSubscription::new(EventFormat::Plain)
1850 .event(EslEventType::Heartbeat)
1851 .custom_subclass("sofia::register")
1852 .unwrap();
1853 assert_eq!(
1854 sub.to_event_string(),
1855 Some("HEARTBEAT CUSTOM sofia::register".to_string())
1856 );
1857 }
1858
1859 #[test]
1860 fn event_string_custom_not_duplicated() {
1861 let sub = EventSubscription::new(EventFormat::Plain)
1862 .event(EslEventType::Custom)
1863 .custom_subclass("sofia::register")
1864 .unwrap();
1865 assert_eq!(
1867 sub.to_event_string(),
1868 Some("CUSTOM sofia::register".to_string())
1869 );
1870 }
1871
1872 #[test]
1873 fn event_string_empty_is_none() {
1874 let sub = EventSubscription::new(EventFormat::Plain);
1875 assert_eq!(sub.to_event_string(), None);
1876 }
1877
1878 #[test]
1879 fn filters_preserve_order() {
1880 let sub = EventSubscription::new(EventFormat::Plain)
1881 .filter(EventHeader::CallDirection, "inbound")
1882 .unwrap()
1883 .filter_raw("X-Custom", "value1")
1884 .unwrap()
1885 .filter(EventHeader::ChannelState, "CS_EXECUTE")
1886 .unwrap();
1887 assert_eq!(
1888 sub.filters(),
1889 &[
1890 ("Call-Direction".to_string(), "inbound".to_string()),
1891 ("X-Custom".to_string(), "value1".to_string()),
1892 ("Channel-State".to_string(), "CS_EXECUTE".to_string()),
1893 ]
1894 );
1895 }
1896
1897 #[test]
1898 fn builder_chain() {
1899 let sub = EventSubscription::new(EventFormat::Plain)
1900 .events(EslEventType::CHANNEL_EVENTS)
1901 .event(EslEventType::Heartbeat)
1902 .custom_subclass("sofia::register")
1903 .unwrap()
1904 .filter(EventHeader::CallDirection, "inbound")
1905 .unwrap()
1906 .with_format(EventFormat::Json);
1907
1908 assert_eq!(sub.format(), EventFormat::Json);
1909 assert!(!sub.is_empty());
1910 assert!(!sub.is_all());
1911 assert!(sub
1912 .event_types()
1913 .contains(&EslEventType::ChannelCreate));
1914 assert!(sub
1915 .event_types()
1916 .contains(&EslEventType::Heartbeat));
1917 assert_eq!(sub.custom_subclass_list(), &["sofia::register"]);
1918 assert_eq!(
1919 sub.filters()
1920 .len(),
1921 1
1922 );
1923 }
1924
1925 #[test]
1926 fn serde_round_trip_subscription() {
1927 let sub = EventSubscription::new(EventFormat::Plain)
1928 .event(EslEventType::ChannelCreate)
1929 .event(EslEventType::Heartbeat)
1930 .custom_subclass("sofia::register")
1931 .unwrap()
1932 .filter(EventHeader::CallDirection, "inbound")
1933 .unwrap();
1934
1935 let json = serde_json::to_string(&sub).unwrap();
1936 let deserialized: EventSubscription = serde_json::from_str(&json).unwrap();
1937 assert_eq!(sub, deserialized);
1938 }
1939
1940 #[test]
1941 fn serde_rejects_invalid_subclass() {
1942 let json =
1943 r#"{"format":"Plain","events":[],"custom_subclasses":["bad subclass"],"filters":[]}"#;
1944 let result: Result<EventSubscription, _> = serde_json::from_str(json);
1945 assert!(result.is_err());
1946 let err = result
1947 .unwrap_err()
1948 .to_string();
1949 assert!(err.contains("space"), "error should mention space: {err}");
1950 }
1951
1952 #[test]
1953 fn serde_rejects_newline_in_filter() {
1954 let json = r#"{"format":"Plain","events":[],"custom_subclasses":[],"filters":[["Header","val\n"]]}"#;
1955 let result: Result<EventSubscription, _> = serde_json::from_str(json);
1956 assert!(result.is_err());
1957 let err = result
1958 .unwrap_err()
1959 .to_string();
1960 assert!(
1961 err.contains("newline"),
1962 "error should mention newline: {err}"
1963 );
1964 }
1965
1966 #[test]
1967 fn custom_subclass_rejects_space() {
1968 let result = EventSubscription::new(EventFormat::Plain).custom_subclass("bad subclass");
1969 assert!(result.is_err());
1970 }
1971
1972 #[test]
1973 fn custom_subclass_rejects_newline() {
1974 let result = EventSubscription::new(EventFormat::Plain).custom_subclass("bad\nsubclass");
1975 assert!(result.is_err());
1976 }
1977
1978 #[test]
1979 fn custom_subclass_rejects_empty() {
1980 let result = EventSubscription::new(EventFormat::Plain).custom_subclass("");
1981 assert!(result.is_err());
1982 }
1983
1984 #[test]
1985 fn filter_raw_rejects_newline_in_header() {
1986 let result = EventSubscription::new(EventFormat::Plain).filter_raw("Bad\nHeader", "value");
1987 assert!(result.is_err());
1988 }
1989
1990 #[test]
1991 fn filter_raw_rejects_newline_in_value() {
1992 let result = EventSubscription::new(EventFormat::Plain).filter_raw("Header", "bad\nvalue");
1993 assert!(result.is_err());
1994 }
1995
1996 #[test]
1997 fn filter_typed_rejects_newline_in_value() {
1998 let result = EventSubscription::new(EventFormat::Plain)
1999 .filter(EventHeader::CallDirection, "bad\nvalue");
2000 assert!(result.is_err());
2001 }
2002}