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