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 percent_encoding::{percent_encode, NON_ALPHANUMERIC};
7use serde::{Deserialize, Serialize};
8use std::collections::HashMap;
9use std::fmt;
10use std::str::FromStr;
11
12/// Event format types supported by FreeSWITCH ESL
13#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, 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, Serialize, Deserialize)]
96        #[non_exhaustive]
97        #[allow(missing_docs)]
98        pub enum EslEventType {
99            $(
100                $(#[$attr])*
101                $variant,
102            )+
103            $(
104                $(#[$extra_attr])*
105                $extra_variant,
106            )*
107        }
108
109        impl fmt::Display for EslEventType {
110            fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
111                f.write_str(self.as_str())
112            }
113        }
114
115        impl EslEventType {
116            /// Returns the canonical wire name as a static string slice.
117            pub const fn as_str(&self) -> &'static str {
118                match self {
119                    $( EslEventType::$variant => $wire, )+
120                    $( EslEventType::$extra_variant => $extra_wire, )*
121                }
122            }
123
124            /// Parse event type from wire name (canonical case).
125            pub fn parse_event_type(s: &str) -> Option<Self> {
126                match s {
127                    $( $wire => Some(EslEventType::$variant), )+
128                    $( $extra_wire => Some(EslEventType::$extra_variant), )*
129                    _ => None,
130                }
131            }
132        }
133
134        impl FromStr for EslEventType {
135            type Err = ParseEventTypeError;
136
137            fn from_str(s: &str) -> Result<Self, Self::Err> {
138                Self::parse_event_type(s).ok_or_else(|| ParseEventTypeError(s.to_string()))
139            }
140        }
141    };
142}
143
144esl_event_types! {
145    Custom => "CUSTOM",
146    Clone => "CLONE",
147    ChannelCreate => "CHANNEL_CREATE",
148    ChannelDestroy => "CHANNEL_DESTROY",
149    ChannelState => "CHANNEL_STATE",
150    ChannelCallstate => "CHANNEL_CALLSTATE",
151    ChannelAnswer => "CHANNEL_ANSWER",
152    ChannelHangup => "CHANNEL_HANGUP",
153    ChannelHangupComplete => "CHANNEL_HANGUP_COMPLETE",
154    ChannelExecute => "CHANNEL_EXECUTE",
155    ChannelExecuteComplete => "CHANNEL_EXECUTE_COMPLETE",
156    ChannelHold => "CHANNEL_HOLD",
157    ChannelUnhold => "CHANNEL_UNHOLD",
158    ChannelBridge => "CHANNEL_BRIDGE",
159    ChannelUnbridge => "CHANNEL_UNBRIDGE",
160    ChannelProgress => "CHANNEL_PROGRESS",
161    ChannelProgressMedia => "CHANNEL_PROGRESS_MEDIA",
162    ChannelOutgoing => "CHANNEL_OUTGOING",
163    ChannelPark => "CHANNEL_PARK",
164    ChannelUnpark => "CHANNEL_UNPARK",
165    ChannelApplication => "CHANNEL_APPLICATION",
166    ChannelOriginate => "CHANNEL_ORIGINATE",
167    ChannelUuid => "CHANNEL_UUID",
168    Api => "API",
169    Log => "LOG",
170    InboundChan => "INBOUND_CHAN",
171    OutboundChan => "OUTBOUND_CHAN",
172    Startup => "STARTUP",
173    Shutdown => "SHUTDOWN",
174    Publish => "PUBLISH",
175    Unpublish => "UNPUBLISH",
176    Talk => "TALK",
177    Notalk => "NOTALK",
178    SessionCrash => "SESSION_CRASH",
179    ModuleLoad => "MODULE_LOAD",
180    ModuleUnload => "MODULE_UNLOAD",
181    Dtmf => "DTMF",
182    Message => "MESSAGE",
183    PresenceIn => "PRESENCE_IN",
184    NotifyIn => "NOTIFY_IN",
185    PresenceOut => "PRESENCE_OUT",
186    PresenceProbe => "PRESENCE_PROBE",
187    MessageWaiting => "MESSAGE_WAITING",
188    MessageQuery => "MESSAGE_QUERY",
189    Roster => "ROSTER",
190    Codec => "CODEC",
191    BackgroundJob => "BACKGROUND_JOB",
192    DetectedSpeech => "DETECTED_SPEECH",
193    DetectedTone => "DETECTED_TONE",
194    PrivateCommand => "PRIVATE_COMMAND",
195    Heartbeat => "HEARTBEAT",
196    Trap => "TRAP",
197    AddSchedule => "ADD_SCHEDULE",
198    DelSchedule => "DEL_SCHEDULE",
199    ExeSchedule => "EXE_SCHEDULE",
200    ReSchedule => "RE_SCHEDULE",
201    ReloadXml => "RELOADXML",
202    Notify => "NOTIFY",
203    PhoneFeature => "PHONE_FEATURE",
204    PhoneFeatureSubscribe => "PHONE_FEATURE_SUBSCRIBE",
205    SendMessage => "SEND_MESSAGE",
206    RecvMessage => "RECV_MESSAGE",
207    RequestParams => "REQUEST_PARAMS",
208    ChannelData => "CHANNEL_DATA",
209    General => "GENERAL",
210    Command => "COMMAND",
211    SessionHeartbeat => "SESSION_HEARTBEAT",
212    ClientDisconnected => "CLIENT_DISCONNECTED",
213    ServerDisconnected => "SERVER_DISCONNECTED",
214    SendInfo => "SEND_INFO",
215    RecvInfo => "RECV_INFO",
216    RecvRtcpMessage => "RECV_RTCP_MESSAGE",
217    SendRtcpMessage => "SEND_RTCP_MESSAGE",
218    CallSecure => "CALL_SECURE",
219    Nat => "NAT",
220    RecordStart => "RECORD_START",
221    RecordStop => "RECORD_STOP",
222    PlaybackStart => "PLAYBACK_START",
223    PlaybackStop => "PLAYBACK_STOP",
224    CallUpdate => "CALL_UPDATE",
225    Failure => "FAILURE",
226    SocketData => "SOCKET_DATA",
227    MediaBugStart => "MEDIA_BUG_START",
228    MediaBugStop => "MEDIA_BUG_STOP",
229    ConferenceDataQuery => "CONFERENCE_DATA_QUERY",
230    ConferenceData => "CONFERENCE_DATA",
231    CallSetupReq => "CALL_SETUP_REQ",
232    CallSetupResult => "CALL_SETUP_RESULT",
233    CallDetail => "CALL_DETAIL",
234    DeviceState => "DEVICE_STATE",
235    Text => "TEXT",
236    ShutdownRequested => "SHUTDOWN_REQUESTED",
237    /// Subscribe to all events
238    All => "ALL";
239    // --- Not in libs/esl/ EVENT_NAMES[], only in switch_event.c ---
240    // check-event-types.sh stops scanning at the All variant above.
241    /// Present in `switch_event.c` but not in `libs/esl/` EVENT_NAMES[].
242    StartRecording => "START_RECORDING",
243}
244
245// -- Event group constants --------------------------------------------------
246//
247// Predefined slices for common subscription patterns. Pass directly to
248// `EslClient::subscribe_events()`.
249//
250// MAINTENANCE: when adding new `EslEventType` variants, check whether they
251// belong in any of these groups and update accordingly.
252
253impl EslEventType {
254    /// Every `CHANNEL_*` event type.
255    ///
256    /// Covers the full channel lifecycle: creation, state changes, execution,
257    /// bridging, hold, park, progress, originate, and destruction.
258    ///
259    /// ```rust
260    /// use freeswitch_types::EslEventType;
261    /// assert!(EslEventType::CHANNEL_EVENTS.contains(&EslEventType::ChannelCreate));
262    /// assert!(EslEventType::CHANNEL_EVENTS.contains(&EslEventType::ChannelHangupComplete));
263    /// ```
264    pub const CHANNEL_EVENTS: &[EslEventType] = &[
265        EslEventType::ChannelCreate,
266        EslEventType::ChannelDestroy,
267        EslEventType::ChannelState,
268        EslEventType::ChannelCallstate,
269        EslEventType::ChannelAnswer,
270        EslEventType::ChannelHangup,
271        EslEventType::ChannelHangupComplete,
272        EslEventType::ChannelExecute,
273        EslEventType::ChannelExecuteComplete,
274        EslEventType::ChannelHold,
275        EslEventType::ChannelUnhold,
276        EslEventType::ChannelBridge,
277        EslEventType::ChannelUnbridge,
278        EslEventType::ChannelProgress,
279        EslEventType::ChannelProgressMedia,
280        EslEventType::ChannelOutgoing,
281        EslEventType::ChannelPark,
282        EslEventType::ChannelUnpark,
283        EslEventType::ChannelApplication,
284        EslEventType::ChannelOriginate,
285        EslEventType::ChannelUuid,
286        EslEventType::ChannelData,
287    ];
288
289    /// In-call events: DTMF, VAD speech detection, media security, and call updates.
290    ///
291    /// Events that fire during an established call, tied to RTP/media activity
292    /// rather than signaling state transitions.
293    ///
294    /// ```rust
295    /// use freeswitch_types::EslEventType;
296    /// assert!(EslEventType::IN_CALL_EVENTS.contains(&EslEventType::Dtmf));
297    /// assert!(EslEventType::IN_CALL_EVENTS.contains(&EslEventType::Talk));
298    /// ```
299    pub const IN_CALL_EVENTS: &[EslEventType] = &[
300        EslEventType::Dtmf,
301        EslEventType::Talk,
302        EslEventType::Notalk,
303        EslEventType::CallSecure,
304        EslEventType::CallUpdate,
305        EslEventType::RecvRtcpMessage,
306        EslEventType::SendRtcpMessage,
307    ];
308
309    /// Media-related events: playback, recording, media bugs, and detection.
310    ///
311    /// Useful for IVR applications that need to track media operations without
312    /// subscribing to the full channel lifecycle.
313    ///
314    /// ```rust
315    /// use freeswitch_types::EslEventType;
316    /// assert!(EslEventType::MEDIA_EVENTS.contains(&EslEventType::PlaybackStart));
317    /// assert!(EslEventType::MEDIA_EVENTS.contains(&EslEventType::DetectedSpeech));
318    /// ```
319    pub const MEDIA_EVENTS: &[EslEventType] = &[
320        EslEventType::PlaybackStart,
321        EslEventType::PlaybackStop,
322        EslEventType::RecordStart,
323        EslEventType::RecordStop,
324        EslEventType::StartRecording,
325        EslEventType::MediaBugStart,
326        EslEventType::MediaBugStop,
327        EslEventType::DetectedSpeech,
328        EslEventType::DetectedTone,
329    ];
330
331    /// Presence and messaging events.
332    ///
333    /// For applications that track user presence (BLF, buddy lists) or
334    /// message-waiting indicators (voicemail MWI).
335    ///
336    /// ```rust
337    /// use freeswitch_types::EslEventType;
338    /// assert!(EslEventType::PRESENCE_EVENTS.contains(&EslEventType::PresenceIn));
339    /// assert!(EslEventType::PRESENCE_EVENTS.contains(&EslEventType::MessageWaiting));
340    /// ```
341    pub const PRESENCE_EVENTS: &[EslEventType] = &[
342        EslEventType::PresenceIn,
343        EslEventType::PresenceOut,
344        EslEventType::PresenceProbe,
345        EslEventType::MessageWaiting,
346        EslEventType::MessageQuery,
347        EslEventType::Roster,
348    ];
349
350    /// System lifecycle events.
351    ///
352    /// Server startup/shutdown, heartbeats, module loading, and XML reloads.
353    /// Useful for monitoring dashboards and operational tooling.
354    ///
355    /// ```rust
356    /// use freeswitch_types::EslEventType;
357    /// assert!(EslEventType::SYSTEM_EVENTS.contains(&EslEventType::Heartbeat));
358    /// assert!(EslEventType::SYSTEM_EVENTS.contains(&EslEventType::Shutdown));
359    /// ```
360    pub const SYSTEM_EVENTS: &[EslEventType] = &[
361        EslEventType::Startup,
362        EslEventType::Shutdown,
363        EslEventType::ShutdownRequested,
364        EslEventType::Heartbeat,
365        EslEventType::SessionHeartbeat,
366        EslEventType::SessionCrash,
367        EslEventType::ModuleLoad,
368        EslEventType::ModuleUnload,
369        EslEventType::ReloadXml,
370    ];
371
372    /// Conference-related events.
373    ///
374    /// ```rust
375    /// use freeswitch_types::EslEventType;
376    /// assert!(EslEventType::CONFERENCE_EVENTS.contains(&EslEventType::ConferenceData));
377    /// ```
378    pub const CONFERENCE_EVENTS: &[EslEventType] = &[
379        EslEventType::ConferenceDataQuery,
380        EslEventType::ConferenceData,
381    ];
382}
383
384/// Error returned when parsing an unknown event type string.
385#[derive(Debug, Clone, PartialEq, Eq)]
386pub struct ParseEventTypeError(pub String);
387
388impl fmt::Display for ParseEventTypeError {
389    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
390        write!(f, "unknown event type: {}", self.0)
391    }
392}
393
394impl std::error::Error for ParseEventTypeError {}
395
396/// Event priority levels matching FreeSWITCH `esl_priority_t`
397#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
398#[non_exhaustive]
399pub enum EslEventPriority {
400    /// Default priority.
401    Normal,
402    /// Lower than normal.
403    Low,
404    /// Higher than normal.
405    High,
406}
407
408impl fmt::Display for EslEventPriority {
409    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
410        match self {
411            EslEventPriority::Normal => write!(f, "NORMAL"),
412            EslEventPriority::Low => write!(f, "LOW"),
413            EslEventPriority::High => write!(f, "HIGH"),
414        }
415    }
416}
417
418/// Error returned when parsing an invalid priority string.
419#[derive(Debug, Clone, PartialEq, Eq)]
420pub struct ParsePriorityError(pub String);
421
422impl fmt::Display for ParsePriorityError {
423    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
424        write!(f, "unknown priority: {}", self.0)
425    }
426}
427
428impl std::error::Error for ParsePriorityError {}
429
430impl FromStr for EslEventPriority {
431    type Err = ParsePriorityError;
432
433    fn from_str(s: &str) -> Result<Self, Self::Err> {
434        match s {
435            "NORMAL" => Ok(EslEventPriority::Normal),
436            "LOW" => Ok(EslEventPriority::Low),
437            "HIGH" => Ok(EslEventPriority::High),
438            _ => Err(ParsePriorityError(s.to_string())),
439        }
440    }
441}
442
443/// ESL Event structure containing headers and optional body
444#[derive(Debug, Clone, Eq, Serialize)]
445pub struct EslEvent {
446    event_type: Option<EslEventType>,
447    headers: HashMap<String, String>,
448    #[serde(skip)]
449    original_keys: HashMap<String, String>,
450    body: Option<String>,
451}
452
453impl EslEvent {
454    /// Create a new empty event
455    pub fn new() -> Self {
456        Self {
457            event_type: None,
458            headers: HashMap::new(),
459            original_keys: HashMap::new(),
460            body: None,
461        }
462    }
463
464    /// Create event with specified type
465    pub fn with_type(event_type: EslEventType) -> Self {
466        Self {
467            event_type: Some(event_type),
468            headers: HashMap::new(),
469            original_keys: HashMap::new(),
470            body: None,
471        }
472    }
473
474    /// Parsed event type, if recognized.
475    pub fn event_type(&self) -> Option<EslEventType> {
476        self.event_type
477    }
478
479    /// Override the event type.
480    pub fn set_event_type(&mut self, event_type: Option<EslEventType>) {
481        self.event_type = event_type;
482    }
483
484    /// Look up a header by its [`EventHeader`] enum variant (case-sensitive).
485    ///
486    /// For headers not covered by `EventHeader`, use [`header_str()`](Self::header_str).
487    pub fn header(&self, name: EventHeader) -> Option<&str> {
488        self.headers
489            .get(name.as_str())
490            .map(|s| s.as_str())
491    }
492
493    /// Look up a header by name, trying the canonical key first then falling
494    /// back through the alias map for non-canonical lookups.
495    ///
496    /// Use [`header()`](Self::header) with an [`EventHeader`] variant for known
497    /// headers. This method is for headers not (yet) covered by the enum,
498    /// such as custom `X-` headers or FreeSWITCH headers added after this
499    /// library was published.
500    pub fn header_str(&self, name: &str) -> Option<&str> {
501        self.headers
502            .get(name)
503            .or_else(|| {
504                self.original_keys
505                    .get(name)
506                    .and_then(|normalized| {
507                        self.headers
508                            .get(normalized)
509                    })
510            })
511            .map(|s| s.as_str())
512    }
513
514    /// Look up a channel variable by its bare name.
515    ///
516    /// Equivalent to [`variable()`](Self::variable) but matches the
517    /// [`HeaderLookup`] trait signature.
518    pub fn variable_str(&self, name: &str) -> Option<&str> {
519        let key = format!("variable_{}", name);
520        self.header_str(&key)
521    }
522
523    /// All headers as a map.
524    pub fn headers(&self) -> &HashMap<String, String> {
525        &self.headers
526    }
527
528    /// Set or overwrite a header, normalizing the key.
529    pub fn set_header(&mut self, name: impl Into<String>, value: impl Into<String>) {
530        let original = name.into();
531        let normalized = normalize_header_key(&original);
532        if original != normalized {
533            self.original_keys
534                .insert(original, normalized.clone());
535        }
536        self.headers
537            .insert(normalized, value.into());
538    }
539
540    /// Remove a header, returning its value if it existed.
541    ///
542    /// Accepts both canonical and original (non-normalized) key names.
543    pub fn remove_header(&mut self, name: impl AsRef<str>) -> Option<String> {
544        let name = name.as_ref();
545        if let Some(value) = self
546            .headers
547            .remove(name)
548        {
549            return Some(value);
550        }
551        if let Some(normalized) = self
552            .original_keys
553            .remove(name)
554        {
555            return self
556                .headers
557                .remove(&normalized);
558        }
559        None
560    }
561
562    /// Event body (the content after the blank line in plain-text events).
563    pub fn body(&self) -> Option<&str> {
564        self.body
565            .as_deref()
566    }
567
568    /// Set the event body.
569    pub fn set_body(&mut self, body: impl Into<String>) {
570        self.body = Some(body.into());
571    }
572
573    /// Sets the `priority` header carried on the event.
574    ///
575    /// FreeSWITCH stores this as metadata but does **not** use it for dispatch
576    /// ordering — all events are delivered FIFO regardless of priority.
577    pub fn set_priority(&mut self, priority: EslEventPriority) {
578        self.set_header(EventHeader::Priority.as_str(), priority.to_string());
579    }
580
581    /// Append a value to a multi-value header (PUSH semantics).
582    ///
583    /// If the header doesn't exist, sets it as a plain value.
584    /// If it exists as a plain value, converts to `ARRAY::old|:new`.
585    /// If it already has an `ARRAY::` prefix, appends the new value.
586    ///
587    /// ```
588    /// # use freeswitch_types::EslEvent;
589    /// let mut event = EslEvent::new();
590    /// event.push_header("X-Test", "first");
591    /// event.push_header("X-Test", "second");
592    /// assert_eq!(event.header_str("X-Test"), Some("ARRAY::first|:second"));
593    /// ```
594    pub fn push_header(&mut self, name: &str, value: &str) {
595        self.stack_header(name, value, EslArray::push);
596    }
597
598    /// Prepend a value to a multi-value header (UNSHIFT semantics).
599    ///
600    /// Same conversion rules as `push_header()`, but inserts at the front.
601    ///
602    /// ```
603    /// # use freeswitch_types::EslEvent;
604    /// let mut event = EslEvent::new();
605    /// event.set_header("X-Test", "ARRAY::b|:c");
606    /// event.unshift_header("X-Test", "a");
607    /// assert_eq!(event.header_str("X-Test"), Some("ARRAY::a|:b|:c"));
608    /// ```
609    pub fn unshift_header(&mut self, name: &str, value: &str) {
610        self.stack_header(name, value, EslArray::unshift);
611    }
612
613    fn stack_header(&mut self, name: &str, value: &str, op: fn(&mut EslArray, String)) {
614        match self
615            .headers
616            .get(name)
617        {
618            None => {
619                self.set_header(name, value);
620            }
621            Some(existing) => {
622                let mut arr = match EslArray::parse(existing) {
623                    Some(arr) => arr,
624                    None => EslArray::new(vec![existing.clone()]),
625                };
626                op(&mut arr, value.into());
627                self.set_header(name, arr.to_string());
628            }
629        }
630    }
631
632    /// Check whether this event matches the given type.
633    pub fn is_event_type(&self, event_type: EslEventType) -> bool {
634        self.event_type == Some(event_type)
635    }
636
637    /// Serialize to ESL plain text wire format with percent-encoded header values.
638    ///
639    /// This is the inverse of `EslParser::parse_plain_event()`. The output can
640    /// be fed back through the parser to reconstruct an equivalent `EslEvent`
641    /// (round-trip).
642    ///
643    /// `Event-Name` is emitted first, remaining headers are sorted alphabetically
644    /// for deterministic output. `Content-Length` from stored headers is skipped
645    /// and recomputed from the body if present.
646    pub fn to_plain_format(&self) -> String {
647        use std::fmt::Write;
648        let mut result = String::new();
649
650        let event_name_key = EventHeader::EventName.as_str();
651        if let Some(event_name) = self
652            .headers
653            .get(event_name_key)
654        {
655            let _ = writeln!(
656                result,
657                "{}: {}",
658                event_name_key,
659                percent_encode(event_name.as_bytes(), NON_ALPHANUMERIC)
660            );
661        }
662
663        let mut sorted_headers: Vec<_> = self
664            .headers
665            .iter()
666            .filter(|(k, _)| k.as_str() != event_name_key && k.as_str() != "Content-Length")
667            .collect();
668        sorted_headers.sort_by_key(|(k, _)| k.as_str());
669
670        for (key, value) in sorted_headers {
671            let _ = writeln!(
672                result,
673                "{}: {}",
674                key,
675                percent_encode(value.as_bytes(), NON_ALPHANUMERIC)
676            );
677        }
678
679        if let Some(body) = &self.body {
680            let _ = writeln!(result, "Content-Length: {}", body.len());
681            result.push('\n');
682            result.push_str(body);
683        } else {
684            result.push('\n');
685        }
686
687        result
688    }
689}
690
691impl Default for EslEvent {
692    fn default() -> Self {
693        Self::new()
694    }
695}
696
697impl HeaderLookup for EslEvent {
698    fn header_str(&self, name: &str) -> Option<&str> {
699        EslEvent::header_str(self, name)
700    }
701
702    fn variable_str(&self, name: &str) -> Option<&str> {
703        let key = format!("variable_{}", name);
704        self.header_str(&key)
705    }
706}
707
708impl PartialEq for EslEvent {
709    fn eq(&self, other: &Self) -> bool {
710        self.event_type == other.event_type
711            && self.headers == other.headers
712            && self.body == other.body
713    }
714}
715
716impl<'de> Deserialize<'de> for EslEvent {
717    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
718    where
719        D: serde::Deserializer<'de>,
720    {
721        #[derive(Deserialize)]
722        struct Raw {
723            event_type: Option<EslEventType>,
724            headers: HashMap<String, String>,
725            body: Option<String>,
726        }
727        let raw = Raw::deserialize(deserializer)?;
728        let mut event = EslEvent::new();
729        event.event_type = raw.event_type;
730        event.body = raw.body;
731        for (k, v) in raw.headers {
732            event.set_header(k, v);
733        }
734        Ok(event)
735    }
736}
737
738#[cfg(test)]
739mod tests {
740    use super::*;
741
742    #[test]
743    fn test_notify_in_parse() {
744        assert_eq!(
745            EslEventType::parse_event_type("NOTIFY_IN"),
746            Some(EslEventType::NotifyIn)
747        );
748        assert_eq!(EslEventType::parse_event_type("notify_in"), None);
749    }
750
751    #[test]
752    fn test_notify_in_display() {
753        assert_eq!(EslEventType::NotifyIn.to_string(), "NOTIFY_IN");
754    }
755
756    #[test]
757    fn test_notify_in_distinct_from_notify() {
758        assert_ne!(EslEventType::Notify, EslEventType::NotifyIn);
759        assert_ne!(
760            EslEventType::Notify.to_string(),
761            EslEventType::NotifyIn.to_string()
762        );
763    }
764
765    #[test]
766    fn test_wire_names_match_c_esl() {
767        assert_eq!(
768            EslEventType::ChannelOutgoing.to_string(),
769            "CHANNEL_OUTGOING"
770        );
771        assert_eq!(EslEventType::Api.to_string(), "API");
772        assert_eq!(EslEventType::ReloadXml.to_string(), "RELOADXML");
773        assert_eq!(EslEventType::PresenceIn.to_string(), "PRESENCE_IN");
774        assert_eq!(EslEventType::Roster.to_string(), "ROSTER");
775        assert_eq!(EslEventType::Text.to_string(), "TEXT");
776        assert_eq!(EslEventType::ReSchedule.to_string(), "RE_SCHEDULE");
777
778        assert_eq!(
779            EslEventType::parse_event_type("CHANNEL_OUTGOING"),
780            Some(EslEventType::ChannelOutgoing)
781        );
782        assert_eq!(
783            EslEventType::parse_event_type("API"),
784            Some(EslEventType::Api)
785        );
786        assert_eq!(
787            EslEventType::parse_event_type("RELOADXML"),
788            Some(EslEventType::ReloadXml)
789        );
790        assert_eq!(
791            EslEventType::parse_event_type("PRESENCE_IN"),
792            Some(EslEventType::PresenceIn)
793        );
794    }
795
796    #[test]
797    fn test_event_type_from_str() {
798        assert_eq!(
799            "CHANNEL_ANSWER".parse::<EslEventType>(),
800            Ok(EslEventType::ChannelAnswer)
801        );
802        assert!("channel_answer"
803            .parse::<EslEventType>()
804            .is_err());
805        assert!("UNKNOWN_EVENT"
806            .parse::<EslEventType>()
807            .is_err());
808    }
809
810    #[test]
811    fn test_remove_header() {
812        let mut event = EslEvent::new();
813        event.set_header("Foo", "bar");
814        event.set_header("Baz", "qux");
815
816        let removed = event.remove_header("Foo");
817        assert_eq!(removed, Some("bar".to_string()));
818        assert!(event
819            .header_str("Foo")
820            .is_none());
821        assert_eq!(event.header_str("Baz"), Some("qux"));
822
823        let removed_again = event.remove_header("Foo");
824        assert_eq!(removed_again, None);
825    }
826
827    #[test]
828    fn test_to_plain_format_basic() {
829        let mut event = EslEvent::with_type(EslEventType::Heartbeat);
830        event.set_header("Event-Name", "HEARTBEAT");
831        event.set_header("Core-UUID", "abc-123");
832
833        let plain = event.to_plain_format();
834
835        assert!(plain.starts_with("Event-Name: "));
836        assert!(plain.contains("Core-UUID: "));
837        assert!(plain.ends_with("\n\n"));
838    }
839
840    #[test]
841    fn test_to_plain_format_percent_encoding() {
842        let mut event = EslEvent::with_type(EslEventType::Heartbeat);
843        event.set_header("Event-Name", "HEARTBEAT");
844        event.set_header("Up-Time", "0 years, 0 days");
845
846        let plain = event.to_plain_format();
847
848        assert!(!plain.contains("0 years, 0 days"));
849        assert!(plain.contains("Up-Time: "));
850        assert!(plain.contains("%20"));
851    }
852
853    #[test]
854    fn test_to_plain_format_with_body() {
855        let mut event = EslEvent::with_type(EslEventType::BackgroundJob);
856        event.set_header("Event-Name", "BACKGROUND_JOB");
857        event.set_header("Job-UUID", "def-456");
858        event.set_body("+OK result\n".to_string());
859
860        let plain = event.to_plain_format();
861
862        assert!(plain.contains("Content-Length: 11\n"));
863        assert!(plain.ends_with("\n\n+OK result\n"));
864    }
865
866    #[test]
867    fn test_set_priority_normal() {
868        let mut event = EslEvent::new();
869        event.set_priority(EslEventPriority::Normal);
870        assert_eq!(
871            event
872                .priority()
873                .unwrap(),
874            Some(EslEventPriority::Normal)
875        );
876        assert_eq!(event.header(EventHeader::Priority), Some("NORMAL"));
877    }
878
879    #[test]
880    fn test_set_priority_high() {
881        let mut event = EslEvent::new();
882        event.set_priority(EslEventPriority::High);
883        assert_eq!(
884            event
885                .priority()
886                .unwrap(),
887            Some(EslEventPriority::High)
888        );
889        assert_eq!(event.header(EventHeader::Priority), Some("HIGH"));
890    }
891
892    #[test]
893    fn test_priority_display() {
894        assert_eq!(EslEventPriority::Normal.to_string(), "NORMAL");
895        assert_eq!(EslEventPriority::Low.to_string(), "LOW");
896        assert_eq!(EslEventPriority::High.to_string(), "HIGH");
897    }
898
899    #[test]
900    fn test_priority_from_str() {
901        assert_eq!(
902            "NORMAL".parse::<EslEventPriority>(),
903            Ok(EslEventPriority::Normal)
904        );
905        assert_eq!("LOW".parse::<EslEventPriority>(), Ok(EslEventPriority::Low));
906        assert_eq!(
907            "HIGH".parse::<EslEventPriority>(),
908            Ok(EslEventPriority::High)
909        );
910        assert!("INVALID"
911            .parse::<EslEventPriority>()
912            .is_err());
913    }
914
915    #[test]
916    fn test_priority_from_str_rejects_wrong_case() {
917        assert!("normal"
918            .parse::<EslEventPriority>()
919            .is_err());
920        assert!("Low"
921            .parse::<EslEventPriority>()
922            .is_err());
923        assert!("hIgH"
924            .parse::<EslEventPriority>()
925            .is_err());
926    }
927
928    #[test]
929    fn test_push_header_new() {
930        let mut event = EslEvent::new();
931        event.push_header("X-Test", "first");
932        assert_eq!(event.header_str("X-Test"), Some("first"));
933    }
934
935    #[test]
936    fn test_push_header_existing_plain() {
937        let mut event = EslEvent::new();
938        event.set_header("X-Test", "first");
939        event.push_header("X-Test", "second");
940        assert_eq!(event.header_str("X-Test"), Some("ARRAY::first|:second"));
941    }
942
943    #[test]
944    fn test_push_header_existing_array() {
945        let mut event = EslEvent::new();
946        event.set_header("X-Test", "ARRAY::a|:b");
947        event.push_header("X-Test", "c");
948        assert_eq!(event.header_str("X-Test"), Some("ARRAY::a|:b|:c"));
949    }
950
951    #[test]
952    fn test_unshift_header_new() {
953        let mut event = EslEvent::new();
954        event.unshift_header("X-Test", "only");
955        assert_eq!(event.header_str("X-Test"), Some("only"));
956    }
957
958    #[test]
959    fn test_unshift_header_existing_array() {
960        let mut event = EslEvent::new();
961        event.set_header("X-Test", "ARRAY::b|:c");
962        event.unshift_header("X-Test", "a");
963        assert_eq!(event.header_str("X-Test"), Some("ARRAY::a|:b|:c"));
964    }
965
966    #[test]
967    fn test_sendevent_with_priority_wire_format() {
968        let mut event = EslEvent::with_type(EslEventType::Custom);
969        event.set_header("Event-Name", "CUSTOM");
970        event.set_header("Event-Subclass", "test::priority");
971        event.set_priority(EslEventPriority::High);
972
973        let plain = event.to_plain_format();
974        assert!(plain.contains("priority: HIGH\n"));
975    }
976
977    #[test]
978    fn test_convenience_accessors() {
979        let mut event = EslEvent::new();
980        event.set_header("Channel-Name", "sofia/internal/1000@example.com");
981        event.set_header("Caller-Caller-ID-Number", "1000");
982        event.set_header("Caller-Caller-ID-Name", "Alice");
983        event.set_header("Hangup-Cause", "NORMAL_CLEARING");
984        event.set_header("Event-Subclass", "sofia::register");
985        event.set_header("variable_sip_from_display", "Bob");
986
987        assert_eq!(
988            event.channel_name(),
989            Some("sofia/internal/1000@example.com")
990        );
991        assert_eq!(event.caller_id_number(), Some("1000"));
992        assert_eq!(event.caller_id_name(), Some("Alice"));
993        assert_eq!(
994            event
995                .hangup_cause()
996                .unwrap(),
997            Some(crate::channel::HangupCause::NormalClearing)
998        );
999        assert_eq!(event.event_subclass(), Some("sofia::register"));
1000        assert_eq!(event.variable_str("sip_from_display"), Some("Bob"));
1001        assert_eq!(event.variable_str("nonexistent"), None);
1002    }
1003
1004    #[test]
1005    fn test_event_format_from_str() {
1006        assert_eq!("plain".parse::<EventFormat>(), Ok(EventFormat::Plain));
1007        assert_eq!("json".parse::<EventFormat>(), Ok(EventFormat::Json));
1008        assert_eq!("xml".parse::<EventFormat>(), Ok(EventFormat::Xml));
1009        assert!("foo"
1010            .parse::<EventFormat>()
1011            .is_err());
1012    }
1013
1014    #[test]
1015    fn test_event_format_from_str_case_insensitive() {
1016        assert_eq!("PLAIN".parse::<EventFormat>(), Ok(EventFormat::Plain));
1017        assert_eq!("Json".parse::<EventFormat>(), Ok(EventFormat::Json));
1018        assert_eq!("XML".parse::<EventFormat>(), Ok(EventFormat::Xml));
1019        assert_eq!("Xml".parse::<EventFormat>(), Ok(EventFormat::Xml));
1020    }
1021
1022    #[test]
1023    fn test_event_format_from_content_type() {
1024        assert_eq!(
1025            EventFormat::from_content_type("text/event-json"),
1026            Ok(EventFormat::Json)
1027        );
1028        assert_eq!(
1029            EventFormat::from_content_type("text/event-xml"),
1030            Ok(EventFormat::Xml)
1031        );
1032        assert_eq!(
1033            EventFormat::from_content_type("text/event-plain"),
1034            Ok(EventFormat::Plain)
1035        );
1036        assert!(EventFormat::from_content_type("unknown").is_err());
1037    }
1038
1039    // --- EslEvent accessor tests (via HeaderLookup trait) ---
1040
1041    #[test]
1042    fn test_event_channel_state_accessor() {
1043        use crate::channel::ChannelState;
1044        let mut event = EslEvent::new();
1045        event.set_header("Channel-State", "CS_EXECUTE");
1046        assert_eq!(
1047            event
1048                .channel_state()
1049                .unwrap(),
1050            Some(ChannelState::CsExecute)
1051        );
1052    }
1053
1054    #[test]
1055    fn test_event_channel_state_number_accessor() {
1056        use crate::channel::ChannelState;
1057        let mut event = EslEvent::new();
1058        event.set_header("Channel-State-Number", "4");
1059        assert_eq!(
1060            event
1061                .channel_state_number()
1062                .unwrap(),
1063            Some(ChannelState::CsExecute)
1064        );
1065    }
1066
1067    #[test]
1068    fn test_event_call_state_accessor() {
1069        use crate::channel::CallState;
1070        let mut event = EslEvent::new();
1071        event.set_header("Channel-Call-State", "ACTIVE");
1072        assert_eq!(
1073            event
1074                .call_state()
1075                .unwrap(),
1076            Some(CallState::Active)
1077        );
1078    }
1079
1080    #[test]
1081    fn test_event_answer_state_accessor() {
1082        use crate::channel::AnswerState;
1083        let mut event = EslEvent::new();
1084        event.set_header("Answer-State", "answered");
1085        assert_eq!(
1086            event
1087                .answer_state()
1088                .unwrap(),
1089            Some(AnswerState::Answered)
1090        );
1091    }
1092
1093    #[test]
1094    fn test_event_call_direction_accessor() {
1095        use crate::channel::CallDirection;
1096        let mut event = EslEvent::new();
1097        event.set_header("Call-Direction", "inbound");
1098        assert_eq!(
1099            event
1100                .call_direction()
1101                .unwrap(),
1102            Some(CallDirection::Inbound)
1103        );
1104    }
1105
1106    #[test]
1107    fn test_event_typed_accessors_missing_headers() {
1108        let event = EslEvent::new();
1109        assert_eq!(
1110            event
1111                .channel_state()
1112                .unwrap(),
1113            None
1114        );
1115        assert_eq!(
1116            event
1117                .channel_state_number()
1118                .unwrap(),
1119            None
1120        );
1121        assert_eq!(
1122            event
1123                .call_state()
1124                .unwrap(),
1125            None
1126        );
1127        assert_eq!(
1128            event
1129                .answer_state()
1130                .unwrap(),
1131            None
1132        );
1133        assert_eq!(
1134            event
1135                .call_direction()
1136                .unwrap(),
1137            None
1138        );
1139    }
1140
1141    // --- Repeating SIP header tests ---
1142
1143    #[test]
1144    fn test_sip_p_asserted_identity_comma_separated() {
1145        let mut event = EslEvent::new();
1146        // RFC 3325: P-Asserted-Identity can carry two identities (one sip:, one tel:)
1147        // FreeSWITCH stores the comma-separated value as a single channel variable
1148        event.set_header(
1149            "variable_sip_P-Asserted-Identity",
1150            "<sip:alice@atlanta.example.com>, <tel:+15551234567>",
1151        );
1152
1153        assert_eq!(
1154            event.variable_str("sip_P-Asserted-Identity"),
1155            Some("<sip:alice@atlanta.example.com>, <tel:+15551234567>")
1156        );
1157    }
1158
1159    #[test]
1160    fn test_sip_p_asserted_identity_array_format() {
1161        let mut event = EslEvent::new();
1162        // When FreeSWITCH stores repeated SIP headers via ARRAY format
1163        event.push_header(
1164            "variable_sip_P-Asserted-Identity",
1165            "<sip:alice@atlanta.example.com>",
1166        );
1167        event.push_header("variable_sip_P-Asserted-Identity", "<tel:+15551234567>");
1168
1169        let raw = event
1170            .header_str("variable_sip_P-Asserted-Identity")
1171            .unwrap();
1172        assert_eq!(
1173            raw,
1174            "ARRAY::<sip:alice@atlanta.example.com>|:<tel:+15551234567>"
1175        );
1176
1177        let arr = crate::variables::EslArray::parse(raw).unwrap();
1178        assert_eq!(arr.len(), 2);
1179        assert_eq!(arr.items()[0], "<sip:alice@atlanta.example.com>");
1180        assert_eq!(arr.items()[1], "<tel:+15551234567>");
1181    }
1182
1183    #[test]
1184    fn test_sip_header_with_colons_in_uri() {
1185        let mut event = EslEvent::new();
1186        // SIP URIs contain colons (sip:, sips:) which must not confuse ARRAY parsing
1187        event.push_header(
1188            "variable_sip_h_Diversion",
1189            "<sip:+15551234567@gw.example.com;reason=unconditional>",
1190        );
1191        event.push_header(
1192            "variable_sip_h_Diversion",
1193            "<sips:+15559876543@secure.example.com;reason=no-answer;counter=3>",
1194        );
1195
1196        let raw = event
1197            .header_str("variable_sip_h_Diversion")
1198            .unwrap();
1199        let arr = crate::variables::EslArray::parse(raw).unwrap();
1200        assert_eq!(arr.len(), 2);
1201        assert_eq!(
1202            arr.items()[0],
1203            "<sip:+15551234567@gw.example.com;reason=unconditional>"
1204        );
1205        assert_eq!(
1206            arr.items()[1],
1207            "<sips:+15559876543@secure.example.com;reason=no-answer;counter=3>"
1208        );
1209    }
1210
1211    #[test]
1212    fn test_sip_p_asserted_identity_plain_format_round_trip() {
1213        let mut event = EslEvent::with_type(EslEventType::ChannelCreate);
1214        event.set_header("Event-Name", "CHANNEL_CREATE");
1215        event.set_header(
1216            "variable_sip_P-Asserted-Identity",
1217            "<sip:alice@atlanta.example.com>, <tel:+15551234567>",
1218        );
1219
1220        let plain = event.to_plain_format();
1221        // The comma-separated value should be percent-encoded on the wire
1222        assert!(plain.contains("variable_sip_P-Asserted-Identity:"));
1223        // Angle brackets and comma should be encoded
1224        assert!(!plain.contains("<sip:alice"));
1225    }
1226
1227    // --- Header key normalization on EslEvent ---
1228    // set_header() normalizes keys so lookups via header(EventHeader::X)
1229    // and header_str() work regardless of the casing used at insertion.
1230
1231    #[test]
1232    fn set_header_normalizes_known_enum_variant() {
1233        let mut event = EslEvent::new();
1234        event.set_header("unique-id", "abc-123");
1235        assert_eq!(event.header(EventHeader::UniqueId), Some("abc-123"));
1236    }
1237
1238    #[test]
1239    fn set_header_normalizes_codec_header() {
1240        let mut event = EslEvent::new();
1241        event.set_header("channel-read-codec-bit-rate", "128000");
1242        assert_eq!(
1243            event.header(EventHeader::ChannelReadCodecBitRate),
1244            Some("128000")
1245        );
1246    }
1247
1248    #[test]
1249    fn header_str_finds_by_original_key() {
1250        let mut event = EslEvent::new();
1251        event.set_header("unique-id", "abc-123");
1252        // Lookup by original non-canonical key should still work
1253        assert_eq!(event.header_str("unique-id"), Some("abc-123"));
1254        // Lookup by canonical key also works
1255        assert_eq!(event.header_str("Unique-ID"), Some("abc-123"));
1256    }
1257
1258    #[test]
1259    fn header_str_finds_unknown_dash_header_by_original() {
1260        let mut event = EslEvent::new();
1261        event.set_header("x-custom-header", "val");
1262        // Stored as Title-Case
1263        assert_eq!(event.header_str("X-Custom-Header"), Some("val"));
1264        // Original key also works via alias
1265        assert_eq!(event.header_str("x-custom-header"), Some("val"));
1266    }
1267
1268    #[test]
1269    fn set_header_underscore_passthrough_preserves_sip_h() {
1270        let mut event = EslEvent::new();
1271        event.set_header("variable_sip_h_X-My-CUSTOM-Header", "val");
1272        assert_eq!(
1273            event.header_str("variable_sip_h_X-My-CUSTOM-Header"),
1274            Some("val")
1275        );
1276    }
1277
1278    #[test]
1279    fn set_header_different_casing_overwrites() {
1280        let mut event = EslEvent::new();
1281        event.set_header("Unique-ID", "first");
1282        event.set_header("unique-id", "second");
1283        // Both normalize to "Unique-ID", second overwrites first
1284        assert_eq!(event.header(EventHeader::UniqueId), Some("second"));
1285    }
1286
1287    #[test]
1288    fn remove_header_by_original_key() {
1289        let mut event = EslEvent::new();
1290        event.set_header("unique-id", "abc-123");
1291        let removed = event.remove_header("unique-id");
1292        assert_eq!(removed, Some("abc-123".to_string()));
1293        assert_eq!(event.header(EventHeader::UniqueId), None);
1294    }
1295
1296    #[test]
1297    fn remove_header_by_canonical_key() {
1298        let mut event = EslEvent::new();
1299        event.set_header("unique-id", "abc-123");
1300        let removed = event.remove_header("Unique-ID");
1301        assert_eq!(removed, Some("abc-123".to_string()));
1302        assert_eq!(event.header_str("unique-id"), None);
1303    }
1304
1305    #[test]
1306    fn serde_round_trip_preserves_canonical_lookups() {
1307        let mut event = EslEvent::new();
1308        event.set_header("unique-id", "abc-123");
1309        event.set_header("channel-read-codec-bit-rate", "128000");
1310        let json = serde_json::to_string(&event).unwrap();
1311        let deserialized: EslEvent = serde_json::from_str(&json).unwrap();
1312        assert_eq!(deserialized.header(EventHeader::UniqueId), Some("abc-123"));
1313        assert_eq!(
1314            deserialized.header(EventHeader::ChannelReadCodecBitRate),
1315            Some("128000")
1316        );
1317    }
1318
1319    #[test]
1320    fn serde_deserialize_normalizes_external_json() {
1321        let json = r#"{"event_type":null,"headers":{"unique-id":"abc-123","channel-read-codec-bit-rate":"128000"},"body":null}"#;
1322        let event: EslEvent = serde_json::from_str(json).unwrap();
1323        assert_eq!(event.header(EventHeader::UniqueId), Some("abc-123"));
1324        assert_eq!(
1325            event.header(EventHeader::ChannelReadCodecBitRate),
1326            Some("128000")
1327        );
1328        assert_eq!(event.header_str("unique-id"), Some("abc-123"));
1329    }
1330
1331    #[test]
1332    fn test_event_typed_accessors_invalid_values() {
1333        let mut event = EslEvent::new();
1334        event.set_header("Channel-State", "BOGUS");
1335        event.set_header("Channel-State-Number", "999");
1336        event.set_header("Channel-Call-State", "BOGUS");
1337        event.set_header("Answer-State", "bogus");
1338        event.set_header("Call-Direction", "bogus");
1339        assert!(event
1340            .channel_state()
1341            .is_err());
1342        assert!(event
1343            .channel_state_number()
1344            .is_err());
1345        assert!(event
1346            .call_state()
1347            .is_err());
1348        assert!(event
1349            .answer_state()
1350            .is_err());
1351        assert!(event
1352            .call_direction()
1353            .is_err());
1354    }
1355}