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            // Group constants, filtered from the `[group, ...]` tag on each row.
151
152            #[doc = "Every `CHANNEL_*` event type."]
153            #[doc = ""]
154            #[doc = "Covers the full channel lifecycle: creation, state changes, execution,"]
155            #[doc = "bridging, hold, park, progress, originate, and destruction."]
156            #[doc = ""]
157            #[doc = "```rust"]
158            #[doc = "use freeswitch_types::EslEventType;"]
159            #[doc = "assert!(EslEventType::CHANNEL_EVENTS.contains(&EslEventType::ChannelCreate));"]
160            #[doc = "assert!(EslEventType::CHANNEL_EVENTS.contains(&EslEventType::ChannelHangupComplete));"]
161            #[doc = "```"]
162            pub const CHANNEL_EVENTS: &[EslEventType] = esl_event_types!(
163                @filter_channel []
164                $(($variant [$($vgroup),*]))+
165                $(($extra_variant [$($egroup),*]))*
166            );
167
168            #[doc = "In-call events: DTMF, VAD speech detection, media security, and call updates."]
169            #[doc = ""]
170            #[doc = "Events that fire during an established call, tied to RTP/media activity"]
171            #[doc = "rather than signaling state transitions."]
172            #[doc = ""]
173            #[doc = "```rust"]
174            #[doc = "use freeswitch_types::EslEventType;"]
175            #[doc = "assert!(EslEventType::IN_CALL_EVENTS.contains(&EslEventType::Dtmf));"]
176            #[doc = "assert!(EslEventType::IN_CALL_EVENTS.contains(&EslEventType::Talk));"]
177            #[doc = "```"]
178            pub const IN_CALL_EVENTS: &[EslEventType] = esl_event_types!(
179                @filter_in_call []
180                $(($variant [$($vgroup),*]))+
181                $(($extra_variant [$($egroup),*]))*
182            );
183
184            #[doc = "Media-related events: playback, recording, media bugs, and detection."]
185            #[doc = ""]
186            #[doc = "Useful for IVR applications that need to track media operations without"]
187            #[doc = "subscribing to the full channel lifecycle."]
188            #[doc = ""]
189            #[doc = "```rust"]
190            #[doc = "use freeswitch_types::EslEventType;"]
191            #[doc = "assert!(EslEventType::MEDIA_EVENTS.contains(&EslEventType::PlaybackStart));"]
192            #[doc = "assert!(EslEventType::MEDIA_EVENTS.contains(&EslEventType::DetectedSpeech));"]
193            #[doc = "```"]
194            pub const MEDIA_EVENTS: &[EslEventType] = esl_event_types!(
195                @filter_media []
196                $(($variant [$($vgroup),*]))+
197                $(($extra_variant [$($egroup),*]))*
198            );
199
200            #[doc = "Presence and messaging events."]
201            #[doc = ""]
202            #[doc = "For applications that track user presence (BLF, buddy lists) or"]
203            #[doc = "message-waiting indicators (voicemail MWI)."]
204            #[doc = ""]
205            #[doc = "```rust"]
206            #[doc = "use freeswitch_types::EslEventType;"]
207            #[doc = "assert!(EslEventType::PRESENCE_EVENTS.contains(&EslEventType::PresenceIn));"]
208            #[doc = "assert!(EslEventType::PRESENCE_EVENTS.contains(&EslEventType::MessageWaiting));"]
209            #[doc = "```"]
210            pub const PRESENCE_EVENTS: &[EslEventType] = esl_event_types!(
211                @filter_presence []
212                $(($variant [$($vgroup),*]))+
213                $(($extra_variant [$($egroup),*]))*
214            );
215
216            #[doc = "System lifecycle events."]
217            #[doc = ""]
218            #[doc = "Server startup/shutdown, heartbeats, module loading, and XML reloads."]
219            #[doc = "Useful for monitoring dashboards and operational tooling."]
220            #[doc = ""]
221            #[doc = "```rust"]
222            #[doc = "use freeswitch_types::EslEventType;"]
223            #[doc = "assert!(EslEventType::SYSTEM_EVENTS.contains(&EslEventType::Heartbeat));"]
224            #[doc = "assert!(EslEventType::SYSTEM_EVENTS.contains(&EslEventType::Shutdown));"]
225            #[doc = "```"]
226            pub const SYSTEM_EVENTS: &[EslEventType] = esl_event_types!(
227                @filter_system []
228                $(($variant [$($vgroup),*]))+
229                $(($extra_variant [$($egroup),*]))*
230            );
231
232            #[doc = "Conference-related events."]
233            #[doc = ""]
234            #[doc = "```rust"]
235            #[doc = "use freeswitch_types::EslEventType;"]
236            #[doc = "assert!(EslEventType::CONFERENCE_EVENTS.contains(&EslEventType::ConferenceData));"]
237            #[doc = "```"]
238            pub const CONFERENCE_EVENTS: &[EslEventType] = esl_event_types!(
239                @filter_conference []
240                $(($variant [$($vgroup),*]))+
241                $(($extra_variant [$($egroup),*]))*
242            );
243        }
244
245        impl FromStr for EslEventType {
246            type Err = ParseEventTypeError;
247
248            fn from_str(s: &str) -> Result<Self, Self::Err> {
249                Self::parse_event_type(s).ok_or_else(|| ParseEventTypeError(s.to_string()))
250            }
251        }
252    };
253}
254
255esl_event_types! {
256    Custom               => "CUSTOM"                   [],
257    Clone                => "CLONE"                    [],
258    ChannelCreate        => "CHANNEL_CREATE"           [channel],
259    ChannelDestroy       => "CHANNEL_DESTROY"          [channel],
260    ChannelState         => "CHANNEL_STATE"            [channel],
261    ChannelCallstate     => "CHANNEL_CALLSTATE"        [channel],
262    ChannelAnswer        => "CHANNEL_ANSWER"           [channel],
263    ChannelHangup        => "CHANNEL_HANGUP"           [channel],
264    ChannelHangupComplete => "CHANNEL_HANGUP_COMPLETE" [channel],
265    ChannelExecute       => "CHANNEL_EXECUTE"          [channel],
266    ChannelExecuteComplete => "CHANNEL_EXECUTE_COMPLETE" [channel],
267    ChannelHold          => "CHANNEL_HOLD"             [channel],
268    ChannelUnhold        => "CHANNEL_UNHOLD"           [channel],
269    ChannelBridge        => "CHANNEL_BRIDGE"           [channel],
270    ChannelUnbridge      => "CHANNEL_UNBRIDGE"         [channel],
271    ChannelProgress      => "CHANNEL_PROGRESS"         [channel],
272    ChannelProgressMedia => "CHANNEL_PROGRESS_MEDIA"   [channel],
273    ChannelOutgoing      => "CHANNEL_OUTGOING"         [channel],
274    ChannelPark          => "CHANNEL_PARK"             [channel],
275    ChannelUnpark        => "CHANNEL_UNPARK"           [channel],
276    ChannelApplication   => "CHANNEL_APPLICATION"      [channel],
277    ChannelOriginate     => "CHANNEL_ORIGINATE"        [channel],
278    ChannelUuid          => "CHANNEL_UUID"             [channel],
279    Api                  => "API"                      [],
280    Log                  => "LOG"                      [],
281    InboundChan          => "INBOUND_CHAN"             [],
282    OutboundChan         => "OUTBOUND_CHAN"            [],
283    Startup              => "STARTUP"                  [system],
284    Shutdown             => "SHUTDOWN"                 [system],
285    Publish              => "PUBLISH"                  [],
286    Unpublish            => "UNPUBLISH"                [],
287    Talk                 => "TALK"                     [in_call],
288    Notalk               => "NOTALK"                   [in_call],
289    SessionCrash         => "SESSION_CRASH"            [system],
290    ModuleLoad           => "MODULE_LOAD"              [system],
291    ModuleUnload         => "MODULE_UNLOAD"            [system],
292    Dtmf                 => "DTMF"                     [in_call],
293    Message              => "MESSAGE"                  [],
294    PresenceIn           => "PRESENCE_IN"              [presence],
295    NotifyIn             => "NOTIFY_IN"                [],
296    PresenceOut          => "PRESENCE_OUT"             [presence],
297    PresenceProbe        => "PRESENCE_PROBE"           [presence],
298    MessageWaiting       => "MESSAGE_WAITING"          [presence],
299    MessageQuery         => "MESSAGE_QUERY"            [presence],
300    Roster               => "ROSTER"                   [presence],
301    Codec                => "CODEC"                    [],
302    BackgroundJob        => "BACKGROUND_JOB"           [],
303    DetectedSpeech       => "DETECTED_SPEECH"          [media],
304    DetectedTone         => "DETECTED_TONE"            [media],
305    PrivateCommand       => "PRIVATE_COMMAND"          [],
306    Heartbeat            => "HEARTBEAT"                [system],
307    Trap                 => "TRAP"                     [],
308    AddSchedule          => "ADD_SCHEDULE"             [],
309    DelSchedule          => "DEL_SCHEDULE"             [],
310    ExeSchedule          => "EXE_SCHEDULE"             [],
311    ReSchedule           => "RE_SCHEDULE"              [],
312    ReloadXml            => "RELOADXML"                [system],
313    Notify               => "NOTIFY"                   [],
314    PhoneFeature         => "PHONE_FEATURE"            [],
315    PhoneFeatureSubscribe => "PHONE_FEATURE_SUBSCRIBE" [],
316    SendMessage          => "SEND_MESSAGE"             [],
317    RecvMessage          => "RECV_MESSAGE"             [],
318    RequestParams        => "REQUEST_PARAMS"           [],
319    ChannelData          => "CHANNEL_DATA"             [channel],
320    General              => "GENERAL"                  [],
321    Command              => "COMMAND"                  [],
322    SessionHeartbeat     => "SESSION_HEARTBEAT"        [system],
323    ClientDisconnected   => "CLIENT_DISCONNECTED"      [],
324    ServerDisconnected   => "SERVER_DISCONNECTED"      [],
325    SendInfo             => "SEND_INFO"                [],
326    RecvInfo             => "RECV_INFO"                [],
327    RecvRtcpMessage      => "RECV_RTCP_MESSAGE"        [in_call],
328    SendRtcpMessage      => "SEND_RTCP_MESSAGE"        [in_call],
329    CallSecure           => "CALL_SECURE"              [in_call],
330    Nat                  => "NAT"                      [],
331    RecordStart          => "RECORD_START"             [media],
332    RecordStop           => "RECORD_STOP"              [media],
333    PlaybackStart        => "PLAYBACK_START"           [media],
334    PlaybackStop         => "PLAYBACK_STOP"            [media],
335    CallUpdate           => "CALL_UPDATE"              [in_call],
336    Failure              => "FAILURE"                  [],
337    SocketData           => "SOCKET_DATA"              [],
338    MediaBugStart        => "MEDIA_BUG_START"          [media],
339    MediaBugStop         => "MEDIA_BUG_STOP"           [media],
340    ConferenceDataQuery  => "CONFERENCE_DATA_QUERY"    [conference],
341    ConferenceData       => "CONFERENCE_DATA"          [conference],
342    CallSetupReq         => "CALL_SETUP_REQ"           [],
343    CallSetupResult      => "CALL_SETUP_RESULT"        [],
344    CallDetail           => "CALL_DETAIL"              [],
345    DeviceState          => "DEVICE_STATE"             [],
346    Text                 => "TEXT"                     [],
347    ShutdownRequested    => "SHUTDOWN_REQUESTED"       [system],
348    /// Subscribe to all events
349    All                  => "ALL"                      [];
350    // --- Not in libs/esl/ EVENT_NAMES[], only in switch_event.c ---
351    // check-event-types.sh stops scanning at the All variant above.
352    /// Present in `switch_event.c` but not in `libs/esl/` EVENT_NAMES[].
353    StartRecording => "START_RECORDING" [media],
354}
355
356parse_error! { ParseEventTypeError("event type"); }
357
358#[cfg(test)]
359mod tests {
360    use super::*;
361
362    #[test]
363    fn test_notify_in_parse() {
364        assert_eq!(
365            EslEventType::parse_event_type("NOTIFY_IN"),
366            Some(EslEventType::NotifyIn)
367        );
368        assert_eq!(EslEventType::parse_event_type("notify_in"), None);
369    }
370
371    #[test]
372    fn test_notify_in_display() {
373        assert_eq!(EslEventType::NotifyIn.to_string(), "NOTIFY_IN");
374    }
375
376    #[test]
377    fn test_notify_in_distinct_from_notify() {
378        assert_ne!(EslEventType::Notify, EslEventType::NotifyIn);
379        assert_ne!(
380            EslEventType::Notify.to_string(),
381            EslEventType::NotifyIn.to_string()
382        );
383    }
384
385    #[test]
386    fn test_wire_names_match_c_esl() {
387        assert_eq!(
388            EslEventType::ChannelOutgoing.to_string(),
389            "CHANNEL_OUTGOING"
390        );
391        assert_eq!(EslEventType::Api.to_string(), "API");
392        assert_eq!(EslEventType::ReloadXml.to_string(), "RELOADXML");
393        assert_eq!(EslEventType::PresenceIn.to_string(), "PRESENCE_IN");
394        assert_eq!(EslEventType::Roster.to_string(), "ROSTER");
395        assert_eq!(EslEventType::Text.to_string(), "TEXT");
396        assert_eq!(EslEventType::ReSchedule.to_string(), "RE_SCHEDULE");
397
398        assert_eq!(
399            EslEventType::parse_event_type("CHANNEL_OUTGOING"),
400            Some(EslEventType::ChannelOutgoing)
401        );
402        assert_eq!(
403            EslEventType::parse_event_type("API"),
404            Some(EslEventType::Api)
405        );
406        assert_eq!(
407            EslEventType::parse_event_type("RELOADXML"),
408            Some(EslEventType::ReloadXml)
409        );
410        assert_eq!(
411            EslEventType::parse_event_type("PRESENCE_IN"),
412            Some(EslEventType::PresenceIn)
413        );
414    }
415
416    #[test]
417    fn test_event_type_from_str() {
418        assert_eq!(
419            "CHANNEL_ANSWER".parse::<EslEventType>(),
420            Ok(EslEventType::ChannelAnswer)
421        );
422        assert!("channel_answer"
423            .parse::<EslEventType>()
424            .is_err());
425        assert!("UNKNOWN_EVENT"
426            .parse::<EslEventType>()
427            .is_err());
428    }
429}