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("[email protected]".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 path:String,
492 patterns:Vec<String>,
493 exclude_patterns:Vec<String>,
494 max_depth:u32,
495 ) -> Result<IndexInfo::Struct, CommonError> {
496 let request_id = generate_request_id();
497 dev_log!(
498 "grpc",
499 "[AirServiceProvider] index_files (request_id: {}, path: {})",
500 request_id,
501 path
502 );
503
504 self.client
505 .index_files(request_id, path, patterns, exclude_patterns, max_depth)
506 .await
507 }
508
509 /// Searches for files matching a query.
510 ///
511 /// # Arguments
512 /// * `query` - Search query string
513 /// * `path` - Path to search in (empty for entire workspace)
514 /// * `max_results` - Maximum number of results to return (0 for unlimited)
515 ///
516 /// # Returns
517 /// * `Ok(results)` - Vector of file search results
518 /// * `Err(CommonError)` - Search error
519 ///
520 /// # Example
521 ///
522 /// ```text
523 /// # use Mountain::Air::AirServiceProvider::AirServiceProvider;
524 /// # use CommonLibrary::Error::CommonError::CommonError;
525 /// # #[tokio::main]
526 /// # async fn main() -> Result<(), CommonError> {
527 /// # let provider = AirServiceProvider::new_default().await?;
528 /// let results = provider
529 /// .search_files("fn main".to_string(), "/path/to/project".to_string(), 50)
530 /// .await?;
531 /// for result in results {
532 /// println!("Found: {} at line {}", result.path, result.line_number);
533 /// }
534 /// # Ok(())
535 /// # }
536 /// ```
537 pub async fn search_files(
538 &self,
539 query:String,
540 path:String,
541 max_results:u32,
542 ) -> Result<Vec<FileResult::Struct>, CommonError> {
543 let request_id = generate_request_id();
544 dev_log!(
545 "grpc",
546 "[AirServiceProvider] search_files (request_id: {}, query: {})",
547 request_id,
548 query
549 );
550
551 self.client.search_files(request_id, query, path, max_results).await
552 }
553
554 /// Gets file information.
555 ///
556 /// # Arguments
557 /// * `path` - Path to the file
558 ///
559 /// # Returns
560 /// * `Ok(file_info)` - Extended file information
561 /// * `Err(CommonError)` - Request error
562 pub async fn get_file_info(&self, path:String) -> Result<ExtendedFileInfo::Struct, CommonError> {
563 let request_id = generate_request_id();
564 dev_log!(
565 "grpc",
566 "[AirServiceProvider] get_file_info (request_id: {}, path: {})",
567 request_id,
568 path
569 );
570
571 self.client.get_file_info(request_id, path).await
572 }
573
574 // =========================================================================
575 // Status and Monitoring Operations
576 // =========================================================================
577
578 /// Gets the status of the Air daemon.
579 ///
580 /// # Returns
581 /// * `Ok(status)` - Air daemon status information
582 /// * `Err(CommonError)` - Request error
583 pub async fn get_status(&self) -> Result<AirStatus::Struct, CommonError> {
584 let request_id = generate_request_id();
585 dev_log!("grpc", "[AirServiceProvider] get_status (request_id: {})", request_id);
586
587 self.client.get_status(request_id).await
588 }
589
590 /// Performs a health check on the Air daemon.
591 ///
592 /// # Returns
593 /// * `Ok(healthy)` - Health status (true if healthy)
594 /// * `Err(CommonError)` - Check error
595 ///
596 /// # Example
597 ///
598 /// ```text
599 /// # use Mountain::Air::AirServiceProvider::AirServiceProvider;
600 /// # use CommonLibrary::Error::CommonError::CommonError;
601 /// # #[tokio::main]
602 /// # async fn main() -> Result<(), CommonError> {
603 /// # let provider = AirServiceProvider::new_default().await?;
604 /// if provider.health_check().await? {
605 /// println!("Air service is healthy");
606 /// }
607 /// # Ok(())
608 /// # }
609 /// ```
610 pub async fn health_check(&self) -> Result<bool, CommonError> {
611 dev_log!("grpc", "[AirServiceProvider] health_check");
612 self.client.health_check().await
613 }
614
615 /// Gets metrics from the Air daemon.
616 ///
617 /// # Arguments
618 /// * `metric_type` - Optional type of metrics (e.g., "performance",
619 /// "resources")
620 ///
621 /// # Returns
622 /// * `Ok(metrics)` - Metrics data
623 /// * `Err(CommonError)` - Request error
624 pub async fn get_metrics(&self, metric_type:Option<String>) -> Result<AirMetrics::Struct, CommonError> {
625 let request_id = generate_request_id();
626 dev_log!("grpc", "[AirServiceProvider] get_metrics (request_id: {})", request_id);
627
628 self.client.get_metrics(request_id, metric_type).await
629 }
630
631 // =========================================================================
632 // Resource Management Operations
633 // =========================================================================
634
635 /// Gets resource usage information.
636 ///
637 /// # Returns
638 /// * `Ok(usage)` - Resource usage data
639 /// * `Err(CommonError)` - Request error
640 pub async fn get_resource_usage(&self) -> Result<ResourceUsage::Struct, CommonError> {
641 let request_id = generate_request_id();
642 dev_log!("grpc", "[AirServiceProvider] get_resource_usage (request_id: {})", request_id);
643
644 self.client.get_resource_usage(request_id).await
645 }
646
647 /// Sets resource limits.
648 ///
649 /// # Arguments
650 /// * `memory_limit_mb` - Memory limit in MB
651 /// * `cpu_limit_percent` - CPU limit as percentage (0-100)
652 /// * `disk_limit_mb` - Disk limit in MB
653 ///
654 /// # Returns
655 /// * `Ok(())` - Limits set successfully
656 /// * `Err(CommonError)` - Set error
657 pub async fn set_resource_limits(
658 &self,
659 memory_limit_mb:u32,
660 cpu_limit_percent:u32,
661 disk_limit_mb:u32,
662 ) -> Result<(), CommonError> {
663 let request_id = generate_request_id();
664 dev_log!("grpc", "[AirServiceProvider] set_resource_limits (request_id: {})", request_id);
665
666 self.client
667 .set_resource_limits(request_id, memory_limit_mb, cpu_limit_percent, disk_limit_mb)
668 .await
669 }
670
671 // =========================================================================
672 // Configuration Management Operations
673 // =========================================================================
674
675 /// Gets configuration.
676 ///
677 /// # Arguments
678 /// * `section` - Configuration section (e.g., "grpc", "authentication",
679 /// "updates")
680 ///
681 /// # Returns
682 /// * `Ok(config)` - Configuration data as key-value pairs
683 /// * `Err(CommonError)` - Request error
684 ///
685 /// # Example
686 ///
687 /// ```text
688 /// # use Mountain::Air::AirServiceProvider::AirServiceProvider;
689 /// # use CommonLibrary::Error::CommonError::CommonError;
690 /// # #[tokio::main]
691 /// # async fn main() -> Result<(), CommonError> {
692 /// # let provider = AirServiceProvider::new_default().await?;
693 /// let config = provider.get_configuration("grpc".to_string()).await?;
694 /// for (key, value) in config {
695 /// println!("{} = {}", key, value);
696 /// }
697 /// # Ok(())
698 /// # }
699 /// ```
700 pub async fn get_configuration(&self, section:String) -> Result<HashMap<String, String>, CommonError> {
701 let request_id = generate_request_id();
702 dev_log!(
703 "grpc",
704 "[AirServiceProvider] get_configuration (request_id: {}, section: {})",
705 request_id,
706 section
707 );
708
709 self.client.get_configuration(request_id, section).await
710 }
711
712 /// Updates configuration.
713 ///
714 /// # Arguments
715 /// * `section` - Configuration section
716 /// * `updates` - Configuration updates as key-value pairs
717 ///
718 /// # Returns
719 /// * `Ok(())` - Configuration updated successfully
720 /// * `Err(CommonError)` - Update error
721 pub async fn update_configuration(
722 &self,
723 section:String,
724 updates:HashMap<String, String>,
725 ) -> Result<(), CommonError> {
726 let request_id = generate_request_id();
727 dev_log!(
728 "grpc",
729 "[AirServiceProvider] update_configuration (request_id: {}, section: {})",
730 request_id,
731 section
732 );
733
734 self.client.update_configuration(request_id, section, updates).await
735 }
736}
737
738// Request-id helper moved to `GenerateRequestID::Fn` in the sibling file
739// declared at the top of this module. Internal callers reach it via the
740// `use self::GenerateRequestID::Fn as generate_request_id;` shorthand.
741
742// ============================================================================
743// Tests
744// ============================================================================
745
746#[cfg(test)]
747mod tests {
748 use super::*;
749
750 #[test]
751 fn test_generate_request_id() {
752 let id1 = generate_request_id();
753 let id2 = generate_request_id();
754
755 // IDs should be unique
756 assert_ne!(id1, id2);
757
758 // IDs should be valid UUIDs (simple format = 32 chars)
759 assert_eq!(id1.len(), 32);
760 assert_eq!(id2.len(), 32);
761
762 // IDs should be hex characters
763 assert!(id1.chars().all(|c| c.is_ascii_hexdigit()));
764 assert!(id2.chars().all(|c| c.is_ascii_hexdigit()));
765 }
766
767 #[test]
768 fn test_default_address() {
769 assert_eq!(DEFAULT_AIR_SERVER_ADDRESS, "[::1]:50053");
770 }
771}