Skip to main content

freeswitch_types/
headers.rs

1//! Typed event header names for FreeSWITCH ESL events.
2
3use serde::{Deserialize, Serialize};
4
5/// Error returned when parsing an unrecognized event header name.
6#[derive(Debug, Clone, PartialEq, Eq)]
7pub struct ParseEventHeaderError(pub String);
8
9impl std::fmt::Display for ParseEventHeaderError {
10    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
11        write!(f, "unknown event header: {}", self.0)
12    }
13}
14
15impl std::error::Error for ParseEventHeaderError {}
16
17define_header_enum! {
18    error_type: ParseEventHeaderError,
19    /// Top-level header names that appear in FreeSWITCH ESL events.
20    ///
21    /// These are the headers on the parsed event itself (not protocol framing
22    /// headers like `Content-Type`). Use with [`EslEvent::header()`](crate::EslEvent::header) for
23    /// type-safe lookups.
24    pub enum EventHeader {
25        EventName => "Event-Name",
26        EventSubclass => "Event-Subclass",
27        UniqueId => "Unique-ID",
28        CallerUniqueId => "Caller-Unique-ID",
29        OtherLegUniqueId => "Other-Leg-Unique-ID",
30        ChannelCallUuid => "Channel-Call-UUID",
31        JobUuid => "Job-UUID",
32        ChannelName => "Channel-Name",
33        ChannelState => "Channel-State",
34        ChannelStateNumber => "Channel-State-Number",
35        ChannelCallState => "Channel-Call-State",
36        AnswerState => "Answer-State",
37        CallDirection => "Call-Direction",
38        HangupCause => "Hangup-Cause",
39        CallerCallerIdName => "Caller-Caller-ID-Name",
40        CallerCallerIdNumber => "Caller-Caller-ID-Number",
41        CallerOrigCallerIdName => "Caller-Orig-Caller-ID-Name",
42        CallerOrigCallerIdNumber => "Caller-Orig-Caller-ID-Number",
43        CallerCalleeIdName => "Caller-Callee-ID-Name",
44        CallerCalleeIdNumber => "Caller-Callee-ID-Number",
45        CallerDestinationNumber => "Caller-Destination-Number",
46        CallerContext => "Caller-Context",
47        CallerDirection => "Caller-Direction",
48        CallerNetworkAddr => "Caller-Network-Addr",
49        CoreUuid => "Core-UUID",
50        DtmfDigit => "DTMF-Digit",
51        Priority => "priority",
52        LogLevel => "Log-Level",
53    }
54}
55
56#[cfg(test)]
57mod tests {
58    use super::*;
59
60    #[test]
61    fn display_round_trip() {
62        assert_eq!(EventHeader::UniqueId.to_string(), "Unique-ID");
63        assert_eq!(
64            EventHeader::ChannelCallState.to_string(),
65            "Channel-Call-State"
66        );
67        assert_eq!(
68            EventHeader::CallerCallerIdName.to_string(),
69            "Caller-Caller-ID-Name"
70        );
71        assert_eq!(EventHeader::Priority.to_string(), "priority");
72    }
73
74    #[test]
75    fn as_ref_str() {
76        let h: &str = EventHeader::UniqueId.as_ref();
77        assert_eq!(h, "Unique-ID");
78    }
79
80    #[test]
81    fn from_str_case_insensitive() {
82        assert_eq!(
83            "unique-id".parse::<EventHeader>(),
84            Ok(EventHeader::UniqueId)
85        );
86        assert_eq!(
87            "UNIQUE-ID".parse::<EventHeader>(),
88            Ok(EventHeader::UniqueId)
89        );
90        assert_eq!(
91            "Unique-ID".parse::<EventHeader>(),
92            Ok(EventHeader::UniqueId)
93        );
94        assert_eq!(
95            "channel-call-state".parse::<EventHeader>(),
96            Ok(EventHeader::ChannelCallState)
97        );
98    }
99
100    #[test]
101    fn from_str_unknown() {
102        let err = "X-Custom-Not-In-Enum".parse::<EventHeader>();
103        assert!(err.is_err());
104        assert_eq!(
105            err.unwrap_err()
106                .to_string(),
107            "unknown event header: X-Custom-Not-In-Enum"
108        );
109    }
110
111    #[test]
112    fn from_str_round_trip_all_variants() {
113        let variants = [
114            EventHeader::EventName,
115            EventHeader::EventSubclass,
116            EventHeader::UniqueId,
117            EventHeader::CallerUniqueId,
118            EventHeader::OtherLegUniqueId,
119            EventHeader::ChannelCallUuid,
120            EventHeader::JobUuid,
121            EventHeader::ChannelName,
122            EventHeader::ChannelState,
123            EventHeader::ChannelStateNumber,
124            EventHeader::ChannelCallState,
125            EventHeader::AnswerState,
126            EventHeader::CallDirection,
127            EventHeader::HangupCause,
128            EventHeader::CallerCallerIdName,
129            EventHeader::CallerCallerIdNumber,
130            EventHeader::CallerOrigCallerIdName,
131            EventHeader::CallerOrigCallerIdNumber,
132            EventHeader::CallerCalleeIdName,
133            EventHeader::CallerCalleeIdNumber,
134            EventHeader::CallerDestinationNumber,
135            EventHeader::CallerContext,
136            EventHeader::CallerDirection,
137            EventHeader::CallerNetworkAddr,
138            EventHeader::CoreUuid,
139            EventHeader::DtmfDigit,
140            EventHeader::Priority,
141            EventHeader::LogLevel,
142        ];
143        for v in variants {
144            let wire = v.to_string();
145            let parsed: EventHeader = wire
146                .parse()
147                .unwrap();
148            assert_eq!(parsed, v, "round-trip failed for {wire}");
149        }
150    }
151}