Skip to main content

freeswitch_types/event/
event_type.rs

1use std::fmt;
2use std::str::FromStr;
3
4/// Generates `EslEventType` enum with `Display`, `FromStr`, `as_str`, `parse_event_type`,
5/// and the six predefined event-group constants.
6///
7/// Each variant row carries an optional `[group, ...]` tag list. The macro
8/// generates `CHANNEL_EVENTS`, `IN_CALL_EVENTS`, `MEDIA_EVENTS`,
9/// `PRESENCE_EVENTS`, `SYSTEM_EVENTS`, and `CONFERENCE_EVENTS` by filtering the
10/// variant table — no separate sync step required.
11macro_rules! esl_event_types {
12    // ------------------------------------------------------------------
13    // Filter arms — TT-munch through variant tuples `(Variant [tags...])`,
14    // accumulating those whose tag list contains the target group.
15    // Accumulator is `[$($acc:ident),*]`; items are space-separated tuples.
16    // ------------------------------------------------------------------
17
18    // channel
19    (@filter_channel [$($acc:ident),*]) => { &[$(EslEventType::$acc,)*] };
20    (@filter_channel [$($acc:ident),*] ($v:ident [channel $(,$_g:ident)*]) $($tail:tt)*) => {
21        esl_event_types!(@filter_channel [$($acc,)* $v] $($tail)*)
22    };
23    (@filter_channel [$($acc:ident),*] ($v:ident [$_h:ident $(,$tg:ident)*]) $($tail:tt)*) => {
24        esl_event_types!(@filter_channel [$($acc),*] ($v [$($tg),*]) $($tail)*)
25    };
26    (@filter_channel [$($acc:ident),*] ($v:ident []) $($tail:tt)*) => {
27        esl_event_types!(@filter_channel [$($acc),*] $($tail)*)
28    };
29
30    // in_call
31    (@filter_in_call [$($acc:ident),*]) => { &[$(EslEventType::$acc,)*] };
32    (@filter_in_call [$($acc:ident),*] ($v:ident [in_call $(,$_g:ident)*]) $($tail:tt)*) => {
33        esl_event_types!(@filter_in_call [$($acc,)* $v] $($tail)*)
34    };
35    (@filter_in_call [$($acc:ident),*] ($v:ident [$_h:ident $(,$tg:ident)*]) $($tail:tt)*) => {
36        esl_event_types!(@filter_in_call [$($acc),*] ($v [$($tg),*]) $($tail)*)
37    };
38    (@filter_in_call [$($acc:ident),*] ($v:ident []) $($tail:tt)*) => {
39        esl_event_types!(@filter_in_call [$($acc),*] $($tail)*)
40    };
41
42    // media
43    (@filter_media [$($acc:ident),*]) => { &[$(EslEventType::$acc,)*] };
44    (@filter_media [$($acc:ident),*] ($v:ident [media $(,$_g:ident)*]) $($tail:tt)*) => {
45        esl_event_types!(@filter_media [$($acc,)* $v] $($tail)*)
46    };
47    (@filter_media [$($acc:ident),*] ($v:ident [$_h:ident $(,$tg:ident)*]) $($tail:tt)*) => {
48        esl_event_types!(@filter_media [$($acc),*] ($v [$($tg),*]) $($tail)*)
49    };
50    (@filter_media [$($acc:ident),*] ($v:ident []) $($tail:tt)*) => {
51        esl_event_types!(@filter_media [$($acc),*] $($tail)*)
52    };
53
54    // presence
55    (@filter_presence [$($acc:ident),*]) => { &[$(EslEventType::$acc,)*] };
56    (@filter_presence [$($acc:ident),*] ($v:ident [presence $(,$_g:ident)*]) $($tail:tt)*) => {
57        esl_event_types!(@filter_presence [$($acc,)* $v] $($tail)*)
58    };
59    (@filter_presence [$($acc:ident),*] ($v:ident [$_h:ident $(,$tg:ident)*]) $($tail:tt)*) => {
60        esl_event_types!(@filter_presence [$($acc),*] ($v [$($tg),*]) $($tail)*)
61    };
62    (@filter_presence [$($acc:ident),*] ($v:ident []) $($tail:tt)*) => {
63        esl_event_types!(@filter_presence [$($acc),*] $($tail)*)
64    };
65
66    // system
67    (@filter_system [$($acc:ident),*]) => { &[$(EslEventType::$acc,)*] };
68    (@filter_system [$($acc:ident),*] ($v:ident [system $(,$_g:ident)*]) $($tail:tt)*) => {
69        esl_event_types!(@filter_system [$($acc,)* $v] $($tail)*)
70    };
71    (@filter_system [$($acc:ident),*] ($v:ident [$_h:ident $(,$tg:ident)*]) $($tail:tt)*) => {
72        esl_event_types!(@filter_system [$($acc),*] ($v [$($tg),*]) $($tail)*)
73    };
74    (@filter_system [$($acc:ident),*] ($v:ident []) $($tail:tt)*) => {
75        esl_event_types!(@filter_system [$($acc),*] $($tail)*)
76    };
77
78    // conference
79    (@filter_conference [$($acc:ident),*]) => { &[$(EslEventType::$acc,)*] };
80    (@filter_conference [$($acc:ident),*] ($v:ident [conference $(,$_g:ident)*]) $($tail:tt)*) => {
81        esl_event_types!(@filter_conference [$($acc,)* $v] $($tail)*)
82    };
83    (@filter_conference [$($acc:ident),*] ($v:ident [$_h:ident $(,$tg:ident)*]) $($tail:tt)*) => {
84        esl_event_types!(@filter_conference [$($acc),*] ($v [$($tg),*]) $($tail)*)
85    };
86    (@filter_conference [$($acc:ident),*] ($v:ident []) $($tail:tt)*) => {
87        esl_event_types!(@filter_conference [$($acc),*] $($tail)*)
88    };
89
90    // ------------------------------------------------------------------
91    // Main arm — generate enum, Display, FromStr, and group constants.
92    // Each variant row: `Variant => "WIRE_NAME" [group, ...]`
93    // Empty tag list `[]` = no group membership.
94    // ------------------------------------------------------------------
95    (
96        $(
97            $(#[$attr:meta])*
98            $variant:ident => $wire:literal [$($vgroup:ident),*]
99        ),+ $(,)?
100        ;
101        // Extra variants not in the main match (after All)
102        $(
103            $(#[$extra_attr:meta])*
104            $extra_variant:ident => $extra_wire:literal [$($egroup:ident),*]
105        ),* $(,)?
106    ) => {
107        /// FreeSWITCH event types matching the canonical order from `esl_event.h`
108        /// and `switch_event.c` EVENT_NAMES[].
109        ///
110        /// Variant names are the canonical wire names (e.g. `ChannelCreate` = `CHANNEL_CREATE`).
111        #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
112        #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
113        #[non_exhaustive]
114        #[allow(missing_docs)]
115        pub enum EslEventType {
116            $(
117                $(#[$attr])*
118                $variant,
119            )+
120            $(
121                $(#[$extra_attr])*
122                $extra_variant,
123            )*
124        }
125
126        impl fmt::Display for EslEventType {
127            fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
128                f.write_str(self.as_str())
129            }
130        }
131
132        impl EslEventType {
133            /// Returns the canonical wire name as a static string slice.
134            pub const fn as_str(&self) -> &'static str {
135                match self {
136                    $( EslEventType::$variant => $wire, )+
137                    $( EslEventType::$extra_variant => $extra_wire, )*
138                }
139            }
140
141            /// Parse event type from wire name (canonical case).
142            pub fn parse_event_type(s: &str) -> Option<Self> {
143                match s {
144                    $( $wire => Some(EslEventType::$variant), )+
145                    $( $extra_wire => Some(EslEventType::$extra_variant), )*
146                    _ => None,
147                }
148            }
149
150            // -- Event group constants ----------------------------------------
151            //
152            // Predefined slices for common subscription patterns. Pass directly
153            // to `EslClient::subscribe_events()`.
154            //
155            // Group membership is declared inline via the `[group, ...]` tag on
156            // each variant row above. Adding a new variant and tagging it
157            // automatically includes it in the right constant — no separate
158            // maintenance step required.
159
160            #[doc = "Every `CHANNEL_*` event type."]
161            #[doc = ""]
162            #[doc = "Covers the full channel lifecycle: creation, state changes, execution,"]
163            #[doc = "bridging, hold, park, progress, originate, and destruction."]
164            #[doc = ""]
165            #[doc = "```rust"]
166            #[doc = "use freeswitch_types::EslEventType;"]
167            #[doc = "assert!(EslEventType::CHANNEL_EVENTS.contains(&EslEventType::ChannelCreate));"]
168            #[doc = "assert!(EslEventType::CHANNEL_EVENTS.contains(&EslEventType::ChannelHangupComplete));"]
169            #[doc = "```"]
170            pub const CHANNEL_EVENTS: &[EslEventType] = esl_event_types!(
171                @filter_channel []
172                $(($variant [$($vgroup),*]))+
173                $(($extra_variant [$($egroup),*]))*
174            );
175
176            #[doc = "In-call events: DTMF, VAD speech detection, media security, and call updates."]
177            #[doc = ""]
178            #[doc = "Events that fire during an established call, tied to RTP/media activity"]
179            #[doc = "rather than signaling state transitions."]
180            #[doc = ""]
181            #[doc = "```rust"]
182            #[doc = "use freeswitch_types::EslEventType;"]
183            #[doc = "assert!(EslEventType::IN_CALL_EVENTS.contains(&EslEventType::Dtmf));"]
184            #[doc = "assert!(EslEventType::IN_CALL_EVENTS.contains(&EslEventType::Talk));"]
185            #[doc = "```"]
186            pub const IN_CALL_EVENTS: &[EslEventType] = esl_event_types!(
187                @filter_in_call []
188                $(($variant [$($vgroup),*]))+
189                $(($extra_variant [$($egroup),*]))*
190            );
191
192            #[doc = "Media-related events: playback, recording, media bugs, and detection."]
193            #[doc = ""]
194            #[doc = "Useful for IVR applications that need to track media operations without"]
195            #[doc = "subscribing to the full channel lifecycle."]
196            #[doc = ""]
197            #[doc = "```rust"]
198            #[doc = "use freeswitch_types::EslEventType;"]
199            #[doc = "assert!(EslEventType::MEDIA_EVENTS.contains(&EslEventType::PlaybackStart));"]
200            #[doc = "assert!(EslEventType::MEDIA_EVENTS.contains(&EslEventType::DetectedSpeech));"]
201            #[doc = "```"]
202            pub const MEDIA_EVENTS: &[EslEventType] = esl_event_types!(
203                @filter_media []
204                $(($variant [$($vgroup),*]))+
205                $(($extra_variant [$($egroup),*]))*
206            );
207
208            #[doc = "Presence and messaging events."]
209            #[doc = ""]
210            #[doc = "For applications that track user presence (BLF, buddy lists) or"]
211            #[doc = "message-waiting indicators (voicemail MWI)."]
212            #[doc = ""]
213            #[doc = "```rust"]
214            #[doc = "use freeswitch_types::EslEventType;"]
215            #[doc = "assert!(EslEventType::PRESENCE_EVENTS.contains(&EslEventType::PresenceIn));"]
216            #[doc = "assert!(EslEventType::PRESENCE_EVENTS.contains(&EslEventType::MessageWaiting));"]
217            #[doc = "```"]
218            pub const PRESENCE_EVENTS: &[EslEventType] = esl_event_types!(
219                @filter_presence []
220                $(($variant [$($vgroup),*]))+
221                $(($extra_variant [$($egroup),*]))*
222            );
223
224            #[doc = "System lifecycle events."]
225            #[doc = ""]
226            #[doc = "Server startup/shutdown, heartbeats, module loading, and XML reloads."]
227            #[doc = "Useful for monitoring dashboards and operational tooling."]
228            #[doc = ""]
229            #[doc = "```rust"]
230            #[doc = "use freeswitch_types::EslEventType;"]
231            #[doc = "assert!(EslEventType::SYSTEM_EVENTS.contains(&EslEventType::Heartbeat));"]
232            #[doc = "assert!(EslEventType::SYSTEM_EVENTS.contains(&EslEventType::Shutdown));"]
233            #[doc = "```"]
234            pub const SYSTEM_EVENTS: &[EslEventType] = esl_event_types!(
235                @filter_system []
236                $(($variant [$($vgroup),*]))+
237                $(($extra_variant [$($egroup),*]))*
238            );
239
240            #[doc = "Conference-related events."]
241            #[doc = ""]
242            #[doc = "```rust"]
243            #[doc = "use freeswitch_types::EslEventType;"]
244            #[doc = "assert!(EslEventType::CONFERENCE_EVENTS.contains(&EslEventType::ConferenceData));"]
245            #[doc = "```"]
246            pub const CONFERENCE_EVENTS: &[EslEventType] = esl_event_types!(
247                @filter_conference []
248                $(($variant [$($vgroup),*]))+
249                $(($extra_variant [$($egroup),*]))*
250            );
251        }
252
253        impl FromStr for EslEventType {
254            type Err = ParseEventTypeError;
255
256            fn from_str(s: &str) -> Result<Self, Self::Err> {
257                Self::parse_event_type(s).ok_or_else(|| ParseEventTypeError(s.to_string()))
258            }
259        }
260    };
261}
262
263esl_event_types! {
264    Custom               => "CUSTOM"                   [],
265    Clone                => "CLONE"                    [],
266    ChannelCreate        => "CHANNEL_CREATE"           [channel],
267    ChannelDestroy       => "CHANNEL_DESTROY"          [channel],
268    ChannelState         => "CHANNEL_STATE"            [channel],
269    ChannelCallstate     => "CHANNEL_CALLSTATE"        [channel],
270    ChannelAnswer        => "CHANNEL_ANSWER"           [channel],
271    ChannelHangup        => "CHANNEL_HANGUP"           [channel],
272    ChannelHangupComplete => "CHANNEL_HANGUP_COMPLETE" [channel],
273    ChannelExecute       => "CHANNEL_EXECUTE"          [channel],
274    ChannelExecuteComplete => "CHANNEL_EXECUTE_COMPLETE" [channel],
275    ChannelHold          => "CHANNEL_HOLD"             [channel],
276    ChannelUnhold        => "CHANNEL_UNHOLD"           [channel],
277    ChannelBridge        => "CHANNEL_BRIDGE"           [channel],
278    ChannelUnbridge      => "CHANNEL_UNBRIDGE"         [channel],
279    ChannelProgress      => "CHANNEL_PROGRESS"         [channel],
280    ChannelProgressMedia => "CHANNEL_PROGRESS_MEDIA"   [channel],
281    ChannelOutgoing      => "CHANNEL_OUTGOING"         [channel],
282    ChannelPark          => "CHANNEL_PARK"             [channel],
283    ChannelUnpark        => "CHANNEL_UNPARK"           [channel],
284    ChannelApplication   => "CHANNEL_APPLICATION"      [channel],
285    ChannelOriginate     => "CHANNEL_ORIGINATE"        [channel],
286    ChannelUuid          => "CHANNEL_UUID"             [channel],
287    Api                  => "API"                      [],
288    Log                  => "LOG"                      [],
289    InboundChan          => "INBOUND_CHAN"             [],
290    OutboundChan         => "OUTBOUND_CHAN"            [],
291    Startup              => "STARTUP"                  [system],
292    Shutdown             => "SHUTDOWN"                 [system],
293    Publish              => "PUBLISH"                  [],
294    Unpublish            => "UNPUBLISH"                [],
295    Talk                 => "TALK"                     [in_call],
296    Notalk               => "NOTALK"                   [in_call],
297    SessionCrash         => "SESSION_CRASH"            [system],
298    ModuleLoad           => "MODULE_LOAD"              [system],
299    ModuleUnload         => "MODULE_UNLOAD"            [system],
300    Dtmf                 => "DTMF"                     [in_call],
301    Message              => "MESSAGE"                  [],
302    PresenceIn           => "PRESENCE_IN"              [presence],
303    NotifyIn             => "NOTIFY_IN"                [],
304    PresenceOut          => "PRESENCE_OUT"             [presence],
305    PresenceProbe        => "PRESENCE_PROBE"           [presence],
306    MessageWaiting       => "MESSAGE_WAITING"          [presence],
307    MessageQuery         => "MESSAGE_QUERY"            [presence],
308    Roster               => "ROSTER"                   [presence],
309    Codec                => "CODEC"                    [],
310    BackgroundJob        => "BACKGROUND_JOB"           [],
311    DetectedSpeech       => "DETECTED_SPEECH"          [media],
312    DetectedTone         => "DETECTED_TONE"            [media],
313    PrivateCommand       => "PRIVATE_COMMAND"          [],
314    Heartbeat            => "HEARTBEAT"                [system],
315    Trap                 => "TRAP"                     [],
316    AddSchedule          => "ADD_SCHEDULE"             [],
317    DelSchedule          => "DEL_SCHEDULE"             [],
318    ExeSchedule          => "EXE_SCHEDULE"             [],
319    ReSchedule           => "RE_SCHEDULE"              [],
320    ReloadXml            => "RELOADXML"                [system],
321    Notify               => "NOTIFY"                   [],
322    PhoneFeature         => "PHONE_FEATURE"            [],
323    PhoneFeatureSubscribe => "PHONE_FEATURE_SUBSCRIBE" [],
324    SendMessage          => "SEND_MESSAGE"             [],
325    RecvMessage          => "RECV_MESSAGE"             [],
326    RequestParams        => "REQUEST_PARAMS"           [],
327    ChannelData          => "CHANNEL_DATA"             [channel],
328    General              => "GENERAL"                  [],
329    Command              => "COMMAND"                  [],
330    SessionHeartbeat     => "SESSION_HEARTBEAT"        [system],
331    ClientDisconnected   => "CLIENT_DISCONNECTED"      [],
332    ServerDisconnected   => "SERVER_DISCONNECTED"      [],
333    SendInfo             => "SEND_INFO"                [],
334    RecvInfo             => "RECV_INFO"                [],
335    RecvRtcpMessage      => "RECV_RTCP_MESSAGE"        [in_call],
336    SendRtcpMessage      => "SEND_RTCP_MESSAGE"        [in_call],
337    CallSecure           => "CALL_SECURE"              [in_call],
338    Nat                  => "NAT"                      [],
339    RecordStart          => "RECORD_START"             [media],
340    RecordStop           => "RECORD_STOP"              [media],
341    PlaybackStart        => "PLAYBACK_START"           [media],
342    PlaybackStop         => "PLAYBACK_STOP"            [media],
343    CallUpdate           => "CALL_UPDATE"              [in_call],
344    Failure              => "FAILURE"                  [],
345    SocketData           => "SOCKET_DATA"              [],
346    MediaBugStart        => "MEDIA_BUG_START"          [media],
347    MediaBugStop         => "MEDIA_BUG_STOP"           [media],
348    ConferenceDataQuery  => "CONFERENCE_DATA_QUERY"    [conference],
349    ConferenceData       => "CONFERENCE_DATA"          [conference],
350    CallSetupReq         => "CALL_SETUP_REQ"           [],
351    CallSetupResult      => "CALL_SETUP_RESULT"        [],
352    CallDetail           => "CALL_DETAIL"              [],
353    DeviceState          => "DEVICE_STATE"             [],
354    Text                 => "TEXT"                     [],
355    ShutdownRequested    => "SHUTDOWN_REQUESTED"       [system],
356    /// Subscribe to all events
357    All                  => "ALL"                      [];
358    // --- Not in libs/esl/ EVENT_NAMES[], only in switch_event.c ---
359    // check-event-types.sh stops scanning at the All variant above.
360    /// Present in `switch_event.c` but not in `libs/esl/` EVENT_NAMES[].
361    StartRecording => "START_RECORDING" [media],
362}
363
364parse_error! { ParseEventTypeError("event type"); }
365
366#[cfg(test)]
367mod tests {
368    use super::*;
369
370    #[test]
371    fn test_notify_in_parse() {
372        assert_eq!(
373            EslEventType::parse_event_type("NOTIFY_IN"),
374            Some(EslEventType::NotifyIn)
375        );
376        assert_eq!(EslEventType::parse_event_type("notify_in"), None);
377    }
378
379    #[test]
380    fn test_notify_in_display() {
381        assert_eq!(EslEventType::NotifyIn.to_string(), "NOTIFY_IN");
382    }
383
384    #[test]
385    fn test_notify_in_distinct_from_notify() {
386        assert_ne!(EslEventType::Notify, EslEventType::NotifyIn);
387        assert_ne!(
388            EslEventType::Notify.to_string(),
389            EslEventType::NotifyIn.to_string()
390        );
391    }
392
393    #[test]
394    fn test_wire_names_match_c_esl() {
395        assert_eq!(
396            EslEventType::ChannelOutgoing.to_string(),
397            "CHANNEL_OUTGOING"
398        );
399        assert_eq!(EslEventType::Api.to_string(), "API");
400        assert_eq!(EslEventType::ReloadXml.to_string(), "RELOADXML");
401        assert_eq!(EslEventType::PresenceIn.to_string(), "PRESENCE_IN");
402        assert_eq!(EslEventType::Roster.to_string(), "ROSTER");
403        assert_eq!(EslEventType::Text.to_string(), "TEXT");
404        assert_eq!(EslEventType::ReSchedule.to_string(), "RE_SCHEDULE");
405
406        assert_eq!(
407            EslEventType::parse_event_type("CHANNEL_OUTGOING"),
408            Some(EslEventType::ChannelOutgoing)
409        );
410        assert_eq!(
411            EslEventType::parse_event_type("API"),
412            Some(EslEventType::Api)
413        );
414        assert_eq!(
415            EslEventType::parse_event_type("RELOADXML"),
416            Some(EslEventType::ReloadXml)
417        );
418        assert_eq!(
419            EslEventType::parse_event_type("PRESENCE_IN"),
420            Some(EslEventType::PresenceIn)
421        );
422    }
423
424    #[test]
425    fn test_event_type_from_str() {
426        assert_eq!(
427            "CHANNEL_ANSWER".parse::<EslEventType>(),
428            Ok(EslEventType::ChannelAnswer)
429        );
430        assert!("channel_answer"
431            .parse::<EslEventType>()
432            .is_err());
433        assert!("UNKNOWN_EVENT"
434            .parse::<EslEventType>()
435            .is_err());
436    }
437}