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