Skip to main content

CommonLibrary/Transport/Registry/
mod.rs

1#![allow(non_snake_case, non_camel_case_types, non_upper_case_globals)]
2//! # Transport Registry
3//!
4//! The Transport Registry enables dynamic transport selection and management,
5//! allowing components to register, select, and switch between transports at
6//! runtime.
7
8use std::{
9	collections::{HashMap, HashSet},
10	sync::Arc,
11	time::Duration,
12};
13
14use super::{
15	Common::{DefaultTransportTypeDetector, TransportType, TransportTypeDetector},
16	TransportError::TransportError,
17	TransportStrategy::{TransportMetrics, TransportStrategy as CommonTransportStrategy},
18};
19
20/// Selection strategy for automatic transport selection.
21pub struct TransportSelector {
22	/// Environment detector for auto-selection
23	#[allow(dead_code)]
24	EnvironmentDetector:Box<dyn TransportTypeDetector + Send + Sync>,
25	/// Priority order for fallback chain
26	PriorityOrder:Vec<TransportType>,
27}
28
29impl TransportSelector {
30	/// Creates a new `TransportSelector` with default settings.
31	pub fn New() -> Self {
32		Self {
33			EnvironmentDetector:Box::new(DefaultTransportTypeDetector),
34			PriorityOrder:Self::DefaultPriorityOrder(),
35		}
36	}
37
38	/// Creates a new `TransportSelector` with custom environment detector.
39	pub fn WithDetector(Detector:Box<dyn TransportTypeDetector + Send + Sync>) -> Self {
40		Self { EnvironmentDetector:Detector, PriorityOrder:Self::DefaultPriorityOrder() }
41	}
42
43	/// Gets the default priority order based on environment.
44	fn DefaultPriorityOrder() -> Vec<TransportType> {
45		let mut Order = Vec::new();
46
47		#[cfg(target_arch = "wasm32")]
48		{
49			Order.push(TransportType::Wasm);
50			Order.push(TransportType::Grpc);
51		}
52
53		#[cfg(not(target_arch = "wasm32"))]
54		{
55			Order.push(TransportType::Ipc);
56			Order.push(TransportType::Grpc);
57		}
58
59		Order
60	}
61
62	/// Selects the best transport based on context and capabilities.
63	pub fn SelectBest(&self, Context:&TransportContext) -> Result<String, TransportError> {
64		let mut Candidates = Vec::new();
65
66		for TransportKind in self.PriorityOrder.iter() {
67			if !Context.TransportAvailable(*TransportKind) {
68				continue;
69			}
70
71			if !Context.IsAllowed(*TransportKind) {
72				continue;
73			}
74
75			let Score = self.CalculateScore(*TransportKind, Context);
76			Candidates.push((*TransportKind, Score));
77		}
78
79		Candidates.sort_by(|Left, Right| Right.1.total_cmp(&Left.1));
80
81		Candidates
82			.first()
83			.map(|(TransportKind, _)| TransportKind.AsString().to_string())
84			.ok_or_else(|| TransportError::NotFound("No suitable transport available for current context"))
85	}
86
87	/// Calculates a suitability score for a transport given the context.
88	fn CalculateScore(&self, TransportKind:TransportType, Context:&TransportContext) -> f64 {
89		let mut Score = 0.0;
90
91		if let Some(Position) = self.PriorityOrder.iter().position(|Kind| *Kind == TransportKind) {
92			Score += (self.PriorityOrder.len() - Position) as f64 * 10.0;
93		}
94
95		let Environment = Context.Environment();
96		match (Environment.IsWeb, TransportKind) {
97			(true, TransportType::Wasm) => Score += 50.0,
98			(false, TransportType::Ipc) => Score += 40.0,
99			_ => {},
100		}
101
102		let Requirements = Context.Requirements();
103		if Requirements.StreamingRequired {
104			match TransportKind {
105				TransportType::Grpc => Score += 30.0,
106				TransportType::Wasm => Score += 20.0,
107				TransportType::Ipc => Score -= 20.0,
108				TransportType::Unknown => {},
109			}
110		}
111
112		if Requirements.CrossNetwork {
113			match TransportKind {
114				TransportType::Grpc => Score += 50.0,
115				TransportType::Ipc => Score -= 50.0,
116				TransportType::Wasm => Score += 10.0,
117				TransportType::Unknown => {},
118			}
119		}
120
121		Score += match Requirements.Performance {
122			PerformanceLevel::Critical => {
123				match TransportKind {
124					TransportType::Ipc => 40.0,
125					TransportType::Grpc => 20.0,
126					TransportType::Wasm => 0.0,
127					TransportType::Unknown => 0.0,
128				}
129			},
130			PerformanceLevel::High => {
131				match TransportKind {
132					TransportType::Ipc => 30.0,
133					TransportType::Grpc => 20.0,
134					TransportType::Wasm => 10.0,
135					TransportType::Unknown => 0.0,
136				}
137			},
138			PerformanceLevel::Medium => 10.0,
139			PerformanceLevel::Low => 0.0,
140		};
141
142		if let Some(MaximumLatency) = Requirements.MaximumLatencyMilliseconds {
143			let EstimatedLatency = self.EstimateLatencyMilliseconds(TransportKind);
144			if EstimatedLatency <= MaximumLatency {
145				Score += 20.0;
146			} else {
147				Score -= 30.0;
148			}
149		}
150
151		Score
152	}
153
154	/// Estimates typical latency for a transport type in milliseconds.
155	fn EstimateLatencyMilliseconds(&self, TransportKind:TransportType) -> u64 {
156		match TransportKind {
157			TransportType::Ipc => 1,
158			TransportType::Grpc => 5,
159			TransportType::Wasm => 20,
160			TransportType::Unknown => u64::MAX,
161		}
162	}
163}
164
165impl Default for TransportSelector {
166	fn default() -> Self { Self::New() }
167}
168
169/// Context information for transport selection.
170#[derive(Debug, Clone)]
171pub struct TransportContext {
172	EnvironmentInfo:EnvironmentInfo,
173	RequirementsInfo:TransportRequirements,
174	ConstraintsInfo:TransportConstraints,
175	AvailableTransports:HashSet<TransportType>,
176}
177
178impl TransportContext {
179	/// Creates a new transport selection context.
180	pub fn New(
181		EnvironmentInfo:EnvironmentInfo,
182		RequirementsInfo:TransportRequirements,
183		ConstraintsInfo:TransportConstraints,
184	) -> Self {
185		let AvailableTransports = DefaultTransportTypeDetector::list_available_transports().into_iter().collect();
186
187		Self { EnvironmentInfo, RequirementsInfo, ConstraintsInfo, AvailableTransports }
188	}
189
190	/// Detects the current environment and creates a context with default
191	/// requirements.
192	pub fn Detect() -> Self {
193		let EnvironmentInfo = DefaultTransportTypeDetector::DetectEnvironment();
194		let RequirementsInfo = TransportRequirements::default();
195		let ConstraintsInfo = TransportConstraints::default();
196
197		Self::New(EnvironmentInfo, RequirementsInfo, ConstraintsInfo)
198	}
199
200	/// Gets the environment information.
201	pub fn Environment(&self) -> &EnvironmentInfo { &self.EnvironmentInfo }
202
203	/// Gets the transport requirements.
204	pub fn Requirements(&self) -> &TransportRequirements { &self.RequirementsInfo }
205
206	/// Gets the transport constraints.
207	pub fn Constraints(&self) -> &TransportConstraints { &self.ConstraintsInfo }
208
209	/// Checks if a transport type is available in this environment.
210	pub fn TransportAvailable(&self, TransportKind:TransportType) -> bool {
211		self.AvailableTransports.contains(&TransportKind)
212	}
213
214	/// Checks if a transport type is allowed by constraints.
215	pub fn IsAllowed(&self, TransportKind:TransportType) -> bool {
216		if self.ConstraintsInfo.ForbiddenTransports.contains(&TransportKind) {
217			return false;
218		}
219
220		if self.ConstraintsInfo.AllowedTransports.is_empty() {
221			true
222		} else {
223			self.ConstraintsInfo.AllowedTransports.contains(&TransportKind)
224		}
225	}
226
227	/// Sets custom available transports (for testing or override).
228	pub fn WithAvailableTransports(mut self, Transports:Vec<TransportType>) -> Self {
229		self.AvailableTransports = Transports.into_iter().collect();
230		self
231	}
232}
233
234/// Environment information for transport selection.
235#[derive(Debug, Clone)]
236pub struct EnvironmentInfo {
237	/// Operating system platform
238	pub Platform:Platform,
239	/// Whether running in a web browser
240	pub IsWeb:bool,
241	/// Whether running as a desktop application
242	pub IsDesktop:bool,
243	/// Browser capability information (if in browser)
244	pub BrowserCapabilities:Option<BrowserCapabilities>,
245}
246
247impl EnvironmentInfo {
248	/// Creates a new environment info.
249	pub fn New(Platform:Platform, IsWeb:bool, IsDesktop:bool, BrowserCapabilities:Option<BrowserCapabilities>) -> Self {
250		Self { Platform, IsWeb, IsDesktop, BrowserCapabilities }
251	}
252}
253
254/// Platform enumeration.
255#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
256pub enum Platform {
257	Windows,
258	MacOS,
259	Linux,
260	Browser,
261	Mobile,
262	Unknown,
263}
264
265impl Platform {
266	/// Gets the current platform.
267	pub fn Current() -> Self {
268		#[cfg(target_os = "windows")]
269		return Self::Windows;
270		#[cfg(target_os = "macos")]
271		return Self::MacOS;
272		#[cfg(target_os = "linux")]
273		return Self::Linux;
274		#[cfg(all(not(target_os = "windows"), not(target_os = "macos"), not(target_os = "linux")))]
275		return Self::Unknown;
276	}
277}
278
279/// Browser capabilities detection.
280#[derive(Debug, Clone)]
281pub struct BrowserCapabilities {
282	pub WasmSupported:bool,
283	pub WebWorkerSupported:bool,
284	pub WebSocketSupported:bool,
285	pub SharedArrayBufferSupported:bool,
286}
287
288impl Default for BrowserCapabilities {
289	fn default() -> Self {
290		Self {
291			WasmSupported:cfg!(target_arch = "wasm32"),
292			WebWorkerSupported:false,
293			WebSocketSupported:false,
294			SharedArrayBufferSupported:false,
295		}
296	}
297}
298
299/// Transport requirements for selection.
300#[derive(Debug, Clone)]
301pub struct TransportRequirements {
302	/// Whether bidirectional streaming is required
303	pub StreamingRequired:bool,
304	/// Whether cross-process communication is needed
305	pub CrossProcess:bool,
306	/// Whether cross-network communication is needed
307	pub CrossNetwork:bool,
308	/// Performance requirement level
309	pub Performance:PerformanceLevel,
310	/// Reliability requirement level
311	pub Reliability:ReliabilityLevel,
312	/// Maximum acceptable latency in milliseconds (optional)
313	pub MaximumLatencyMilliseconds:Option<u64>,
314}
315
316impl Default for TransportRequirements {
317	fn default() -> Self {
318		Self {
319			StreamingRequired:false,
320			CrossProcess:false,
321			CrossNetwork:false,
322			Performance:PerformanceLevel::Medium,
323			Reliability:ReliabilityLevel::Medium,
324			MaximumLatencyMilliseconds:None,
325		}
326	}
327}
328
329/// Performance requirement level.
330#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
331pub enum PerformanceLevel {
332	Low = 1,
333	Medium = 2,
334	High = 3,
335	Critical = 4,
336}
337
338/// Reliability requirement level.
339#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
340pub enum ReliabilityLevel {
341	Low = 1,
342	Medium = 2,
343	High = 3,
344	Critical = 4,
345}
346
347/// Transport selection constraints.
348#[derive(Debug, Clone)]
349pub struct TransportConstraints {
350	/// Allowed transport types (empty means all allowed)
351	pub AllowedTransports:Vec<TransportType>,
352	/// Forbidden transport types
353	pub ForbiddenTransports:Vec<TransportType>,
354	/// Maximum allowed latency in milliseconds
355	pub MaximumLatencyMilliseconds:Option<u64>,
356	/// Maximum allowed bandwidth in bytes per second
357	pub MaximumBandwidthBytesPerSecond:Option<u64>,
358}
359
360impl Default for TransportConstraints {
361	fn default() -> Self {
362		Self {
363			AllowedTransports:Vec::new(),
364			ForbiddenTransports:Vec::new(),
365			MaximumLatencyMilliseconds:None,
366			MaximumBandwidthBytesPerSecond:None,
367		}
368	}
369}
370
371/// Central registry for managing transport strategies.
372pub struct TransportRegistry {
373	/// Registered transports (name -> Arc\<dyn CommonTransportStrategy\>)
374	Transports:HashMap<String, Arc<dyn CommonTransportStrategy>>,
375	/// Currently active transport name
376	Active:Option<String>,
377	/// Transport selector for auto-selection
378	Selector:TransportSelector,
379}
380
381impl TransportRegistry {
382	/// Creates a new, empty transport registry.
383	pub fn New() -> Self { Self { Transports:HashMap::new(), Active:None, Selector:TransportSelector::New() } }
384
385	/// Creates a new registry with a custom selector.
386	pub fn WithSelector(Selector:TransportSelector) -> Self {
387		Self { Transports:HashMap::new(), Active:None, Selector }
388	}
389
390	/// Registers a new transport with the registry.
391	pub fn Register(&mut self, Name:String, Transport:Arc<dyn CommonTransportStrategy>) {
392		log::info!("Registering transport: {}", Name);
393		self.Transports.insert(Name, Transport);
394	}
395
396	/// Unregisters a transport from the registry.
397	pub async fn Unregister(&mut self, Name:&str) -> Result<(), TransportError> {
398		let TransportOption = self.Transports.remove(Name);
399
400		if let Some(_Transport) = TransportOption {
401			// Arc<dyn CommonTransportStrategy> does not expose mutable disconnect here;
402			// callers should disconnect before unregistering if needed.
403			log::info!("Unregistered transport: {}", Name);
404			Ok(())
405		} else {
406			Err(TransportError::NotFound(format!("Transport '{}' not found", Name)))
407		}
408	}
409
410	/// Selects a transport by name as the active transport.
411	pub async fn Select(&mut self, Name:&str) -> Result<(), TransportError> {
412		if !self.Transports.contains_key(Name) {
413			return Err(TransportError::NotFound(format!("Transport '{}' not found", Name)));
414		}
415
416		log::info!("Selecting transport: {}", Name);
417		self.Active = Some(Name.to_string());
418		Ok(())
419	}
420
421	/// Automatically selects the best transport based on the provided context.
422	pub async fn AutoSelect(&mut self, Context:&TransportContext) -> Result<String, TransportError> {
423		let SelectedName = self.Selector.SelectBest(Context)?;
424		self.Select(&SelectedName).await?;
425		Ok(SelectedName)
426	}
427
428	/// Gets the currently active transport, if any.
429	pub fn GetActive(&self) -> Option<Arc<dyn CommonTransportStrategy>> {
430		self.Active.as_ref().and_then(|Name| self.Transports.get(Name)).cloned()
431	}
432
433	/// Gets a specific transport by name.
434	pub fn Get(&self, Name:&str) -> Option<Arc<dyn CommonTransportStrategy>> { self.Transports.get(Name).cloned() }
435
436	/// Lists all registered transport names.
437	pub fn List(&self) -> Vec<String> { self.Transports.keys().cloned().collect() }
438
439	/// Checks if a transport with the given name is registered.
440	pub fn Has(&self, Name:&str) -> bool { self.Transports.contains_key(Name) }
441
442	/// Gets metrics for all registered transports.
443	pub fn GetAllMetrics(&self) -> HashMap<String, TransportMetrics> {
444		let mut Metrics = HashMap::new();
445		for (Name, Transport) in &self.Transports {
446			Metrics.insert(Name.clone(), Transport.Metrics());
447		}
448		Metrics
449	}
450
451	/// Gets health status (connected/not connected) for all transports.
452	pub fn GetHealthStatus(&self) -> HashMap<String, bool> {
453		let mut Status = HashMap::new();
454		for (Name, Transport) in &self.Transports {
455			Status.insert(Name.clone(), Transport.IsConnected());
456		}
457		Status
458	}
459
460	/// Gets the name of the currently active transport.
461	pub fn ActiveName(&self) -> Option<&str> { self.Active.as_deref() }
462
463	/// Sets the selector to use for auto-selection.
464	pub fn SetSelector(&mut self, Selector:TransportSelector) { self.Selector = Selector; }
465
466	/// Waits for a transport to be ready (connected) with timeout.
467	pub async fn WaitForReady(&self, Name:&str, Timeout:Duration) -> Result<(), TransportError> {
468		use tokio::time::Instant;
469
470		let Start = Instant::now();
471		let Transport = self
472			.Get(Name)
473			.ok_or_else(|| TransportError::NotFound(format!("Transport '{}' not found", Name)))?;
474
475		loop {
476			if Transport.IsConnected() {
477				return Ok(());
478			}
479
480			if Start.elapsed() >= Timeout {
481				return Err(TransportError::Timeout("Transport did not become ready within timeout"));
482			}
483
484			tokio::time::sleep(Duration::from_millis(50)).await;
485		}
486	}
487}
488
489impl Default for TransportRegistry {
490	fn default() -> Self { Self::New() }
491}
492
493/// Provides environment detection for DefaultTransportTypeDetector.
494impl DefaultTransportTypeDetector {
495	/// Detects the current environment information.
496	pub fn DetectEnvironment() -> EnvironmentInfo {
497		let CurrentPlatform = Platform::Current();
498		let IsDesktop = !cfg!(target_arch = "wasm32");
499
500		let Environment =
501			EnvironmentInfo { Platform:CurrentPlatform, IsWeb:false, IsDesktop, BrowserCapabilities:None };
502
503		#[cfg(target_arch = "wasm32")]
504		{
505			EnvironmentInfo {
506				IsWeb:true,
507				IsDesktop:false,
508				BrowserCapabilities:Some(BrowserCapabilities::default()),
509				..Environment
510			}
511		}
512
513		#[cfg(not(target_arch = "wasm32"))]
514		{
515			Environment
516		}
517	}
518}
519
520#[cfg(test)]
521mod tests {
522	use async_trait::async_trait;
523
524	use super::{
525		super::{
526			TransportConfig::TransportConfig,
527			TransportError::TransportError,
528			TransportStrategy::{TransportCapabilities, TransportMetrics},
529			UnifiedRequest::UnifiedRequest,
530			UnifiedResponse::UnifiedResponse,
531		},
532		*,
533	};
534
535	#[test]
536	fn TestTransportSelectorCreation() {
537		let Selector = TransportSelector::New();
538		assert!(!Selector.PriorityOrder.is_empty());
539	}
540
541	#[test]
542	fn TestTransportContextCreation() {
543		let Environment = EnvironmentInfo::New(Platform::Linux, false, true, None);
544		let Requirements = TransportRequirements::default();
545		let Constraints = TransportConstraints::default();
546
547		let Context = TransportContext::New(Environment, Requirements, Constraints);
548		// Without real detector injection, available transports come from
549		// DefaultTransportTypeDetector
550		assert!(Context.TransportAvailable(TransportType::Grpc));
551	}
552
553	#[test]
554	fn TestTransportRegistryCreation() {
555		let Registry = TransportRegistry::New();
556		assert!(Registry.List().is_empty());
557		assert!(Registry.ActiveName().is_none());
558	}
559
560	#[tokio::test]
561	async fn TestRegistryRegisterUnregister() {
562		let mut Registry = TransportRegistry::New();
563
564		let MockTransportInstance = Arc::new(MockTransport::New());
565		Registry.Register("mock".to_string(), MockTransportInstance);
566
567		assert!(Registry.Has("mock"));
568		assert_eq!(Registry.List().len(), 1);
569
570		Registry.Unregister("mock").await.unwrap();
571		assert!(!Registry.Has("mock"));
572	}
573
574	/// Mock transport for testing
575	#[derive(Debug, Clone)]
576	struct MockTransport;
577
578	impl MockTransport {
579		fn New() -> Self { Self }
580	}
581
582	#[async_trait]
583	impl CommonTransportStrategy for MockTransport {
584		async fn Connect(&mut self) -> Result<(), TransportError> { Ok(()) }
585
586		async fn Disconnect(&mut self) -> Result<(), TransportError> { Ok(()) }
587
588		async fn SendRequest(&mut self, Request:UnifiedRequest) -> Result<UnifiedResponse, TransportError> {
589			Ok(UnifiedResponse::Success(
590				Request.CorrelationIdentifier.clone().unwrap_or_default(),
591				Vec::new(),
592			))
593		}
594
595		async fn SendNotification(&mut self, _Notification:UnifiedRequest) -> Result<(), TransportError> { Ok(()) }
596
597		fn StreamEvents(
598			&self,
599		) -> std::result::Result<futures::stream::BoxStream<'static, UnifiedResponse>, TransportError> {
600			Err(TransportError::NotSupported("Streaming not supported"))
601		}
602
603		fn IsConnected(&self) -> bool { true }
604
605		fn LatencyMilliseconds(&self) -> u64 { 0 }
606
607		fn TransportKind(&self) -> TransportType { TransportType::Grpc }
608
609		fn Configuration(&self) -> &TransportConfig {
610			static CONFIG:std::sync::OnceLock<TransportConfig> = std::sync::OnceLock::new();
611			CONFIG.get_or_init(TransportConfig::default)
612		}
613
614		fn Capabilities(&self) -> TransportCapabilities { TransportCapabilities::default() }
615
616		fn Metrics(&self) -> TransportMetrics { TransportMetrics::New() }
617
618		fn SupportsStreaming(&self) -> bool { false }
619	}
620}