tcproxy 0.1.1

A TCP proxy for PostgreSQL connections with SSH tunnel support and runtime target switching
Documentation
use clap::{Parser, Subcommand};
use std::path::PathBuf;

#[derive(Parser)]
#[command(name = "tcproxy")]
#[command(about = "A TCP proxy for PostgreSQL connections with SSH tunnel support")]
#[command(version = "0.1.0")]
pub struct Cli {
    /// Configuration file path
    #[arg(short, long, default_value = "config.yaml")]
    pub config: PathBuf,

    /// Log level (trace, debug, info, warn, error)
    #[arg(short, long, default_value = "info")]
    pub log_level: String,

    /// Enable JSON logging format
    #[arg(long)]
    pub json_logs: bool,

    #[command(subcommand)]
    pub command: Option<Commands>,
}

#[derive(Subcommand)]
pub enum Commands {
    /// Start the proxy server
    Start {
        /// Target to proxy to (required)
        #[arg(short, long)]
        target: String,

        /// Override listen port
        #[arg(short, long)]
        port: Option<u16>,

        /// Override listen host
        #[arg(long)]
        host: Option<String>,
    },

    /// Generate a default configuration file
    InitConfig {
        /// Output file path
        #[arg(short, long, default_value = "config.yaml")]
        output: PathBuf,

        /// Overwrite existing file
        #[arg(long)]
        force: bool,
    },

    /// Validate configuration file
    ValidateConfig,

    /// Show current configuration
    ShowConfig,

    /// List available targets
    ListTargets,

    /// Check health status of targets
    HealthCheck {
        /// Specific target to check (optional, checks all if not specified)
        #[arg(short, long)]
        target: Option<String>,
    },
}

impl Cli {
    pub fn parse_args() -> Self {
        Self::parse()
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use clap::Parser;

    #[test]
    fn test_cli_default_values() {
        let cli = Cli::try_parse_from(&["tcproxy", "start", "--target", "local"]).unwrap();

        assert_eq!(cli.config, PathBuf::from("config.yaml"));
        assert_eq!(cli.log_level, "info");
        assert!(!cli.json_logs);

        if let Some(Commands::Start { target, port, host }) = cli.command {
            assert_eq!(target, "local");
            assert!(port.is_none());
            assert!(host.is_none());
        } else {
            panic!("Expected Start command");
        }
    }

    #[test]
    fn test_cli_custom_values() {
        let cli = Cli::try_parse_from(&[
            "tcproxy",
            "--config",
            "custom.yaml",
            "--log-level",
            "debug",
            "--json-logs",
            "start",
            "--target",
            "production",
            "--port",
            "5434",
            "--host",
            "0.0.0.0",
        ])
        .unwrap();

        assert_eq!(cli.config, PathBuf::from("custom.yaml"));
        assert_eq!(cli.log_level, "debug");
        assert!(cli.json_logs);

        if let Some(Commands::Start { target, port, host }) = cli.command {
            assert_eq!(target, "production");
            assert_eq!(port, Some(5434));
            assert_eq!(host, Some("0.0.0.0".to_string()));
        } else {
            panic!("Expected Start command");
        }
    }

    #[test]
    fn test_init_config_command() {
        let cli =
            Cli::try_parse_from(&["tcproxy", "init-config", "--output", "test.yaml", "--force"])
                .unwrap();

        if let Some(Commands::InitConfig { output, force }) = cli.command {
            assert_eq!(output, PathBuf::from("test.yaml"));
            assert!(force);
        } else {
            panic!("Expected InitConfig command");
        }
    }

    #[test]
    fn test_health_check_command() {
        let cli = Cli::try_parse_from(&["tcproxy", "health-check", "--target", "local"]).unwrap();

        if let Some(Commands::HealthCheck { target }) = cli.command {
            assert_eq!(target, Some("local".to_string()));
        } else {
            panic!("Expected HealthCheck command");
        }
    }

    #[test]
    fn test_validate_config_command() {
        let cli = Cli::try_parse_from(&["tcproxy", "validate-config"]).unwrap();

        if let Some(Commands::ValidateConfig) = cli.command {
            // Success
        } else {
            panic!("Expected ValidateConfig command");
        }
    }

    #[test]
    fn test_show_config_command() {
        let cli = Cli::try_parse_from(&["tcproxy", "show-config"]).unwrap();

        if let Some(Commands::ShowConfig) = cli.command {
            // Success
        } else {
            panic!("Expected ShowConfig command");
        }
    }

    #[test]
    fn test_list_targets_command() {
        let cli = Cli::try_parse_from(&["tcproxy", "list-targets"]).unwrap();

        if let Some(Commands::ListTargets) = cli.command {
            // Success
        } else {
            panic!("Expected ListTargets command");
        }
    }

    #[test]
    fn test_missing_target_for_start() {
        let result = Cli::try_parse_from(&["tcproxy", "start"]);
        assert!(result.is_err());
    }
}