use clap::{Args, ValueEnum};
use std::path::PathBuf;
#[derive(Debug, Clone, Args)]
pub struct BackendArgs {
#[arg(long, value_name = "BACKEND", group = "backend-type")]
pub backend: Option<BackendType>,
#[arg(long, value_name = "COMMAND", requires = "backend")]
pub cmd: Option<String>,
#[arg(long, value_name = "ARGS", requires = "cmd")]
pub args: Vec<String>,
#[arg(long, value_name = "DIR", requires = "cmd")]
pub working_dir: Option<PathBuf>,
#[arg(long, value_name = "URL", group = "backend-type")]
pub http: Option<String>,
#[arg(long, value_name = "ADDR", group = "backend-type")]
pub tcp: Option<String>,
#[cfg(unix)]
#[arg(long, value_name = "PATH", group = "backend-type")]
pub unix: Option<String>,
#[arg(long, value_name = "URL", group = "backend-type")]
pub websocket: Option<String>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, ValueEnum)]
pub enum BackendType {
Stdio,
Http,
Tcp,
#[cfg(unix)]
Unix,
Websocket,
}
impl BackendArgs {
#[must_use]
pub fn backend_type(&self) -> Option<BackendType> {
self.backend.or_else(|| {
if self.http.is_some() {
Some(BackendType::Http)
} else if self.tcp.is_some() {
Some(BackendType::Tcp)
} else {
#[cfg(unix)]
if self.unix.is_some() {
return Some(BackendType::Unix);
}
if self.websocket.is_some() {
Some(BackendType::Websocket)
} else {
None
}
}
})
}
pub fn validate(&self) -> Result<(), String> {
match self.backend_type() {
Some(BackendType::Stdio) => {
if self.cmd.is_none() {
return Err("--cmd is required for stdio backend".to_string());
}
}
Some(BackendType::Http) => {
if self.http.is_none() && self.backend == Some(BackendType::Http) {
return Err("--http URL is required for http backend".to_string());
}
}
Some(BackendType::Tcp) => {
if self.tcp.is_none() && self.backend == Some(BackendType::Tcp) {
return Err(
"--tcp address is required for tcp backend (format: host:port)".to_string(),
);
}
}
#[cfg(unix)]
Some(BackendType::Unix) => {
if self.unix.is_none() && self.backend == Some(BackendType::Unix) {
return Err("--unix path is required for unix backend".to_string());
}
}
Some(BackendType::Websocket) => {
if self.websocket.is_none() && self.backend == Some(BackendType::Websocket) {
return Err("--websocket URL is required for websocket backend".to_string());
}
}
None => return Err("No backend specified".to_string()),
}
Ok(())
}
}
#[derive(Debug, Clone, Args)]
pub struct OutputArgs {
#[arg(short = 'o', long, value_name = "FILE")]
pub output: Option<PathBuf>,
#[arg(long, requires = "output")]
pub append: bool,
}
#[cfg(test)]
mod tests {
use super::*;
fn make_args(
backend: Option<BackendType>,
cmd: Option<String>,
http: Option<String>,
tcp: Option<String>,
websocket: Option<String>,
) -> BackendArgs {
BackendArgs {
backend,
cmd,
args: vec![],
working_dir: None,
http,
tcp,
#[cfg(unix)]
unix: None,
websocket,
}
}
#[test]
fn test_backend_type_detection() {
let args = make_args(
Some(BackendType::Stdio),
Some("python".to_string()),
None,
None,
None,
);
assert_eq!(args.backend_type(), Some(BackendType::Stdio));
}
#[test]
fn test_backend_type_detection_tcp() {
let args = make_args(None, None, None, Some("localhost:5000".to_string()), None);
assert_eq!(args.backend_type(), Some(BackendType::Tcp));
}
#[cfg(unix)]
#[test]
fn test_backend_type_detection_unix() {
let args = BackendArgs {
backend: None,
cmd: None,
args: vec![],
working_dir: None,
http: None,
tcp: None,
unix: Some("/tmp/mcp.sock".to_string()),
websocket: None,
};
assert_eq!(args.backend_type(), Some(BackendType::Unix));
}
#[test]
fn test_backend_validation_stdio() {
let args = make_args(Some(BackendType::Stdio), None, None, None, None);
assert!(args.validate().is_err());
let args = make_args(
Some(BackendType::Stdio),
Some("python".to_string()),
None,
None,
None,
);
assert!(args.validate().is_ok());
}
#[test]
fn test_backend_validation_tcp() {
let args = make_args(
Some(BackendType::Tcp),
None,
None,
Some("localhost:5000".to_string()),
None,
);
assert!(args.validate().is_ok());
}
#[cfg(unix)]
#[test]
fn test_backend_validation_unix() {
let args = BackendArgs {
backend: Some(BackendType::Unix),
cmd: None,
args: vec![],
working_dir: None,
http: None,
tcp: None,
unix: Some("/tmp/mcp.sock".to_string()),
websocket: None,
};
assert!(args.validate().is_ok());
}
}