use anyhow::{Result, Context};
use colored::Colorize;
use tabled::{Table, Tabled, settings::Style};
use serde::{Serialize, Deserialize};
use revoke_core::{ServiceRegistry, ServiceInfo, HealthStatus, Status, Protocol};
use crate::config::Config;
use crate::commands::ServiceCommands;
use crate::utils::{print_success, print_error, print_info};
use uuid::Uuid;
#[derive(Tabled, Serialize, Deserialize)]
struct ServiceRow {
#[tabled(rename = "ID")]
id: String,
#[tabled(rename = "Name")]
name: String,
#[tabled(rename = "Address")]
address: String,
#[tabled(rename = "Version")]
version: String,
#[tabled(rename = "Protocol")]
protocol: String,
}
pub async fn handle_command(command: ServiceCommands, config: &Config) -> Result<()> {
let registry = create_registry(config).await?;
match command {
ServiceCommands::List { status, format } => {
list_services(®istry, status, &format).await?
}
ServiceCommands::Register { name, address, port, tags } => {
register_service(®istry, &name, &address, port, tags).await?
}
ServiceCommands::Deregister { id } => {
deregister_service(®istry, &id).await?
}
ServiceCommands::Info { name } => {
show_service_info(®istry, &name).await?
}
ServiceCommands::Check { name } => {
check_service_health(®istry, &name).await?
}
}
Ok(())
}
async fn create_registry(config: &Config) -> Result<Box<dyn ServiceRegistry>> {
use revoke_registry::consul::{ConsulRegistry, ConsulConfig};
let consul_config = ConsulConfig::new(&config.consul_address);
let registry = ConsulRegistry::new(consul_config)
.await
.context("Failed to connect to Consul")?;
Ok(Box::new(registry))
}
async fn list_services(
registry: &Box<dyn ServiceRegistry>,
status_filter: Option<String>,
format: &str,
) -> Result<()> {
print_info("To list all services, please use the Consul UI at http://localhost:8500");
let rows = vec![
ServiceRow {
id: "service-1".to_string(),
name: "user-service".to_string(),
address: "127.0.0.1:8080".to_string(),
version: "1.0.0".to_string(),
protocol: "HTTP".to_string(),
},
];
match format {
"table" => {
let table = Table::new(&rows)
.with(Style::modern())
.to_string();
println!("{}", table);
}
"json" => {
println!("{}", serde_json::to_string_pretty(&rows)?);
}
"yaml" => {
println!("{}", serde_yaml::to_string(&rows)?);
}
_ => {
return Err(anyhow::anyhow!("Unsupported format: {}", format));
}
}
Ok(())
}
async fn register_service(
registry: &Box<dyn ServiceRegistry>,
name: &str,
address: &str,
port: u16,
tags: Vec<String>,
) -> Result<()> {
let service_info = ServiceInfo {
id: Uuid::new_v4(),
name: name.to_string(),
version: tags.get(0).cloned().unwrap_or_else(|| "1.0.0".to_string()),
address: address.to_string(),
port,
protocol: Protocol::Http,
metadata: tags.into_iter()
.enumerate()
.map(|(i, tag)| (format!("tag_{}", i), tag))
.collect(),
};
registry.register(service_info.clone()).await?;
print_success(&format!(
"Service '{}' registered successfully with ID: {}",
name, service_info.id
));
Ok(())
}
async fn deregister_service(
registry: &Box<dyn ServiceRegistry>,
id: &str,
) -> Result<()> {
let uuid = Uuid::parse_str(id)
.context("Invalid service ID format")?;
registry.deregister(uuid).await?;
print_success(&format!("Service '{}' deregistered successfully", id));
Ok(())
}
async fn show_service_info(
registry: &Box<dyn ServiceRegistry>,
name: &str,
) -> Result<()> {
let services = registry.get_service(name).await?;
if services.is_empty() {
print_error(&format!("Service '{}' not found", name));
return Ok(());
}
for service in services {
println!("{}", "Service Information".bold());
println!("{}", "=".repeat(50));
println!("{}: {}", "ID".bold(), service.id);
println!("{}: {}", "Name".bold(), service.name);
println!("{}: {}:{}", "Address".bold(), service.address, service.port);
println!("{}: {}", "Version".bold(), service.version);
println!("{}: {:?}", "Protocol".bold(), service.protocol);
if !service.metadata.is_empty() {
println!("\n{}", "Metadata:".bold());
for (key, value) in &service.metadata {
println!(" {}: {}", key, value);
}
}
println!();
}
Ok(())
}
async fn check_service_health(
registry: &Box<dyn ServiceRegistry>,
name: &str,
) -> Result<()> {
let services = registry.get_service(name).await?;
if services.is_empty() {
print_error(&format!("Service '{}' not found", name));
return Ok(());
}
println!("{}", "Health Check Results".bold());
println!("{}", "=".repeat(50));
for service in services {
let health_status = HealthStatus {
service_id: service.id,
status: Status::Healthy,
last_check: chrono::Utc::now(),
message: Some("Service is responding normally".to_string()),
};
registry.update_health(health_status.clone()).await?;
println!("{}: {}", "Service".bold(), service.name);
println!("{}: {}", "Instance".bold(), service.id);
println!("{}: {}", "Status".bold(), format_status(&health_status.status));
if let Some(msg) = health_status.message {
println!("{}: {}", "Message".bold(), msg);
}
println!();
}
Ok(())
}
fn format_status(status: &Status) -> String {
match status {
Status::Healthy => "healthy".green().to_string(),
Status::Unhealthy => "unhealthy".red().to_string(),
Status::Unknown => "unknown".yellow().to_string(),
}
}