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