Skip to main content

stormchaser_cli/
lib.rs

1//! Main entry point for the Stormchaser CLI.
2//!
3//! Provides commands for interacting with the Stormchaser API and running local workflows.
4
5pub 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/// Main CLI arguments and configuration.
17#[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    /// The specific subcommand to execute.
23    #[command(subcommand)]
24    pub command: Commands,
25
26    /// The base URL of the Stormchaser API.
27    #[arg(
28        short,
29        long,
30        env = "STORMCHASER_URL",
31        default_value = "http://localhost:3000"
32    )]
33    pub url: String,
34
35    /// The authentication token for the API.
36    #[arg(short, long, env = "STORMCHASER_TOKEN")]
37    pub token: Option<String>,
38}
39
40/// Available CLI subcommands.
41#[derive(Subcommand)]
42pub enum Commands {
43    /// Run a local workflow DSL file (direct execution)
44    Run {
45        /// Path to the .storm file
46        file: PathBuf,
47
48        /// Input parameters in key=value format
49        #[arg(short, long)]
50        input: Vec<String>,
51
52        /// Stream all logs from the workflow run until it completes
53        #[arg(long, default_value_t = false)]
54        tail: bool,
55
56        /// Stream real-time state transition events for a run until it completes
57        #[arg(long, default_value_t = false)]
58        watch: bool,
59    },
60
61    /// Manage workflow runs
62    Runs {
63        /// Subcommands for runs
64        #[command(subcommand)]
65        command: runs::RunCommands,
66    },
67
68    /// Manage webhooks
69    Webhooks {
70        /// Subcommands for webhooks
71        #[command(subcommand)]
72        command: webhooks::WebhookCommands,
73    },
74
75    /// Manage event rules
76    Rules {
77        /// Subcommands for event rules
78        #[command(subcommand)]
79        command: rules::RuleCommands,
80    },
81
82    /// Manage storage backends
83    Storage {
84        /// Subcommands for storage
85        #[command(subcommand)]
86        command: storage::StorageCommands,
87    },
88
89    /// Manage scheduled workflows (Cron)
90    Cron {
91        /// Subcommands for cron workflows
92        #[command(subcommand)]
93        command: cron::CronCommands,
94    },
95
96    /// Lint a workflow file against the schema
97    Lint(lint::LintCommand),
98
99    /// Manage schemas
100    Schema {
101        /// Subcommands for schemas
102        #[command(subcommand)]
103        command: schema::SchemaCommands,
104    },
105
106    /// Authentication commands
107    Auth {
108        /// Subcommands for authentication
109        #[command(subcommand)]
110        command: auth::AuthCommands,
111    },
112
113    /// Interactive browser-based login
114    Login {
115        /// The OIDC issuer URL.
116        #[arg(long, default_value = "http://localhost:5556/dex")]
117        issuer: String,
118        /// The OIDC client ID.
119        #[arg(long, default_value = "stormchaser-cli")]
120        client_id: String,
121    },
122}
123
124/// Executes the specified CLI command.
125///
126/// This function sets up the HTTP client and dispatches the command logic
127/// to the appropriate handler based on the parsed CLI options.
128pub 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}