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