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