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}