actr_cli/commands/
discovery.rs

1//! Discovery Command Implementation
2//!
3//! Demonstrates multi-level reuse patterns: Service Discovery -> Validation -> Optional Install
4
5use anyhow::Result;
6use async_trait::async_trait;
7use clap::Args;
8
9use crate::core::{
10    ActrCliError, Command, CommandContext, CommandResult, ComponentType, DependencySpec,
11    ServiceInfo,
12};
13
14/// Discovery 命什
15#[derive(Args, Debug)]
16#[command(
17    about = "Discover network services",
18    long_about = "Discover Actor services in the network, view available services and choose to install"
19)]
20pub struct DiscoveryCommand {
21    /// Service name filter pattern (e.g., user-*)
22    #[arg(long, value_name = "PATTERN")]
23    pub filter: Option<String>,
24
25    /// Show detailed information
26    #[arg(long)]
27    pub verbose: bool,
28
29    /// Automatically install selected services
30    #[arg(long)]
31    pub auto_install: bool,
32}
33
34#[async_trait]
35impl Command for DiscoveryCommand {
36    async fn execute(&self, context: &CommandContext) -> Result<CommandResult> {
37        // Get reusable components
38        let (service_discovery, user_interface, _config_manager) = {
39            let container = context.container.lock().unwrap();
40            (
41                container.get_service_discovery()?,
42                container.get_user_interface()?,
43                container.get_config_manager()?,
44            )
45        };
46
47        // Phase 1: Service Discovery
48        println!("πŸ” Scanning for Actor services in the network...");
49
50        let filter = self.create_service_filter();
51        let services = service_discovery.discover_services(filter.as_ref()).await?;
52
53        if services.is_empty() {
54            println!("ℹ️ No available Actor services discovered in the current network");
55            return Ok(CommandResult::Success("No services discovered".to_string()));
56        }
57
58        // Display discovered services
59        self.display_services_table(&services);
60
61        // Phase 2: User Interaction Selection
62        let selected_index = user_interface
63            .select_service_from_list(&services, |s| format!("{} ({})", s.name, s.version))
64            .await?;
65
66        let selected_service = &services[selected_index];
67
68        // Display service details and action menu
69        self.display_service_details(selected_service).await?;
70
71        // Ask user for action
72        let action_menu = vec![
73            "View service details".to_string(),
74            "Export proto files".to_string(),
75            "Add to configuration file".to_string(),
76        ];
77
78        let action_choice = user_interface
79            .select_string_from_list(&action_menu, |s| s.clone())
80            .await?;
81
82        match action_choice {
83            0 => {
84                // View details
85                self.show_detailed_service_info(selected_service, &service_discovery)
86                    .await?;
87                Ok(CommandResult::Success(
88                    "Service details displayed".to_string(),
89                ))
90            }
91            1 => {
92                // Export proto files
93                self.export_proto_files(selected_service, &service_discovery)
94                    .await?;
95                Ok(CommandResult::Success("Proto files exported".to_string()))
96            }
97            2 => {
98                // Add to configuration file - core flow of reuse architecture
99                self.add_to_config_with_validation(selected_service, context)
100                    .await
101            }
102            _ => Ok(CommandResult::Success("Invalid choice".to_string())),
103        }
104    }
105
106    fn required_components(&self) -> Vec<ComponentType> {
107        // Components needed for Discovery command (supports complete reuse flow)
108        vec![
109            ComponentType::ServiceDiscovery,     // Core service discovery
110            ComponentType::UserInterface,        // User interface
111            ComponentType::ConfigManager,        // Configuration management
112            ComponentType::DependencyResolver,   // Dependency resolution (validation phase)
113            ComponentType::NetworkValidator,     // Network validation (validation phase)
114            ComponentType::FingerprintValidator, // Fingerprint validation (validation phase)
115            ComponentType::CacheManager,         // Cache management (install phase)
116            ComponentType::ProtoProcessor,       // Proto processing (install phase)
117        ]
118    }
119
120    fn name(&self) -> &str {
121        "discovery"
122    }
123
124    fn description(&self) -> &str {
125        "Discover available Actor services in the network (Reuse architecture + check-first)"
126    }
127}
128
129impl DiscoveryCommand {
130    pub fn new(filter: Option<String>, verbose: bool, auto_install: bool) -> Self {
131        Self {
132            filter,
133            verbose,
134            auto_install,
135        }
136    }
137
138    // Create from clap Args
139    pub fn from_args(args: &DiscoveryCommand) -> Self {
140        DiscoveryCommand {
141            filter: args.filter.clone(),
142            verbose: args.verbose,
143            auto_install: args.auto_install,
144        }
145    }
146
147    /// Create service filter
148    fn create_service_filter(&self) -> Option<crate::core::ServiceFilter> {
149        self.filter
150            .as_ref()
151            .map(|pattern| crate::core::ServiceFilter {
152                name_pattern: Some(pattern.clone()),
153                version_range: None,
154                tags: None,
155            })
156    }
157
158    /// Display services table
159    fn display_services_table(&self, services: &[ServiceInfo]) {
160        println!();
161        println!("πŸ” Discovered Actor services:");
162        println!();
163        println!("β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”");
164        println!("β”‚ Service Name    β”‚ Version β”‚ Description                     β”‚");
165        println!("β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€");
166
167        for service in services {
168            let description = service
169                .description
170                .as_deref()
171                .unwrap_or("No description")
172                .chars()
173                .take(28)
174                .collect::<String>();
175
176            println!(
177                "β”‚ {:15} β”‚ {:7} β”‚ {:31} β”‚",
178                service.name.chars().take(15).collect::<String>(),
179                service.version.chars().take(7).collect::<String>(),
180                description
181            );
182        }
183
184        println!("β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜");
185        println!();
186        println!("β†’ Use ↑↓ to select service, Enter to view options, q to quit");
187        println!();
188    }
189
190    /// Display service details
191    async fn display_service_details(&self, service: &ServiceInfo) -> Result<()> {
192        println!(
193            "πŸ“‹ Selected service: {} ({})",
194            service.name, service.version
195        );
196        if let Some(desc) = &service.description {
197            println!("πŸ“ Description: {desc}");
198        }
199        println!("πŸ”— URI: {}", service.uri);
200        println!("πŸ” Fingerprint: {}", service.fingerprint);
201        println!("πŸ“Š Methods count: {}", service.methods.len());
202        println!();
203        Ok(())
204    }
205
206    /// Show detailed service information
207    async fn show_detailed_service_info(
208        &self,
209        service: &ServiceInfo,
210        service_discovery: &std::sync::Arc<dyn crate::core::ServiceDiscovery>,
211    ) -> Result<()> {
212        println!("πŸ“– {} Detailed Information:", service.name);
213        println!("════════════════════════════════════════");
214
215        let details = service_discovery.get_service_details(&service.uri).await?;
216
217        println!("🏷️  Service Name: {}", details.info.name);
218        println!("πŸ“¦ Version: {}", details.info.version);
219        println!("πŸ”— URI: {}", details.info.uri);
220        println!("πŸ” Fingerprint: {}", details.info.fingerprint);
221
222        if let Some(desc) = &details.info.description {
223            println!("πŸ“ Description: {desc}");
224        }
225
226        println!();
227        println!("πŸ“‹ Available Methods:");
228        for method in &details.info.methods {
229            println!(
230                "  β€’ {}: {} β†’ {}",
231                method.name, method.input_type, method.output_type
232            );
233        }
234
235        if !details.dependencies.is_empty() {
236            println!();
237            println!("πŸ”— Dependent Services:");
238            for dep in &details.dependencies {
239                println!("  β€’ {dep}");
240            }
241        }
242
243        println!();
244        println!("πŸ“ Proto Files:");
245        for proto in &details.proto_files {
246            println!("  β€’ {} ({} services)", proto.name, proto.services.len());
247        }
248
249        Ok(())
250    }
251
252    /// Export proto files
253    async fn export_proto_files(
254        &self,
255        service: &ServiceInfo,
256        service_discovery: &std::sync::Arc<dyn crate::core::ServiceDiscovery>,
257    ) -> Result<()> {
258        println!("πŸ“€ Exporting proto files for {}...", service.name);
259
260        let proto_files = service_discovery.get_service_proto(&service.uri).await?;
261
262        for proto in &proto_files {
263            let file_path = format!("./exported_{}", proto.name);
264            std::fs::write(&file_path, &proto.content)?;
265            println!("βœ… Exported: {file_path}");
266        }
267
268        println!("πŸŽ‰ Export completed, total {} files", proto_files.len());
269        Ok(())
270    }
271
272    /// Add to configuration file - core flow of reuse architecture
273    async fn add_to_config_with_validation(
274        &self,
275        service: &ServiceInfo,
276        context: &CommandContext,
277    ) -> Result<CommandResult> {
278        let (config_manager, user_interface) = {
279            let container = context.container.lock().unwrap();
280            (
281                container.get_config_manager()?,
282                container.get_user_interface()?,
283            )
284        };
285
286        // Convert to dependency spec
287        let dependency_spec = DependencySpec {
288            name: service.name.clone(),
289            uri: service.uri.clone(),
290            version: Some(service.version.clone()),
291            fingerprint: Some(service.fingerprint.clone()),
292        };
293
294        println!("πŸ“ Adding {} to configuration file...", service.name);
295
296        // Backup configuration
297        let backup = config_manager.backup_config().await?;
298
299        // Update configuration
300        match config_manager.update_dependency(&dependency_spec).await {
301            Ok(_) => {
302                println!("βœ… Added {} to configuration file", service.name);
303            }
304            Err(e) => {
305                config_manager.restore_backup(backup).await?;
306                return Err(ActrCliError::Config {
307                    message: format!("Configuration update failed: {e}"),
308                }
309                .into());
310            }
311        }
312
313        // πŸ” 倍用 check 桁程ιͺŒθ―ζ–°δΎθ΅–
314        println!();
315        println!("πŸ” Verifying new dependency...");
316
317        let validation_pipeline = {
318            let mut container = context.container.lock().unwrap();
319            container.get_validation_pipeline()?
320        };
321
322        match validation_pipeline
323            .validate_dependencies(std::slice::from_ref(&dependency_spec))
324            .await
325        {
326            Ok(validation_results) => {
327                let all_passed = validation_results.iter().all(|v| v.is_available);
328
329                if !all_passed {
330                    // Verification failed, rollback configuration
331                    println!(
332                        "❌ Dependency verification failed, rolling back configuration changes..."
333                    );
334                    config_manager.restore_backup(backup).await?;
335
336                    // Show detailed verification failure information
337                    for validation in &validation_results {
338                        if !validation.is_available {
339                            println!(
340                                "  β€’ {}: {}",
341                                validation.dependency,
342                                validation.error.as_deref().unwrap_or("Verification failed")
343                            );
344                        }
345                    }
346
347                    return Err(ActrCliError::ValidationFailed {
348                        details: "Dependency verification failed".to_string(),
349                    }
350                    .into());
351                } else {
352                    // Verification successful
353                    println!("  β”œβ”€ πŸ“‹ Service existence check βœ…");
354                    println!("  β”œβ”€ 🌐 Network connectivity test βœ…");
355                    println!("  └─ πŸ” Fingerprint integrity verification βœ…");
356
357                    // Clean up backup
358                    config_manager.remove_backup(backup).await?;
359                }
360            }
361            Err(e) => {
362                // Verification error, rollback configuration
363                println!("❌ Error during verification, rolling back configuration changes...");
364                config_manager.restore_backup(backup).await?;
365                return Err(e);
366            }
367        }
368
369        // Ask if user wants to install immediately
370        println!();
371        let should_install = if self.auto_install {
372            true
373        } else {
374            user_interface
375                .confirm("πŸ€” Install this dependency now?")
376                .await?
377        };
378
379        if should_install {
380            // Reuse install flow
381            println!();
382            println!("πŸ“¦ Installing {}...", service.name);
383
384            let install_pipeline = {
385                let mut container = context.container.lock().unwrap();
386                container.get_install_pipeline()?
387            };
388
389            match install_pipeline
390                .install_dependencies(&[dependency_spec])
391                .await
392            {
393                Ok(install_result) => {
394                    println!("  β”œβ”€ πŸ“¦ Cache proto files βœ…");
395                    println!("  β”œβ”€ πŸ”’ Update lock file βœ…");
396                    println!("  └─ βœ… Installation complete");
397                    println!();
398                    println!("πŸ’‘ Tip: Run 'actr gen' to generate the latest code");
399
400                    Ok(CommandResult::Install(install_result))
401                }
402                Err(e) => {
403                    eprintln!("❌ Installation failed: {e}");
404                    Ok(CommandResult::Success(
405                        "Dependency added but installation failed".to_string(),
406                    ))
407                }
408            }
409        } else {
410            println!("βœ… Dependency added to configuration file");
411            println!("πŸ’‘ Tip: Run 'actr install' to install dependencies");
412            Ok(CommandResult::Success(
413                "Dependency added to configuration".to_string(),
414            ))
415        }
416    }
417}
418
419impl Default for DiscoveryCommand {
420    fn default() -> Self {
421        Self::new(None, false, false)
422    }
423}
424
425#[cfg(test)]
426mod tests {
427    use super::*;
428
429    #[test]
430    fn test_create_service_filter() {
431        let cmd = DiscoveryCommand::new(Some("user-*".to_string()), false, false);
432        let filter = cmd.create_service_filter();
433
434        assert!(filter.is_some());
435        let filter = filter.unwrap();
436        assert_eq!(filter.name_pattern, Some("user-*".to_string()));
437    }
438
439    #[test]
440    fn test_create_service_filter_none() {
441        let cmd = DiscoveryCommand::new(None, false, false);
442        let filter = cmd.create_service_filter();
443
444        assert!(filter.is_none());
445    }
446
447    #[test]
448    fn test_required_components() {
449        let cmd = DiscoveryCommand::default();
450        let components = cmd.required_components();
451
452        // Discovery command needs to support complete reuse flow
453        assert!(components.contains(&ComponentType::ServiceDiscovery));
454        assert!(components.contains(&ComponentType::UserInterface));
455        assert!(components.contains(&ComponentType::ConfigManager));
456        assert!(components.contains(&ComponentType::DependencyResolver));
457        assert!(components.contains(&ComponentType::NetworkValidator));
458        assert!(components.contains(&ComponentType::FingerprintValidator));
459        assert!(components.contains(&ComponentType::CacheManager));
460        assert!(components.contains(&ComponentType::ProtoProcessor));
461    }
462}