Mountain/IPC/Connection/
Types.rs

1//! # Connection Types (IPC Connection)
2//!
3//! ## RESPONSIBILITIES
4//! This module defines the core data structures for connection management in
5//! the IPC layer, including connection handles, statistics, and status
6//! tracking.
7//!
8//! ## ARCHITECTURAL ROLE
9//! This module provides the type definitions used throughout the connection
10//! management subsystem, ensuring type safety and consistency.
11//!
12//! ## KEY COMPONENTS
13//!
14//! - **ConnectionHandle**: Represents an active connection with health tracking
15//! - **ConnectionStats**: Statistics about the connection pool
16//! - **ConnectionStatus**: Connection health status
17//!
18//! ## ERROR HANDLING
19//! N/A - This is a data definition module.
20//!
21//! ## LOGGING
22//! N/A - Status changes are logged by the ConnectionManager.
23//!
24//! ## PERFORMANCE CONSIDERATIONS
25//! - ConnectionHandle uses health scoring for efficient monitoring
26//! - Stats are calculated on-demand to avoid overhead
27//! - Simple structures minimize memory footprint
28//!
29//! ## TODO
30//! - Add connection metadata (protocol, endpoint)
31//! - Implement connection duration tracking
32//! - Add connection quality metrics
33//! - Support connection tagging for categorization
34
35use serde::{Deserialize, Serialize};
36
37/// Connection status
38///
39/// This enum represents the current state of an IPC connection, allowing
40/// the system to track and report connection health.
41#[derive(Debug, Clone, Serialize, Deserialize)]
42pub enum ConnectionStatus {
43	/// Connection is active and healthy
44	Connected,
45
46	/// Connection is disconnected
47	Disconnected,
48
49	/// Connection is degraded (intermittent issues)
50	Degraded,
51
52	/// Connection has failed
53	Failed,
54}
55
56impl ConnectionStatus {
57	/// Check if connection is active
58	pub fn is_connected(&self) -> bool { matches!(self, ConnectionStatus::Connected) }
59
60	/// Check if connection has issues
61	pub fn has_issues(&self) -> bool { matches!(self, ConnectionStatus::Degraded | ConnectionStatus::Failed) }
62
63	/// Get human-readable description
64	pub fn description(&self) -> &'static str {
65		match self {
66			ConnectionStatus::Connected => "Connected and healthy",
67			ConnectionStatus::Disconnected => "Disconnected",
68			ConnectionStatus::Degraded => "Degraded - experiencing issues",
69			ConnectionStatus::Failed => "Failed - connection lost",
70		}
71	}
72
73	/// Get the status level (0=failed, 1=degraded, 2=disconnected, 3=connected)
74	pub fn level(&self) -> u8 {
75		match self {
76			ConnectionStatus::Failed => 0,
77			ConnectionStatus::Degraded => 1,
78			ConnectionStatus::Disconnected => 2,
79			ConnectionStatus::Connected => 3,
80		}
81	}
82}
83
84impl From<bool> for ConnectionStatus {
85	fn from(connected:bool) -> Self {
86		if connected {
87			ConnectionStatus::Connected
88		} else {
89			ConnectionStatus::Disconnected
90		}
91	}
92}
93
94/// Handle representing an active connection
95///
96/// This structure tracks the state and health of an individual connection
97/// in the connection pool.
98///
99/// ## Health Scoring
100///
101/// The health score ranges from 0.0 to 100.0:
102/// - 100.0: Perfect health
103/// - 75.0-99.9: Good health
104/// - 50.0-74.9: Degraded health
105/// - 0.0-49.9: Poor health
106///
107/// Health is updated based on operation success/failure:
108/// - Success: +10 points (max 100)
109/// - Failure: -25 points (min 0)
110///
111/// ## Example Usage
112///
113/// ```rust,ignore
114/// let mut handle = ConnectionHandle::new();
115///
116/// // Update health based on operation success
117/// handle.update_health(true); // Success
118/// handle.update_health(false); // Failure
119///
120/// // Check if connection is healthy
121/// if handle.is_healthy() {
122///     // Use the connection
123/// }
124/// ```
125#[derive(Clone, Serialize, Deserialize)]
126pub struct ConnectionHandle {
127	/// Unique connection identifier (UUID)
128	pub id:String,
129
130	/// When the connection was created (as SystemTime for serialization)
131	pub created_at:std::time::SystemTime,
132
133	/// When the connection was last used (as SystemTime for serialization)
134	pub last_used:std::time::SystemTime,
135
136	/// Health score (0.0 to 100.0)
137	pub health_score:f64,
138
139	/// Number of consecutive errors
140	pub error_count:usize,
141}
142
143impl ConnectionHandle {
144	/// Create a new connection handle with health monitoring
145	pub fn new() -> Self {
146		let now = std::time::SystemTime::now();
147		Self {
148			id:uuid::Uuid::new_v4().to_string(),
149			created_at:now,
150			last_used:now,
151			health_score:100.0,
152			error_count:0,
153		}
154	}
155
156	/// Update health score based on operation success
157	///
158	/// ## Parameters
159	/// - `success`: Whether the operation succeeded
160	///
161	/// ## Behavior
162	/// - Success: +10 points (capped at 100), reset error count
163	/// - Failure: -25 points (floored at 0), increment error count
164	pub fn update_health(&mut self, success:bool) {
165		if success {
166			self.health_score = (self.health_score + 10.0).min(100.0);
167			self.error_count = 0;
168		} else {
169			self.health_score = (self.health_score - 25.0).max(0.0);
170			self.error_count += 1;
171		}
172		self.last_used = std::time::SystemTime::now();
173	}
174
175	/// Check if connection is healthy
176	///
177	/// A connection is considered healthy if:
178	/// - Health score > 50.0
179	/// - Error count < 5
180	///
181	/// ## Returns
182	/// - `true`: Connection is healthy
183	/// - `false`: Connection is unhealthy
184	pub fn is_healthy(&self) -> bool { self.health_score > 50.0 && self.error_count < 5 }
185
186	/// Get connection age in seconds
187	pub fn age_seconds(&self) -> u64 {
188		self.created_at
189			.duration_since(std::time::UNIX_EPOCH)
190			.map(|d| d.as_secs())
191			.unwrap_or(0)
192	}
193
194	/// Get time since last use in seconds
195	pub fn idle_seconds(&self) -> u64 {
196		self.last_used
197			.duration_since(std::time::UNIX_EPOCH)
198			.map(|d| d.as_secs())
199			.unwrap_or(0)
200	}
201
202	/// Get connection status
203	pub fn status(&self) -> ConnectionStatus {
204		if self.is_healthy() {
205			ConnectionStatus::Connected
206		} else if self.health_score > 25.0 {
207			ConnectionStatus::Degraded
208		} else {
209			ConnectionStatus::Failed
210		}
211	}
212
213	/// Manually update the last used time
214	pub fn touch(&mut self) { self.last_used = std::time::SystemTime::now(); }
215
216	/// Reset health score to perfect
217	pub fn reset_health(&mut self) {
218		self.health_score = 100.0;
219		self.error_count = 0;
220		self.last_used = std::time::SystemTime::now();
221	}
222}
223
224/// Helper trait to get duration since UNIX epoch for SystemTime
225trait SystemTimeExt {
226	/// Get the duration since UNIX epoch in seconds
227	fn duration_since_epoch_secs(&self) -> Result<u64, std::time::SystemTimeError>;
228}
229
230impl SystemTimeExt for std::time::SystemTime {
231	fn duration_since_epoch_secs(&self) -> Result<u64, std::time::SystemTimeError> {
232		self.duration_since(std::time::UNIX_EPOCH).map(|d| d.as_secs())
233	}
234}
235
236impl std::fmt::Debug for ConnectionHandle {
237	fn fmt(&self, f:&mut std::fmt::Formatter<'_>) -> std::fmt::Result {
238		let created_age = self
239			.created_at
240			.duration_since(std::time::UNIX_EPOCH)
241			.map(|d| d.as_secs())
242			.unwrap_or(0);
243		let last_used_age = self
244			.last_used
245			.duration_since(std::time::UNIX_EPOCH)
246			.map(|d| d.as_secs())
247			.unwrap_or(0);
248
249		f.debug_struct("ConnectionHandle")
250			.field("id", &self.id)
251			.field("created_at_age_seconds", &created_age)
252			.field("last_used_age_seconds", &last_used_age)
253			.field("health_score", &self.health_score)
254			.field("error_count", &self.error_count)
255			.field("status", &self.status())
256			.finish()
257	}
258}
259
260/// Connection statistics for monitoring
261///
262/// This structure provides aggregate statistics about the connection pool,
263/// useful for monitoring and debugging.
264///
265/// ## Example Usage
266///
267/// ```rust,ignore
268/// let stats = pool.GetStats().await;
269///
270/// println!("Total connections: {}", stats.total_connections);
271/// println!("Healthy: {}", stats.healthy_connections);
272/// println!("Available: {}", stats.available_permits);
273/// ```
274#[derive(Debug, Clone, Default)]
275pub struct ConnectionStats {
276	/// Total number of active connections
277	pub total_connections:usize,
278
279	/// Number of healthy connections
280	pub healthy_connections:usize,
281
282	/// Maximum number of connections allowed
283	pub max_connections:usize,
284
285	/// Number of available connection permits
286	pub available_permits:usize,
287
288	/// Connection timeout duration
289	pub connection_timeout:std::time::Duration,
290}
291
292impl ConnectionStats {
293	/// Calculate connection pool utilization percentage
294	///
295	/// ## Returns
296	/// Percentage of connections in use (0.0 to 100.0)
297	pub fn utilization(&self) -> f64 {
298		if self.max_connections == 0 {
299			return 0.0;
300		}
301
302		let used = self.max_connections - self.available_permits;
303		(used as f64 / self.max_connections as f64) * 100.0
304	}
305
306	/// Calculate health percentage
307	///
308	/// ## Returns
309	/// Percentage of connections that are healthy (0.0 to 100.0)
310	pub fn health_percentage(&self) -> f64 {
311		if self.total_connections == 0 {
312			return 100.0;
313		}
314
315		(self.healthy_connections as f64 / self.total_connections as f64) * 100.0
316	}
317
318	/// Check if pool is under stress
319	///
320	/// Pool is under stress if:
321	/// - Utilization > 80%
322	/// - Health percentage < 70%
323	///
324	/// ## Returns
325	/// - `true`: Pool is under stress
326	/// - `false`: Pool is healthy
327	pub fn is_under_stress(&self) -> bool { self.utilization() > 80.0 || self.health_percentage() < 70.0 }
328
329	/// Get a human-readable status summary
330	pub fn summary(&self) -> String {
331		format!(
332			"Connections: {}/{} ({}%), Healthy: {}%, Utilization: {}%",
333			self.total_connections,
334			self.max_connections,
335			self.health_percentage(),
336			self.health_percentage(),
337			self.utilization()
338		)
339	}
340}
341
342#[cfg(test)]
343mod tests {
344	use super::*;
345
346	#[test]
347	fn test_connection_status_from_bool() {
348		assert!(matches!(ConnectionStatus::from(true), ConnectionStatus::Connected));
349		assert!(matches!(ConnectionStatus::from(false), ConnectionStatus::Disconnected));
350	}
351
352	#[test]
353	fn test_connection_status_description() {
354		assert_eq!(ConnectionStatus::Connected.description(), "Connected and healthy");
355		assert_eq!(ConnectionStatus::Disconnected.description(), "Disconnected");
356		assert_eq!(ConnectionStatus::Degraded.description(), "Degraded - experiencing issues");
357		assert_eq!(ConnectionStatus::Failed.description(), "Failed - connection lost");
358	}
359
360	#[test]
361	fn test_connection_status_level() {
362		assert_eq!(ConnectionStatus::Failed.level(), 0);
363		assert_eq!(ConnectionStatus::Degraded.level(), 1);
364		assert_eq!(ConnectionStatus::Disconnected.level(), 2);
365		assert_eq!(ConnectionStatus::Connected.level(), 3);
366	}
367
368	#[test]
369	fn test_connection_handle_creation() {
370		let handle = ConnectionHandle::new();
371		assert!(!handle.id.is_empty());
372		assert_eq!(handle.health_score, 100.0);
373		assert_eq!(handle.error_count, 0);
374		assert!(handle.is_healthy());
375	}
376
377	#[test]
378	fn test_connection_handle_health_update_success() {
379		let mut handle = ConnectionHandle::new();
380
381		// Initially healthy
382		assert_eq!(handle.health_score, 100.0);
383		assert!(handle.is_healthy());
384
385		// Simulate success (already at 100, should stay at 100)
386		handle.update_health(true);
387		assert_eq!(handle.health_score, 100.0);
388		assert_eq!(handle.error_count, 0);
389
390		// Simulate failure
391		handle.update_health(false);
392		assert_eq!(handle.health_score, 75.0);
393		assert_eq!(handle.error_count, 1);
394		assert!(handle.is_healthy());
395
396		// More failures
397		handle.update_health(false);
398		assert_eq!(handle.health_score, 50.0);
399		assert_eq!(handle.error_count, 2);
400		assert!(!handle.is_healthy()); // Health <= 50
401
402		// Recovery
403		handle.update_health(true);
404		assert_eq!(handle.health_score, 60.0);
405		assert_eq!(handle.error_count, 0);
406		assert!(handle.is_healthy());
407	}
408
409	#[test]
410	fn test_connection_handle_health_boundaries() {
411		let mut handle = ConnectionHandle::new();
412
413		// Test upper bound (100)
414		for _ in 0..20 {
415			handle.update_health(true);
416		}
417		assert_eq!(handle.health_score, 100.0);
418
419		// Reset
420		handle.health_score = 50.0;
421
422		// Test lower bound (0)
423		for _ in 0..10 {
424			handle.update_health(false);
425		}
426		assert_eq!(handle.health_score, 0.0);
427	}
428
429	#[test]
430	fn test_connection_handle_is_healthy() {
431		let mut handle = ConnectionHandle::new();
432
433		assert!(handle.is_healthy());
434
435		// Make unhealthy via health score
436		handle.health_score = 50.0;
437		handle.error_count = 0;
438		assert!(!handle.is_healthy()); // Health <= 50
439
440		// Make unhealthy via error count
441		handle.health_score = 60.0;
442		handle.error_count = 5;
443		assert!(!handle.is_healthy()); // Errors >= 5
444	}
445
446	#[test]
447	fn test_connection_handle_status() {
448		let mut handle = ConnectionHandle::new();
449		assert!(matches!(handle.status(), ConnectionStatus::Connected));
450
451		handle.health_score = 75.0;
452		assert!(matches!(handle.status(), ConnectionStatus::Connected));
453
454		handle.health_score = 50.0;
455		assert!(matches!(handle.status(), ConnectionStatus::Degraded));
456
457		handle.health_score = 25.0;
458		assert!(matches!(handle.status(), ConnectionStatus::Failed));
459	}
460
461	#[test]
462	fn test_connection_handle_reset() {
463		let mut handle = ConnectionHandle::new();
464
465		// Degrade the connection
466		for _ in 0..3 {
467			handle.update_health(false);
468		}
469		assert!(handle.health_score < 100.0);
470
471		// Reset
472		handle.reset_health();
473		assert_eq!(handle.health_score, 100.0);
474		assert_eq!(handle.error_count, 0);
475	}
476
477	#[test]
478	fn test_connection_stats_utilization() {
479		let stats = ConnectionStats {
480			total_connections:50,
481			healthy_connections:45,
482			max_connections:100,
483			available_permits:50,
484			connection_timeout:std::time::Duration::from_secs(30),
485		};
486
487		// 50 used out of 100 = 50%
488		assert_eq!(stats.utilization(), 50.0);
489	}
490
491	#[test]
492	fn test_connection_stats_health_percentage() {
493		let stats = ConnectionStats {
494			total_connections:50,
495			healthy_connections:45,
496			max_connections:100,
497			available_permits:50,
498			connection_timeout:std::time::Duration::from_secs(30),
499		};
500
501		// 45 healthy out of 50 total = 90%
502		assert_eq!(stats.health_percentage(), 90.0);
503	}
504
505	#[test]
506	fn test_connection_stats_is_under_stress() {
507		let mut stats = ConnectionStats {
508			total_connections:50,
509			healthy_connections:45,
510			max_connections:100,
511			available_permits:50,
512			connection_timeout:std::time::Duration::from_secs(30),
513		};
514
515		// Not under stress
516		assert!(!stats.is_under_stress());
517
518		// High utilization (90%)
519		stats.available_permits = 10;
520		assert!(stats.is_under_stress());
521
522		// Low health percentage
523		stats.available_permits = 50;
524		stats.healthy_connections = 30; // 60%
525		assert!(stats.is_under_stress());
526	}
527
528	#[test]
529	fn test_connection_stats_empty_pool() {
530		let stats = ConnectionStats {
531			total_connections:0,
532			healthy_connections:0,
533			max_connections:100,
534			available_permits:100,
535			connection_timeout:std::time::Duration::from_secs(30),
536		};
537
538		assert_eq!(stats.utilization(), 0.0);
539		assert_eq!(stats.health_percentage(), 100.0); // Empty pool is healthy
540		assert!(!stats.is_under_stress());
541	}
542}