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}