use serde::{Deserialize, Serialize};
use std::path::Path;
use thiserror::Error;
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(default)]
pub struct ServiceConfig {
pub service_name: String,
pub service_id: String,
pub http_port: u16,
pub manifest_path: String,
pub tags: Vec<String>,
pub priority: u8,
pub udp_port: u16,
pub announce_on_startup: bool,
pub announce_interval: u32,
#[serde(default)]
pub manifest_data: serde_json::Value,
}
impl ServiceConfig {
pub fn from_manifest_json(manifest: serde_json::Value, http_port: u16) -> Self {
let meta = manifest.get("meta");
let endpoints = manifest.get("endpoints");
Self {
service_name: meta
.and_then(|m| m.get("name"))
.and_then(|v| v.as_str())
.unwrap_or("Unknown")
.to_string(),
service_id: meta
.and_then(|m| m.get("serviceId"))
.and_then(|v| v.as_str())
.unwrap_or("unknown")
.to_string(),
http_port,
manifest_path: endpoints
.and_then(|e| e.get("invoke"))
.and_then(|v| v.as_str())
.unwrap_or("/ai_manifest")
.to_string(),
tags: vec![],
priority: 1,
udp_port: 53535,
announce_on_startup: true,
announce_interval: 30,
manifest_data: manifest,
}
}
pub fn from_echo(
echo_path: &Path,
manifest: Option<serde_json::Value>,
http_port: u16,
) -> Self {
if let Some(manifest) = manifest {
return Self::from_manifest_json(manifest, http_port);
}
let dir_name = echo_path
.file_stem()
.and_then(|s| s.to_str())
.unwrap_or("unknown");
Self {
service_name: format!("Service on port {}", http_port),
service_id: format!("{}-{}", dir_name, http_port),
http_port,
manifest_path: "/ai_manifest".to_string(),
tags: vec![],
priority: 1,
udp_port: 53535,
announce_on_startup: true,
announce_interval: 30,
manifest_data: serde_json::Value::Object(Default::default()),
}
}
pub fn base_url(&self) -> String {
format!("http://localhost:{}", self.http_port)
}
pub fn manifest_url(&self) -> String {
format!("{}{}", self.base_url(), self.manifest_path)
}
pub fn new(
service_name: impl Into<String>,
service_id: impl Into<String>,
http_port: u16,
) -> Self {
Self {
service_name: service_name.into(),
service_id: service_id.into(),
http_port,
manifest_path: "/ai_manifest".to_string(),
tags: vec![],
priority: 1,
udp_port: 53535,
announce_on_startup: true,
announce_interval: 30,
manifest_data: serde_json::Value::Object(Default::default()),
}
}
}
impl Default for ServiceConfig {
fn default() -> Self {
Self::new("Unknown Service", "unknown-001", 8080)
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ClientConfig {
#[serde(rename = "udpPort")]
pub udp_port: u16,
pub timeout: f64,
#[serde(rename = "outputFormat")]
pub output_format: String,
#[serde(rename = "outputFile")]
pub output_file: Option<String>,
#[serde(rename = "watchMode")]
pub watch_mode: bool,
#[serde(rename = "scanInterval")]
pub scan_interval: u32,
#[serde(rename = "fetchManifest")]
pub fetch_manifest: bool,
#[serde(rename = "maxConcurrent")]
pub max_concurrent: usize,
}
impl Default for ClientConfig {
fn default() -> Self {
Self {
udp_port: 53535,
timeout: 2.0,
output_format: "json".to_string(),
output_file: None,
watch_mode: false,
scan_interval: 30,
fetch_manifest: true,
max_concurrent: 10,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(default)]
pub struct EchoConfig {
pub port: u16,
pub enable: bool,
}
impl EchoConfig {
pub fn from_file<P: AsRef<Path>>(path: P) -> std::result::Result<Self, ConfigError> {
let path = path.as_ref();
if !path.exists() {
return Err(ConfigError::FileNotFound(path.display().to_string()));
}
let content =
std::fs::read_to_string(path).map_err(|e| ConfigError::IoError(e.to_string()))?;
let echo_config =
serde_json::from_str(&content).map_err(|e| ConfigError::ParseError(e.to_string()))?;
Ok(echo_config)
}
}
impl Default for EchoConfig {
fn default() -> Self {
Self {
port: 8080,
enable: true,
}
}
}
#[derive(Debug, Error)]
pub enum ConfigError {
#[error("File not found: {0}")]
FileNotFound(String),
#[error("IO error: {0}")]
IoError(String),
#[error("Parse error: {0}")]
ParseError(String),
}