Skip to main content

freeswitch_types/
headers.rs

1//! Typed event header names for FreeSWITCH ESL events.
2
3/// Error returned when parsing an unrecognized event header name.
4#[derive(Debug, Clone, PartialEq, Eq)]
5pub struct ParseEventHeaderError(pub String);
6
7impl std::fmt::Display for ParseEventHeaderError {
8    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
9        write!(f, "unknown event header: {}", self.0)
10    }
11}
12
13impl std::error::Error for ParseEventHeaderError {}
14
15sip_header::define_header_enum! {
16    error_type: ParseEventHeaderError,
17    /// Top-level header names that appear in FreeSWITCH ESL events.
18    ///
19    /// These are the headers on the parsed event itself (not protocol framing
20    /// headers like `Content-Type`). Use with [`EslEvent::header()`](crate::EslEvent::header) for
21    /// type-safe lookups.
22    pub enum EventHeader {
23        EventName => "Event-Name",
24        EventSubclass => "Event-Subclass",
25        UniqueId => "Unique-ID",
26        CallerUniqueId => "Caller-Unique-ID",
27        OtherLegUniqueId => "Other-Leg-Unique-ID",
28        ChannelCallUuid => "Channel-Call-UUID",
29        JobUuid => "Job-UUID",
30        ChannelName => "Channel-Name",
31        ChannelState => "Channel-State",
32        ChannelStateNumber => "Channel-State-Number",
33        ChannelCallState => "Channel-Call-State",
34        AnswerState => "Answer-State",
35        CallDirection => "Call-Direction",
36        HangupCause => "Hangup-Cause",
37        CallerCallerIdName => "Caller-Caller-ID-Name",
38        CallerCallerIdNumber => "Caller-Caller-ID-Number",
39        CallerOrigCallerIdName => "Caller-Orig-Caller-ID-Name",
40        CallerOrigCallerIdNumber => "Caller-Orig-Caller-ID-Number",
41        CallerCalleeIdName => "Caller-Callee-ID-Name",
42        CallerCalleeIdNumber => "Caller-Callee-ID-Number",
43        CallerDestinationNumber => "Caller-Destination-Number",
44        CallerContext => "Caller-Context",
45        CallerDirection => "Caller-Direction",
46        CallerNetworkAddr => "Caller-Network-Addr",
47        CoreUuid => "Core-UUID",
48        DtmfDigit => "DTMF-Digit",
49        Priority => "priority",
50        LogLevel => "Log-Level",
51        /// SIP NOTIFY body content (JSON payload from `NOTIFY_IN` events).
52        PlData => "pl_data",
53        /// SIP event package name from `NOTIFY_IN` events (e.g. `emergency-AbandonedCall`).
54        SipEvent => "event",
55        /// SIP content type from `NOTIFY_IN` events.
56        SipContentType => "sip_content_type",
57        /// Gateway that received the SIP NOTIFY.
58        GatewayName => "gateway_name",
59
60        // --- Codec (from switch_channel_event_set_data / switch_core_codec.c) ---
61        // Audio read
62        ChannelReadCodecName => "Channel-Read-Codec-Name",
63        ChannelReadCodecRate => "Channel-Read-Codec-Rate",
64        ChannelReadCodecBitRate => "Channel-Read-Codec-Bit-Rate",
65        /// Only present when actual_samples_per_second != samples_per_second.
66        ChannelReportedReadCodecRate => "Channel-Reported-Read-Codec-Rate",
67        // Audio write
68        ChannelWriteCodecName => "Channel-Write-Codec-Name",
69        ChannelWriteCodecRate => "Channel-Write-Codec-Rate",
70        ChannelWriteCodecBitRate => "Channel-Write-Codec-Bit-Rate",
71        /// Only present when actual_samples_per_second != samples_per_second.
72        ChannelReportedWriteCodecRate => "Channel-Reported-Write-Codec-Rate",
73        // Video read/write
74        ChannelVideoReadCodecName => "Channel-Video-Read-Codec-Name",
75        ChannelVideoReadCodecRate => "Channel-Video-Read-Codec-Rate",
76        ChannelVideoWriteCodecName => "Channel-Video-Write-Codec-Name",
77        ChannelVideoWriteCodecRate => "Channel-Video-Write-Codec-Rate",
78        /// Active session count from `HEARTBEAT` events.
79        SessionCount => "Session-Count",
80        FreeswitchHostname => "FreeSWITCH-Hostname",
81        FreeswitchSwitchname => "FreeSWITCH-Switchname",
82        FreeswitchIpv4 => "FreeSWITCH-IPv4",
83        FreeswitchIpv6 => "FreeSWITCH-IPv6",
84        FreeswitchVersion => "FreeSWITCH-Version",
85        FreeswitchDomain => "FreeSWITCH-Domain",
86        FreeswitchUser => "FreeSWITCH-User",
87
88        // --- Application (from switch_core_session.c) ---
89        Application => "Application",
90        ApplicationData => "Application-Data",
91        ApplicationResponse => "Application-Response",
92        ApplicationUuid => "Application-UUID",
93
94        // --- Event metadata (from switch_event_prep_for_delivery_detailed) ---
95        EventDateLocal => "Event-Date-Local",
96        EventDateGmt => "Event-Date-GMT",
97        EventDateTimestamp => "Event-Date-Timestamp",
98        EventCallingFile => "Event-Calling-File",
99        EventCallingFunction => "Event-Calling-Function",
100        EventCallingLineNumber => "Event-Calling-Line-Number",
101        EventSequence => "Event-Sequence",
102
103        // --- Channel basic data (from switch_channel_event_set_basic_data) ---
104        ChannelPresenceId => "Channel-Presence-ID",
105        ChannelPresenceData => "Channel-Presence-Data",
106        PresenceDataCols => "Presence-Data-Cols",
107        PresenceCallDirection => "Presence-Call-Direction",
108        ChannelHitDialplan => "Channel-HIT-Dialplan",
109        SessionExternalId => "Session-External-ID",
110        /// `originator` or `originatee` on bridged channel events.
111        OtherType => "Other-Type",
112
113        // --- Callstate change (from switch_channel_perform_set_callstate) ---
114        ChannelCallStateNumber => "Channel-Call-State-Number",
115        OriginalChannelCallState => "Original-Channel-Call-State",
116
117        // --- DTMF (from switch_channel_dequeue_dtmf) ---
118        DtmfDuration => "DTMF-Duration",
119        DtmfSource => "DTMF-Source",
120
121        // --- Caller profile (from switch_caller_profile_event_set_data, "Caller-" prefix) ---
122        CallerLogicalDirection => "Caller-Logical-Direction",
123        CallerUsername => "Caller-Username",
124        CallerDialplan => "Caller-Dialplan",
125        CallerAni => "Caller-ANI",
126        CallerAniii => "Caller-ANI-II",
127        CallerSource => "Caller-Source",
128        CallerTransferSource => "Caller-Transfer-Source",
129        CallerRdnis => "Caller-RDNIS",
130        CallerChannelName => "Caller-Channel-Name",
131        CallerProfileIndex => "Caller-Profile-Index",
132        CallerScreenBit => "Caller-Screen-Bit",
133        CallerPrivacyHideName => "Caller-Privacy-Hide-Name",
134        CallerPrivacyHideNumber => "Caller-Privacy-Hide-Number",
135
136        // --- Other-leg profile (from switch_caller_profile_event_set_data, "Other-Leg" prefix) ---
137        OtherLegDirection => "Other-Leg-Direction",
138        OtherLegLogicalDirection => "Other-Leg-Logical-Direction",
139        OtherLegUsername => "Other-Leg-Username",
140        OtherLegDialplan => "Other-Leg-Dialplan",
141        OtherLegCallerIdName => "Other-Leg-Caller-ID-Name",
142        OtherLegCallerIdNumber => "Other-Leg-Caller-ID-Number",
143        OtherLegOrigCallerIdName => "Other-Leg-Orig-Caller-ID-Name",
144        OtherLegOrigCallerIdNumber => "Other-Leg-Orig-Caller-ID-Number",
145        OtherLegCalleeIdName => "Other-Leg-Callee-ID-Name",
146        OtherLegCalleeIdNumber => "Other-Leg-Callee-ID-Number",
147        OtherLegNetworkAddr => "Other-Leg-Network-Addr",
148        OtherLegAni => "Other-Leg-ANI",
149        OtherLegAniii => "Other-Leg-ANI-II",
150        OtherLegDestinationNumber => "Other-Leg-Destination-Number",
151        OtherLegSource => "Other-Leg-Source",
152        OtherLegTransferSource => "Other-Leg-Transfer-Source",
153        OtherLegContext => "Other-Leg-Context",
154        OtherLegRdnis => "Other-Leg-RDNIS",
155        OtherLegChannelName => "Other-Leg-Channel-Name",
156        OtherLegProfileIndex => "Other-Leg-Profile-Index",
157        OtherLegScreenBit => "Other-Leg-Screen-Bit",
158        OtherLegPrivacyHideName => "Other-Leg-Privacy-Hide-Name",
159        OtherLegPrivacyHideNumber => "Other-Leg-Privacy-Hide-Number",
160
161        // --- Heartbeat (from send_heartbeat in switch_core.c) ---
162        /// Seconds since FreeSWITCH startup.
163        UpTime => "Up-Time",
164        /// Milliseconds since FreeSWITCH startup.
165        UptimeMsec => "Uptime-msec",
166        MaxSessions => "Max-Sessions",
167        SessionPeakMax => "Session-Peak-Max",
168        SessionPeakFiveMin => "Session-Peak-FiveMin",
169        SessionPerSec => "Session-Per-Sec",
170        SessionPerSecFiveMin => "Session-Per-Sec-FiveMin",
171        SessionPerSecMax => "Session-Per-Sec-Max",
172        SessionPerSecLast => "Session-Per-Sec-Last",
173        SessionSinceStartup => "Session-Since-Startup",
174        IdleCpu => "Idle-CPU",
175        HeartbeatInterval => "Heartbeat-Interval",
176        EventInfo => "Event-Info",
177
178        // --- Log (from switch_log_meta_vprintf in switch_log.c) ---
179        LogData => "Log-Data",
180        LogFile => "Log-File",
181        LogFunction => "Log-Function",
182        LogLine => "Log-Line",
183        UserData => "User-Data",
184
185        // --- Application (from switch_core_session_exec in switch_core_session.c) ---
186        ApplicationUuidName => "Application-UUID-Name",
187
188        // --- Sofia event headers (from mod_sofia CUSTOM events) ---
189        Gateway => "Gateway",
190        State => "State",
191        PingStatus => "Ping-Status",
192        Phrase => "Phrase",
193        ProfileName => "profile-name",
194    }
195}
196
197/// Normalize a header key to its canonical form for case-insensitive storage.
198///
199/// FreeSWITCH's C ESL uses case-insensitive header lookups (`strcasecmp`), but
200/// stores header names verbatim. Multiple C code paths emit the same logical
201/// header with different casing (e.g. `switch_channel.c` sends `Unique-ID`
202/// while `switch_event.c` sends `unique-id`). This function normalizes keys
203/// so that both resolve to the same `HashMap` entry.
204///
205/// **Strategy:**
206/// 1. Known [`EventHeader`] variants are matched first (case-insensitive) and
207///    returned in their canonical wire form (e.g. `unique-id` → `Unique-ID`).
208/// 2. Unknown keys containing underscores are returned **unchanged** -- these
209///    are channel variables (`variable_*`) or `sip_h_*` passthrough headers
210///    where the suffix preserves the original SIP header casing.
211/// 3. Unknown dash-separated keys are Title-Cased to match FreeSWITCH's
212///    dominant convention for event and framing headers.
213pub fn normalize_header_key(raw: &str) -> String {
214    if let Ok(eh) = raw.parse::<EventHeader>() {
215        return eh
216            .as_str()
217            .to_string();
218    }
219    if raw.contains('_') {
220        raw.to_string()
221    } else {
222        title_case_dashes(raw)
223    }
224}
225
226fn title_case_dashes(s: &str) -> String {
227    let mut result = String::with_capacity(s.len());
228    let mut capitalize_next = true;
229    for c in s.chars() {
230        if c == '-' {
231            result.push('-');
232            capitalize_next = true;
233        } else if capitalize_next {
234            result.push(c.to_ascii_uppercase());
235            capitalize_next = false;
236        } else {
237            result.push(c.to_ascii_lowercase());
238        }
239    }
240    result
241}
242
243#[cfg(test)]
244mod tests {
245    use super::*;
246
247    #[test]
248    fn display_round_trip() {
249        assert_eq!(EventHeader::UniqueId.to_string(), "Unique-ID");
250        assert_eq!(
251            EventHeader::ChannelCallState.to_string(),
252            "Channel-Call-State"
253        );
254        assert_eq!(
255            EventHeader::CallerCallerIdName.to_string(),
256            "Caller-Caller-ID-Name"
257        );
258        assert_eq!(EventHeader::Priority.to_string(), "priority");
259    }
260
261    #[test]
262    fn as_ref_str() {
263        let h: &str = EventHeader::UniqueId.as_ref();
264        assert_eq!(h, "Unique-ID");
265    }
266
267    #[test]
268    fn from_str_case_insensitive() {
269        assert_eq!(
270            "unique-id".parse::<EventHeader>(),
271            Ok(EventHeader::UniqueId)
272        );
273        assert_eq!(
274            "UNIQUE-ID".parse::<EventHeader>(),
275            Ok(EventHeader::UniqueId)
276        );
277        assert_eq!(
278            "Unique-ID".parse::<EventHeader>(),
279            Ok(EventHeader::UniqueId)
280        );
281        assert_eq!(
282            "channel-call-state".parse::<EventHeader>(),
283            Ok(EventHeader::ChannelCallState)
284        );
285    }
286
287    #[test]
288    fn from_str_unknown() {
289        let err = "X-Custom-Not-In-Enum".parse::<EventHeader>();
290        assert!(err.is_err());
291        assert_eq!(
292            err.unwrap_err()
293                .to_string(),
294            "unknown event header: X-Custom-Not-In-Enum"
295        );
296    }
297
298    // --- normalize_header_key tests ---
299    // FreeSWITCH C ESL uses strcasecmp for header lookups but stores names
300    // verbatim. Multiple C code paths emit the same logical header with
301    // different casing (switch_channel.c Title-Case vs switch_event.c lowercase
302    // vs switch_core_codec.c mixed). normalize_header_key canonicalizes keys
303    // so they collapse to a single HashMap entry.
304
305    #[test]
306    fn normalize_known_enum_variants_return_canonical_form() {
307        // EventHeader::from_str is case-insensitive; canonical as_str() is returned
308        assert_eq!(normalize_header_key("unique-id"), "Unique-ID");
309        assert_eq!(normalize_header_key("UNIQUE-ID"), "Unique-ID");
310        assert_eq!(normalize_header_key("Unique-ID"), "Unique-ID");
311        assert_eq!(normalize_header_key("dtmf-digit"), "DTMF-Digit");
312        assert_eq!(normalize_header_key("DTMF-DIGIT"), "DTMF-Digit");
313        assert_eq!(
314            normalize_header_key("channel-call-uuid"),
315            "Channel-Call-UUID"
316        );
317        assert_eq!(normalize_header_key("event-name"), "Event-Name");
318    }
319
320    #[test]
321    fn normalize_known_underscore_variants_return_canonical_form() {
322        // Headers whose canonical form contains underscores
323        assert_eq!(normalize_header_key("priority"), "priority");
324        assert_eq!(normalize_header_key("PRIORITY"), "priority");
325        assert_eq!(normalize_header_key("pl_data"), "pl_data");
326        assert_eq!(normalize_header_key("PL_DATA"), "pl_data");
327        assert_eq!(normalize_header_key("sip_content_type"), "sip_content_type");
328        assert_eq!(normalize_header_key("gateway_name"), "gateway_name");
329        assert_eq!(normalize_header_key("event"), "event");
330        assert_eq!(normalize_header_key("EVENT"), "event");
331    }
332
333    #[test]
334    fn normalize_codec_headers_from_switch_core_codec() {
335        // switch_core_codec.c sends lowercase, switch_channel_event_set_data sends Title-Case
336        // Both must normalize to the canonical EventHeader form
337        assert_eq!(
338            normalize_header_key("channel-read-codec-bit-rate"),
339            "Channel-Read-Codec-Bit-Rate"
340        );
341        assert_eq!(
342            normalize_header_key("Channel-Read-Codec-Bit-Rate"),
343            "Channel-Read-Codec-Bit-Rate"
344        );
345        // switch_core_codec.c mixed case for write: "Channel-Write-codec-bit-rate"
346        assert_eq!(
347            normalize_header_key("Channel-Write-codec-bit-rate"),
348            "Channel-Write-Codec-Bit-Rate"
349        );
350        assert_eq!(
351            normalize_header_key("channel-video-read-codec-name"),
352            "Channel-Video-Read-Codec-Name"
353        );
354    }
355
356    #[test]
357    fn normalize_unknown_underscore_keys_passthrough() {
358        // Channel variables and sip_h_* passthrough preserve original casing
359        assert_eq!(
360            normalize_header_key("variable_sip_call_id"),
361            "variable_sip_call_id"
362        );
363        assert_eq!(
364            normalize_header_key("variable_sip_h_X-My-CUSTOM-Header"),
365            "variable_sip_h_X-My-CUSTOM-Header"
366        );
367        assert_eq!(
368            normalize_header_key("variable_sip_h_Diversion"),
369            "variable_sip_h_Diversion"
370        );
371    }
372
373    #[test]
374    fn normalize_unknown_dash_keys_title_case() {
375        // Framing and unknown event headers get Title-Cased
376        assert_eq!(normalize_header_key("content-type"), "Content-Type");
377        assert_eq!(normalize_header_key("Content-Type"), "Content-Type");
378        assert_eq!(normalize_header_key("CONTENT-TYPE"), "Content-Type");
379        assert_eq!(normalize_header_key("x-custom-header"), "X-Custom-Header");
380        assert_eq!(
381            normalize_header_key("Content-Disposition"),
382            "Content-Disposition"
383        );
384        assert_eq!(normalize_header_key("reply-text"), "Reply-Text");
385    }
386
387    #[test]
388    fn normalize_idempotent_for_all_enum_variants() {
389        // Normalizing an already-canonical wire string must return it unchanged
390        let variants = [
391            EventHeader::EventName,
392            EventHeader::UniqueId,
393            EventHeader::ChannelCallUuid,
394            EventHeader::DtmfDigit,
395            EventHeader::Priority,
396            EventHeader::PlData,
397            EventHeader::SipEvent,
398            EventHeader::GatewayName,
399            EventHeader::SipContentType,
400            EventHeader::ChannelReadCodecBitRate,
401            EventHeader::ChannelVideoWriteCodecRate,
402            EventHeader::LogLevel,
403        ];
404        for v in variants {
405            let canonical = v.as_str();
406            assert_eq!(
407                normalize_header_key(canonical),
408                canonical,
409                "normalization not idempotent for {canonical}"
410            );
411        }
412    }
413
414    #[test]
415    fn parse_missing_event_default_headers() {
416        // From switch_event_prep_for_delivery_detailed (switch_event.c)
417        assert!("Event-Date-Local"
418            .parse::<EventHeader>()
419            .is_ok());
420        assert!("Event-Date-GMT"
421            .parse::<EventHeader>()
422            .is_ok());
423        assert!("Event-Date-Timestamp"
424            .parse::<EventHeader>()
425            .is_ok());
426        assert!("Event-Calling-File"
427            .parse::<EventHeader>()
428            .is_ok());
429        assert!("Event-Calling-Function"
430            .parse::<EventHeader>()
431            .is_ok());
432        assert!("Event-Calling-Line-Number"
433            .parse::<EventHeader>()
434            .is_ok());
435        assert!("Event-Sequence"
436            .parse::<EventHeader>()
437            .is_ok());
438    }
439
440    #[test]
441    fn parse_missing_channel_basic_data_headers() {
442        // From switch_channel_event_set_basic_data (switch_channel.c)
443        assert!("Channel-Presence-ID"
444            .parse::<EventHeader>()
445            .is_ok());
446        assert!("Channel-Presence-Data"
447            .parse::<EventHeader>()
448            .is_ok());
449        assert!("Presence-Data-Cols"
450            .parse::<EventHeader>()
451            .is_ok());
452        assert!("Presence-Call-Direction"
453            .parse::<EventHeader>()
454            .is_ok());
455        assert!("Channel-HIT-Dialplan"
456            .parse::<EventHeader>()
457            .is_ok());
458        assert!("Session-External-ID"
459            .parse::<EventHeader>()
460            .is_ok());
461        assert!("Other-Type"
462            .parse::<EventHeader>()
463            .is_ok());
464    }
465
466    #[test]
467    fn parse_missing_callstate_and_dtmf_headers() {
468        // From switch_channel_perform_set_callstate (switch_channel.c)
469        assert!("Channel-Call-State-Number"
470            .parse::<EventHeader>()
471            .is_ok());
472        assert!("Original-Channel-Call-State"
473            .parse::<EventHeader>()
474            .is_ok());
475        // From switch_channel_dequeue_dtmf (switch_channel.c)
476        assert!("DTMF-Duration"
477            .parse::<EventHeader>()
478            .is_ok());
479        assert!("DTMF-Source"
480            .parse::<EventHeader>()
481            .is_ok());
482    }
483
484    #[test]
485    fn parse_missing_caller_profile_headers() {
486        // From switch_caller_profile_event_set_data (switch_caller.c) with "Caller-" prefix
487        assert!("Caller-Logical-Direction"
488            .parse::<EventHeader>()
489            .is_ok());
490        assert!("Caller-Username"
491            .parse::<EventHeader>()
492            .is_ok());
493        assert!("Caller-Dialplan"
494            .parse::<EventHeader>()
495            .is_ok());
496        assert!("Caller-ANI"
497            .parse::<EventHeader>()
498            .is_ok());
499        assert!("Caller-ANI-II"
500            .parse::<EventHeader>()
501            .is_ok());
502        assert!("Caller-Source"
503            .parse::<EventHeader>()
504            .is_ok());
505        assert!("Caller-Transfer-Source"
506            .parse::<EventHeader>()
507            .is_ok());
508        assert!("Caller-RDNIS"
509            .parse::<EventHeader>()
510            .is_ok());
511        assert!("Caller-Channel-Name"
512            .parse::<EventHeader>()
513            .is_ok());
514        assert!("Caller-Profile-Index"
515            .parse::<EventHeader>()
516            .is_ok());
517        assert!("Caller-Screen-Bit"
518            .parse::<EventHeader>()
519            .is_ok());
520        assert!("Caller-Privacy-Hide-Name"
521            .parse::<EventHeader>()
522            .is_ok());
523        assert!("Caller-Privacy-Hide-Number"
524            .parse::<EventHeader>()
525            .is_ok());
526    }
527
528    #[test]
529    fn parse_heartbeat_headers() {
530        assert!("Up-Time"
531            .parse::<EventHeader>()
532            .is_ok());
533        assert!("Uptime-msec"
534            .parse::<EventHeader>()
535            .is_ok());
536        assert!("Max-Sessions"
537            .parse::<EventHeader>()
538            .is_ok());
539        assert!("Session-Peak-Max"
540            .parse::<EventHeader>()
541            .is_ok());
542        assert!("Session-Peak-FiveMin"
543            .parse::<EventHeader>()
544            .is_ok());
545        assert!("Session-Per-Sec"
546            .parse::<EventHeader>()
547            .is_ok());
548        assert!("Session-Per-Sec-FiveMin"
549            .parse::<EventHeader>()
550            .is_ok());
551        assert!("Session-Per-Sec-Max"
552            .parse::<EventHeader>()
553            .is_ok());
554        assert!("Session-Per-Sec-Last"
555            .parse::<EventHeader>()
556            .is_ok());
557        assert!("Session-Since-Startup"
558            .parse::<EventHeader>()
559            .is_ok());
560        assert!("Idle-CPU"
561            .parse::<EventHeader>()
562            .is_ok());
563        assert!("Heartbeat-Interval"
564            .parse::<EventHeader>()
565            .is_ok());
566        assert!("Event-Info"
567            .parse::<EventHeader>()
568            .is_ok());
569    }
570
571    #[test]
572    fn parse_log_headers() {
573        assert!("Log-Data"
574            .parse::<EventHeader>()
575            .is_ok());
576        assert!("Log-File"
577            .parse::<EventHeader>()
578            .is_ok());
579        assert!("Log-Function"
580            .parse::<EventHeader>()
581            .is_ok());
582        assert!("Log-Line"
583            .parse::<EventHeader>()
584            .is_ok());
585        assert!("User-Data"
586            .parse::<EventHeader>()
587            .is_ok());
588    }
589
590    #[test]
591    fn parse_application_uuid_name() {
592        assert!("Application-UUID-Name"
593            .parse::<EventHeader>()
594            .is_ok());
595    }
596
597    #[test]
598    fn parse_sofia_event_headers() {
599        assert_eq!("Gateway".parse::<EventHeader>(), Ok(EventHeader::Gateway));
600        assert_eq!("State".parse::<EventHeader>(), Ok(EventHeader::State));
601        assert_eq!(
602            "Ping-Status".parse::<EventHeader>(),
603            Ok(EventHeader::PingStatus)
604        );
605        assert_eq!("Phrase".parse::<EventHeader>(), Ok(EventHeader::Phrase));
606        assert_eq!(
607            "profile-name".parse::<EventHeader>(),
608            Ok(EventHeader::ProfileName)
609        );
610    }
611
612    #[test]
613    fn parse_missing_other_leg_headers() {
614        // From switch_caller_profile_event_set_data with "Other-Leg" prefix
615        assert!("Other-Leg-Direction"
616            .parse::<EventHeader>()
617            .is_ok());
618        assert!("Other-Leg-Logical-Direction"
619            .parse::<EventHeader>()
620            .is_ok());
621        assert!("Other-Leg-Username"
622            .parse::<EventHeader>()
623            .is_ok());
624        assert!("Other-Leg-Dialplan"
625            .parse::<EventHeader>()
626            .is_ok());
627        assert!("Other-Leg-Caller-ID-Name"
628            .parse::<EventHeader>()
629            .is_ok());
630        assert!("Other-Leg-Caller-ID-Number"
631            .parse::<EventHeader>()
632            .is_ok());
633        assert!("Other-Leg-Orig-Caller-ID-Name"
634            .parse::<EventHeader>()
635            .is_ok());
636        assert!("Other-Leg-Orig-Caller-ID-Number"
637            .parse::<EventHeader>()
638            .is_ok());
639        assert!("Other-Leg-Callee-ID-Name"
640            .parse::<EventHeader>()
641            .is_ok());
642        assert!("Other-Leg-Callee-ID-Number"
643            .parse::<EventHeader>()
644            .is_ok());
645        assert!("Other-Leg-Network-Addr"
646            .parse::<EventHeader>()
647            .is_ok());
648        assert!("Other-Leg-ANI"
649            .parse::<EventHeader>()
650            .is_ok());
651        assert!("Other-Leg-ANI-II"
652            .parse::<EventHeader>()
653            .is_ok());
654        assert!("Other-Leg-Destination-Number"
655            .parse::<EventHeader>()
656            .is_ok());
657        assert!("Other-Leg-Source"
658            .parse::<EventHeader>()
659            .is_ok());
660        assert!("Other-Leg-Transfer-Source"
661            .parse::<EventHeader>()
662            .is_ok());
663        assert!("Other-Leg-Context"
664            .parse::<EventHeader>()
665            .is_ok());
666        assert!("Other-Leg-RDNIS"
667            .parse::<EventHeader>()
668            .is_ok());
669        assert!("Other-Leg-Channel-Name"
670            .parse::<EventHeader>()
671            .is_ok());
672        assert!("Other-Leg-Profile-Index"
673            .parse::<EventHeader>()
674            .is_ok());
675        assert!("Other-Leg-Screen-Bit"
676            .parse::<EventHeader>()
677            .is_ok());
678        assert!("Other-Leg-Privacy-Hide-Name"
679            .parse::<EventHeader>()
680            .is_ok());
681        assert!("Other-Leg-Privacy-Hide-Number"
682            .parse::<EventHeader>()
683            .is_ok());
684    }
685
686    #[test]
687    fn from_str_round_trip_all_variants() {
688        let variants = [
689            EventHeader::EventName,
690            EventHeader::EventSubclass,
691            EventHeader::UniqueId,
692            EventHeader::CallerUniqueId,
693            EventHeader::OtherLegUniqueId,
694            EventHeader::ChannelCallUuid,
695            EventHeader::JobUuid,
696            EventHeader::ChannelName,
697            EventHeader::ChannelState,
698            EventHeader::ChannelStateNumber,
699            EventHeader::ChannelCallState,
700            EventHeader::AnswerState,
701            EventHeader::CallDirection,
702            EventHeader::HangupCause,
703            EventHeader::CallerCallerIdName,
704            EventHeader::CallerCallerIdNumber,
705            EventHeader::CallerOrigCallerIdName,
706            EventHeader::CallerOrigCallerIdNumber,
707            EventHeader::CallerCalleeIdName,
708            EventHeader::CallerCalleeIdNumber,
709            EventHeader::CallerDestinationNumber,
710            EventHeader::CallerContext,
711            EventHeader::CallerDirection,
712            EventHeader::CallerNetworkAddr,
713            EventHeader::CoreUuid,
714            EventHeader::DtmfDigit,
715            EventHeader::Priority,
716            EventHeader::LogLevel,
717            EventHeader::PlData,
718            EventHeader::SipEvent,
719            EventHeader::SipContentType,
720            EventHeader::GatewayName,
721            EventHeader::ChannelReadCodecName,
722            EventHeader::ChannelReadCodecRate,
723            EventHeader::ChannelReadCodecBitRate,
724            EventHeader::ChannelReportedReadCodecRate,
725            EventHeader::ChannelWriteCodecName,
726            EventHeader::ChannelWriteCodecRate,
727            EventHeader::ChannelWriteCodecBitRate,
728            EventHeader::ChannelReportedWriteCodecRate,
729            EventHeader::ChannelVideoReadCodecName,
730            EventHeader::ChannelVideoReadCodecRate,
731            EventHeader::ChannelVideoWriteCodecName,
732            EventHeader::ChannelVideoWriteCodecRate,
733            EventHeader::Application,
734            EventHeader::ApplicationData,
735            EventHeader::ApplicationResponse,
736            EventHeader::ApplicationUuid,
737            EventHeader::EventDateLocal,
738            EventHeader::EventDateGmt,
739            EventHeader::EventDateTimestamp,
740            EventHeader::EventCallingFile,
741            EventHeader::EventCallingFunction,
742            EventHeader::EventCallingLineNumber,
743            EventHeader::EventSequence,
744            EventHeader::ChannelPresenceId,
745            EventHeader::ChannelPresenceData,
746            EventHeader::PresenceDataCols,
747            EventHeader::PresenceCallDirection,
748            EventHeader::ChannelHitDialplan,
749            EventHeader::SessionExternalId,
750            EventHeader::OtherType,
751            EventHeader::ChannelCallStateNumber,
752            EventHeader::OriginalChannelCallState,
753            EventHeader::DtmfDuration,
754            EventHeader::DtmfSource,
755            EventHeader::CallerLogicalDirection,
756            EventHeader::CallerUsername,
757            EventHeader::CallerDialplan,
758            EventHeader::CallerAni,
759            EventHeader::CallerAniii,
760            EventHeader::CallerSource,
761            EventHeader::CallerTransferSource,
762            EventHeader::CallerRdnis,
763            EventHeader::CallerChannelName,
764            EventHeader::CallerProfileIndex,
765            EventHeader::CallerScreenBit,
766            EventHeader::CallerPrivacyHideName,
767            EventHeader::CallerPrivacyHideNumber,
768            EventHeader::OtherLegDirection,
769            EventHeader::OtherLegLogicalDirection,
770            EventHeader::OtherLegUsername,
771            EventHeader::OtherLegDialplan,
772            EventHeader::OtherLegCallerIdName,
773            EventHeader::OtherLegCallerIdNumber,
774            EventHeader::OtherLegOrigCallerIdName,
775            EventHeader::OtherLegOrigCallerIdNumber,
776            EventHeader::OtherLegCalleeIdName,
777            EventHeader::OtherLegCalleeIdNumber,
778            EventHeader::OtherLegNetworkAddr,
779            EventHeader::OtherLegAni,
780            EventHeader::OtherLegAniii,
781            EventHeader::OtherLegDestinationNumber,
782            EventHeader::OtherLegSource,
783            EventHeader::OtherLegTransferSource,
784            EventHeader::OtherLegContext,
785            EventHeader::OtherLegRdnis,
786            EventHeader::OtherLegChannelName,
787            EventHeader::OtherLegProfileIndex,
788            EventHeader::OtherLegScreenBit,
789            EventHeader::OtherLegPrivacyHideName,
790            EventHeader::OtherLegPrivacyHideNumber,
791            EventHeader::UpTime,
792            EventHeader::UptimeMsec,
793            EventHeader::MaxSessions,
794            EventHeader::SessionPeakMax,
795            EventHeader::SessionPeakFiveMin,
796            EventHeader::SessionPerSec,
797            EventHeader::SessionPerSecFiveMin,
798            EventHeader::SessionPerSecMax,
799            EventHeader::SessionPerSecLast,
800            EventHeader::SessionSinceStartup,
801            EventHeader::IdleCpu,
802            EventHeader::HeartbeatInterval,
803            EventHeader::EventInfo,
804            EventHeader::LogData,
805            EventHeader::LogFile,
806            EventHeader::LogFunction,
807            EventHeader::LogLine,
808            EventHeader::UserData,
809            EventHeader::ApplicationUuidName,
810            EventHeader::Gateway,
811            EventHeader::State,
812            EventHeader::PingStatus,
813            EventHeader::Phrase,
814            EventHeader::ProfileName,
815        ];
816        for v in variants {
817            let wire = v.to_string();
818            let parsed: EventHeader = wire
819                .parse()
820                .unwrap();
821            assert_eq!(parsed, v, "round-trip failed for {wire}");
822        }
823    }
824}