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