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}
189
190/// Normalize a header key to its canonical form for case-insensitive storage.
191///
192/// FreeSWITCH's C ESL uses case-insensitive header lookups (`strcasecmp`), but
193/// stores header names verbatim. Multiple C code paths emit the same logical
194/// header with different casing (e.g. `switch_channel.c` sends `Unique-ID`
195/// while `switch_event.c` sends `unique-id`). This function normalizes keys
196/// so that both resolve to the same `HashMap` entry.
197///
198/// **Strategy:**
199/// 1. Known [`EventHeader`] variants are matched first (case-insensitive) and
200///    returned in their canonical wire form (e.g. `unique-id` → `Unique-ID`).
201/// 2. Unknown keys containing underscores are returned **unchanged** -- these
202///    are channel variables (`variable_*`) or `sip_h_*` passthrough headers
203///    where the suffix preserves the original SIP header casing.
204/// 3. Unknown dash-separated keys are Title-Cased to match FreeSWITCH's
205///    dominant convention for event and framing headers.
206pub fn normalize_header_key(raw: &str) -> String {
207    if let Ok(eh) = raw.parse::<EventHeader>() {
208        return eh
209            .as_str()
210            .to_string();
211    }
212    if raw.contains('_') {
213        raw.to_string()
214    } else {
215        title_case_dashes(raw)
216    }
217}
218
219fn title_case_dashes(s: &str) -> String {
220    let mut result = String::with_capacity(s.len());
221    let mut capitalize_next = true;
222    for c in s.chars() {
223        if c == '-' {
224            result.push('-');
225            capitalize_next = true;
226        } else if capitalize_next {
227            result.push(c.to_ascii_uppercase());
228            capitalize_next = false;
229        } else {
230            result.push(c.to_ascii_lowercase());
231        }
232    }
233    result
234}
235
236#[cfg(test)]
237mod tests {
238    use super::*;
239
240    #[test]
241    fn display_round_trip() {
242        assert_eq!(EventHeader::UniqueId.to_string(), "Unique-ID");
243        assert_eq!(
244            EventHeader::ChannelCallState.to_string(),
245            "Channel-Call-State"
246        );
247        assert_eq!(
248            EventHeader::CallerCallerIdName.to_string(),
249            "Caller-Caller-ID-Name"
250        );
251        assert_eq!(EventHeader::Priority.to_string(), "priority");
252    }
253
254    #[test]
255    fn as_ref_str() {
256        let h: &str = EventHeader::UniqueId.as_ref();
257        assert_eq!(h, "Unique-ID");
258    }
259
260    #[test]
261    fn from_str_case_insensitive() {
262        assert_eq!(
263            "unique-id".parse::<EventHeader>(),
264            Ok(EventHeader::UniqueId)
265        );
266        assert_eq!(
267            "UNIQUE-ID".parse::<EventHeader>(),
268            Ok(EventHeader::UniqueId)
269        );
270        assert_eq!(
271            "Unique-ID".parse::<EventHeader>(),
272            Ok(EventHeader::UniqueId)
273        );
274        assert_eq!(
275            "channel-call-state".parse::<EventHeader>(),
276            Ok(EventHeader::ChannelCallState)
277        );
278    }
279
280    #[test]
281    fn from_str_unknown() {
282        let err = "X-Custom-Not-In-Enum".parse::<EventHeader>();
283        assert!(err.is_err());
284        assert_eq!(
285            err.unwrap_err()
286                .to_string(),
287            "unknown event header: X-Custom-Not-In-Enum"
288        );
289    }
290
291    // --- normalize_header_key tests ---
292    // FreeSWITCH C ESL uses strcasecmp for header lookups but stores names
293    // verbatim. Multiple C code paths emit the same logical header with
294    // different casing (switch_channel.c Title-Case vs switch_event.c lowercase
295    // vs switch_core_codec.c mixed). normalize_header_key canonicalizes keys
296    // so they collapse to a single HashMap entry.
297
298    #[test]
299    fn normalize_known_enum_variants_return_canonical_form() {
300        // EventHeader::from_str is case-insensitive; canonical as_str() is returned
301        assert_eq!(normalize_header_key("unique-id"), "Unique-ID");
302        assert_eq!(normalize_header_key("UNIQUE-ID"), "Unique-ID");
303        assert_eq!(normalize_header_key("Unique-ID"), "Unique-ID");
304        assert_eq!(normalize_header_key("dtmf-digit"), "DTMF-Digit");
305        assert_eq!(normalize_header_key("DTMF-DIGIT"), "DTMF-Digit");
306        assert_eq!(
307            normalize_header_key("channel-call-uuid"),
308            "Channel-Call-UUID"
309        );
310        assert_eq!(normalize_header_key("event-name"), "Event-Name");
311    }
312
313    #[test]
314    fn normalize_known_underscore_variants_return_canonical_form() {
315        // Headers whose canonical form contains underscores
316        assert_eq!(normalize_header_key("priority"), "priority");
317        assert_eq!(normalize_header_key("PRIORITY"), "priority");
318        assert_eq!(normalize_header_key("pl_data"), "pl_data");
319        assert_eq!(normalize_header_key("PL_DATA"), "pl_data");
320        assert_eq!(normalize_header_key("sip_content_type"), "sip_content_type");
321        assert_eq!(normalize_header_key("gateway_name"), "gateway_name");
322        assert_eq!(normalize_header_key("event"), "event");
323        assert_eq!(normalize_header_key("EVENT"), "event");
324    }
325
326    #[test]
327    fn normalize_codec_headers_from_switch_core_codec() {
328        // switch_core_codec.c sends lowercase, switch_channel_event_set_data sends Title-Case
329        // Both must normalize to the canonical EventHeader form
330        assert_eq!(
331            normalize_header_key("channel-read-codec-bit-rate"),
332            "Channel-Read-Codec-Bit-Rate"
333        );
334        assert_eq!(
335            normalize_header_key("Channel-Read-Codec-Bit-Rate"),
336            "Channel-Read-Codec-Bit-Rate"
337        );
338        // switch_core_codec.c mixed case for write: "Channel-Write-codec-bit-rate"
339        assert_eq!(
340            normalize_header_key("Channel-Write-codec-bit-rate"),
341            "Channel-Write-Codec-Bit-Rate"
342        );
343        assert_eq!(
344            normalize_header_key("channel-video-read-codec-name"),
345            "Channel-Video-Read-Codec-Name"
346        );
347    }
348
349    #[test]
350    fn normalize_unknown_underscore_keys_passthrough() {
351        // Channel variables and sip_h_* passthrough preserve original casing
352        assert_eq!(
353            normalize_header_key("variable_sip_call_id"),
354            "variable_sip_call_id"
355        );
356        assert_eq!(
357            normalize_header_key("variable_sip_h_X-My-CUSTOM-Header"),
358            "variable_sip_h_X-My-CUSTOM-Header"
359        );
360        assert_eq!(
361            normalize_header_key("variable_sip_h_Diversion"),
362            "variable_sip_h_Diversion"
363        );
364    }
365
366    #[test]
367    fn normalize_unknown_dash_keys_title_case() {
368        // Framing and unknown event headers get Title-Cased
369        assert_eq!(normalize_header_key("content-type"), "Content-Type");
370        assert_eq!(normalize_header_key("Content-Type"), "Content-Type");
371        assert_eq!(normalize_header_key("CONTENT-TYPE"), "Content-Type");
372        assert_eq!(normalize_header_key("x-custom-header"), "X-Custom-Header");
373        assert_eq!(
374            normalize_header_key("Content-Disposition"),
375            "Content-Disposition"
376        );
377        assert_eq!(normalize_header_key("reply-text"), "Reply-Text");
378    }
379
380    #[test]
381    fn normalize_idempotent_for_all_enum_variants() {
382        // Normalizing an already-canonical wire string must return it unchanged
383        let variants = [
384            EventHeader::EventName,
385            EventHeader::UniqueId,
386            EventHeader::ChannelCallUuid,
387            EventHeader::DtmfDigit,
388            EventHeader::Priority,
389            EventHeader::PlData,
390            EventHeader::SipEvent,
391            EventHeader::GatewayName,
392            EventHeader::SipContentType,
393            EventHeader::ChannelReadCodecBitRate,
394            EventHeader::ChannelVideoWriteCodecRate,
395            EventHeader::LogLevel,
396        ];
397        for v in variants {
398            let canonical = v.as_str();
399            assert_eq!(
400                normalize_header_key(canonical),
401                canonical,
402                "normalization not idempotent for {canonical}"
403            );
404        }
405    }
406
407    #[test]
408    fn parse_missing_event_default_headers() {
409        // From switch_event_prep_for_delivery_detailed (switch_event.c)
410        assert!("Event-Date-Local"
411            .parse::<EventHeader>()
412            .is_ok());
413        assert!("Event-Date-GMT"
414            .parse::<EventHeader>()
415            .is_ok());
416        assert!("Event-Date-Timestamp"
417            .parse::<EventHeader>()
418            .is_ok());
419        assert!("Event-Calling-File"
420            .parse::<EventHeader>()
421            .is_ok());
422        assert!("Event-Calling-Function"
423            .parse::<EventHeader>()
424            .is_ok());
425        assert!("Event-Calling-Line-Number"
426            .parse::<EventHeader>()
427            .is_ok());
428        assert!("Event-Sequence"
429            .parse::<EventHeader>()
430            .is_ok());
431    }
432
433    #[test]
434    fn parse_missing_channel_basic_data_headers() {
435        // From switch_channel_event_set_basic_data (switch_channel.c)
436        assert!("Channel-Presence-ID"
437            .parse::<EventHeader>()
438            .is_ok());
439        assert!("Channel-Presence-Data"
440            .parse::<EventHeader>()
441            .is_ok());
442        assert!("Presence-Data-Cols"
443            .parse::<EventHeader>()
444            .is_ok());
445        assert!("Presence-Call-Direction"
446            .parse::<EventHeader>()
447            .is_ok());
448        assert!("Channel-HIT-Dialplan"
449            .parse::<EventHeader>()
450            .is_ok());
451        assert!("Session-External-ID"
452            .parse::<EventHeader>()
453            .is_ok());
454        assert!("Other-Type"
455            .parse::<EventHeader>()
456            .is_ok());
457    }
458
459    #[test]
460    fn parse_missing_callstate_and_dtmf_headers() {
461        // From switch_channel_perform_set_callstate (switch_channel.c)
462        assert!("Channel-Call-State-Number"
463            .parse::<EventHeader>()
464            .is_ok());
465        assert!("Original-Channel-Call-State"
466            .parse::<EventHeader>()
467            .is_ok());
468        // From switch_channel_dequeue_dtmf (switch_channel.c)
469        assert!("DTMF-Duration"
470            .parse::<EventHeader>()
471            .is_ok());
472        assert!("DTMF-Source"
473            .parse::<EventHeader>()
474            .is_ok());
475    }
476
477    #[test]
478    fn parse_missing_caller_profile_headers() {
479        // From switch_caller_profile_event_set_data (switch_caller.c) with "Caller-" prefix
480        assert!("Caller-Logical-Direction"
481            .parse::<EventHeader>()
482            .is_ok());
483        assert!("Caller-Username"
484            .parse::<EventHeader>()
485            .is_ok());
486        assert!("Caller-Dialplan"
487            .parse::<EventHeader>()
488            .is_ok());
489        assert!("Caller-ANI"
490            .parse::<EventHeader>()
491            .is_ok());
492        assert!("Caller-ANI-II"
493            .parse::<EventHeader>()
494            .is_ok());
495        assert!("Caller-Source"
496            .parse::<EventHeader>()
497            .is_ok());
498        assert!("Caller-Transfer-Source"
499            .parse::<EventHeader>()
500            .is_ok());
501        assert!("Caller-RDNIS"
502            .parse::<EventHeader>()
503            .is_ok());
504        assert!("Caller-Channel-Name"
505            .parse::<EventHeader>()
506            .is_ok());
507        assert!("Caller-Profile-Index"
508            .parse::<EventHeader>()
509            .is_ok());
510        assert!("Caller-Screen-Bit"
511            .parse::<EventHeader>()
512            .is_ok());
513        assert!("Caller-Privacy-Hide-Name"
514            .parse::<EventHeader>()
515            .is_ok());
516        assert!("Caller-Privacy-Hide-Number"
517            .parse::<EventHeader>()
518            .is_ok());
519    }
520
521    #[test]
522    fn parse_heartbeat_headers() {
523        assert!("Up-Time"
524            .parse::<EventHeader>()
525            .is_ok());
526        assert!("Uptime-msec"
527            .parse::<EventHeader>()
528            .is_ok());
529        assert!("Max-Sessions"
530            .parse::<EventHeader>()
531            .is_ok());
532        assert!("Session-Peak-Max"
533            .parse::<EventHeader>()
534            .is_ok());
535        assert!("Session-Peak-FiveMin"
536            .parse::<EventHeader>()
537            .is_ok());
538        assert!("Session-Per-Sec"
539            .parse::<EventHeader>()
540            .is_ok());
541        assert!("Session-Per-Sec-FiveMin"
542            .parse::<EventHeader>()
543            .is_ok());
544        assert!("Session-Per-Sec-Max"
545            .parse::<EventHeader>()
546            .is_ok());
547        assert!("Session-Per-Sec-Last"
548            .parse::<EventHeader>()
549            .is_ok());
550        assert!("Session-Since-Startup"
551            .parse::<EventHeader>()
552            .is_ok());
553        assert!("Idle-CPU"
554            .parse::<EventHeader>()
555            .is_ok());
556        assert!("Heartbeat-Interval"
557            .parse::<EventHeader>()
558            .is_ok());
559        assert!("Event-Info"
560            .parse::<EventHeader>()
561            .is_ok());
562    }
563
564    #[test]
565    fn parse_log_headers() {
566        assert!("Log-Data"
567            .parse::<EventHeader>()
568            .is_ok());
569        assert!("Log-File"
570            .parse::<EventHeader>()
571            .is_ok());
572        assert!("Log-Function"
573            .parse::<EventHeader>()
574            .is_ok());
575        assert!("Log-Line"
576            .parse::<EventHeader>()
577            .is_ok());
578        assert!("User-Data"
579            .parse::<EventHeader>()
580            .is_ok());
581    }
582
583    #[test]
584    fn parse_application_uuid_name() {
585        assert!("Application-UUID-Name"
586            .parse::<EventHeader>()
587            .is_ok());
588    }
589
590    #[test]
591    fn parse_missing_other_leg_headers() {
592        // From switch_caller_profile_event_set_data with "Other-Leg" prefix
593        assert!("Other-Leg-Direction"
594            .parse::<EventHeader>()
595            .is_ok());
596        assert!("Other-Leg-Logical-Direction"
597            .parse::<EventHeader>()
598            .is_ok());
599        assert!("Other-Leg-Username"
600            .parse::<EventHeader>()
601            .is_ok());
602        assert!("Other-Leg-Dialplan"
603            .parse::<EventHeader>()
604            .is_ok());
605        assert!("Other-Leg-Caller-ID-Name"
606            .parse::<EventHeader>()
607            .is_ok());
608        assert!("Other-Leg-Caller-ID-Number"
609            .parse::<EventHeader>()
610            .is_ok());
611        assert!("Other-Leg-Orig-Caller-ID-Name"
612            .parse::<EventHeader>()
613            .is_ok());
614        assert!("Other-Leg-Orig-Caller-ID-Number"
615            .parse::<EventHeader>()
616            .is_ok());
617        assert!("Other-Leg-Callee-ID-Name"
618            .parse::<EventHeader>()
619            .is_ok());
620        assert!("Other-Leg-Callee-ID-Number"
621            .parse::<EventHeader>()
622            .is_ok());
623        assert!("Other-Leg-Network-Addr"
624            .parse::<EventHeader>()
625            .is_ok());
626        assert!("Other-Leg-ANI"
627            .parse::<EventHeader>()
628            .is_ok());
629        assert!("Other-Leg-ANI-II"
630            .parse::<EventHeader>()
631            .is_ok());
632        assert!("Other-Leg-Destination-Number"
633            .parse::<EventHeader>()
634            .is_ok());
635        assert!("Other-Leg-Source"
636            .parse::<EventHeader>()
637            .is_ok());
638        assert!("Other-Leg-Transfer-Source"
639            .parse::<EventHeader>()
640            .is_ok());
641        assert!("Other-Leg-Context"
642            .parse::<EventHeader>()
643            .is_ok());
644        assert!("Other-Leg-RDNIS"
645            .parse::<EventHeader>()
646            .is_ok());
647        assert!("Other-Leg-Channel-Name"
648            .parse::<EventHeader>()
649            .is_ok());
650        assert!("Other-Leg-Profile-Index"
651            .parse::<EventHeader>()
652            .is_ok());
653        assert!("Other-Leg-Screen-Bit"
654            .parse::<EventHeader>()
655            .is_ok());
656        assert!("Other-Leg-Privacy-Hide-Name"
657            .parse::<EventHeader>()
658            .is_ok());
659        assert!("Other-Leg-Privacy-Hide-Number"
660            .parse::<EventHeader>()
661            .is_ok());
662    }
663
664    #[test]
665    fn from_str_round_trip_all_variants() {
666        let variants = [
667            EventHeader::EventName,
668            EventHeader::EventSubclass,
669            EventHeader::UniqueId,
670            EventHeader::CallerUniqueId,
671            EventHeader::OtherLegUniqueId,
672            EventHeader::ChannelCallUuid,
673            EventHeader::JobUuid,
674            EventHeader::ChannelName,
675            EventHeader::ChannelState,
676            EventHeader::ChannelStateNumber,
677            EventHeader::ChannelCallState,
678            EventHeader::AnswerState,
679            EventHeader::CallDirection,
680            EventHeader::HangupCause,
681            EventHeader::CallerCallerIdName,
682            EventHeader::CallerCallerIdNumber,
683            EventHeader::CallerOrigCallerIdName,
684            EventHeader::CallerOrigCallerIdNumber,
685            EventHeader::CallerCalleeIdName,
686            EventHeader::CallerCalleeIdNumber,
687            EventHeader::CallerDestinationNumber,
688            EventHeader::CallerContext,
689            EventHeader::CallerDirection,
690            EventHeader::CallerNetworkAddr,
691            EventHeader::CoreUuid,
692            EventHeader::DtmfDigit,
693            EventHeader::Priority,
694            EventHeader::LogLevel,
695            EventHeader::PlData,
696            EventHeader::SipEvent,
697            EventHeader::SipContentType,
698            EventHeader::GatewayName,
699            EventHeader::ChannelReadCodecName,
700            EventHeader::ChannelReadCodecRate,
701            EventHeader::ChannelReadCodecBitRate,
702            EventHeader::ChannelReportedReadCodecRate,
703            EventHeader::ChannelWriteCodecName,
704            EventHeader::ChannelWriteCodecRate,
705            EventHeader::ChannelWriteCodecBitRate,
706            EventHeader::ChannelReportedWriteCodecRate,
707            EventHeader::ChannelVideoReadCodecName,
708            EventHeader::ChannelVideoReadCodecRate,
709            EventHeader::ChannelVideoWriteCodecName,
710            EventHeader::ChannelVideoWriteCodecRate,
711            EventHeader::Application,
712            EventHeader::ApplicationData,
713            EventHeader::ApplicationResponse,
714            EventHeader::ApplicationUuid,
715            EventHeader::EventDateLocal,
716            EventHeader::EventDateGmt,
717            EventHeader::EventDateTimestamp,
718            EventHeader::EventCallingFile,
719            EventHeader::EventCallingFunction,
720            EventHeader::EventCallingLineNumber,
721            EventHeader::EventSequence,
722            EventHeader::ChannelPresenceId,
723            EventHeader::ChannelPresenceData,
724            EventHeader::PresenceDataCols,
725            EventHeader::PresenceCallDirection,
726            EventHeader::ChannelHitDialplan,
727            EventHeader::SessionExternalId,
728            EventHeader::OtherType,
729            EventHeader::ChannelCallStateNumber,
730            EventHeader::OriginalChannelCallState,
731            EventHeader::DtmfDuration,
732            EventHeader::DtmfSource,
733            EventHeader::CallerLogicalDirection,
734            EventHeader::CallerUsername,
735            EventHeader::CallerDialplan,
736            EventHeader::CallerAni,
737            EventHeader::CallerAniii,
738            EventHeader::CallerSource,
739            EventHeader::CallerTransferSource,
740            EventHeader::CallerRdnis,
741            EventHeader::CallerChannelName,
742            EventHeader::CallerProfileIndex,
743            EventHeader::CallerScreenBit,
744            EventHeader::CallerPrivacyHideName,
745            EventHeader::CallerPrivacyHideNumber,
746            EventHeader::OtherLegDirection,
747            EventHeader::OtherLegLogicalDirection,
748            EventHeader::OtherLegUsername,
749            EventHeader::OtherLegDialplan,
750            EventHeader::OtherLegCallerIdName,
751            EventHeader::OtherLegCallerIdNumber,
752            EventHeader::OtherLegOrigCallerIdName,
753            EventHeader::OtherLegOrigCallerIdNumber,
754            EventHeader::OtherLegCalleeIdName,
755            EventHeader::OtherLegCalleeIdNumber,
756            EventHeader::OtherLegNetworkAddr,
757            EventHeader::OtherLegAni,
758            EventHeader::OtherLegAniii,
759            EventHeader::OtherLegDestinationNumber,
760            EventHeader::OtherLegSource,
761            EventHeader::OtherLegTransferSource,
762            EventHeader::OtherLegContext,
763            EventHeader::OtherLegRdnis,
764            EventHeader::OtherLegChannelName,
765            EventHeader::OtherLegProfileIndex,
766            EventHeader::OtherLegScreenBit,
767            EventHeader::OtherLegPrivacyHideName,
768            EventHeader::OtherLegPrivacyHideNumber,
769            EventHeader::UpTime,
770            EventHeader::UptimeMsec,
771            EventHeader::MaxSessions,
772            EventHeader::SessionPeakMax,
773            EventHeader::SessionPeakFiveMin,
774            EventHeader::SessionPerSec,
775            EventHeader::SessionPerSecFiveMin,
776            EventHeader::SessionPerSecMax,
777            EventHeader::SessionPerSecLast,
778            EventHeader::SessionSinceStartup,
779            EventHeader::IdleCpu,
780            EventHeader::HeartbeatInterval,
781            EventHeader::EventInfo,
782            EventHeader::LogData,
783            EventHeader::LogFile,
784            EventHeader::LogFunction,
785            EventHeader::LogLine,
786            EventHeader::UserData,
787            EventHeader::ApplicationUuidName,
788        ];
789        for v in variants {
790            let wire = v.to_string();
791            let parsed: EventHeader = wire
792                .parse()
793                .unwrap();
794            assert_eq!(parsed, v, "round-trip failed for {wire}");
795        }
796    }
797}