via-cli 0.2.0

Run commands and API requests with 1Password-backed credentials without exposing secrets to your shell
Documentation
use std::ffi::OsString;
use std::path::Path;
use std::process::ExitCode;

use crate::cli::{print_help, Cli, Command, DaemonCommand};
use crate::config::Config;
use crate::error::ViaError;
use crate::executor;
use crate::providers::ProviderRegistry;

pub fn run(args: impl IntoIterator<Item = OsString>) -> ExitCode {
    crate::tls::install_crypto_provider();
    exit_code_from_result(try_run(args))
}

fn exit_code_from_result(result: Result<ExitCode, ViaError>) -> ExitCode {
    match result {
        Ok(code) => code,
        Err(ViaError::Clap(error)) => {
            let exit_code = if error.use_stderr() { 2 } else { 0 };
            if error.use_stderr() {
                eprint!("{error}");
            } else {
                print!("{error}");
            }
            ExitCode::from(exit_code)
        }
        Err(error) => {
            eprintln!("error: {error}");
            ExitCode::from(error.exit_code())
        }
    }
}

fn try_run(args: impl IntoIterator<Item = OsString>) -> Result<ExitCode, ViaError> {
    let cli = Cli::parse(args)?;
    run_cli(cli)
}

fn run_cli(cli: Cli) -> Result<ExitCode, ViaError> {
    let config_path = cli.config_path.as_deref();
    match cli.command {
        Command::Help => run_help_command(),
        Command::Version => run_version_command(),
        Command::Login { provider } => run_login_command(config_path, provider.as_deref()),
        Command::Capabilities { json } => run_capabilities_command(config_path, json),
        Command::Config(command) => run_config_command(config_path, command),
        Command::Daemon(command) => run_daemon_cli_command(command),
        Command::SkillPrint => run_skill_print_command(config_path),
        Command::Invoke {
            service,
            capability,
            args,
        } => run_invoke_command(config_path, service, capability, args),
    }
}

fn run_help_command() -> Result<ExitCode, ViaError> {
    print_help();
    Ok(ExitCode::SUCCESS)
}

fn run_version_command() -> Result<ExitCode, ViaError> {
    println!("via {}", env!("CARGO_PKG_VERSION"));
    Ok(ExitCode::SUCCESS)
}

fn run_login_command(
    config_path: Option<&Path>,
    provider: Option<&str>,
) -> Result<ExitCode, ViaError> {
    crate::login::run(config_path, provider)?;
    Ok(ExitCode::SUCCESS)
}

fn run_capabilities_command(config_path: Option<&Path>, json: bool) -> Result<ExitCode, ViaError> {
    let config = Config::load(config_path)?;
    crate::capabilities::print(&config, json)?;
    Ok(ExitCode::SUCCESS)
}

fn run_config_command(
    config_path: Option<&Path>,
    command: crate::cli::ConfigCommand,
) -> Result<ExitCode, ViaError> {
    crate::config_command::run(config_path, command)?;
    Ok(ExitCode::SUCCESS)
}

fn run_daemon_cli_command(command: DaemonCommand) -> Result<ExitCode, ViaError> {
    run_daemon_command(command)?;
    Ok(ExitCode::SUCCESS)
}

fn run_skill_print_command(config_path: Option<&Path>) -> Result<ExitCode, ViaError> {
    let config = Config::load(config_path)?;
    crate::skill::print(&config);
    Ok(ExitCode::SUCCESS)
}

fn run_invoke_command(
    config_path: Option<&Path>,
    service: String,
    capability: String,
    args: Vec<String>,
) -> Result<ExitCode, ViaError> {
    let config = Config::load(config_path)?;
    let providers = ProviderRegistry::from_config(&config)?;
    executor::invoke(&config, &providers, &service, &capability, args)?;
    Ok(ExitCode::SUCCESS)
}

fn run_daemon_command(command: DaemonCommand) -> Result<(), ViaError> {
    match command {
        DaemonCommand::Status => crate::daemon::status(),
        DaemonCommand::Clear => crate::daemon::clear(),
        DaemonCommand::Stop => crate::daemon::stop(),
        DaemonCommand::Serve => crate::daemon::serve(),
    }
}

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

    #[test]
    fn capabilities_command_returns_success() {
        let code = try_run([
            OsString::from("via"),
            OsString::from("--config"),
            OsString::from("examples/github.toml"),
            OsString::from("capabilities"),
        ])
        .unwrap();

        assert_eq!(code, ExitCode::SUCCESS);
    }

    #[test]
    fn version_command_returns_success() {
        let code = try_run([OsString::from("via"), OsString::from("version")]).unwrap();

        assert_eq!(code, ExitCode::SUCCESS);
    }

    #[test]
    fn login_command_without_config_returns_runtime_error() {
        let code = run([
            OsString::from("via"),
            OsString::from("--config"),
            OsString::from("/definitely/missing/via.toml"),
            OsString::from("login"),
        ]);

        assert_eq!(code, ExitCode::from(1));
    }

    #[test]
    fn maps_success_result_to_exit_code() {
        let code = exit_code_from_result(Ok(ExitCode::SUCCESS));

        assert_eq!(code, ExitCode::SUCCESS);
    }

    #[test]
    fn run_returns_usage_code_for_cli_error() {
        let code = run([OsString::from("via"), OsString::from("--unknown")]);

        assert_eq!(code, ExitCode::from(2));
    }

    #[test]
    fn run_returns_runtime_code_for_runtime_error() {
        let code = run([
            OsString::from("via"),
            OsString::from("--config"),
            OsString::from("/definitely/missing/via.toml"),
            OsString::from("capabilities"),
        ]);

        assert_eq!(code, ExitCode::from(1));
    }
}