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
241#[allow(dead_code)]
242trait SystemTimeExt {
243	/// Get the duration since UNIX epoch in seconds
244	fn duration_since_epoch_secs(&self) -> Result<u64, std::time::SystemTimeError>;
245}
246
247impl SystemTimeExt for std::time::SystemTime {
248	fn duration_since_epoch_secs(&self) -> Result<u64, std::time::SystemTimeError> {
249		self.duration_since(std::time::UNIX_EPOCH).map(|d| d.as_secs())
250	}
251}
252
253impl std::fmt::Debug for ConnectionHandle {
254	fn fmt(&self, f:&mut std::fmt::Formatter<'_>) -> std::fmt::Result {
255		let created_age = self
256			.created_at
257			.duration_since(std::time::UNIX_EPOCH)
258			.map(|d| d.as_secs())
259			.unwrap_or(0);
260
261		let last_used_age = self
262			.last_used
263			.duration_since(std::time::UNIX_EPOCH)
264			.map(|d| d.as_secs())
265			.unwrap_or(0);
266
267		f.debug_struct("ConnectionHandle")
268			.field("id", &self.id)
269			.field("created_at_age_seconds", &created_age)
270			.field("last_used_age_seconds", &last_used_age)
271			.field("health_score", &self.health_score)
272			.field("error_count", &self.error_count)
273			.field("status", &self.status())
274			.finish()
275	}
276}
277
278/// Connection statistics for monitoring
279///
280/// This structure provides aggregate statistics about the connection pool,
281/// useful for monitoring and debugging.
282///
283/// ## Example Usage
284///
285/// ```rust,ignore
286/// let stats = pool.GetStats().await;
287///
288/// println!("Total connections: {}", stats.total_connections);
289/// println!("Healthy: {}", stats.healthy_connections);
290/// println!("Available: {}", stats.available_permits);
291/// ```
292#[derive(Debug, Clone, Default)]
293pub struct ConnectionStats {
294	/// Total number of active connections
295	pub total_connections:usize,
296
297	/// Number of healthy connections
298	pub healthy_connections:usize,
299
300	/// Maximum number of connections allowed
301	pub max_connections:usize,
302
303	/// Number of available connection permits
304	pub available_permits:usize,
305
306	/// Connection timeout duration
307	pub connection_timeout:std::time::Duration,
308}
309
310impl ConnectionStats {
311	/// Calculate connection pool utilization percentage
312	///
313	/// ## Returns
314	/// Percentage of connections in use (0.0 to 100.0)
315	pub fn utilization(&self) -> f64 {
316		if self.max_connections == 0 {
317			return 0.0;
318		}
319
320		let used = self.max_connections - self.available_permits;
321
322		(used as f64 / self.max_connections as f64) * 100.0
323	}
324
325	/// Calculate health percentage
326	///
327	/// ## Returns
328	/// Percentage of connections that are healthy (0.0 to 100.0)
329	pub fn health_percentage(&self) -> f64 {
330		if self.total_connections == 0 {
331			return 100.0;
332		}
333
334		(self.healthy_connections as f64 / self.total_connections as f64) * 100.0
335	}
336
337	/// Check if pool is under stress
338	///
339	/// Pool is under stress if:
340	/// - Utilization > 80%
341	/// - Health percentage < 70%
342	///
343	/// ## Returns
344	/// - `true`: Pool is under stress
345	/// - `false`: Pool is healthy
346	pub fn is_under_stress(&self) -> bool { self.utilization() > 80.0 || self.health_percentage() < 70.0 }
347
348	/// Get a human-readable status summary
349	pub fn summary(&self) -> String {
350		format!(
351			"Connections: {}/{} ({}%), Healthy: {}%, Utilization: {}%",
352			self.total_connections,
353			self.max_connections,
354			self.health_percentage(),
355			self.health_percentage(),
356			self.utilization()
357		)
358	}
359}
360
361#[cfg(test)]
362mod tests {
363
364	use super::*;
365
366	#[test]
367	fn test_connection_status_from_bool() {
368		assert!(matches!(ConnectionStatus::from(true), ConnectionStatus::Connected));
369
370		assert!(matches!(ConnectionStatus::from(false), ConnectionStatus::Disconnected));
371	}
372
373	#[test]
374	fn test_connection_status_description() {
375		assert_eq!(ConnectionStatus::Connected.description(), "Connected and healthy");
376
377		assert_eq!(ConnectionStatus::Disconnected.description(), "Disconnected");
378
379		assert_eq!(ConnectionStatus::Degraded.description(), "Degraded - experiencing issues");
380
381		assert_eq!(ConnectionStatus::Failed.description(), "Failed - connection lost");
382	}
383
384	#[test]
385	fn test_connection_status_level() {
386		assert_eq!(ConnectionStatus::Failed.level(), 0);
387
388		assert_eq!(ConnectionStatus::Degraded.level(), 1);
389
390		assert_eq!(ConnectionStatus::Disconnected.level(), 2);
391
392		assert_eq!(ConnectionStatus::Connected.level(), 3);
393	}
394
395	#[test]
396	fn test_connection_handle_creation() {
397		let handle = ConnectionHandle::new();
398
399		assert!(!handle.id.is_empty());
400
401		assert_eq!(handle.health_score, 100.0);
402
403		assert_eq!(handle.error_count, 0);
404
405		assert!(handle.is_healthy());
406	}
407
408	#[test]
409	fn test_connection_handle_health_update_success() {
410		let mut handle = ConnectionHandle::new();
411
412		// Initially healthy
413		assert_eq!(handle.health_score, 100.0);
414
415		assert!(handle.is_healthy());
416
417		// Simulate success (already at 100, should stay at 100)
418		handle.update_health(true);
419
420		assert_eq!(handle.health_score, 100.0);
421
422		assert_eq!(handle.error_count, 0);
423
424		// Simulate failure
425		handle.update_health(false);
426
427		assert_eq!(handle.health_score, 75.0);
428
429		assert_eq!(handle.error_count, 1);
430
431		assert!(handle.is_healthy());
432
433		// More failures
434		handle.update_health(false);
435
436		assert_eq!(handle.health_score, 50.0);
437
438		assert_eq!(handle.error_count, 2);
439
440		assert!(!handle.is_healthy()); // Health <= 50
441
442		// Recovery
443		handle.update_health(true);
444
445		assert_eq!(handle.health_score, 60.0);
446
447		assert_eq!(handle.error_count, 0);
448
449		assert!(handle.is_healthy());
450	}
451
452	#[test]
453	fn test_connection_handle_health_boundaries() {
454		let mut handle = ConnectionHandle::new();
455
456		// Test upper bound (100)
457		for _ in 0..20 {
458			handle.update_health(true);
459		}
460
461		assert_eq!(handle.health_score, 100.0);
462
463		// Reset
464		handle.health_score = 50.0;
465
466		// Test lower bound (0)
467		for _ in 0..10 {
468			handle.update_health(false);
469		}
470
471		assert_eq!(handle.health_score, 0.0);
472	}
473
474	#[test]
475	fn test_connection_handle_is_healthy() {
476		let mut handle = ConnectionHandle::new();
477
478		assert!(handle.is_healthy());
479
480		// Make unhealthy via health score
481		handle.health_score = 50.0;
482
483		handle.error_count = 0;
484
485		assert!(!handle.is_healthy()); // Health <= 50
486
487		// Make unhealthy via error count
488		handle.health_score = 60.0;
489
490		handle.error_count = 5;
491
492		assert!(!handle.is_healthy()); // Errors >= 5
493	}
494
495	#[test]
496	fn test_connection_handle_status() {
497		let mut handle = ConnectionHandle::new();
498
499		assert!(matches!(handle.status(), ConnectionStatus::Connected));
500
501		handle.health_score = 75.0;
502
503		assert!(matches!(handle.status(), ConnectionStatus::Connected));
504
505		handle.health_score = 50.0;
506
507		assert!(matches!(handle.status(), ConnectionStatus::Degraded));
508
509		handle.health_score = 25.0;
510
511		assert!(matches!(handle.status(), ConnectionStatus::Failed));
512	}
513
514	#[test]
515	fn test_connection_handle_reset() {
516		let mut handle = ConnectionHandle::new();
517
518		// Degrade the connection
519		for _ in 0..3 {
520			handle.update_health(false);
521		}
522
523		assert!(handle.health_score < 100.0);
524
525		// Reset
526		handle.reset_health();
527
528		assert_eq!(handle.health_score, 100.0);
529
530		assert_eq!(handle.error_count, 0);
531	}
532
533	#[test]
534	fn test_connection_stats_utilization() {
535		let stats = ConnectionStats {
536			total_connections:50,
537
538			healthy_connections:45,
539
540			max_connections:100,
541
542			available_permits:50,
543
544			connection_timeout:std::time::Duration::from_secs(30),
545		};
546
547		// 50 used out of 100 = 50%
548		assert_eq!(stats.utilization(), 50.0);
549	}
550
551	#[test]
552	fn test_connection_stats_health_percentage() {
553		let stats = ConnectionStats {
554			total_connections:50,
555
556			healthy_connections:45,
557
558			max_connections:100,
559
560			available_permits:50,
561
562			connection_timeout:std::time::Duration::from_secs(30),
563		};
564
565		// 45 healthy out of 50 total = 90%
566		assert_eq!(stats.health_percentage(), 90.0);
567	}
568
569	#[test]
570	fn test_connection_stats_is_under_stress() {
571		let mut stats = ConnectionStats {
572			total_connections:50,
573
574			healthy_connections:45,
575
576			max_connections:100,
577
578			available_permits:50,
579
580			connection_timeout:std::time::Duration::from_secs(30),
581		};
582
583		// Not under stress
584		assert!(!stats.is_under_stress());
585
586		// High utilization (90%)
587		stats.available_permits = 10;
588
589		assert!(stats.is_under_stress());
590
591		// Low health percentage
592		stats.available_permits = 50;
593
594		stats.healthy_connections = 30; // 60%
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		assert!(!stats.is_under_stress());
616	}
617}