1pub mod commands;
6pub mod utils;
7
8use anyhow::Result;
9use clap::{Parser, Subcommand};
10use reqwest_middleware::ClientBuilder;
11use reqwest_retry::{policies::ExponentialBackoff, RetryTransientMiddleware};
12use std::path::PathBuf;
13
14use crate::commands::{auth, cron, lint, rules, run, runs, schema, storage, webhooks};
15
16#[derive(Parser)]
18#[command(name = "stormchaser")]
19#[command(about = "Stormchaser CLI", long_about = None)]
20#[command(version = concat!(env!("CARGO_PKG_VERSION"), " (rev: ", env!("VERGEN_GIT_SHA"), ", branch: ", env!("VERGEN_GIT_BRANCH"), ", built: ", env!("VERGEN_BUILD_TIMESTAMP"), ")"))]
21pub struct Cli {
22 #[command(subcommand)]
24 pub command: Commands,
25
26 #[arg(
28 short,
29 long,
30 env = "STORMCHASER_URL",
31 default_value = "http://localhost:3000"
32 )]
33 pub url: String,
34
35 #[arg(short, long, env = "STORMCHASER_TOKEN")]
37 pub token: Option<String>,
38}
39
40#[derive(Subcommand)]
42pub enum Commands {
43 Run {
45 file: PathBuf,
47
48 #[arg(short, long)]
50 input: Vec<String>,
51
52 #[arg(long, default_value_t = false)]
54 tail: bool,
55
56 #[arg(long, default_value_t = false)]
58 watch: bool,
59 },
60
61 Runs {
63 #[command(subcommand)]
65 command: runs::RunCommands,
66 },
67
68 Webhooks {
70 #[command(subcommand)]
72 command: webhooks::WebhookCommands,
73 },
74
75 Rules {
77 #[command(subcommand)]
79 command: rules::RuleCommands,
80 },
81
82 Storage {
84 #[command(subcommand)]
86 command: storage::StorageCommands,
87 },
88
89 Cron {
91 #[command(subcommand)]
93 command: cron::CronCommands,
94 },
95
96 Lint(lint::LintCommand),
98
99 Schema {
101 #[command(subcommand)]
103 command: schema::SchemaCommands,
104 },
105
106 Auth {
108 #[command(subcommand)]
110 command: auth::AuthCommands,
111 },
112
113 Login {
115 #[arg(long, default_value = "http://localhost:5556/dex")]
117 issuer: String,
118 #[arg(long, default_value = "stormchaser-cli")]
120 client_id: String,
121 },
122}
123
124pub async fn run_cli(cli: Cli) -> Result<()> {
129 let retry_policy = ExponentialBackoff::builder().build_with_max_retries(3);
130 let http_client = ClientBuilder::new(reqwest::Client::new())
131 .with(RetryTransientMiddleware::new_with_policy(retry_policy))
132 .build();
133
134 let token_opt = cli.token.as_deref();
135
136 match cli.command {
137 Commands::Run {
138 file,
139 input,
140 tail,
141 watch,
142 } => {
143 run::handle(&cli.url, token_opt, &http_client, file, input, tail, watch).await?;
144 }
145
146 Commands::Runs { command } => {
147 runs::handle(&cli.url, token_opt, &http_client, command).await?;
148 }
149
150 Commands::Webhooks { command } => {
151 webhooks::handle(&cli.url, token_opt, &http_client, command).await?;
152 }
153
154 Commands::Rules { command } => {
155 rules::handle(&cli.url, token_opt, &http_client, command).await?;
156 }
157
158 Commands::Storage { command } => {
159 storage::handle(&cli.url, token_opt, &http_client, command).await?;
160 }
161
162 Commands::Cron { command } => {
163 cron::handle(&cli.url, token_opt, &http_client, command).await?;
164 }
165
166 Commands::Lint(command) => {
167 lint::handle(&cli.url, &http_client, command).await?;
168 }
169
170 Commands::Schema { command } => {
171 schema::handle(command)?;
172 }
173
174 Commands::Auth { command } => {
175 auth::handle(&cli.url, &http_client, command).await?;
176 }
177
178 Commands::Login { issuer, client_id } => {
179 auth::handle_login(&cli.url, &issuer, &client_id, &http_client).await?;
180 }
181 }
182
183 Ok(())
184}
185
186#[cfg(test)]
187mod tests {
188 use super::*;
189 use clap::CommandFactory;
190
191 #[test]
192 fn verify_cli() {
193 Cli::command().debug_assert();
194 }
195
196 #[test]
197 fn parse_cli_basic() {
198 use clap::Parser;
199
200 let cli = Cli::try_parse_from(["stormchaser", "--url", "http://test", "webhooks", "list"])
201 .unwrap();
202 assert_eq!(cli.url, "http://test");
203 assert!(matches!(cli.command, Commands::Webhooks { .. }));
204 }
205}