diff --git a/src/cli.rs b/src/cli.rs index 0cdfb23..6f05966 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -1,10 +1,82 @@ -use clap::Command; - -pub fn get_cli_matches() -> clap::ArgMatches { - Command::new("glint") - .version("0.1.0") - .author("John Freeman") - .about("Send HTTP requests based on a request chain defined in TOML") - .allow_external_subcommands(true) - .get_matches() +use clap::{Args, Parser, Subcommand}; + +/// A CLI application for managing and executing HTTP requests or collections. +#[derive(Parser)] +#[command(version, about, long_about = None)] +pub struct Cli { + /// The primary command to execute. + #[command(subcommand)] + pub command: Commands, +} + +/// Top-level commands for the CLI. +#[derive(Subcommand)] +pub enum Commands { + /// Work with request collections (grouped sets of HTTP requests). + Collections { + /// Subcommands related to request collections. + #[command(subcommand)] + command: CollectionCommands, + }, + /// Work with individual HTTP requests. + Requests { + /// Subcommands related to individual HTTP requests. + #[command(subcommand)] + command: RequestCommands, + }, +} + +/// Subcommands for managing and executing request collections. +#[derive(Subcommand)] +pub enum CollectionCommands { + /// Execute an entire collection of requests. + Run { + /// The collection file containing the requests to execute. + collection: String, + + /// Additional options for controlling output behavior. + #[command(flatten)] + output_options: OutputOptions, + }, +} + +/// Additional output options for fine-tuning the CLI's behavior. +#[derive(Args, Clone)] +pub struct OutputOptions { + /// Displays the HTTP headers in the output (disabled by default). + #[arg(short = 'h', long, default_value_t = false)] + pub show_headers: bool, + + /// Suppresses the HTTP response status (enabled by default). + #[arg(short = 's', long, default_value_t = false)] + pub hide_status: bool, + + /// Suppresses the HTTP response body (enabled by default). + #[arg(short = 'b', long, default_value_t = false)] + pub hide_body: bool, + + /// Disables pretty-printing for the HTTP response (enabled by default). + #[arg(short = 'r', long, default_value_t = false)] + pub raw_output: bool, + + /// Disables pre-output masking (enabled by default). + #[arg(short = 'm', long, default_value_t = false)] + pub disable_masking: bool, +} + +/// Subcommands for managing and executing individual requests. +#[derive(Subcommand)] +pub enum RequestCommands { + /// Execute a specific request within a collection. + Run { + /// The collection file containing the requests. + collection: String, + + /// The specific request to execute within the collection. + request: String, + + /// Additional options for controlling output behavior. + #[command(flatten)] + output_options: OutputOptions, + }, } diff --git a/src/executor.rs b/src/executor.rs index ce77fe6..52715ce 100644 --- a/src/executor.rs +++ b/src/executor.rs @@ -1,5 +1,5 @@ +use crate::cli::OutputOptions; use crate::masking::mask_json; -use crate::options::Options; use crate::request::{Dependencies, Dependency, Request, RequestBody}; use crate::resolvers::env_var_resolver::EnvVarResolver; use crate::resolvers::one_password_resolver::{OnePasswordResolver, OnePasswordResolverError}; @@ -52,7 +52,6 @@ static ENV_FILES_CACHE: Lazy>>> = #[derive(Debug)] pub struct Executor { requests: HashMap, - options: Options, http: Client, env_var_resolver: EnvVarResolver, prompt_resolver: PromptResolver, @@ -61,13 +60,12 @@ pub struct Executor { } impl Executor { - pub fn new(requests: Vec, options: Options) -> Self { + pub fn new(requests: Vec) -> Self { Self { requests: requests .into_iter() .map(|request| (request.name.clone(), request)) .collect(), - options, http: Client::new(), env_var_resolver: EnvVarResolver::new(), prompt_resolver: PromptResolver::new(), @@ -76,32 +74,17 @@ impl Executor { } } - pub async fn execute(&mut self) -> Result<(), Box> { - match &self.options.request { - Some(request_name) => { - let request = self - .requests - .get(request_name) - .ok_or(ExecutionError::RequestNotFound { - request: request_name.to_owned(), - })? - .clone(); - - let response = self.execute_request(request.clone()).await?; - - self.render_output(response).await?; - } - None => { - let cloned_requests: Vec<_> = self.requests.values().cloned().collect(); - - for request in cloned_requests { - let response = self.execute_request(request.clone()).await?; - - self.render_output(response).await?; - } - } - } - Ok(()) + pub async fn execute_request_named( + &mut self, + request: String, + ) -> Result { + let request = self + .requests + .get(&request) + .cloned() + .ok_or_else(|| ExecutionError::RequestNotFound { request })?; + + self.execute_request(request).await } pub async fn execute_request(&mut self, request: Request) -> Result { @@ -220,9 +203,13 @@ impl Executor { Ok(response) } - async fn render_output(&mut self, response: Response) -> Result<(), ExecutionError> { - if !self.options.hide_status { - if self.options.raw_output { + pub async fn render_output( + &mut self, + response: Response, + options: OutputOptions, + ) -> Result<(), ExecutionError> { + if !options.hide_status { + if options.raw_output { println!( "{}: {}", response.status.as_str(), @@ -249,10 +236,10 @@ impl Executor { } } - if self.options.show_headers { + if options.show_headers { let mut headers = response.headers.clone(); - if !self.options.disable_masking { + if !options.disable_masking { for (_key, value) in &mut headers { if let Some(value_str) = value.to_str().ok() { let masked_value = mask_json( @@ -266,7 +253,7 @@ impl Executor { } } - if self.options.raw_output { + if options.raw_output { for (key, value) in headers { println!( "{}: {}", @@ -290,16 +277,16 @@ impl Executor { } } - if !self.options.hide_body { + if !options.hide_body { let mut body = serde_json::from_str::(&response.text) .map_err(|error| ExecutionError::Unknown(error.to_string()))?; - if !self.options.disable_masking { + if !options.disable_masking { body = mask_json(body, &response.request.masking_rules) .map_err(|error| ExecutionError::Unknown(error.to_string()))?; } - if self.options.raw_output { + if options.raw_output { println!( "{}", serde_json::to_string(&body) diff --git a/src/main.rs b/src/main.rs index 061e9c2..24c2ae8 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,24 +1,57 @@ +mod cli; mod executor; mod logging; mod masking; -mod options; mod request; mod resolvers; mod response; + use clap::Parser; +use cli::{Cli, CollectionCommands, Commands, RequestCommands}; use executor::Executor; use logging::init_logging; -use options::Options; #[tokio::main] async fn main() -> Result<(), Box> { let guard = init_logging()?; - let options = Options::parse(); + match Cli::parse().command { + Commands::Collections { command } => match command { + CollectionCommands::Run { + collection, + output_options, + } => { + let requests = request::load_requests_from_toml(collection.as_str())?; + + let cloned_requests: Vec<_> = requests.clone(); + + let mut executor = Executor::new(requests); + + for request in cloned_requests { + let response = executor.execute_request(request.clone()).await?; + + executor + .render_output(response, output_options.clone()) + .await?; + } + } + }, + Commands::Requests { command } => match command { + RequestCommands::Run { + collection, + request, + output_options, + } => { + let requests = request::load_requests_from_toml(collection.as_str())?; + + let mut executor = Executor::new(requests); - let requests = request::load_requests_from_toml(options.collection.as_str())?; + let response = executor.execute_request_named(request).await?; - Executor::new(requests, options).execute().await?; + executor.render_output(response, output_options).await?; + } + }, + } drop(guard); diff --git a/src/options.rs b/src/options.rs deleted file mode 100644 index b532765..0000000 --- a/src/options.rs +++ /dev/null @@ -1,31 +0,0 @@ -use clap::Parser; - -#[derive(Debug, Parser)] -#[command(version, about, long_about = None)] -pub struct Options { - /// The collection file to use - pub collection: String, - - /// The specific request to execute within the collection (optional) - pub request: Option, - - /// Displays the HTTP headers in the output (disabled by default) - #[arg(short = 'h', long, default_value_t = false)] - pub show_headers: bool, - - /// Suppresses the HTTP response status (enabled by default) - #[arg(short = 's', long, default_value_t = false)] - pub hide_status: bool, - - /// Suppresses the HTTP response body (enabled by default) - #[arg(short = 'b', long, default_value_t = false)] - pub hide_body: bool, - - /// Disables pretty-printing for the HTTP response (enabled by default) - #[arg(short = 'r', long, default_value_t = false)] - pub raw_output: bool, - - /// Disables pre-output masking (enabled by default) - #[arg(short = 'm', long, default_value_t = false)] - pub disable_masking: bool, -}