Skip to main content

freeswitch_types/
event.rs

1//! ESL event types and structures
2
3use 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/// Event format types supported by FreeSWITCH ESL
12#[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 text format (default)
17    Plain,
18    /// JSON format
19    Json,
20    /// XML format
21    Xml,
22}
23
24impl EventFormat {
25    /// Determine event format from a Content-Type header value.
26    ///
27    /// Returns `Err` for unrecognized content types to avoid silently
28    /// misparsing events if FreeSWITCH adds a new format.
29    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/// Error returned when parsing an invalid event format string.
66#[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
77/// Generates `EslEventType` enum with `Display`, `FromStr`, `as_str`, and `parse_event_type`.
78macro_rules! esl_event_types {
79    (
80        $(
81            $(#[$attr:meta])*
82            $variant:ident => $wire:literal
83        ),+ $(,)?
84        ;
85        // Extra variants not in the main match (after All)
86        $(
87            $(#[$extra_attr:meta])*
88            $extra_variant:ident => $extra_wire:literal
89        ),* $(,)?
90    ) => {
91        /// FreeSWITCH event types matching the canonical order from `esl_event.h`
92        /// and `switch_event.c` EVENT_NAMES[].
93        ///
94        /// Variant names are the canonical wire names (e.g. `ChannelCreate` = `CHANNEL_CREATE`).
95        #[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            /// Returns the canonical wire name as a static string slice.
118            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            /// Parse event type from wire name (canonical case).
126            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    /// Subscribe to all events
239    All => "ALL";
240    // --- Not in libs/esl/ EVENT_NAMES[], only in switch_event.c ---
241    // check-event-types.sh stops scanning at the All variant above.
242    /// Present in `switch_event.c` but not in `libs/esl/` EVENT_NAMES[].
243    StartRecording => "START_RECORDING",
244}
245
246// -- Event group constants --------------------------------------------------
247//
248// Predefined slices for common subscription patterns. Pass directly to
249// `EslClient::subscribe_events()`.
250//
251// MAINTENANCE: when adding new `EslEventType` variants, check whether they
252// belong in any of these groups and update accordingly.
253
254impl EslEventType {
255    /// Every `CHANNEL_*` event type.
256    ///
257    /// Covers the full channel lifecycle: creation, state changes, execution,
258    /// bridging, hold, park, progress, originate, and destruction.
259    ///
260    /// ```rust
261    /// use freeswitch_types::EslEventType;
262    /// assert!(EslEventType::CHANNEL_EVENTS.contains(&EslEventType::ChannelCreate));
263    /// assert!(EslEventType::CHANNEL_EVENTS.contains(&EslEventType::ChannelHangupComplete));
264    /// ```
265    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    /// In-call events: DTMF, VAD speech detection, media security, and call updates.
291    ///
292    /// Events that fire during an established call, tied to RTP/media activity
293    /// rather than signaling state transitions.
294    ///
295    /// ```rust
296    /// use freeswitch_types::EslEventType;
297    /// assert!(EslEventType::IN_CALL_EVENTS.contains(&EslEventType::Dtmf));
298    /// assert!(EslEventType::IN_CALL_EVENTS.contains(&EslEventType::Talk));
299    /// ```
300    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    /// Media-related events: playback, recording, media bugs, and detection.
311    ///
312    /// Useful for IVR applications that need to track media operations without
313    /// subscribing to the full channel lifecycle.
314    ///
315    /// ```rust
316    /// use freeswitch_types::EslEventType;
317    /// assert!(EslEventType::MEDIA_EVENTS.contains(&EslEventType::PlaybackStart));
318    /// assert!(EslEventType::MEDIA_EVENTS.contains(&EslEventType::DetectedSpeech));
319    /// ```
320    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    /// Presence and messaging events.
333    ///
334    /// For applications that track user presence (BLF, buddy lists) or
335    /// message-waiting indicators (voicemail MWI).
336    ///
337    /// ```rust
338    /// use freeswitch_types::EslEventType;
339    /// assert!(EslEventType::PRESENCE_EVENTS.contains(&EslEventType::PresenceIn));
340    /// assert!(EslEventType::PRESENCE_EVENTS.contains(&EslEventType::MessageWaiting));
341    /// ```
342    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    /// System lifecycle events.
352    ///
353    /// Server startup/shutdown, heartbeats, module loading, and XML reloads.
354    /// Useful for monitoring dashboards and operational tooling.
355    ///
356    /// ```rust
357    /// use freeswitch_types::EslEventType;
358    /// assert!(EslEventType::SYSTEM_EVENTS.contains(&EslEventType::Heartbeat));
359    /// assert!(EslEventType::SYSTEM_EVENTS.contains(&EslEventType::Shutdown));
360    /// ```
361    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    /// Conference-related events.
374    ///
375    /// ```rust
376    /// use freeswitch_types::EslEventType;
377    /// assert!(EslEventType::CONFERENCE_EVENTS.contains(&EslEventType::ConferenceData));
378    /// ```
379    pub const CONFERENCE_EVENTS: &[EslEventType] = &[
380        EslEventType::ConferenceDataQuery,
381        EslEventType::ConferenceData,
382    ];
383}
384
385/// Error returned when parsing an unknown event type string.
386#[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/// Error returned when an [`EventSubscription`] builder method receives invalid input.
398///
399/// Custom subclasses and filter values are validated against ESL wire-safety
400/// constraints: no newlines, carriage returns, or (for subclasses) spaces.
401#[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/// Declarative description of an ESL event subscription.
413///
414/// Captures the event format, event types, custom subclasses, and filters
415/// as a single unit. Useful for config-driven subscriptions and reconnection
416/// patterns where the caller needs to rebuild subscriptions from a saved
417/// description.
418///
419/// # Wire safety
420///
421/// Builder methods validate inputs against ESL wire injection risks.
422/// Custom subclasses reject `\n`, `\r`, spaces, and empty strings.
423/// Filter headers and values reject `\n` and `\r`.
424///
425/// # Example
426///
427/// ```rust
428/// use freeswitch_types::{EventSubscription, EventFormat, EslEventType, EventHeader};
429///
430/// let sub = EventSubscription::new(EventFormat::Plain)
431///     .events(EslEventType::CHANNEL_EVENTS)
432///     .event(EslEventType::Heartbeat)
433///     .custom_subclass("sofia::register").unwrap()
434///     .filter(EventHeader::CallDirection, "inbound").unwrap();
435///
436/// assert!(!sub.is_empty());
437/// assert!(!sub.is_all());
438/// ```
439#[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
448/// Validates that a custom subclass token is safe for ESL wire use.
449fn 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
470/// Validates that a filter header or value has no newline characters.
471fn 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    /// Create an empty subscription with the given format.
483    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    /// Create a subscription for all events.
493    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    /// Add a single event type.
503    pub fn event(mut self, event: EslEventType) -> Self {
504        self.events
505            .push(event);
506        self
507    }
508
509    /// Add multiple event types (e.g. from group constants like `EslEventType::CHANNEL_EVENTS`).
510    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    /// Add a custom subclass (e.g. `"sofia::register"`).
524    ///
525    /// Returns `Err` if the subclass contains spaces, newlines, or is empty.
526    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    /// Add multiple custom subclasses.
538    ///
539    /// Returns `Err` on the first invalid subclass.
540    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    /// Add a filter with a typed header.
554    ///
555    /// The header enum is always valid; only the value is validated.
556    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    /// Add a filter with raw header and value strings.
575    ///
576    /// Both header and value are validated against newline injection.
577    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    /// Change the event format.
593    pub fn with_format(mut self, format: EventFormat) -> Self {
594        self.format = format;
595        self
596    }
597
598    /// The event format.
599    pub fn format(&self) -> EventFormat {
600        self.format
601    }
602
603    /// Mutable reference to the event format.
604    pub fn format_mut(&mut self) -> &mut EventFormat {
605        &mut self.format
606    }
607
608    /// The subscribed event types.
609    pub fn event_types(&self) -> &[EslEventType] {
610        &self.events
611    }
612
613    /// Mutable access to the event types list.
614    pub fn event_types_mut(&mut self) -> &mut Vec<EslEventType> {
615        &mut self.events
616    }
617
618    /// The subscribed custom subclasses.
619    pub fn custom_subclass_list(&self) -> &[String] {
620        &self.custom_subclasses
621    }
622
623    /// Mutable access to the custom subclasses list.
624    pub fn custom_subclasses_mut(&mut self) -> &mut Vec<String> {
625        &mut self.custom_subclasses
626    }
627
628    /// The event filters as (header, value) pairs.
629    pub fn filters(&self) -> &[(String, String)] {
630        &self.filters
631    }
632
633    /// Mutable access to the filters list.
634    pub fn filters_mut(&mut self) -> &mut Vec<(String, String)> {
635        &mut self.filters
636    }
637
638    /// Whether the subscription includes all events.
639    pub fn is_all(&self) -> bool {
640        self.events
641            .contains(&EslEventType::All)
642    }
643
644    /// Whether the subscription has no events and no custom subclasses.
645    pub fn is_empty(&self) -> bool {
646        self.events
647            .is_empty()
648            && self
649                .custom_subclasses
650                .is_empty()
651    }
652
653    /// Build the event string for the ESL `event` command.
654    ///
655    /// Returns `None` if no events or custom subclasses are configured.
656    /// Returns `Some("ALL")` if `EslEventType::All` is present.
657    /// Otherwise returns space-separated event names with custom subclasses
658    /// appended after a `CUSTOM` token.
659    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/// Event priority levels matching FreeSWITCH `esl_priority_t`
759#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
760#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
761#[non_exhaustive]
762pub enum EslEventPriority {
763    /// Default priority.
764    Normal,
765    /// Lower than normal.
766    Low,
767    /// Higher than normal.
768    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/// Error returned when parsing an invalid priority string.
782#[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/// ESL Event structure containing headers and optional body
807#[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    /// Create a new empty event
819    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    /// Create event with specified type
829    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    /// Parsed event type, if recognized.
839    pub fn event_type(&self) -> Option<EslEventType> {
840        self.event_type
841    }
842
843    /// Override the event type.
844    pub fn set_event_type(&mut self, event_type: Option<EslEventType>) {
845        self.event_type = event_type;
846    }
847
848    /// Look up a header by its [`EventHeader`] enum variant (case-sensitive).
849    ///
850    /// For headers not covered by `EventHeader`, use [`header_str()`](Self::header_str).
851    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    /// Look up a header by name, trying the canonical key first then falling
858    /// back through the alias map for non-canonical lookups.
859    ///
860    /// Use [`header()`](Self::header) with an [`EventHeader`] variant for known
861    /// headers. This method is for headers not (yet) covered by the enum,
862    /// such as custom `X-` headers or FreeSWITCH headers added after this
863    /// library was published.
864    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    /// Look up a channel variable by its bare name.
879    ///
880    /// Equivalent to [`variable()`](Self::variable) but matches the
881    /// [`HeaderLookup`] trait signature.
882    pub fn variable_str(&self, name: &str) -> Option<&str> {
883        let key = format!("variable_{}", name);
884        self.header_str(&key)
885    }
886
887    /// All headers as a map.
888    pub fn headers(&self) -> &IndexMap<String, String> {
889        &self.headers
890    }
891
892    /// Set or overwrite a header, normalizing the key.
893    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    /// Remove a header, returning its value if it existed.
905    ///
906    /// Accepts both canonical and original (non-normalized) key names.
907    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    /// Event body (the content after the blank line in plain-text events).
927    pub fn body(&self) -> Option<&str> {
928        self.body
929            .as_deref()
930    }
931
932    /// Set the event body.
933    pub fn set_body(&mut self, body: impl Into<String>) {
934        self.body = Some(body.into());
935    }
936
937    /// Sets the `priority` header carried on the event.
938    ///
939    /// FreeSWITCH stores this as metadata but does **not** use it for dispatch
940    /// ordering — all events are delivered FIFO regardless of priority.
941    pub fn set_priority(&mut self, priority: EslEventPriority) {
942        self.set_header(EventHeader::Priority.as_str(), priority.to_string());
943    }
944
945    /// Append a value to a multi-value header (PUSH semantics).
946    ///
947    /// If the header doesn't exist, sets it as a plain value.
948    /// If it exists as a plain value, converts to `ARRAY::old|:new`.
949    /// If it already has an `ARRAY::` prefix, appends the new value.
950    ///
951    /// ```
952    /// # use freeswitch_types::EslEvent;
953    /// let mut event = EslEvent::new();
954    /// event.push_header("X-Test", "first");
955    /// event.push_header("X-Test", "second");
956    /// assert_eq!(event.header_str("X-Test"), Some("ARRAY::first|:second"));
957    /// ```
958    pub fn push_header(&mut self, name: &str, value: &str) {
959        self.stack_header(name, value, EslArray::push);
960    }
961
962    /// Prepend a value to a multi-value header (UNSHIFT semantics).
963    ///
964    /// Same conversion rules as `push_header()`, but inserts at the front.
965    ///
966    /// ```
967    /// # use freeswitch_types::EslEvent;
968    /// let mut event = EslEvent::new();
969    /// event.set_header("X-Test", "ARRAY::b|:c");
970    /// event.unshift_header("X-Test", "a");
971    /// assert_eq!(event.header_str("X-Test"), Some("ARRAY::a|:b|:c"));
972    /// ```
973    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    /// Check whether this event matches the given type.
997    pub fn is_event_type(&self, event_type: EslEventType) -> bool {
998        self.event_type == Some(event_type)
999    }
1000
1001    /// Serialize to ESL plain text wire format with percent-encoded header values.
1002    ///
1003    /// This is the inverse of `EslParser::parse_plain_event()`. The output can
1004    /// be fed back through the parser to reconstruct an equivalent `EslEvent`
1005    /// (round-trip).
1006    ///
1007    /// Headers are emitted in insertion order (which matches wire order when the
1008    /// event was parsed from the network). `Content-Length` from stored headers
1009    /// is skipped and recomputed from the body if present.
1010    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        // Simulate what EslParser::parse_plain_event does
1271        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    // --- EslEvent accessor tests (via HeaderLookup trait) ---
1480
1481    #[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    // --- Repeating SIP header tests ---
1582
1583    #[test]
1584    fn test_sip_p_asserted_identity_comma_separated() {
1585        let mut event = EslEvent::new();
1586        // RFC 3325: P-Asserted-Identity can carry two identities (one sip:, one tel:)
1587        // FreeSWITCH stores the comma-separated value as a single channel variable
1588        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        // When FreeSWITCH stores repeated SIP headers via ARRAY format
1603        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        // SIP URIs contain colons (sip:, sips:) which must not confuse ARRAY parsing
1627        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        // The comma-separated value should be percent-encoded on the wire
1662        assert!(plain.contains("variable_sip_P-Asserted-Identity:"));
1663        // Angle brackets and comma should be encoded
1664        assert!(!plain.contains("<sip:alice"));
1665    }
1666
1667    // --- Header key normalization on EslEvent ---
1668    // set_header() normalizes keys so lookups via header(EventHeader::X)
1669    // and header_str() work regardless of the casing used at insertion.
1670
1671    #[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        // Lookup by original non-canonical key should still work
1693        assert_eq!(event.header_str("unique-id"), Some("abc-123"));
1694        // Lookup by canonical key also works
1695        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        // Stored as Title-Case
1703        assert_eq!(event.header_str("X-Custom-Header"), Some("val"));
1704        // Original key also works via alias
1705        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        // Both normalize to "Unique-ID", second overwrites first
1724        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    // --- EventSubscription tests ---
1797
1798    #[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        // Should not have "CUSTOM" twice
1866        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}