1use crate::channel::{
9 AnswerState, CallDirection, CallState, ChannelState, ChannelTimetable, HangupCause,
10 ParseAnswerStateError, ParseCallDirectionError, ParseCallStateError, ParseChannelStateError,
11 ParseHangupCauseError, ParseTimetableError,
12};
13#[cfg(feature = "esl")]
14use crate::event::{EslEventPriority, ParsePriorityError};
15use crate::headers::EventHeader;
16use crate::variables::VariableName;
17
18pub trait HeaderLookup {
64 fn header_str(&self, name: &str) -> Option<&str>;
66
67 fn variable_str(&self, name: &str) -> Option<&str>;
71
72 fn header(&self, name: EventHeader) -> Option<&str> {
74 self.header_str(name.as_str())
75 }
76
77 fn variable(&self, name: impl VariableName) -> Option<&str> {
79 self.variable_str(name.as_str())
80 }
81
82 fn unique_id(&self) -> Option<&str> {
84 self.header(EventHeader::UniqueId)
85 .or_else(|| self.header(EventHeader::CallerUniqueId))
86 }
87
88 fn job_uuid(&self) -> Option<&str> {
90 self.header(EventHeader::JobUuid)
91 }
92
93 fn channel_name(&self) -> Option<&str> {
95 self.header(EventHeader::ChannelName)
96 }
97
98 fn caller_id_number(&self) -> Option<&str> {
100 self.header(EventHeader::CallerCallerIdNumber)
101 }
102
103 fn caller_id_name(&self) -> Option<&str> {
105 self.header(EventHeader::CallerCallerIdName)
106 }
107
108 fn destination_number(&self) -> Option<&str> {
110 self.header(EventHeader::CallerDestinationNumber)
111 }
112
113 fn callee_id_number(&self) -> Option<&str> {
115 self.header(EventHeader::CallerCalleeIdNumber)
116 }
117
118 fn callee_id_name(&self) -> Option<&str> {
120 self.header(EventHeader::CallerCalleeIdName)
121 }
122
123 fn hangup_cause(&self) -> Result<Option<HangupCause>, ParseHangupCauseError> {
127 match self.header(EventHeader::HangupCause) {
128 Some(s) => Ok(Some(s.parse()?)),
129 None => Ok(None),
130 }
131 }
132
133 fn event_subclass(&self) -> Option<&str> {
135 self.header(EventHeader::EventSubclass)
136 }
137
138 fn pl_data(&self) -> Option<&str> {
144 self.header(EventHeader::PlData)
145 }
146
147 fn sip_event(&self) -> Option<&str> {
151 self.header(EventHeader::SipEvent)
152 }
153
154 fn gateway_name(&self) -> Option<&str> {
156 self.header(EventHeader::GatewayName)
157 }
158
159 fn channel_state(&self) -> Result<Option<ChannelState>, ParseChannelStateError> {
163 match self.header(EventHeader::ChannelState) {
164 Some(s) => Ok(Some(s.parse()?)),
165 None => Ok(None),
166 }
167 }
168
169 fn channel_state_number(&self) -> Result<Option<ChannelState>, ParseChannelStateError> {
173 match self.header(EventHeader::ChannelStateNumber) {
174 Some(s) => {
175 let n: u8 = s
176 .parse()
177 .map_err(|_| ParseChannelStateError(s.to_string()))?;
178 ChannelState::from_number(n)
179 .ok_or_else(|| ParseChannelStateError(s.to_string()))
180 .map(Some)
181 }
182 None => Ok(None),
183 }
184 }
185
186 fn call_state(&self) -> Result<Option<CallState>, ParseCallStateError> {
190 match self.header(EventHeader::ChannelCallState) {
191 Some(s) => Ok(Some(s.parse()?)),
192 None => Ok(None),
193 }
194 }
195
196 fn answer_state(&self) -> Result<Option<AnswerState>, ParseAnswerStateError> {
200 match self.header(EventHeader::AnswerState) {
201 Some(s) => Ok(Some(s.parse()?)),
202 None => Ok(None),
203 }
204 }
205
206 fn call_direction(&self) -> Result<Option<CallDirection>, ParseCallDirectionError> {
210 match self.header(EventHeader::CallDirection) {
211 Some(s) => Ok(Some(s.parse()?)),
212 None => Ok(None),
213 }
214 }
215
216 #[cfg(feature = "esl")]
220 fn priority(&self) -> Result<Option<EslEventPriority>, ParsePriorityError> {
221 match self.header(EventHeader::Priority) {
222 Some(s) => Ok(Some(s.parse()?)),
223 None => Ok(None),
224 }
225 }
226
227 fn timetable(&self, prefix: &str) -> Result<Option<ChannelTimetable>, ParseTimetableError> {
232 ChannelTimetable::from_lookup(prefix, |key| self.header_str(key))
233 }
234
235 fn caller_timetable(&self) -> Result<Option<ChannelTimetable>, ParseTimetableError> {
237 self.timetable("Caller")
238 }
239
240 fn other_leg_timetable(&self) -> Result<Option<ChannelTimetable>, ParseTimetableError> {
242 self.timetable("Other-Leg")
243 }
244}
245
246impl HeaderLookup for std::collections::HashMap<String, String> {
247 fn header_str(&self, name: &str) -> Option<&str> {
248 self.get(name)
249 .map(|s| s.as_str())
250 }
251
252 fn variable_str(&self, name: &str) -> Option<&str> {
253 self.get(&format!("variable_{name}"))
254 .map(|s| s.as_str())
255 }
256}
257
258#[cfg(feature = "esl")]
259impl HeaderLookup for indexmap::IndexMap<String, String> {
260 fn header_str(&self, name: &str) -> Option<&str> {
261 self.get(name)
262 .map(|s| s.as_str())
263 }
264
265 fn variable_str(&self, name: &str) -> Option<&str> {
266 self.get(&format!("variable_{name}"))
267 .map(|s| s.as_str())
268 }
269}
270
271#[cfg(test)]
272mod tests {
273 use super::*;
274 use crate::variables::ChannelVariable;
275 use std::collections::HashMap;
276
277 struct TestStore(HashMap<String, String>);
278
279 impl HeaderLookup for TestStore {
280 fn header_str(&self, name: &str) -> Option<&str> {
281 self.0
282 .get(name)
283 .map(|s| s.as_str())
284 }
285 fn variable_str(&self, name: &str) -> Option<&str> {
286 self.0
287 .get(&format!("variable_{}", name))
288 .map(|s| s.as_str())
289 }
290 }
291
292 fn store_with(pairs: &[(&str, &str)]) -> TestStore {
293 let map: HashMap<String, String> = pairs
294 .iter()
295 .map(|(k, v)| (k.to_string(), v.to_string()))
296 .collect();
297 TestStore(map)
298 }
299
300 #[test]
301 fn header_str_direct() {
302 let s = store_with(&[("Unique-ID", "abc-123")]);
303 assert_eq!(s.header_str("Unique-ID"), Some("abc-123"));
304 assert_eq!(s.header_str("Missing"), None);
305 }
306
307 #[test]
308 fn header_by_enum() {
309 let s = store_with(&[("Unique-ID", "abc-123")]);
310 assert_eq!(s.header(EventHeader::UniqueId), Some("abc-123"));
311 }
312
313 #[test]
314 fn variable_str_direct() {
315 let s = store_with(&[("variable_read_codec", "PCMU")]);
316 assert_eq!(s.variable_str("read_codec"), Some("PCMU"));
317 assert_eq!(s.variable_str("missing"), None);
318 }
319
320 #[test]
321 fn variable_by_enum() {
322 let s = store_with(&[("variable_read_codec", "PCMU")]);
323 assert_eq!(s.variable(ChannelVariable::ReadCodec), Some("PCMU"));
324 }
325
326 #[test]
327 fn unique_id_primary() {
328 let s = store_with(&[("Unique-ID", "uuid-1")]);
329 assert_eq!(s.unique_id(), Some("uuid-1"));
330 }
331
332 #[test]
333 fn unique_id_fallback() {
334 let s = store_with(&[("Caller-Unique-ID", "uuid-2")]);
335 assert_eq!(s.unique_id(), Some("uuid-2"));
336 }
337
338 #[test]
339 fn unique_id_none() {
340 let s = store_with(&[]);
341 assert_eq!(s.unique_id(), None);
342 }
343
344 #[test]
345 fn job_uuid() {
346 let s = store_with(&[("Job-UUID", "job-1")]);
347 assert_eq!(s.job_uuid(), Some("job-1"));
348 }
349
350 #[test]
351 fn channel_name() {
352 let s = store_with(&[("Channel-Name", "sofia/internal/1000@example.com")]);
353 assert_eq!(s.channel_name(), Some("sofia/internal/1000@example.com"));
354 }
355
356 #[test]
357 fn caller_id_number_and_name() {
358 let s = store_with(&[
359 ("Caller-Caller-ID-Number", "1000"),
360 ("Caller-Caller-ID-Name", "Alice"),
361 ]);
362 assert_eq!(s.caller_id_number(), Some("1000"));
363 assert_eq!(s.caller_id_name(), Some("Alice"));
364 }
365
366 #[test]
367 fn hangup_cause_typed() {
368 let s = store_with(&[("Hangup-Cause", "NORMAL_CLEARING")]);
369 assert_eq!(
370 s.hangup_cause()
371 .unwrap(),
372 Some(crate::channel::HangupCause::NormalClearing)
373 );
374 }
375
376 #[test]
377 fn hangup_cause_invalid_is_error() {
378 let s = store_with(&[("Hangup-Cause", "BOGUS_CAUSE")]);
379 assert!(s
380 .hangup_cause()
381 .is_err());
382 }
383
384 #[test]
385 fn destination_number() {
386 let s = store_with(&[("Caller-Destination-Number", "1000")]);
387 assert_eq!(s.destination_number(), Some("1000"));
388 }
389
390 #[test]
391 fn callee_id() {
392 let s = store_with(&[
393 ("Caller-Callee-ID-Number", "2000"),
394 ("Caller-Callee-ID-Name", "Bob"),
395 ]);
396 assert_eq!(s.callee_id_number(), Some("2000"));
397 assert_eq!(s.callee_id_name(), Some("Bob"));
398 }
399
400 #[test]
401 fn event_subclass() {
402 let s = store_with(&[("Event-Subclass", "sofia::register")]);
403 assert_eq!(s.event_subclass(), Some("sofia::register"));
404 }
405
406 #[test]
407 fn channel_state_typed() {
408 let s = store_with(&[("Channel-State", "CS_EXECUTE")]);
409 assert_eq!(
410 s.channel_state()
411 .unwrap(),
412 Some(ChannelState::CsExecute)
413 );
414 }
415
416 #[test]
417 fn channel_state_number_typed() {
418 let s = store_with(&[("Channel-State-Number", "4")]);
419 assert_eq!(
420 s.channel_state_number()
421 .unwrap(),
422 Some(ChannelState::CsExecute)
423 );
424 }
425
426 #[test]
427 fn call_state_typed() {
428 let s = store_with(&[("Channel-Call-State", "ACTIVE")]);
429 assert_eq!(
430 s.call_state()
431 .unwrap(),
432 Some(CallState::Active)
433 );
434 }
435
436 #[test]
437 fn answer_state_typed() {
438 let s = store_with(&[("Answer-State", "answered")]);
439 assert_eq!(
440 s.answer_state()
441 .unwrap(),
442 Some(AnswerState::Answered)
443 );
444 }
445
446 #[test]
447 fn call_direction_typed() {
448 let s = store_with(&[("Call-Direction", "inbound")]);
449 assert_eq!(
450 s.call_direction()
451 .unwrap(),
452 Some(CallDirection::Inbound)
453 );
454 }
455
456 #[test]
457 fn priority_typed() {
458 let s = store_with(&[("priority", "HIGH")]);
459 assert_eq!(
460 s.priority()
461 .unwrap(),
462 Some(EslEventPriority::High)
463 );
464 }
465
466 #[test]
467 fn timetable_extraction() {
468 let s = store_with(&[
469 ("Caller-Channel-Created-Time", "1700000001000000"),
470 ("Caller-Channel-Answered-Time", "1700000005000000"),
471 ]);
472 let tt = s
473 .caller_timetable()
474 .unwrap()
475 .expect("should have timetable");
476 assert_eq!(tt.created, Some(1700000001000000));
477 assert_eq!(tt.answered, Some(1700000005000000));
478 assert_eq!(tt.hungup, None);
479 }
480
481 #[test]
482 fn timetable_other_leg() {
483 let s = store_with(&[("Other-Leg-Channel-Created-Time", "1700000001000000")]);
484 let tt = s
485 .other_leg_timetable()
486 .unwrap()
487 .expect("should have timetable");
488 assert_eq!(tt.created, Some(1700000001000000));
489 }
490
491 #[test]
492 fn timetable_none_when_absent() {
493 let s = store_with(&[]);
494 assert_eq!(
495 s.caller_timetable()
496 .unwrap(),
497 None
498 );
499 }
500
501 #[test]
502 fn timetable_invalid_is_error() {
503 let s = store_with(&[("Caller-Channel-Created-Time", "not_a_number")]);
504 let err = s
505 .caller_timetable()
506 .unwrap_err();
507 assert_eq!(err.header, "Caller-Channel-Created-Time");
508 }
509
510 #[test]
511 fn missing_headers_return_none() {
512 let s = store_with(&[]);
513 assert_eq!(
514 s.channel_state()
515 .unwrap(),
516 None
517 );
518 assert_eq!(
519 s.channel_state_number()
520 .unwrap(),
521 None
522 );
523 assert_eq!(
524 s.call_state()
525 .unwrap(),
526 None
527 );
528 assert_eq!(
529 s.answer_state()
530 .unwrap(),
531 None
532 );
533 assert_eq!(
534 s.call_direction()
535 .unwrap(),
536 None
537 );
538 assert_eq!(
539 s.priority()
540 .unwrap(),
541 None
542 );
543 assert_eq!(
544 s.hangup_cause()
545 .unwrap(),
546 None
547 );
548 assert_eq!(s.channel_name(), None);
549 assert_eq!(s.caller_id_number(), None);
550 assert_eq!(s.caller_id_name(), None);
551 assert_eq!(s.destination_number(), None);
552 assert_eq!(s.callee_id_number(), None);
553 assert_eq!(s.callee_id_name(), None);
554 assert_eq!(s.event_subclass(), None);
555 assert_eq!(s.job_uuid(), None);
556 assert_eq!(s.pl_data(), None);
557 assert_eq!(s.sip_event(), None);
558 assert_eq!(s.gateway_name(), None);
559 }
560
561 #[test]
562 fn notify_in_headers() {
563 let s = store_with(&[
564 ("pl_data", r#"{"invite":"INVITE ..."}"#),
565 ("event", "emergency-AbandonedCall"),
566 ("gateway_name", "ng911-bcf"),
567 ]);
568 assert_eq!(s.pl_data(), Some(r#"{"invite":"INVITE ..."}"#));
569 assert_eq!(s.sip_event(), Some("emergency-AbandonedCall"));
570 assert_eq!(s.gateway_name(), Some("ng911-bcf"));
571 }
572
573 #[test]
574 fn invalid_values_return_err() {
575 let s = store_with(&[
576 ("Channel-State", "BOGUS"),
577 ("Channel-State-Number", "999"),
578 ("Channel-Call-State", "BOGUS"),
579 ("Answer-State", "bogus"),
580 ("Call-Direction", "bogus"),
581 ("priority", "BOGUS"),
582 ("Hangup-Cause", "BOGUS"),
583 ]);
584 assert!(s
585 .channel_state()
586 .is_err());
587 assert!(s
588 .channel_state_number()
589 .is_err());
590 assert!(s
591 .call_state()
592 .is_err());
593 assert!(s
594 .answer_state()
595 .is_err());
596 assert!(s
597 .call_direction()
598 .is_err());
599 assert!(s
600 .priority()
601 .is_err());
602 assert!(s
603 .hangup_cause()
604 .is_err());
605 }
606}