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}