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        /// SIP NOTIFY body content (JSON payload from `NOTIFY_IN` events).
54        PlData => "pl_data",
55        /// SIP event package name from `NOTIFY_IN` events (e.g. `emergency-AbandonedCall`).
56        SipEvent => "event",
57        /// SIP content type from `NOTIFY_IN` events.
58        SipContentType => "sip_content_type",
59        /// Gateway that received the SIP NOTIFY.
60        GatewayName => "gateway_name",
61    }
62}
63
64#[cfg(test)]
65mod tests {
66    use super::*;
67
68    #[test]
69    fn display_round_trip() {
70        assert_eq!(EventHeader::UniqueId.to_string(), "Unique-ID");
71        assert_eq!(
72            EventHeader::ChannelCallState.to_string(),
73            "Channel-Call-State"
74        );
75        assert_eq!(
76            EventHeader::CallerCallerIdName.to_string(),
77            "Caller-Caller-ID-Name"
78        );
79        assert_eq!(EventHeader::Priority.to_string(), "priority");
80    }
81
82    #[test]
83    fn as_ref_str() {
84        let h: &str = EventHeader::UniqueId.as_ref();
85        assert_eq!(h, "Unique-ID");
86    }
87
88    #[test]
89    fn from_str_case_insensitive() {
90        assert_eq!(
91            "unique-id".parse::<EventHeader>(),
92            Ok(EventHeader::UniqueId)
93        );
94        assert_eq!(
95            "UNIQUE-ID".parse::<EventHeader>(),
96            Ok(EventHeader::UniqueId)
97        );
98        assert_eq!(
99            "Unique-ID".parse::<EventHeader>(),
100            Ok(EventHeader::UniqueId)
101        );
102        assert_eq!(
103            "channel-call-state".parse::<EventHeader>(),
104            Ok(EventHeader::ChannelCallState)
105        );
106    }
107
108    #[test]
109    fn from_str_unknown() {
110        let err = "X-Custom-Not-In-Enum".parse::<EventHeader>();
111        assert!(err.is_err());
112        assert_eq!(
113            err.unwrap_err()
114                .to_string(),
115            "unknown event header: X-Custom-Not-In-Enum"
116        );
117    }
118
119    #[test]
120    fn from_str_round_trip_all_variants() {
121        let variants = [
122            EventHeader::EventName,
123            EventHeader::EventSubclass,
124            EventHeader::UniqueId,
125            EventHeader::CallerUniqueId,
126            EventHeader::OtherLegUniqueId,
127            EventHeader::ChannelCallUuid,
128            EventHeader::JobUuid,
129            EventHeader::ChannelName,
130            EventHeader::ChannelState,
131            EventHeader::ChannelStateNumber,
132            EventHeader::ChannelCallState,
133            EventHeader::AnswerState,
134            EventHeader::CallDirection,
135            EventHeader::HangupCause,
136            EventHeader::CallerCallerIdName,
137            EventHeader::CallerCallerIdNumber,
138            EventHeader::CallerOrigCallerIdName,
139            EventHeader::CallerOrigCallerIdNumber,
140            EventHeader::CallerCalleeIdName,
141            EventHeader::CallerCalleeIdNumber,
142            EventHeader::CallerDestinationNumber,
143            EventHeader::CallerContext,
144            EventHeader::CallerDirection,
145            EventHeader::CallerNetworkAddr,
146            EventHeader::CoreUuid,
147            EventHeader::DtmfDigit,
148            EventHeader::Priority,
149            EventHeader::LogLevel,
150            EventHeader::PlData,
151            EventHeader::SipEvent,
152            EventHeader::SipContentType,
153            EventHeader::GatewayName,
154        ];
155        for v in variants {
156            let wire = v.to_string();
157            let parsed: EventHeader = wire
158                .parse()
159                .unwrap();
160            assert_eq!(parsed, v, "round-trip failed for {wire}");
161        }
162    }
163}