1#[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 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 PlData => "pl_data",
53 SipEvent => "event",
55 SipContentType => "sip_content_type",
57 GatewayName => "gateway_name",
59
60 ChannelReadCodecName => "Channel-Read-Codec-Name",
63 ChannelReadCodecRate => "Channel-Read-Codec-Rate",
64 ChannelReadCodecBitRate => "Channel-Read-Codec-Bit-Rate",
65 ChannelReportedReadCodecRate => "Channel-Reported-Read-Codec-Rate",
67 ChannelWriteCodecName => "Channel-Write-Codec-Name",
69 ChannelWriteCodecRate => "Channel-Write-Codec-Rate",
70 ChannelWriteCodecBitRate => "Channel-Write-Codec-Bit-Rate",
71 ChannelReportedWriteCodecRate => "Channel-Reported-Write-Codec-Rate",
73 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 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}
89
90pub fn normalize_header_key(raw: &str) -> String {
107 if let Ok(eh) = raw.parse::<EventHeader>() {
108 return eh
109 .as_str()
110 .to_string();
111 }
112 if raw.contains('_') {
113 raw.to_string()
114 } else {
115 title_case_dashes(raw)
116 }
117}
118
119fn title_case_dashes(s: &str) -> String {
120 let mut result = String::with_capacity(s.len());
121 let mut capitalize_next = true;
122 for c in s.chars() {
123 if c == '-' {
124 result.push('-');
125 capitalize_next = true;
126 } else if capitalize_next {
127 result.push(c.to_ascii_uppercase());
128 capitalize_next = false;
129 } else {
130 result.push(c.to_ascii_lowercase());
131 }
132 }
133 result
134}
135
136#[cfg(test)]
137mod tests {
138 use super::*;
139
140 #[test]
141 fn display_round_trip() {
142 assert_eq!(EventHeader::UniqueId.to_string(), "Unique-ID");
143 assert_eq!(
144 EventHeader::ChannelCallState.to_string(),
145 "Channel-Call-State"
146 );
147 assert_eq!(
148 EventHeader::CallerCallerIdName.to_string(),
149 "Caller-Caller-ID-Name"
150 );
151 assert_eq!(EventHeader::Priority.to_string(), "priority");
152 }
153
154 #[test]
155 fn as_ref_str() {
156 let h: &str = EventHeader::UniqueId.as_ref();
157 assert_eq!(h, "Unique-ID");
158 }
159
160 #[test]
161 fn from_str_case_insensitive() {
162 assert_eq!(
163 "unique-id".parse::<EventHeader>(),
164 Ok(EventHeader::UniqueId)
165 );
166 assert_eq!(
167 "UNIQUE-ID".parse::<EventHeader>(),
168 Ok(EventHeader::UniqueId)
169 );
170 assert_eq!(
171 "Unique-ID".parse::<EventHeader>(),
172 Ok(EventHeader::UniqueId)
173 );
174 assert_eq!(
175 "channel-call-state".parse::<EventHeader>(),
176 Ok(EventHeader::ChannelCallState)
177 );
178 }
179
180 #[test]
181 fn from_str_unknown() {
182 let err = "X-Custom-Not-In-Enum".parse::<EventHeader>();
183 assert!(err.is_err());
184 assert_eq!(
185 err.unwrap_err()
186 .to_string(),
187 "unknown event header: X-Custom-Not-In-Enum"
188 );
189 }
190
191 #[test]
199 fn normalize_known_enum_variants_return_canonical_form() {
200 assert_eq!(normalize_header_key("unique-id"), "Unique-ID");
202 assert_eq!(normalize_header_key("UNIQUE-ID"), "Unique-ID");
203 assert_eq!(normalize_header_key("Unique-ID"), "Unique-ID");
204 assert_eq!(normalize_header_key("dtmf-digit"), "DTMF-Digit");
205 assert_eq!(normalize_header_key("DTMF-DIGIT"), "DTMF-Digit");
206 assert_eq!(
207 normalize_header_key("channel-call-uuid"),
208 "Channel-Call-UUID"
209 );
210 assert_eq!(normalize_header_key("event-name"), "Event-Name");
211 }
212
213 #[test]
214 fn normalize_known_underscore_variants_return_canonical_form() {
215 assert_eq!(normalize_header_key("priority"), "priority");
217 assert_eq!(normalize_header_key("PRIORITY"), "priority");
218 assert_eq!(normalize_header_key("pl_data"), "pl_data");
219 assert_eq!(normalize_header_key("PL_DATA"), "pl_data");
220 assert_eq!(normalize_header_key("sip_content_type"), "sip_content_type");
221 assert_eq!(normalize_header_key("gateway_name"), "gateway_name");
222 assert_eq!(normalize_header_key("event"), "event");
223 assert_eq!(normalize_header_key("EVENT"), "event");
224 }
225
226 #[test]
227 fn normalize_codec_headers_from_switch_core_codec() {
228 assert_eq!(
231 normalize_header_key("channel-read-codec-bit-rate"),
232 "Channel-Read-Codec-Bit-Rate"
233 );
234 assert_eq!(
235 normalize_header_key("Channel-Read-Codec-Bit-Rate"),
236 "Channel-Read-Codec-Bit-Rate"
237 );
238 assert_eq!(
240 normalize_header_key("Channel-Write-codec-bit-rate"),
241 "Channel-Write-Codec-Bit-Rate"
242 );
243 assert_eq!(
244 normalize_header_key("channel-video-read-codec-name"),
245 "Channel-Video-Read-Codec-Name"
246 );
247 }
248
249 #[test]
250 fn normalize_unknown_underscore_keys_passthrough() {
251 assert_eq!(
253 normalize_header_key("variable_sip_call_id"),
254 "variable_sip_call_id"
255 );
256 assert_eq!(
257 normalize_header_key("variable_sip_h_X-My-CUSTOM-Header"),
258 "variable_sip_h_X-My-CUSTOM-Header"
259 );
260 assert_eq!(
261 normalize_header_key("variable_sip_h_Diversion"),
262 "variable_sip_h_Diversion"
263 );
264 }
265
266 #[test]
267 fn normalize_unknown_dash_keys_title_case() {
268 assert_eq!(normalize_header_key("content-type"), "Content-Type");
270 assert_eq!(normalize_header_key("Content-Type"), "Content-Type");
271 assert_eq!(normalize_header_key("CONTENT-TYPE"), "Content-Type");
272 assert_eq!(normalize_header_key("x-custom-header"), "X-Custom-Header");
273 assert_eq!(
274 normalize_header_key("Content-Disposition"),
275 "Content-Disposition"
276 );
277 assert_eq!(normalize_header_key("reply-text"), "Reply-Text");
278 }
279
280 #[test]
281 fn normalize_idempotent_for_all_enum_variants() {
282 let variants = [
284 EventHeader::EventName,
285 EventHeader::UniqueId,
286 EventHeader::ChannelCallUuid,
287 EventHeader::DtmfDigit,
288 EventHeader::Priority,
289 EventHeader::PlData,
290 EventHeader::SipEvent,
291 EventHeader::GatewayName,
292 EventHeader::SipContentType,
293 EventHeader::ChannelReadCodecBitRate,
294 EventHeader::ChannelVideoWriteCodecRate,
295 EventHeader::LogLevel,
296 ];
297 for v in variants {
298 let canonical = v.as_str();
299 assert_eq!(
300 normalize_header_key(canonical),
301 canonical,
302 "normalization not idempotent for {canonical}"
303 );
304 }
305 }
306
307 #[test]
308 fn from_str_round_trip_all_variants() {
309 let variants = [
310 EventHeader::EventName,
311 EventHeader::EventSubclass,
312 EventHeader::UniqueId,
313 EventHeader::CallerUniqueId,
314 EventHeader::OtherLegUniqueId,
315 EventHeader::ChannelCallUuid,
316 EventHeader::JobUuid,
317 EventHeader::ChannelName,
318 EventHeader::ChannelState,
319 EventHeader::ChannelStateNumber,
320 EventHeader::ChannelCallState,
321 EventHeader::AnswerState,
322 EventHeader::CallDirection,
323 EventHeader::HangupCause,
324 EventHeader::CallerCallerIdName,
325 EventHeader::CallerCallerIdNumber,
326 EventHeader::CallerOrigCallerIdName,
327 EventHeader::CallerOrigCallerIdNumber,
328 EventHeader::CallerCalleeIdName,
329 EventHeader::CallerCalleeIdNumber,
330 EventHeader::CallerDestinationNumber,
331 EventHeader::CallerContext,
332 EventHeader::CallerDirection,
333 EventHeader::CallerNetworkAddr,
334 EventHeader::CoreUuid,
335 EventHeader::DtmfDigit,
336 EventHeader::Priority,
337 EventHeader::LogLevel,
338 EventHeader::PlData,
339 EventHeader::SipEvent,
340 EventHeader::SipContentType,
341 EventHeader::GatewayName,
342 EventHeader::ChannelReadCodecName,
343 EventHeader::ChannelReadCodecRate,
344 EventHeader::ChannelReadCodecBitRate,
345 EventHeader::ChannelReportedReadCodecRate,
346 EventHeader::ChannelWriteCodecName,
347 EventHeader::ChannelWriteCodecRate,
348 EventHeader::ChannelWriteCodecBitRate,
349 EventHeader::ChannelReportedWriteCodecRate,
350 EventHeader::ChannelVideoReadCodecName,
351 EventHeader::ChannelVideoReadCodecRate,
352 EventHeader::ChannelVideoWriteCodecName,
353 EventHeader::ChannelVideoWriteCodecRate,
354 ];
355 for v in variants {
356 let wire = v.to_string();
357 let parsed: EventHeader = wire
358 .parse()
359 .unwrap();
360 assert_eq!(parsed, v, "round-trip failed for {wire}");
361 }
362 }
363}