Skip to main content

Mountain/Air/
AirServiceProvider.rs

1//! # AirServiceProvider
2//!
3//! High-level API surface for Air service methods.
4//!
5//! ## RESPONSIBILITIES
6//!
7//! - **Service Facade**: Provide convenient, high-level interface to Air daemon
8//! - **Authentication**: Manage user authentication and credentials
9//! - **Updates**: Check for and download application updates
10//! - **File Indexing**: Query Air's file search and indexing capabilities
11//! - **System Monitoring**: Retrieve system metrics and health data
12//! - **Graceful Degradation**: Handle Air unavailability with fallbacks
13//!
14//! ## ARCHITECTURAL ROLE
15//!
16//! AirServiceProvider acts as a facade over the raw `AirClient`, providing:
17//! - Simplified API for common operations
18//! - Automatic error handling and translation
19//! - Request ID generation for tracing
20//! - Connection state management
21//!
22//! ```text
23//! Application ──► AirServiceProvider ──► AirClient ──► gRPC ──► Air Daemon
24//! ```
25//!
26//! ### Dependencies
27//! - `AirClient`: Low-level gRPC client
28//! - `uuid`: For generating request identifiers
29//! - `CommonLibrary::Error::CommonError`: Error types
30//!
31//! ### Dependents
32//! - `Binary::Service::VineStart`: Initializes Air service
33//! - `MountainEnvironment`: Can delegate to Air when available
34//!
35//! ## IMPLEMENTATION
36//!
37//! This implementation provides a fully functional provider that wraps the
38//! AirClient type with automatic request ID generation and error handling.
39//!
40//! ## ERROR HANDLING
41//!
42//! All operations return `Result<T, CommonError>` with:
43//! - Translated gRPC errors to appropriate CommonError types
44//! - Request IDs included in logs for tracing
45//! - Graceful fallback to local operations when Air is unavailable
46//!
47//! ## PERFORMANCE
48//!
49//! - Request ID generation uses UUID v4 (cryptographically random)
50//! - Thread-safe operations via `Arc<AirClient>`
51//! - Non-blocking async operations via tokio
52//!
53//! ## VSCODE REFERENCE
54//!
55//! Patterns borrowed from VS Code:
56//! - `vs/platform/update/common/updateService.ts` - Update management
57//! - `vs/platform/authentication/common/authenticationService.ts` - Auth
58//!   handling
59//! - `vs/platform/filesystem/common/filesystem.ts` - File indexing
60//!
61//! ## MODULE CONTENTS
62//!
63//! - [`AirServiceProvider`]: Main provider struct
64//! - [`generate_request_id`]: Helper function for UUID generation
65
66use std::{collections::HashMap, sync::Arc};
67
68use CommonLibrary::Error::CommonError::CommonError;
69use uuid::Uuid;
70
71#[allow(unused_imports)]
72use super::{
73	AirClient::{
74		AirClient,
75		AirMetrics,
76		AirStatus,
77		DownloadStream,
78		DownloadStreamChunk,
79		ExtendedFileInfo,
80		FileInfo,
81		FileResult,
82		IndexInfo,
83		ResourceUsage,
84		UpdateInfo,
85	},
86	AirClient::DEFAULT_AIR_SERVER_ADDRESS,
87};
88use crate::dev_log;
89
90// ============================================================================
91// AirServiceProvider - High-level API Implementation
92// ============================================================================
93
94/// AirServiceProvider provides a high-level, convenient interface to the Air
95/// daemon service.
96///
97/// This provider wraps the AirClient and provides simplified methods with
98/// automatic request ID generation and error handling. It acts as a facade
99/// pattern, hiding the complexity of gRPC communication from the rest of the
100/// Mountain application.
101///
102/// # Example
103///
104/// ```text
105/// use Mountain::Air::AirServiceProvider::{AirServiceProvider, DEFAULT_AIR_SERVER_ADDRESS};
106/// use CommonLibrary::Error::CommonError::CommonError;
107///
108/// # #[tokio::main]
109/// # async fn main() -> Result<(), CommonError> {
110/// let provider = AirServiceProvider::new(DEFAULT_AIR_SERVER_ADDRESS.to_string()).await?;
111///
112/// // Check for health
113/// let is_healthy = provider.health_check().await?;
114/// println!("Air service healthy: {}", is_healthy);
115///
116/// // Check for updates
117/// if let Some(update) =
118/// 	provider.check_for_updates("1.0.0".to_string(), "stable".to_string()).await?
119/// {
120/// 	println!("Update available: {}", update.version);
121/// }
122///
123/// # Ok(())
124/// # }
125/// ```
126#[derive(Debug, Clone)]
127pub struct AirServiceProvider {
128	/// The underlying Air client wrapped in Arc for thread safety
129	client:Arc<AirClient>,
130}
131
132impl AirServiceProvider {
133	/// Creates a new AirServiceProvider and connects to the Air daemon.
134	///
135	/// # Arguments
136	/// * `address` - The gRPC server address (defaults to `[::1]:50053`)
137	///
138	/// # Returns
139	/// * `Ok(Self)` - Successfully created provider
140	/// * `Err(CommonError)` - Connection failure
141	///
142	/// # Example
143	///
144	/// ```text
145	/// use Mountain::Air::AirServiceProvider::AirServiceProvider;
146	/// use CommonLibrary::Error::CommonError::CommonError;
147	///
148	/// # #[tokio::main]
149	/// # async fn main() -> Result<(), CommonError> {
150	/// let provider = AirServiceProvider::new("http://[::1]:50053".to_string()).await?;
151	/// # Ok(())
152	/// # }
153	/// ```
154	pub async fn new(address:String) -> Result<Self, CommonError> {
155		dev_log!("grpc", "[AirServiceProvider] Creating AirServiceProvider at: {}", address);
156
157		let client = AirClient::new(&address).await?;
158
159		dev_log!("grpc", "[AirServiceProvider] AirServiceProvider created successfully");
160
161		Ok(Self { client:Arc::new(client) })
162	}
163
164	/// Creates a new AirServiceProvider with the default address.
165	///
166	/// This is a convenience method that uses [`DEFAULT_AIR_SERVER_ADDRESS`].
167	///
168	/// # Returns
169	/// * `Ok(Self)` - Successfully created provider
170	/// * `Err(CommonError)` - Connection failure
171	///
172	/// # Example
173	///
174	/// ```text
175	/// use Mountain::Air::AirServiceProvider::AirServiceProvider;
176	/// use CommonLibrary::Error::CommonError::CommonError;
177	///
178	/// # #[tokio::main]
179	/// # async fn main() -> Result<(), CommonError> {
180	/// let provider = AirServiceProvider::new_default().await?;
181	/// # Ok(())
182	/// # }
183	/// ```
184	pub async fn new_default() -> Result<Self, CommonError> { Self::new(DEFAULT_AIR_SERVER_ADDRESS.to_string()).await }
185
186	/// Creates a new AirServiceProvider from an existing AirClient.
187	///
188	/// This is useful when you need to share a client or have special
189	/// connection requirements.
190	///
191	/// # Arguments
192	/// * `client` - The AirClient to wrap
193	///
194	/// # Returns
195	/// * `Self` - The new provider
196	pub fn from_client(client:Arc<AirClient>) -> Self {
197		dev_log!("grpc", "[AirServiceProvider] Creating AirServiceProvider from existing client");
198		Self { client }
199	}
200
201	/// Gets a reference to the underlying AirClient.
202	///
203	/// This provides access to the low-level client when needed.
204	///
205	/// # Returns
206	/// Reference to the AirClient
207	pub fn client(&self) -> &Arc<AirClient> { &self.client }
208
209	/// Checks if the provider is connected to Air.
210	///
211	/// # Returns
212	/// * `true` - Connected
213	/// * `false` - Not connected
214	pub fn is_connected(&self) -> bool { self.client.is_connected() }
215
216	/// Gets the address of the Air daemon.
217	///
218	/// # Returns
219	/// The address string
220	pub fn address(&self) -> &str { self.client.address() }
221
222	// =========================================================================
223	// Authentication Operations
224	// =========================================================================
225
226	/// Authenticates a user with the Air daemon.
227	///
228	/// This method handles request ID generation and provides a simplified
229	/// interface for authentication.
230	///
231	/// # Arguments
232	/// * `username` - User's username
233	/// * `password` - User's password
234	/// * `provider` - Authentication provider (e.g., "github", "gitlab",
235	///   "microsoft")
236	///
237	/// # Returns
238	/// * `Ok(token)` - Authentication token if successful
239	/// * `Err(CommonError)` - Authentication error
240	///
241	/// # Example
242	///
243	/// ```text
244	/// # use Mountain::Air::AirServiceProvider::AirServiceProvider;
245	/// # use CommonLibrary::Error::CommonError::CommonError;
246	/// # #[tokio::main]
247	/// # async fn main() -> Result<(), CommonError> {
248	/// # let provider = AirServiceProvider::new_default().await?;
249	/// let token = provider
250	/// 	.authenticate("[email protected]".to_string(), "password".to_string(), "github".to_string())
251	/// 	.await?;
252	/// println!("Auth token: {}", token);
253	/// # Ok(())
254	/// # }
255	/// ```
256	pub async fn authenticate(&self, username:String, password:String, provider:String) -> Result<String, CommonError> {
257		let request_id = generate_request_id();
258		dev_log!("grpc", "[AirServiceProvider] authenticate (request_id: {})", request_id);
259
260		self.client.authenticate(request_id, username, password, provider).await
261	}
262
263	// =========================================================================
264	// Update Operations
265	// =========================================================================
266
267	/// Checks for available updates.
268	///
269	/// Returns None if no update is available, Some with update info otherwise.
270	///
271	/// # Arguments
272	/// * `current_version` - Current application version
273	/// * `channel` - Update channel (e.g., "stable", "beta", "nightly")
274	///
275	/// # Returns
276	/// * `Ok(Some(update))` - Update available with information
277	/// * `Ok(None)` - No update available
278	/// * `Err(CommonError)` - Check error
279	///
280	/// # Example
281	///
282	/// ```text
283	/// # use Mountain::Air::AirServiceProvider::AirServiceProvider;
284	/// # use CommonLibrary::Error::CommonError::CommonError;
285	/// # #[tokio::main]
286	/// # async fn main() -> Result<(), CommonError> {
287	/// # let provider = AirServiceProvider::new_default().await?;
288	/// if let Some(update) =
289	/// 	provider.check_for_updates("1.0.0".to_string(), "stable".to_string()).await?
290	/// {
291	/// 	println!("Update available: version {}", update.version);
292	/// }
293	/// # Ok(())
294	/// # }
295	/// ```
296	pub async fn check_for_updates(
297		&self,
298		current_version:String,
299		channel:String,
300	) -> Result<Option<UpdateInfo>, CommonError> {
301		let request_id = generate_request_id();
302		dev_log!("grpc", "[AirServiceProvider] check_for_updates (request_id: {})", request_id);
303
304		let info = self.client.check_for_updates(request_id, current_version, channel).await?;
305
306		if info.update_available { Ok(Some(info)) } else { Ok(None) }
307	}
308
309	/// Downloads an update package.
310	///
311	/// # Arguments
312	/// * `url` - URL of the update package
313	/// * `destination_path` - Local path to save the downloaded file
314	/// * `checksum` - Optional SHA256 checksum for verification
315	///
316	/// # Returns
317	/// * `Ok(file_info)` - Downloaded file information
318	/// * `Err(CommonError)` - Download error
319	pub async fn download_update(
320		&self,
321		url:String,
322		destination_path:String,
323		checksum:String,
324	) -> Result<FileInfo, CommonError> {
325		let request_id = generate_request_id();
326		dev_log!("grpc", "[AirServiceProvider] download_update (request_id: {})", request_id);
327
328		self.client
329			.download_update(request_id, url, destination_path, checksum, HashMap::new())
330			.await
331	}
332
333	/// Applies an update package.
334	///
335	/// # Arguments
336	/// * `version` - Version of the update
337	/// * `update_path` - Path to the update package
338	///
339	/// # Returns
340	/// * `Ok(())` - Update applied successfully
341	/// * `Err(CommonError)` - Application error
342	pub async fn apply_update(&self, version:String, update_path:String) -> Result<(), CommonError> {
343		let request_id = generate_request_id();
344		dev_log!("grpc", "[AirServiceProvider] apply_update (request_id: {})", request_id);
345
346		self.client.apply_update(request_id, version, update_path).await
347	}
348
349	// =========================================================================
350	// Download Operations
351	// =========================================================================
352
353	/// Downloads a file.
354	///
355	/// # Arguments
356	/// * `url` - URL of the file to download
357	/// * `destination_path` - Local path to save the downloaded file
358	/// * `checksum` - Optional SHA256 checksum for verification
359	///
360	/// # Returns
361	/// * `Ok(file_info)` - Downloaded file information
362	/// * `Err(CommonError)` - Download error
363	pub async fn download_file(
364		&self,
365		url:String,
366		destination_path:String,
367		checksum:String,
368	) -> Result<FileInfo, CommonError> {
369		let request_id = generate_request_id();
370		dev_log!("grpc", "[AirServiceProvider] download_file (request_id: {})", request_id);
371
372		self.client
373			.download_file(request_id, url, destination_path, checksum, HashMap::new())
374			.await
375	}
376
377	/// Downloads a file as a stream.
378	///
379	/// This method initiates a streaming download from the given URL, returning
380	/// a stream of chunks that can be processed incrementally without loading
381	/// the entire file into memory.
382	///
383	/// # Arguments
384	/// * `url` - URL of the file to download
385	/// * `headers` - Optional HTTP headers
386	///
387	/// # Returns
388	/// * `Ok(stream)` - Stream that yields download chunks
389	/// * `Err(CommonError)` - Download error
390	///
391	/// # Example
392	///
393	/// ```text
394	/// # use Mountain::Air::AirServiceProvider::AirServiceProvider;
395	/// # use CommonLibrary::Error::CommonError::CommonError;
396	/// # #[tokio::main]
397	/// # async fn main() -> Result<(), CommonError> {
398	/// # let provider = AirServiceProvider::new_default().await?;
399	/// let mut stream = provider
400	/// 	.download_stream(
401	/// 		"https://example.com/large-file.zip".to_string(),
402	/// 		std::collections::HashMap::new(),
403	/// 	)
404	/// 	.await?;
405	///
406	/// let mut buffer = Vec::new();
407	/// while let Some(chunk) = stream.next().await {
408	/// 	let chunk = chunk?;
409	/// 	buffer.extend_from_slice(&chunk.data);
410	/// 	println!("Downloaded: {} / {} bytes", chunk.downloaded, chunk.total_size);
411	/// 	if chunk.completed {
412	/// 		break;
413	/// 	}
414	/// }
415	/// # Ok(())
416	/// # }
417	/// ```
418	pub async fn download_stream(
419		&self,
420		url:String,
421		headers:HashMap<String, String>,
422	) -> Result<DownloadStream, CommonError> {
423		let request_id = generate_request_id();
424		dev_log!(
425			"grpc",
426			"[AirServiceProvider] download_stream (request_id: {}, url: {})",
427			request_id,
428			url
429		);
430
431		self.client.download_stream(request_id, url, headers).await
432	}
433
434	// =========================================================================
435	// File Indexing Operations
436	// =========================================================================
437
438	/// Indexes files in a directory.
439	///
440	/// # Arguments
441	/// * `path` - Path to the directory to index
442	/// * `patterns` - File patterns to include (e.g., ["*.rs", "*.ts"])
443	/// * `exclude_patterns` - File patterns to exclude (e.g.,
444	///   ["node_modules/*"])
445	/// * `max_depth` - Maximum depth for recursion (0 for unlimited)
446	///
447	/// # Returns
448	/// * `Ok(index_info)` - Index information with file count and total size
449	/// * `Err(CommonError)` - Indexing error
450	///
451	/// # Example
452	///
453	/// ```text
454	/// # use Mountain::Air::AirServiceProvider::AirServiceProvider;
455	/// # use CommonLibrary::Error::CommonError::CommonError;
456	/// # #[tokio::main]
457	/// # async fn main() -> Result<(), CommonError> {
458	/// # let provider = AirServiceProvider::new_default().await?;
459	/// let info = provider
460	/// 	.index_files(
461	/// 		"/path/to/project".to_string(),
462	/// 		vec!["*.rs".to_string(), "*.ts".to_string()],
463	/// 		vec!["node_modules/*".to_string()],
464	/// 		10,
465	/// 	)
466	/// 	.await?;
467	/// println!("Indexed {} files ({} bytes)", info.files_indexed, info.total_size);
468	/// # Ok(())
469	/// # }
470	/// ```
471	pub async fn index_files(
472		&self,
473		path:String,
474		patterns:Vec<String>,
475		exclude_patterns:Vec<String>,
476		max_depth:u32,
477	) -> Result<IndexInfo, CommonError> {
478		let request_id = generate_request_id();
479		dev_log!(
480			"grpc",
481			"[AirServiceProvider] index_files (request_id: {}, path: {})",
482			request_id,
483			path
484		);
485
486		self.client
487			.index_files(request_id, path, patterns, exclude_patterns, max_depth)
488			.await
489	}
490
491	/// Searches for files matching a query.
492	///
493	/// # Arguments
494	/// * `query` - Search query string
495	/// * `path` - Path to search in (empty for entire workspace)
496	/// * `max_results` - Maximum number of results to return (0 for unlimited)
497	///
498	/// # Returns
499	/// * `Ok(results)` - Vector of file search results
500	/// * `Err(CommonError)` - Search error
501	///
502	/// # Example
503	///
504	/// ```text
505	/// # use Mountain::Air::AirServiceProvider::AirServiceProvider;
506	/// # use CommonLibrary::Error::CommonError::CommonError;
507	/// # #[tokio::main]
508	/// # async fn main() -> Result<(), CommonError> {
509	/// # let provider = AirServiceProvider::new_default().await?;
510	/// let results = provider
511	/// 	.search_files("fn main".to_string(), "/path/to/project".to_string(), 50)
512	/// 	.await?;
513	/// for result in results {
514	/// 	println!("Found: {} at line {}", result.path, result.line_number);
515	/// }
516	/// # Ok(())
517	/// # }
518	/// ```
519	pub async fn search_files(
520		&self,
521		query:String,
522		path:String,
523		max_results:u32,
524	) -> Result<Vec<FileResult>, CommonError> {
525		let request_id = generate_request_id();
526		dev_log!(
527			"grpc",
528			"[AirServiceProvider] search_files (request_id: {}, query: {})",
529			request_id,
530			query
531		);
532
533		self.client.search_files(request_id, query, path, max_results).await
534	}
535
536	/// Gets file information.
537	///
538	/// # Arguments
539	/// * `path` - Path to the file
540	///
541	/// # Returns
542	/// * `Ok(file_info)` - Extended file information
543	/// * `Err(CommonError)` - Request error
544	pub async fn get_file_info(&self, path:String) -> Result<ExtendedFileInfo, CommonError> {
545		let request_id = generate_request_id();
546		dev_log!(
547			"grpc",
548			"[AirServiceProvider] get_file_info (request_id: {}, path: {})",
549			request_id,
550			path
551		);
552
553		self.client.get_file_info(request_id, path).await
554	}
555
556	// =========================================================================
557	// Status and Monitoring Operations
558	// =========================================================================
559
560	/// Gets the status of the Air daemon.
561	///
562	/// # Returns
563	/// * `Ok(status)` - Air daemon status information
564	/// * `Err(CommonError)` - Request error
565	pub async fn get_status(&self) -> Result<AirStatus, CommonError> {
566		let request_id = generate_request_id();
567		dev_log!("grpc", "[AirServiceProvider] get_status (request_id: {})", request_id);
568
569		self.client.get_status(request_id).await
570	}
571
572	/// Performs a health check on the Air daemon.
573	///
574	/// # Returns
575	/// * `Ok(healthy)` - Health status (true if healthy)
576	/// * `Err(CommonError)` - Check error
577	///
578	/// # Example
579	///
580	/// ```text
581	/// # use Mountain::Air::AirServiceProvider::AirServiceProvider;
582	/// # use CommonLibrary::Error::CommonError::CommonError;
583	/// # #[tokio::main]
584	/// # async fn main() -> Result<(), CommonError> {
585	/// # let provider = AirServiceProvider::new_default().await?;
586	/// if provider.health_check().await? {
587	/// 	println!("Air service is healthy");
588	/// }
589	/// # Ok(())
590	/// # }
591	/// ```
592	pub async fn health_check(&self) -> Result<bool, CommonError> {
593		dev_log!("grpc", "[AirServiceProvider] health_check");
594		self.client.health_check().await
595	}
596
597	/// Gets metrics from the Air daemon.
598	///
599	/// # Arguments
600	/// * `metric_type` - Optional type of metrics (e.g., "performance",
601	///   "resources")
602	///
603	/// # Returns
604	/// * `Ok(metrics)` - Metrics data
605	/// * `Err(CommonError)` - Request error
606	pub async fn get_metrics(&self, metric_type:Option<String>) -> Result<AirMetrics, CommonError> {
607		let request_id = generate_request_id();
608		dev_log!("grpc", "[AirServiceProvider] get_metrics (request_id: {})", request_id);
609
610		self.client.get_metrics(request_id, metric_type).await
611	}
612
613	// =========================================================================
614	// Resource Management Operations
615	// =========================================================================
616
617	/// Gets resource usage information.
618	///
619	/// # Returns
620	/// * `Ok(usage)` - Resource usage data
621	/// * `Err(CommonError)` - Request error
622	pub async fn get_resource_usage(&self) -> Result<ResourceUsage, CommonError> {
623		let request_id = generate_request_id();
624		dev_log!("grpc", "[AirServiceProvider] get_resource_usage (request_id: {})", request_id);
625
626		self.client.get_resource_usage(request_id).await
627	}
628
629	/// Sets resource limits.
630	///
631	/// # Arguments
632	/// * `memory_limit_mb` - Memory limit in MB
633	/// * `cpu_limit_percent` - CPU limit as percentage (0-100)
634	/// * `disk_limit_mb` - Disk limit in MB
635	///
636	/// # Returns
637	/// * `Ok(())` - Limits set successfully
638	/// * `Err(CommonError)` - Set error
639	pub async fn set_resource_limits(
640		&self,
641		memory_limit_mb:u32,
642		cpu_limit_percent:u32,
643		disk_limit_mb:u32,
644	) -> Result<(), CommonError> {
645		let request_id = generate_request_id();
646		dev_log!("grpc", "[AirServiceProvider] set_resource_limits (request_id: {})", request_id);
647
648		self.client
649			.set_resource_limits(request_id, memory_limit_mb, cpu_limit_percent, disk_limit_mb)
650			.await
651	}
652
653	// =========================================================================
654	// Configuration Management Operations
655	// =========================================================================
656
657	/// Gets configuration.
658	///
659	/// # Arguments
660	/// * `section` - Configuration section (e.g., "grpc", "authentication",
661	///   "updates")
662	///
663	/// # Returns
664	/// * `Ok(config)` - Configuration data as key-value pairs
665	/// * `Err(CommonError)` - Request error
666	///
667	/// # Example
668	///
669	/// ```text
670	/// # use Mountain::Air::AirServiceProvider::AirServiceProvider;
671	/// # use CommonLibrary::Error::CommonError::CommonError;
672	/// # #[tokio::main]
673	/// # async fn main() -> Result<(), CommonError> {
674	/// # let provider = AirServiceProvider::new_default().await?;
675	/// let config = provider.get_configuration("grpc".to_string()).await?;
676	/// for (key, value) in config {
677	/// 	println!("{} = {}", key, value);
678	/// }
679	/// # Ok(())
680	/// # }
681	/// ```
682	pub async fn get_configuration(&self, section:String) -> Result<HashMap<String, String>, CommonError> {
683		let request_id = generate_request_id();
684		dev_log!(
685			"grpc",
686			"[AirServiceProvider] get_configuration (request_id: {}, section: {})",
687			request_id,
688			section
689		);
690
691		self.client.get_configuration(request_id, section).await
692	}
693
694	/// Updates configuration.
695	///
696	/// # Arguments
697	/// * `section` - Configuration section
698	/// * `updates` - Configuration updates as key-value pairs
699	///
700	/// # Returns
701	/// * `Ok(())` - Configuration updated successfully
702	/// * `Err(CommonError)` - Update error
703	pub async fn update_configuration(
704		&self,
705		section:String,
706		updates:HashMap<String, String>,
707	) -> Result<(), CommonError> {
708		let request_id = generate_request_id();
709		dev_log!(
710			"grpc",
711			"[AirServiceProvider] update_configuration (request_id: {}, section: {})",
712			request_id,
713			section
714		);
715
716		self.client.update_configuration(request_id, section, updates).await
717	}
718}
719
720// ============================================================================
721// Helper Function - Request ID Generation
722// ============================================================================
723
724/// Generates a unique request ID for Air operations.
725///
726/// Uses UUID v4 to generate a cryptographically random unique identifier.
727/// This is used to correlate requests with responses and for tracing.
728///
729/// # Returns
730/// A UUID string in simple format (without dashes)
731///
732/// # Example
733///
734/// ```text
735/// use Mountain::Air::AirServiceProvider::generate_request_id;
736///
737/// let id = generate_request_id();
738/// println!("Request ID: {}", id);
739/// // Output example: Request ID: a1b2c3d4e5f67890...
740/// ```
741pub fn generate_request_id() -> String { Uuid::new_v4().simple().to_string() }
742
743// ============================================================================
744// Tests
745// ============================================================================
746
747#[cfg(test)]
748mod tests {
749	use super::*;
750
751	#[test]
752	fn test_generate_request_id() {
753		let id1 = generate_request_id();
754		let id2 = generate_request_id();
755
756		// IDs should be unique
757		assert_ne!(id1, id2);
758
759		// IDs should be valid UUIDs (simple format = 32 chars)
760		assert_eq!(id1.len(), 32);
761		assert_eq!(id2.len(), 32);
762
763		// IDs should be hex characters
764		assert!(id1.chars().all(|c| c.is_ascii_hexdigit()));
765		assert!(id2.chars().all(|c| c.is_ascii_hexdigit()));
766	}
767
768	#[test]
769	fn test_default_address() {
770		assert_eq!(DEFAULT_AIR_SERVER_ADDRESS, "[::1]:50053");
771	}
772}