Skip to main content

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
68			ConnectionStatus::Disconnected => "Disconnected",
69
70			ConnectionStatus::Degraded => "Degraded - experiencing issues",
71
72			ConnectionStatus::Failed => "Failed - connection lost",
73		}
74	}
75
76	/// Get the status level (0=failed, 1=degraded, 2=disconnected, 3=connected)
77	pub fn level(&self) -> u8 {
78		match self {
79			ConnectionStatus::Failed => 0,
80
81			ConnectionStatus::Degraded => 1,
82
83			ConnectionStatus::Disconnected => 2,
84
85			ConnectionStatus::Connected => 3,
86		}
87	}
88}
89
90impl From<bool> for ConnectionStatus {
91	fn from(connected:bool) -> Self {
92		if connected {
93			ConnectionStatus::Connected
94		} else {
95			ConnectionStatus::Disconnected
96		}
97	}
98}
99
100/// Handle representing an active connection
101///
102/// This structure tracks the state and health of an individual connection
103/// in the connection pool.
104///
105/// ## Health Scoring
106///
107/// The health score ranges from 0.0 to 100.0:
108/// - 100.0: Perfect health
109/// - 75.0-99.9: Good health
110/// - 50.0-74.9: Degraded health
111/// - 0.0-49.9: Poor health
112///
113/// Health is updated based on operation success/failure:
114/// - Success: +10 points (max 100)
115/// - Failure: -25 points (min 0)
116///
117/// ## Example Usage
118///
119/// ```rust,ignore
120/// let mut handle = ConnectionHandle::new();
121///
122/// // Update health based on operation success
123/// handle.update_health(true); // Success
124/// handle.update_health(false); // Failure
125///
126/// // Check if connection is healthy
127/// if handle.is_healthy() {
128///     // Use the connection
129/// }
130/// ```
131#[derive(Clone, Serialize, Deserialize)]
132pub struct ConnectionHandle {
133	/// Unique connection identifier (UUID)
134	pub id:String,
135
136	/// When the connection was created (as SystemTime for serialization)
137	pub created_at:std::time::SystemTime,
138
139	/// When the connection was last used (as SystemTime for serialization)
140	pub last_used:std::time::SystemTime,
141
142	/// Health score (0.0 to 100.0)
143	pub health_score:f64,
144
145	/// Number of consecutive errors
146	pub error_count:usize,
147}
148
149impl ConnectionHandle {
150	/// Create a new connection handle with health monitoring
151	pub fn new() -> Self {
152		let now = std::time::SystemTime::now();
153
154		Self {
155			id:uuid::Uuid::new_v4().to_string(),
156
157			created_at:now,
158
159			last_used:now,
160
161			health_score:100.0,
162
163			error_count:0,
164		}
165	}
166
167	/// Update health score based on operation success
168	///
169	/// ## Parameters
170	/// - `success`: Whether the operation succeeded
171	///
172	/// ## Behavior
173	/// - Success: +10 points (capped at 100), reset error count
174	/// - Failure: -25 points (floored at 0), increment error count
175	pub fn update_health(&mut self, success:bool) {
176		if success {
177			self.health_score = (self.health_score + 10.0).min(100.0);
178
179			self.error_count = 0;
180		} else {
181			self.health_score = (self.health_score - 25.0).max(0.0);
182
183			self.error_count += 1;
184		}
185
186		self.last_used = std::time::SystemTime::now();
187	}
188
189	/// Check if connection is healthy
190	///
191	/// A connection is considered healthy if:
192	/// - Health score > 50.0
193	/// - Error count < 5
194	///
195	/// ## Returns
196	/// - `true`: Connection is healthy
197	/// - `false`: Connection is unhealthy
198	pub fn is_healthy(&self) -> bool { self.health_score > 50.0 && self.error_count < 5 }
199
200	/// Get connection age in seconds
201	pub fn age_seconds(&self) -> u64 {
202		self.created_at
203			.duration_since(std::time::UNIX_EPOCH)
204			.map(|d| d.as_secs())
205			.unwrap_or(0)
206	}
207
208	/// Get time since last use in seconds
209	pub fn idle_seconds(&self) -> u64 {
210		self.last_used
211			.duration_since(std::time::UNIX_EPOCH)
212			.map(|d| d.as_secs())
213			.unwrap_or(0)
214	}
215
216	/// Get connection status
217	pub fn status(&self) -> ConnectionStatus {
218		if self.is_healthy() {
219			ConnectionStatus::Connected
220		} else if self.health_score > 25.0 {
221			ConnectionStatus::Degraded
222		} else {
223			ConnectionStatus::Failed
224		}
225	}
226
227	/// Manually update the last used time
228	pub fn touch(&mut self) { self.last_used = std::time::SystemTime::now(); }
229
230	/// Reset health score to perfect
231	pub fn reset_health(&mut self) {
232		self.health_score = 100.0;
233
234		self.error_count = 0;
235
236		self.last_used = std::time::SystemTime::now();
237	}
238}
239
240/// Helper trait to get duration since UNIX epoch for SystemTime
241trait SystemTimeExt {
242	/// Get the duration since UNIX epoch in seconds
243	fn duration_since_epoch_secs(&self) -> Result<u64, std::time::SystemTimeError>;
244}
245
246impl SystemTimeExt for std::time::SystemTime {
247	fn duration_since_epoch_secs(&self) -> Result<u64, std::time::SystemTimeError> {
248		self.duration_since(std::time::UNIX_EPOCH).map(|d| d.as_secs())
249	}
250}
251
252impl std::fmt::Debug for ConnectionHandle {
253	fn fmt(&self, f:&mut std::fmt::Formatter<'_>) -> std::fmt::Result {
254		let created_age = self
255			.created_at
256			.duration_since(std::time::UNIX_EPOCH)
257			.map(|d| d.as_secs())
258			.unwrap_or(0);
259
260		let last_used_age = self
261			.last_used
262			.duration_since(std::time::UNIX_EPOCH)
263			.map(|d| d.as_secs())
264			.unwrap_or(0);
265
266		f.debug_struct("ConnectionHandle")
267			.field("id", &self.id)
268			.field("created_at_age_seconds", &created_age)
269			.field("last_used_age_seconds", &last_used_age)
270			.field("health_score", &self.health_score)
271			.field("error_count", &self.error_count)
272			.field("status", &self.status())
273			.finish()
274	}
275}
276
277/// Connection statistics for monitoring
278///
279/// This structure provides aggregate statistics about the connection pool,
280/// useful for monitoring and debugging.
281///
282/// ## Example Usage
283///
284/// ```rust,ignore
285/// let stats = pool.GetStats().await;
286///
287/// println!("Total connections: {}", stats.total_connections);
288/// println!("Healthy: {}", stats.healthy_connections);
289/// println!("Available: {}", stats.available_permits);
290/// ```
291#[derive(Debug, Clone, Default)]
292pub struct ConnectionStats {
293	/// Total number of active connections
294	pub total_connections:usize,
295
296	/// Number of healthy connections
297	pub healthy_connections:usize,
298
299	/// Maximum number of connections allowed
300	pub max_connections:usize,
301
302	/// Number of available connection permits
303	pub available_permits:usize,
304
305	/// Connection timeout duration
306	pub connection_timeout:std::time::Duration,
307}
308
309impl ConnectionStats {
310	/// Calculate connection pool utilization percentage
311	///
312	/// ## Returns
313	/// Percentage of connections in use (0.0 to 100.0)
314	pub fn utilization(&self) -> f64 {
315		if self.max_connections == 0 {
316			return 0.0;
317		}
318
319		let used = self.max_connections - self.available_permits;
320
321		(used as f64 / self.max_connections as f64) * 100.0
322	}
323
324	/// Calculate health percentage
325	///
326	/// ## Returns
327	/// Percentage of connections that are healthy (0.0 to 100.0)
328	pub fn health_percentage(&self) -> f64 {
329		if self.total_connections == 0 {
330			return 100.0;
331		}
332
333		(self.healthy_connections as f64 / self.total_connections as f64) * 100.0
334	}
335
336	/// Check if pool is under stress
337	///
338	/// Pool is under stress if:
339	/// - Utilization > 80%
340	/// - Health percentage < 70%
341	///
342	/// ## Returns
343	/// - `true`: Pool is under stress
344	/// - `false`: Pool is healthy
345	pub fn is_under_stress(&self) -> bool { self.utilization() > 80.0 || self.health_percentage() < 70.0 }
346
347	/// Get a human-readable status summary
348	pub fn summary(&self) -> String {
349		format!(
350			"Connections: {}/{} ({}%), Healthy: {}%, Utilization: {}%",
351			self.total_connections,
352			self.max_connections,
353			self.health_percentage(),
354			self.health_percentage(),
355			self.utilization()
356		)
357	}
358}
359
360#[cfg(test)]
361mod tests {
362
363	use super::*;
364
365	#[test]
366	fn test_connection_status_from_bool() {
367		assert!(matches!(ConnectionStatus::from(true), ConnectionStatus::Connected));
368
369		assert!(matches!(ConnectionStatus::from(false), ConnectionStatus::Disconnected));
370	}
371
372	#[test]
373	fn test_connection_status_description() {
374		assert_eq!(ConnectionStatus::Connected.description(), "Connected and healthy");
375
376		assert_eq!(ConnectionStatus::Disconnected.description(), "Disconnected");
377
378		assert_eq!(ConnectionStatus::Degraded.description(), "Degraded - experiencing issues");
379
380		assert_eq!(ConnectionStatus::Failed.description(), "Failed - connection lost");
381	}
382
383	#[test]
384	fn test_connection_status_level() {
385		assert_eq!(ConnectionStatus::Failed.level(), 0);
386
387		assert_eq!(ConnectionStatus::Degraded.level(), 1);
388
389		assert_eq!(ConnectionStatus::Disconnected.level(), 2);
390
391		assert_eq!(ConnectionStatus::Connected.level(), 3);
392	}
393
394	#[test]
395	fn test_connection_handle_creation() {
396		let handle = ConnectionHandle::new();
397
398		assert!(!handle.id.is_empty());
399
400		assert_eq!(handle.health_score, 100.0);
401
402		assert_eq!(handle.error_count, 0);
403
404		assert!(handle.is_healthy());
405	}
406
407	#[test]
408	fn test_connection_handle_health_update_success() {
409		let mut handle = ConnectionHandle::new();
410
411		// Initially healthy
412		assert_eq!(handle.health_score, 100.0);
413
414		assert!(handle.is_healthy());
415
416		// Simulate success (already at 100, should stay at 100)
417		handle.update_health(true);
418
419		assert_eq!(handle.health_score, 100.0);
420
421		assert_eq!(handle.error_count, 0);
422
423		// Simulate failure
424		handle.update_health(false);
425
426		assert_eq!(handle.health_score, 75.0);
427
428		assert_eq!(handle.error_count, 1);
429
430		assert!(handle.is_healthy());
431
432		// More failures
433		handle.update_health(false);
434
435		assert_eq!(handle.health_score, 50.0);
436
437		assert_eq!(handle.error_count, 2);
438
439		assert!(!handle.is_healthy()); // Health <= 50
440
441		// Recovery
442		handle.update_health(true);
443
444		assert_eq!(handle.health_score, 60.0);
445
446		assert_eq!(handle.error_count, 0);
447
448		assert!(handle.is_healthy());
449	}
450
451	#[test]
452	fn test_connection_handle_health_boundaries() {
453		let mut handle = ConnectionHandle::new();
454
455		// Test upper bound (100)
456		for _ in 0..20 {
457			handle.update_health(true);
458		}
459
460		assert_eq!(handle.health_score, 100.0);
461
462		// Reset
463		handle.health_score = 50.0;
464
465		// Test lower bound (0)
466		for _ in 0..10 {
467			handle.update_health(false);
468		}
469
470		assert_eq!(handle.health_score, 0.0);
471	}
472
473	#[test]
474	fn test_connection_handle_is_healthy() {
475		let mut handle = ConnectionHandle::new();
476
477		assert!(handle.is_healthy());
478
479		// Make unhealthy via health score
480		handle.health_score = 50.0;
481
482		handle.error_count = 0;
483
484		assert!(!handle.is_healthy()); // Health <= 50
485
486		// Make unhealthy via error count
487		handle.health_score = 60.0;
488
489		handle.error_count = 5;
490
491		assert!(!handle.is_healthy()); // Errors >= 5
492	}
493
494	#[test]
495	fn test_connection_handle_status() {
496		let mut handle = ConnectionHandle::new();
497
498		assert!(matches!(handle.status(), ConnectionStatus::Connected));
499
500		handle.health_score = 75.0;
501
502		assert!(matches!(handle.status(), ConnectionStatus::Connected));
503
504		handle.health_score = 50.0;
505
506		assert!(matches!(handle.status(), ConnectionStatus::Degraded));
507
508		handle.health_score = 25.0;
509
510		assert!(matches!(handle.status(), ConnectionStatus::Failed));
511	}
512
513	#[test]
514	fn test_connection_handle_reset() {
515		let mut handle = ConnectionHandle::new();
516
517		// Degrade the connection
518		for _ in 0..3 {
519			handle.update_health(false);
520		}
521
522		assert!(handle.health_score < 100.0);
523
524		// Reset
525		handle.reset_health();
526
527		assert_eq!(handle.health_score, 100.0);
528
529		assert_eq!(handle.error_count, 0);
530	}
531
532	#[test]
533	fn test_connection_stats_utilization() {
534		let stats = ConnectionStats {
535			total_connections:50,
536
537			healthy_connections:45,
538
539			max_connections:100,
540
541			available_permits:50,
542
543			connection_timeout:std::time::Duration::from_secs(30),
544		};
545
546		// 50 used out of 100 = 50%
547		assert_eq!(stats.utilization(), 50.0);
548	}
549
550	#[test]
551	fn test_connection_stats_health_percentage() {
552		let stats = ConnectionStats {
553			total_connections:50,
554
555			healthy_connections:45,
556
557			max_connections:100,
558
559			available_permits:50,
560
561			connection_timeout:std::time::Duration::from_secs(30),
562		};
563
564		// 45 healthy out of 50 total = 90%
565		assert_eq!(stats.health_percentage(), 90.0);
566	}
567
568	#[test]
569	fn test_connection_stats_is_under_stress() {
570		let mut stats = ConnectionStats {
571			total_connections:50,
572
573			healthy_connections:45,
574
575			max_connections:100,
576
577			available_permits:50,
578
579			connection_timeout:std::time::Duration::from_secs(30),
580		};
581
582		// Not under stress
583		assert!(!stats.is_under_stress());
584
585		// High utilization (90%)
586		stats.available_permits = 10;
587
588		assert!(stats.is_under_stress());
589
590		// Low health percentage
591		stats.available_permits = 50;
592
593		stats.healthy_connections = 30; // 60%
594
595		assert!(stats.is_under_stress());
596	}
597
598	#[test]
599	fn test_connection_stats_empty_pool() {
600		let stats = ConnectionStats {
601			total_connections:0,
602
603			healthy_connections:0,
604
605			max_connections:100,
606
607			available_permits:100,
608
609			connection_timeout:std::time::Duration::from_secs(30),
610		};
611
612		assert_eq!(stats.utilization(), 0.0);
613
614		assert_eq!(stats.health_percentage(), 100.0); // Empty pool is healthy
615
616		assert!(!stats.is_under_stress());
617	}
618}