rvoip_dialog_core/api/server/
core.rs

1//! Core DialogServer Implementation
2//!
3//! This module contains the core DialogServer struct and its constructor methods.
4//! It handles server initialization, configuration validation, and dependency injection.
5
6use std::sync::Arc;
7use std::net::SocketAddr;
8use tokio::sync::mpsc;
9use tracing::{info, warn};
10
11use rvoip_transaction_core::{TransactionManager, TransactionEvent};
12use crate::manager::DialogManager;
13use crate::events::SessionCoordinationEvent;
14use super::super::{ApiResult, ApiError, DialogApi, DialogStats};
15use super::super::config::ServerConfig;
16
17/// High-level server interface for SIP dialog management
18/// 
19/// Provides a clean, intuitive API for server-side SIP operations including:
20/// - Automatic INVITE handling
21/// - Response generation
22/// - Dialog lifecycle management
23/// - Session coordination
24/// - **NEW**: Dialog-level coordination for session-core integration
25/// 
26/// ## Example Usage
27/// 
28/// ```rust,no_run
29/// use rvoip_dialog_core::api::{DialogServer, DialogApi};
30/// use tokio::sync::mpsc;
31/// 
32/// #[tokio::main]
33/// async fn main() -> Result<(), Box<dyn std::error::Error>> {
34///     // Create server with simple configuration
35///     let server = DialogServer::new("0.0.0.0:5060").await?;
36///     
37///     // Set up session coordination
38///     let (session_tx, session_rx) = mpsc::channel(100);
39///     server.set_session_coordinator(session_tx).await?;
40///     
41///     // Start processing SIP messages
42///     server.start().await?;
43///     
44///     Ok(())
45/// }
46/// ```
47pub struct DialogServer {
48    /// Underlying dialog manager
49    pub(crate) dialog_manager: Arc<DialogManager>,
50    
51    /// Server configuration
52    pub(crate) config: ServerConfig,
53    
54    /// Statistics tracking
55    pub(crate) stats: Arc<tokio::sync::RwLock<ServerStats>>,
56}
57
58/// Internal statistics tracking
59#[derive(Debug, Default)]
60pub struct ServerStats {
61    pub active_dialogs: usize,
62    pub total_dialogs: u64,
63    pub successful_calls: u64,
64    pub failed_calls: u64,
65    pub total_call_duration: f64,
66}
67
68impl DialogServer {
69    /// Create a new dialog server with simple configuration
70    /// 
71    /// This is the easiest way to create a server - just provide a local address
72    /// and the server will be configured with sensible defaults.
73    /// 
74    /// # Arguments
75    /// * `local_address` - Address to bind to (e.g., "0.0.0.0:5060")
76    /// 
77    /// # Returns
78    /// A configured DialogServer ready to start
79    pub async fn new(local_address: &str) -> ApiResult<Self> {
80        let addr: SocketAddr = local_address.parse()
81            .map_err(|e| ApiError::Configuration { 
82                message: format!("Invalid local address '{}': {}", local_address, e) 
83            })?;
84        
85        let config = ServerConfig::new(addr);
86        Self::with_config(config).await
87    }
88    
89    /// Create a dialog server with custom configuration
90    /// 
91    /// **ARCHITECTURAL NOTE**: This method requires dependency injection to maintain
92    /// proper separation of concerns. dialog-core should not directly manage transport
93    /// concerns - that's the responsibility of transaction-core.
94    /// 
95    /// Use `with_global_events()` or `with_dependencies()` instead, where you provide
96    /// a pre-configured TransactionManager that handles all transport setup.
97    /// 
98    /// # Arguments
99    /// * `config` - Server configuration (for validation and future use)
100    /// 
101    /// # Returns
102    /// An error directing users to the proper dependency injection constructors
103    pub async fn with_config(config: ServerConfig) -> ApiResult<Self> {
104        // Validate configuration for future use
105        config.validate()
106            .map_err(|e| ApiError::Configuration { message: e })?;
107            
108        // Return architectural guidance error
109        Err(ApiError::Configuration { 
110            message: format!(
111                "Simple construction violates architectural separation of concerns. \
112                 dialog-core should not manage transport directly. \
113                 \nUse dependency injection instead:\
114                 \n\n1. with_global_events(transaction_manager, events, config) - RECOMMENDED\
115                 \n2. with_dependencies(transaction_manager, config)\
116                 \n\nExample setup in your application:\
117                 \n  // Set up transport and transaction manager in your app\
118                 \n  let (tx_mgr, events) = TransactionManager::with_transport(transport).await?;\
119                 \n  let server = DialogServer::with_global_events(tx_mgr, events, config).await?;\
120                 \n\nSee examples/ directory for complete setup patterns.",
121            )
122        })
123    }
124    
125    /// Create a dialog server with dependency injection and global events (RECOMMENDED)
126    /// 
127    /// This constructor follows the working pattern from transaction-core examples
128    /// by using global transaction event subscription for proper event consumption.
129    /// 
130    /// # Arguments
131    /// * `transaction_manager` - Pre-configured transaction manager
132    /// * `transaction_events` - Global transaction event receiver
133    /// * `config` - Server configuration
134    /// 
135    /// # Returns
136    /// A configured DialogServer ready to start
137    pub async fn with_global_events(
138        transaction_manager: Arc<TransactionManager>,
139        transaction_events: mpsc::Receiver<TransactionEvent>,
140        config: ServerConfig,
141    ) -> ApiResult<Self> {
142        // Validate configuration
143        config.validate()
144            .map_err(|e| ApiError::Configuration { message: e })?;
145        
146        info!("Creating DialogServer with global transaction events (RECOMMENDED PATTERN)");
147        
148        // Create dialog manager with global event subscription (ROOT CAUSE FIX)
149        let dialog_manager = Arc::new(
150            DialogManager::with_global_events(transaction_manager, transaction_events, config.dialog.local_address).await
151                .map_err(|e| ApiError::Internal { 
152                    message: format!("Failed to create dialog manager with global events: {}", e) 
153                })?
154        );
155        
156        Ok(Self {
157            dialog_manager,
158            config,
159            stats: Arc::new(tokio::sync::RwLock::new(ServerStats::default())),
160        })
161    }
162    
163    /// Create a dialog server with dependency injection
164    /// 
165    /// Use this when you want full control over dependencies, particularly
166    /// useful for testing or when integrating with existing infrastructure.
167    /// 
168    /// **NOTE**: This method still uses the old individual transaction subscription pattern.
169    /// For proper event consumption, use `with_global_events()` instead.
170    /// 
171    /// # Arguments
172    /// * `transaction_manager` - Pre-configured transaction manager
173    /// * `config` - Server configuration
174    /// 
175    /// # Returns
176    /// A configured DialogServer ready to start
177    pub async fn with_dependencies(
178        transaction_manager: Arc<TransactionManager>,
179        config: ServerConfig,
180    ) -> ApiResult<Self> {
181        // Validate configuration
182        config.validate()
183            .map_err(|e| ApiError::Configuration { message: e })?;
184        
185        info!("Creating DialogServer with injected dependencies");
186        warn!("WARNING: Using old DialogManager::new() pattern - consider upgrading to with_global_events() for better reliability");
187        
188        // Create dialog manager with injected dependencies (OLD PATTERN - may have event issues)
189        let dialog_manager = Arc::new(
190            DialogManager::new(transaction_manager, config.dialog.local_address).await
191                .map_err(|e| ApiError::Internal { 
192                    message: format!("Failed to create dialog manager: {}", e) 
193                })?
194        );
195        
196        Ok(Self {
197            dialog_manager,
198            config,
199            stats: Arc::new(tokio::sync::RwLock::new(ServerStats::default())),
200        })
201    }
202    
203    /// Get server configuration
204    pub fn config(&self) -> &ServerConfig {
205        &self.config
206    }
207}
208
209/// Implementation of DialogApi trait
210impl DialogApi for DialogServer {
211    fn dialog_manager(&self) -> &Arc<DialogManager> {
212        &self.dialog_manager
213    }
214    
215    async fn set_session_coordinator(&self, sender: mpsc::Sender<SessionCoordinationEvent>) -> ApiResult<()> {
216        self.dialog_manager.set_session_coordinator(sender).await;
217        Ok(())
218    }
219    
220    async fn start(&self) -> ApiResult<()> {
221        info!("Starting DialogServer on {}", self.config.dialog.local_address);
222        self.dialog_manager.start().await
223            .map_err(ApiError::from)
224    }
225    
226    async fn stop(&self) -> ApiResult<()> {
227        info!("Stopping DialogServer");
228        self.dialog_manager.stop().await
229            .map_err(ApiError::from)
230    }
231    
232    async fn get_stats(&self) -> DialogStats {
233        let stats = self.stats.read().await;
234        DialogStats {
235            active_dialogs: stats.active_dialogs,
236            total_dialogs: stats.total_dialogs,
237            successful_calls: stats.successful_calls,
238            failed_calls: stats.failed_calls,
239            avg_call_duration: if stats.successful_calls > 0 {
240                stats.total_call_duration / stats.successful_calls as f64
241            } else {
242                0.0
243            },
244        }
245    }
246}