rvoip_session_core/api/handlers.rs
1//! Event Handlers for Session Management
2//!
3//! This module provides the `CallHandler` trait and pre-built implementations for
4//! handling incoming calls and call lifecycle events in your VoIP applications.
5//!
6//! # Overview
7//!
8//! Call handlers are the primary mechanism for customizing how your application
9//! responds to incoming calls. They support two decision patterns:
10//!
11//! 1. **Immediate Decision**: Synchronously decide to accept/reject/forward
12//! 2. **Deferred Decision**: Defer for asynchronous processing with external systems
13//!
14//! # The CallHandler Trait
15//!
16//! ```rust
17//! use rvoip_session_core::api::*;
18//! use async_trait::async_trait;
19//!
20//! #[async_trait]
21//! pub trait CallHandler: Send + Sync + std::fmt::Debug {
22//! /// Decide what to do with an incoming call
23//! async fn on_incoming_call(&self, call: IncomingCall) -> CallDecision;
24//!
25//! /// Handle when a call ends
26//! async fn on_call_ended(&self, call: CallSession, reason: &str);
27//!
28//! /// Handle when a call is established (optional)
29//! async fn on_call_established(&self, call: CallSession, local_sdp: Option<String>, remote_sdp: Option<String>) {
30//! // Default implementation
31//! }
32//! }
33//! ```
34//!
35//! # Built-in Handlers
36//!
37//! ## AutoAnswerHandler
38//!
39//! Automatically accepts all incoming calls. Useful for testing or simple scenarios.
40//!
41//! ```rust
42//! use rvoip_session_core::{SessionManagerBuilder, SessionControl, Result};
43//! use rvoip_session_core::handlers::AutoAnswerHandler;
44//! use std::sync::Arc;
45//!
46//! #[tokio::main]
47//! async fn main() -> Result<()> {
48//! let coordinator = SessionManagerBuilder::new()
49//! .with_sip_port(5060)
50//! .with_handler(Arc::new(AutoAnswerHandler))
51//! .build()
52//! .await?;
53//!
54//! SessionControl::start(&coordinator).await?;
55//!
56//! // All incoming calls will be automatically accepted
57//! Ok(())
58//! }
59//! ```
60//!
61//! ## QueueHandler
62//!
63//! Queues incoming calls for later processing. Perfect for call centers or when
64//! you need to process calls asynchronously.
65//!
66//! ```rust
67//! use rvoip_session_core::{SessionManagerBuilder, SessionControl, MediaControl, Result};
68//! use rvoip_session_core::handlers::QueueHandler;
69//! use tokio::sync::mpsc;
70//! use std::sync::Arc;
71//!
72//! async fn setup_queue_handler() -> Result<()> {
73//! // Create queue handler with max 100 calls
74//! let queue_handler = Arc::new(QueueHandler::new(100));
75//!
76//! // Set up notification channel
77//! let (tx, mut rx) = mpsc::unbounded_channel();
78//! queue_handler.set_notify_channel(tx);
79//!
80//! let coordinator = SessionManagerBuilder::new()
81//! .with_sip_port(5060)
82//! .with_handler(queue_handler.clone())
83//! .build()
84//! .await?;
85//!
86//! SessionControl::start(&coordinator).await?;
87//!
88//! // Process queued calls in background
89//! let coord_clone = coordinator.clone();
90//! tokio::spawn(async move {
91//! while let Some(call) = rx.recv().await {
92//! // Perform async operations (database lookup, etc.)
93//! // In a real application, you would check permissions here
94//! let allowed = !call.from.contains("blocked");
95//!
96//! if allowed {
97//! if let Some(ref offer) = call.sdp {
98//! let sdp = MediaControl::generate_sdp_answer(
99//! &coord_clone,
100//! &call.id,
101//! offer
102//! ).await.unwrap();
103//!
104//! SessionControl::accept_incoming_call(
105//! &coord_clone,
106//! &call,
107//! Some(sdp)
108//! ).await.unwrap();
109//! }
110//! } else {
111//! SessionControl::reject_incoming_call(
112//! &coord_clone,
113//! &call,
114//! "Not authorized"
115//! ).await.unwrap();
116//! }
117//! }
118//! });
119//!
120//! Ok(())
121//! }
122//! ```
123//!
124//! ## RoutingHandler
125//!
126//! Routes calls based on destination patterns. Supports prefix matching and
127//! default actions for unmatched calls.
128//!
129//! ```rust
130//! use rvoip_session_core::CallDecision;
131//! use rvoip_session_core::handlers::RoutingHandler;
132//!
133//! fn create_pbx_router() -> RoutingHandler {
134//! let mut router = RoutingHandler::new();
135//!
136//! // Department routing
137//! router.add_route("sip:support@", "sip:queue@support.internal");
138//! router.add_route("sip:sales@", "sip:queue@sales.internal");
139//! router.add_route("sip:billing@", "sip:queue@billing.internal");
140//!
141//! // Geographic routing
142//! router.add_route("sip:+1212", "sip:nyc@gateway.com");
143//! router.add_route("sip:+1415", "sip:sf@gateway.com");
144//!
145//! // Toll-free routing
146//! router.add_route("sip:+1800", "sip:tollfree@special.gateway.com");
147//! router.add_route("sip:+1888", "sip:tollfree@special.gateway.com");
148//!
149//! // Set default for unknown destinations
150//! router.set_default_action(
151//! CallDecision::Forward("sip:operator@default.internal".to_string())
152//! );
153//!
154//! router
155//! }
156//! ```
157//!
158//! ## CompositeHandler
159//!
160//! Chains multiple handlers together. Handlers are tried in order until one
161//! makes a decision (doesn't defer).
162//!
163//! ```rust
164//! use rvoip_session_core::handlers::{CompositeHandler, QueueHandler, RoutingHandler, CallHandler};
165//! use rvoip_session_core::{CallDecision, IncomingCall, CallSession};
166//! use std::sync::Arc;
167//! use std::time::Duration;
168//!
169//! // Example of building advanced handler
170//! // In a real application, you would implement the other handlers
171//! fn create_advanced_handler() -> Arc<CompositeHandler> {
172//! let composite = CompositeHandler::new()
173//! // Add a simple routing handler
174//! .add_handler(Arc::new(create_pbx_router()))
175//! // Finally, queue any remaining calls
176//! .add_handler(Arc::new(QueueHandler::new(50)));
177//!
178//! Arc::new(composite)
179//! }
180//!
181//! fn create_pbx_router() -> RoutingHandler {
182//! let mut router = RoutingHandler::new();
183//! router.add_route("sip:support@", "sip:queue@support.internal");
184//! router
185//! }
186//! ```
187//!
188//! # Custom Handler Examples
189//!
190//! ## Example: Business Hours Handler
191//!
192//! ```rust
193//! use rvoip_session_core::{CallHandler, IncomingCall, CallDecision, CallSession};
194//! use chrono::{Local, Timelike, Datelike, Weekday};
195//!
196//! #[derive(Debug)]
197//! struct BusinessHoursHandler {
198//! start_hour: u32,
199//! end_hour: u32,
200//! timezone: String,
201//! }
202//!
203//! #[async_trait::async_trait]
204//! impl CallHandler for BusinessHoursHandler {
205//! async fn on_incoming_call(&self, call: IncomingCall) -> CallDecision {
206//! let now = Local::now();
207//! let hour = now.hour();
208//! let weekday = now.weekday();
209//!
210//! // Check if weekend
211//! if weekday == Weekday::Sat || weekday == Weekday::Sun {
212//! return CallDecision::Forward("sip:weekend@voicemail.com".to_string());
213//! }
214//!
215//! // Check business hours
216//! if hour >= self.start_hour && hour < self.end_hour {
217//! // During business hours, let next handler decide
218//! CallDecision::Defer
219//! } else {
220//! // After hours, send to voicemail
221//! CallDecision::Forward("sip:afterhours@voicemail.com".to_string())
222//! }
223//! }
224//!
225//! async fn on_call_ended(&self, call: CallSession, reason: &str) {
226//! // Log for business analytics
227//! println!(
228//! "Call {} ended after {} seconds: {}",
229//! call.id(),
230//! call.started_at.map(|t| t.elapsed().as_secs()).unwrap_or(0),
231//! reason
232//! );
233//! }
234//! }
235//! ```
236//!
237//! ## Example: Database-Backed VIP Handler
238//!
239//! ```rust
240//! use rvoip_session_core::{CallHandler, IncomingCall, CallDecision, CallSession, SessionCoordinator, SessionControl};
241//! use std::sync::Arc;
242//! use tokio::sync::RwLock;
243//! use std::time::Duration;
244//!
245//! // Example database stub - in real code this would be your actual database
246//! #[derive(Debug)]
247//! struct Database;
248//!
249//! impl Database {
250//! async fn get_caller_info(&self, from: &str) -> Result<CallerInfo, Box<dyn std::error::Error>> {
251//! Ok(CallerInfo { is_vip: from.contains("vip") })
252//! }
253//!
254//! async fn record_vip_call(&self, call: &CallSession, local_sdp: &Option<String>, remote_sdp: &Option<String>) {
255//! // Record call details
256//! }
257//!
258//! async fn update_vip_call_stats(&self, session_id: &str, reason: &str) {
259//! // Update statistics
260//! }
261//! }
262//!
263//! struct CallerInfo {
264//! is_vip: bool,
265//! }
266//!
267//! #[derive(Debug)]
268//! struct VipHandler {
269//! db: Arc<Database>,
270//! vip_queue: Arc<RwLock<Vec<IncomingCall>>>,
271//! }
272//!
273//! #[async_trait::async_trait]
274//! impl CallHandler for VipHandler {
275//! async fn on_incoming_call(&self, call: IncomingCall) -> CallDecision {
276//! // Always defer for async database lookup
277//! self.vip_queue.write().await.push(call);
278//! CallDecision::Defer
279//! }
280//!
281//! async fn on_call_established(&self, call: CallSession, local_sdp: Option<String>, remote_sdp: Option<String>) {
282//! // Record high-priority call establishment
283//! self.db.record_vip_call(&call, &local_sdp, &remote_sdp).await;
284//! }
285//!
286//! async fn on_call_ended(&self, call: CallSession, reason: &str) {
287//! // Update VIP call statistics
288//! self.db.update_vip_call_stats(&call.id().to_string(), reason).await;
289//! }
290//! }
291//!
292//! // Background processor for VIP calls
293//! async fn process_vip_calls(
294//! handler: Arc<VipHandler>,
295//! coordinator: Arc<SessionCoordinator>
296//! ) {
297//! loop {
298//! let calls = handler.vip_queue.write().await.drain(..).collect::<Vec<_>>();
299//!
300//! for call in calls {
301//! // Check VIP status in database
302//! let caller_info = handler.db.get_caller_info(&call.from).await;
303//!
304//! if let Ok(info) = caller_info {
305//! if info.is_vip {
306//! // VIP gets high-quality codec and priority routing
307//! // In real code, you would generate proper HD audio SDP
308//! let sdp = "v=0\r\no=- 0 0 IN IP4 127.0.0.1\r\ns=-\r\nc=IN IP4 127.0.0.1\r\nt=0 0\r\nm=audio 5004 RTP/AVP 96\r\na=rtpmap:96 opus/48000/2\r\n".to_string();
309//! SessionControl::accept_incoming_call(
310//! &coordinator,
311//! &call,
312//! Some(sdp)
313//! ).await.unwrap();
314//! } else {
315//! // Non-VIP goes to regular queue
316//! SessionControl::reject_incoming_call(
317//! &coordinator,
318//! &call,
319//! "Please use regular support line"
320//! ).await.unwrap();
321//! }
322//! }
323//! }
324//!
325//! tokio::time::sleep(Duration::from_millis(100)).await;
326//! }
327//! }
328//! ```
329//!
330//! ## Example: Geographic Load Balancer
331//!
332//! ```rust
333//! use rvoip_session_core::{CallHandler, IncomingCall, CallDecision, CallSession};
334//! use std::collections::HashMap;
335//! use std::sync::{Arc, Mutex};
336//!
337//! #[derive(Debug)]
338//! struct GeoLoadBalancer {
339//! regions: HashMap<String, Vec<String>>,
340//! current_index: Arc<Mutex<HashMap<String, usize>>>,
341//! }
342//!
343//! impl GeoLoadBalancer {
344//! fn new() -> Self {
345//! let mut regions = HashMap::new();
346//! regions.insert("US-East".to_string(), vec![
347//! "sip:server1@east.example.com".to_string(),
348//! "sip:server2@east.example.com".to_string(),
349//! ]);
350//! regions.insert("US-West".to_string(), vec![
351//! "sip:server1@west.example.com".to_string(),
352//! "sip:server2@west.example.com".to_string(),
353//! ]);
354//!
355//! Self {
356//! regions,
357//! current_index: Arc::new(Mutex::new(HashMap::new())),
358//! }
359//! }
360//!
361//! fn get_region_from_number(&self, number: &str) -> &str {
362//! if number.starts_with("sip:+1212") || number.starts_with("sip:+1646") {
363//! "US-East"
364//! } else if number.starts_with("sip:+1415") || number.starts_with("sip:+1650") {
365//! "US-West"
366//! } else {
367//! "US-East" // default
368//! }
369//! }
370//! }
371//!
372//! #[async_trait::async_trait]
373//! impl CallHandler for GeoLoadBalancer {
374//! async fn on_incoming_call(&self, call: IncomingCall) -> CallDecision {
375//! let region = self.get_region_from_number(&call.from);
376//!
377//! if let Some(servers) = self.regions.get(region) {
378//! // Round-robin within region
379//! let mut indices = self.current_index.lock().unwrap();
380//! let index = indices.entry(region.to_string()).or_insert(0);
381//! let server = &servers[*index % servers.len()];
382//! *index = (*index + 1) % servers.len();
383//!
384//! CallDecision::Forward(server.clone())
385//! } else {
386//! CallDecision::Reject("No servers available in region".to_string())
387//! }
388//! }
389//!
390//! async fn on_call_ended(&self, call: CallSession, reason: &str) {
391//! // Could track server performance metrics here
392//! }
393//! }
394//! ```
395//!
396//! # Best Practices
397//!
398//! 1. **Use Defer for Async Operations**: Don't block in `on_incoming_call`
399//! 2. **Chain Handlers**: Use CompositeHandler for complex logic
400//! 3. **Handle Errors Gracefully**: Always have a fallback decision
401//! 4. **Log Important Events**: Use the callbacks for monitoring
402//! 5. **Clean Up Resources**: Use `on_call_ended` for cleanup
403//!
404//! # Thread Safety
405//!
406//! All handlers must be `Send + Sync` as they're called from multiple async tasks.
407//! Use `Arc<Mutex<>>` or `Arc<RwLock<>>` for shared mutable state.
408
409use async_trait::async_trait;
410use std::collections::HashMap;
411use std::sync::{Arc, Mutex};
412use tokio::sync::mpsc;
413
414use crate::api::types::{IncomingCall, CallSession, CallDecision, SessionId, CallState};
415use crate::errors::Result;
416use crate::manager::events::{MediaQualityAlertLevel, MediaFlowDirection, WarningCategory};
417
418/// Main trait for handling call events
419#[async_trait]
420pub trait CallHandler: Send + Sync + std::fmt::Debug {
421 /// Handle an incoming call and decide what to do with it
422 async fn on_incoming_call(&self, call: IncomingCall) -> CallDecision;
423
424 /// Handle when a call ends
425 async fn on_call_ended(&self, call: CallSession, reason: &str);
426
427 /// Handle when a call is established (200 OK received/sent)
428 /// This is called when the call is fully established and media can flow
429 ///
430 /// # Arguments
431 /// * `call` - The established call session
432 /// * `local_sdp` - The local SDP (offer or answer)
433 /// * `remote_sdp` - The remote SDP (answer or offer)
434 async fn on_call_established(&self, call: CallSession, local_sdp: Option<String>, remote_sdp: Option<String>) {
435 // Default implementation does nothing
436 tracing::info!("Call {} established", call.id());
437 if let Some(remote) = remote_sdp {
438 tracing::debug!("Remote SDP: {}", remote);
439 }
440 }
441
442 // === New optional methods with default implementations ===
443
444 /// Called on any session state change
445 ///
446 /// This method is called for all state transitions, allowing fine-grained
447 /// monitoring of call progress. The default implementation does nothing.
448 ///
449 /// # Arguments
450 /// * `session_id` - The session that changed state
451 /// * `old_state` - The previous state
452 /// * `new_state` - The new state
453 /// * `reason` - Optional reason for the state change
454 async fn on_call_state_changed(
455 &self,
456 session_id: &SessionId,
457 old_state: &CallState,
458 new_state: &CallState,
459 reason: Option<&str>
460 ) {
461 // Default: do nothing - maintains backward compatibility
462 tracing::debug!(
463 "Call {} state changed from {:?} to {:?} (reason: {:?})",
464 session_id, old_state, new_state, reason
465 );
466 }
467
468 /// Called when media quality metrics are available
469 ///
470 /// This method provides real-time quality metrics for active calls.
471 /// The alert level is calculated based on the MOS score.
472 ///
473 /// # Arguments
474 /// * `session_id` - The session with quality metrics
475 /// * `mos_score` - Mean Opinion Score (1.0-5.0)
476 /// * `packet_loss` - Packet loss percentage
477 /// * `alert_level` - Calculated quality alert level
478 async fn on_media_quality(
479 &self,
480 session_id: &SessionId,
481 mos_score: f32,
482 packet_loss: f32,
483 alert_level: MediaQualityAlertLevel
484 ) {
485 // Default: do nothing
486 if matches!(alert_level, MediaQualityAlertLevel::Poor | MediaQualityAlertLevel::Critical) {
487 tracing::warn!(
488 "Call {} has poor quality: MOS={}, packet_loss={}%",
489 session_id, mos_score, packet_loss
490 );
491 }
492 }
493
494 /// Called when DTMF digit is received
495 ///
496 /// This method is called for each DTMF digit received during a call.
497 ///
498 /// # Arguments
499 /// * `session_id` - The session that received DTMF
500 /// * `digit` - The DTMF digit (0-9, *, #, A-D)
501 /// * `duration_ms` - Duration of the digit press in milliseconds
502 async fn on_dtmf(&self, session_id: &SessionId, digit: char, duration_ms: u32) {
503 // Default: do nothing
504 tracing::debug!(
505 "Call {} received DTMF digit '{}' (duration: {}ms)",
506 session_id, digit, duration_ms
507 );
508 }
509
510 /// Called when media starts/stops flowing
511 ///
512 /// This method notifies when media flow changes for a session.
513 ///
514 /// # Arguments
515 /// * `session_id` - The session with media flow change
516 /// * `direction` - Direction of media flow (Send/Receive/Both)
517 /// * `active` - Whether media is now flowing or stopped
518 /// * `codec` - The codec being used
519 async fn on_media_flow(
520 &self,
521 session_id: &SessionId,
522 direction: MediaFlowDirection,
523 active: bool,
524 codec: &str
525 ) {
526 // Default: do nothing
527 tracing::debug!(
528 "Call {} media flow {:?} {} (codec: {})",
529 session_id,
530 direction,
531 if active { "started" } else { "stopped" },
532 codec
533 );
534 }
535
536 /// Called on non-fatal warnings
537 ///
538 /// This method is called for warnings that don't prevent operation
539 /// but might indicate issues that should be addressed.
540 ///
541 /// # Arguments
542 /// * `session_id` - Optional session ID if warning is session-specific
543 /// * `category` - Category of the warning
544 /// * `message` - Warning message
545 async fn on_warning(
546 &self,
547 session_id: Option<&SessionId>,
548 category: WarningCategory,
549 message: &str
550 ) {
551 // Default: do nothing
552 match session_id {
553 Some(id) => tracing::warn!("Warning for call {} ({:?}): {}", id, category, message),
554 None => tracing::warn!("Warning ({:?}): {}", category, message),
555 }
556 }
557}
558
559/// Automatically accepts all incoming calls
560#[derive(Debug, Default)]
561pub struct AutoAnswerHandler;
562
563#[async_trait]
564impl CallHandler for AutoAnswerHandler {
565 async fn on_incoming_call(&self, _call: IncomingCall) -> CallDecision {
566 CallDecision::Accept(None) // Auto-accept without SDP answer
567 }
568
569 async fn on_call_ended(&self, call: CallSession, reason: &str) {
570 tracing::info!("Call {} ended: {}", call.id(), reason);
571 }
572}
573
574/// Queues incoming calls up to a maximum limit
575#[derive(Debug)]
576pub struct QueueHandler {
577 max_queue_size: usize,
578 queue: Arc<Mutex<Vec<IncomingCall>>>,
579 notify: Arc<Mutex<Option<mpsc::UnboundedSender<IncomingCall>>>>,
580}
581
582impl QueueHandler {
583 /// Create a new queue handler with the specified maximum queue size
584 pub fn new(max_queue_size: usize) -> Self {
585 Self {
586 max_queue_size,
587 queue: Arc::new(Mutex::new(Vec::new())),
588 notify: Arc::new(Mutex::new(None)),
589 }
590 }
591
592 /// Set up a notification channel for when calls are queued
593 pub fn set_notify_channel(&self, sender: mpsc::UnboundedSender<IncomingCall>) {
594 *self.notify.lock().unwrap() = Some(sender);
595 }
596
597 /// Get the next call from the queue
598 pub fn dequeue(&self) -> Option<IncomingCall> {
599 self.queue.lock().unwrap().pop()
600 }
601
602 /// Get the current queue size
603 pub fn queue_size(&self) -> usize {
604 self.queue.lock().unwrap().len()
605 }
606
607 /// Add a call to the queue (internal use)
608 pub async fn enqueue(&self, call: IncomingCall) {
609 let mut queue = self.queue.lock().unwrap();
610 queue.push(call.clone());
611
612 // Notify if there's a listener
613 if let Some(sender) = self.notify.lock().unwrap().as_ref() {
614 let _ = sender.send(call);
615 }
616 }
617}
618
619#[async_trait]
620impl CallHandler for QueueHandler {
621 async fn on_incoming_call(&self, call: IncomingCall) -> CallDecision {
622 let queue_size = {
623 let queue = self.queue.lock().unwrap();
624 queue.len()
625 };
626
627 if queue_size >= self.max_queue_size {
628 CallDecision::Reject("Queue full".to_string())
629 } else {
630 self.enqueue(call).await;
631 CallDecision::Defer
632 }
633 }
634
635 async fn on_call_ended(&self, call: CallSession, reason: &str) {
636 tracing::info!("Queued call {} ended: {}", call.id(), reason);
637 }
638}
639
640/// Routes calls based on destination patterns
641#[derive(Debug)]
642pub struct RoutingHandler {
643 routes: HashMap<String, String>,
644 default_action: CallDecision,
645}
646
647impl RoutingHandler {
648 /// Create a new routing handler
649 pub fn new() -> Self {
650 Self {
651 routes: HashMap::new(),
652 default_action: CallDecision::Reject("No route found".to_string()),
653 }
654 }
655
656 /// Add a routing rule (pattern -> target)
657 pub fn add_route(&mut self, pattern: &str, target: &str) {
658 self.routes.insert(pattern.to_string(), target.to_string());
659 }
660
661 /// Set the default action when no route matches
662 pub fn set_default_action(&mut self, action: CallDecision) {
663 self.default_action = action;
664 }
665
666 /// Find a route for the given destination
667 fn find_route(&self, destination: &str) -> Option<&str> {
668 // Simple prefix matching for now
669 for (pattern, target) in &self.routes {
670 if destination.starts_with(pattern) {
671 return Some(target);
672 }
673 }
674 None
675 }
676}
677
678impl Default for RoutingHandler {
679 fn default() -> Self {
680 Self::new()
681 }
682}
683
684#[async_trait]
685impl CallHandler for RoutingHandler {
686 async fn on_incoming_call(&self, call: IncomingCall) -> CallDecision {
687 if let Some(target) = self.find_route(&call.to) {
688 CallDecision::Forward(target.to_string())
689 } else {
690 self.default_action.clone()
691 }
692 }
693
694 async fn on_call_ended(&self, call: CallSession, reason: &str) {
695 tracing::info!("Routed call {} ended: {}", call.id(), reason);
696 }
697}
698
699/// Combines multiple handlers using a chain-of-responsibility pattern
700#[derive(Debug)]
701pub struct CompositeHandler {
702 handlers: Vec<Arc<dyn CallHandler>>,
703}
704
705impl CompositeHandler {
706 /// Create a new composite handler
707 pub fn new() -> Self {
708 Self {
709 handlers: Vec::new(),
710 }
711 }
712
713 /// Add a handler to the chain
714 pub fn add_handler(mut self, handler: Arc<dyn CallHandler>) -> Self {
715 self.handlers.push(handler);
716 self
717 }
718}
719
720impl Default for CompositeHandler {
721 fn default() -> Self {
722 Self::new()
723 }
724}
725
726#[async_trait]
727impl CallHandler for CompositeHandler {
728 async fn on_incoming_call(&self, call: IncomingCall) -> CallDecision {
729 // Try each handler in sequence
730 for handler in &self.handlers {
731 let decision = handler.on_incoming_call(call.clone()).await;
732
733 // Return the decision from the first handler that doesn't defer
734 // OR if any handler explicitly defers (like queue handler), return that
735 match decision {
736 CallDecision::Defer => return CallDecision::Defer,
737 CallDecision::Accept(sdp) => return CallDecision::Accept(sdp),
738 CallDecision::Reject(_) => return decision,
739 CallDecision::Forward(_) => return decision,
740 }
741 }
742
743 // If no handlers, reject the call
744 CallDecision::Reject("No handlers configured".to_string())
745 }
746
747 async fn on_call_ended(&self, call: CallSession, reason: &str) {
748 // Notify all handlers
749 for handler in &self.handlers {
750 handler.on_call_ended(call.clone(), reason).await;
751 }
752 }
753
754 async fn on_call_established(&self, call: CallSession, local_sdp: Option<String>, remote_sdp: Option<String>) {
755 // Notify all handlers
756 for handler in &self.handlers {
757 handler.on_call_established(call.clone(), local_sdp.clone(), remote_sdp.clone()).await;
758 }
759 }
760}