Skip to main content

via/
app.rs

1use std::ffi::OsString;
2use std::path::Path;
3use std::process::ExitCode;
4
5use crate::cli::{print_help, Cli, Command, DaemonCommand};
6use crate::config::Config;
7use crate::error::ViaError;
8use crate::executor;
9use crate::providers::ProviderRegistry;
10
11pub fn run(args: impl IntoIterator<Item = OsString>) -> ExitCode {
12    crate::tls::install_crypto_provider();
13    exit_code_from_result(try_run(args))
14}
15
16fn exit_code_from_result(result: Result<ExitCode, ViaError>) -> ExitCode {
17    match result {
18        Ok(code) => code,
19        Err(ViaError::Clap(error)) => {
20            let exit_code = if error.use_stderr() { 2 } else { 0 };
21            if error.use_stderr() {
22                eprint!("{error}");
23            } else {
24                print!("{error}");
25            }
26            ExitCode::from(exit_code)
27        }
28        Err(error) => {
29            eprintln!("error: {error}");
30            ExitCode::from(error.exit_code())
31        }
32    }
33}
34
35fn try_run(args: impl IntoIterator<Item = OsString>) -> Result<ExitCode, ViaError> {
36    let cli = Cli::parse(args)?;
37    run_cli(cli)
38}
39
40fn run_cli(cli: Cli) -> Result<ExitCode, ViaError> {
41    let config_path = cli.config_path.as_deref();
42    match cli.command {
43        Command::Help => run_help_command(),
44        Command::Version => run_version_command(),
45        Command::Login { provider } => run_login_command(config_path, provider.as_deref()),
46        Command::Capabilities { json } => run_capabilities_command(config_path, json),
47        Command::Config(command) => run_config_command(config_path, command),
48        Command::Daemon(command) => run_daemon_cli_command(command),
49        Command::SkillPrint => run_skill_print_command(config_path),
50        Command::Invoke {
51            service,
52            capability,
53            args,
54        } => run_invoke_command(config_path, service, capability, args),
55    }
56}
57
58fn run_help_command() -> Result<ExitCode, ViaError> {
59    print_help();
60    Ok(ExitCode::SUCCESS)
61}
62
63fn run_version_command() -> Result<ExitCode, ViaError> {
64    println!("via {}", env!("CARGO_PKG_VERSION"));
65    Ok(ExitCode::SUCCESS)
66}
67
68fn run_login_command(
69    config_path: Option<&Path>,
70    provider: Option<&str>,
71) -> Result<ExitCode, ViaError> {
72    crate::login::run(config_path, provider)?;
73    Ok(ExitCode::SUCCESS)
74}
75
76fn run_capabilities_command(config_path: Option<&Path>, json: bool) -> Result<ExitCode, ViaError> {
77    let config = Config::load(config_path)?;
78    crate::capabilities::print(&config, json)?;
79    Ok(ExitCode::SUCCESS)
80}
81
82fn run_config_command(
83    config_path: Option<&Path>,
84    command: crate::cli::ConfigCommand,
85) -> Result<ExitCode, ViaError> {
86    crate::config_command::run(config_path, command)?;
87    Ok(ExitCode::SUCCESS)
88}
89
90fn run_daemon_cli_command(command: DaemonCommand) -> Result<ExitCode, ViaError> {
91    run_daemon_command(command)?;
92    Ok(ExitCode::SUCCESS)
93}
94
95fn run_skill_print_command(config_path: Option<&Path>) -> Result<ExitCode, ViaError> {
96    let config = Config::load(config_path)?;
97    crate::skill::print(&config);
98    Ok(ExitCode::SUCCESS)
99}
100
101fn run_invoke_command(
102    config_path: Option<&Path>,
103    service: String,
104    capability: String,
105    args: Vec<String>,
106) -> Result<ExitCode, ViaError> {
107    let config = Config::load(config_path)?;
108    let providers = ProviderRegistry::from_config(&config)?;
109    executor::invoke(&config, &providers, &service, &capability, args)?;
110    Ok(ExitCode::SUCCESS)
111}
112
113fn run_daemon_command(command: DaemonCommand) -> Result<(), ViaError> {
114    match command {
115        DaemonCommand::Status => crate::daemon::status(),
116        DaemonCommand::Clear => crate::daemon::clear(),
117        DaemonCommand::Stop => crate::daemon::stop(),
118        DaemonCommand::Serve => crate::daemon::serve(),
119    }
120}
121
122#[cfg(test)]
123mod tests {
124    use super::*;
125
126    #[test]
127    fn capabilities_command_returns_success() {
128        let code = try_run([
129            OsString::from("via"),
130            OsString::from("--config"),
131            OsString::from("examples/github.toml"),
132            OsString::from("capabilities"),
133        ])
134        .unwrap();
135
136        assert_eq!(code, ExitCode::SUCCESS);
137    }
138
139    #[test]
140    fn version_command_returns_success() {
141        let code = try_run([OsString::from("via"), OsString::from("version")]).unwrap();
142
143        assert_eq!(code, ExitCode::SUCCESS);
144    }
145
146    #[test]
147    fn login_command_without_config_returns_runtime_error() {
148        let code = run([
149            OsString::from("via"),
150            OsString::from("--config"),
151            OsString::from("/definitely/missing/via.toml"),
152            OsString::from("login"),
153        ]);
154
155        assert_eq!(code, ExitCode::from(1));
156    }
157
158    #[test]
159    fn maps_success_result_to_exit_code() {
160        let code = exit_code_from_result(Ok(ExitCode::SUCCESS));
161
162        assert_eq!(code, ExitCode::SUCCESS);
163    }
164
165    #[test]
166    fn run_returns_usage_code_for_cli_error() {
167        let code = run([OsString::from("via"), OsString::from("--unknown")]);
168
169        assert_eq!(code, ExitCode::from(2));
170    }
171
172    #[test]
173    fn run_returns_runtime_code_for_runtime_error() {
174        let code = run([
175            OsString::from("via"),
176            OsString::from("--config"),
177            OsString::from("/definitely/missing/via.toml"),
178            OsString::from("capabilities"),
179        ]);
180
181        assert_eq!(code, ExitCode::from(1));
182    }
183}