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