1use 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#[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 #[arg(long, value_name = "PATTERN")]
23 pub filter: Option<String>,
24
25 #[arg(long)]
27 pub verbose: bool,
28
29 #[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 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 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 self.display_services_table(&services);
60
61 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 self.display_service_details(selected_service).await?;
70
71 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 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 self.export_proto_files(selected_service, &service_discovery)
94 .await?;
95 Ok(CommandResult::Success("Proto files exported".to_string()))
96 }
97 2 => {
98 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 vec![
109 ComponentType::ServiceDiscovery, ComponentType::UserInterface, ComponentType::ConfigManager, ComponentType::DependencyResolver, ComponentType::NetworkValidator, ComponentType::FingerprintValidator, ComponentType::CacheManager, ComponentType::ProtoProcessor, ]
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 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 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 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 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 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 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 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 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 let backup = config_manager.backup_config().await?;
298
299 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 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 println!(
332 "β Dependency verification failed, rolling back configuration changes..."
333 );
334 config_manager.restore_backup(backup).await?;
335
336 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 println!(" ββ π Service existence check β
");
354 println!(" ββ π Network connectivity test β
");
355 println!(" ββ π Fingerprint integrity verification β
");
356
357 config_manager.remove_backup(backup).await?;
359 }
360 }
361 Err(e) => {
362 println!("β Error during verification, rolling back configuration changes...");
364 config_manager.restore_backup(backup).await?;
365 return Err(e);
366 }
367 }
368
369 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 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 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}