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}