jirust_cli/
lib.rs

1//! JiRust CLI library entry points for parsing arguments and dispatching Jira commands.
2//! This crate wires the CLI parsing to the executor layer and exposes helpers used by
3//! both the binary and the WebAssembly target.
4#[macro_use]
5extern crate prettytable;
6
7use crate::args::commands::Commands;
8use crate::executors::jira_commands_executors::jira_version_executor::VersionExecutor;
9
10use config::config_file::ConfigFile;
11use executors::config_executor::ConfigExecutor;
12use executors::jira_commands_executors::ExecJiraCommand;
13use executors::jira_commands_executors::jira_issue_executor::IssueExecutor;
14use executors::jira_commands_executors::jira_issue_link_executor::LinkIssueExecutor;
15use executors::jira_commands_executors::jira_issue_transition_executor::IssueTransitionExecutor;
16use executors::jira_commands_executors::jira_project_executor::ProjectExecutor;
17use std::io::{Error, ErrorKind};
18use utils::PrintableData;
19
20#[cfg(target_family = "wasm")]
21use args::commands::JirustCliArgs;
22#[cfg(target_family = "wasm")]
23use clap::Parser;
24#[cfg(target_family = "wasm")]
25use wasm_bindgen::{JsValue, prelude::wasm_bindgen};
26#[cfg(target_family = "wasm")]
27use wasm_bindgen_futures::js_sys;
28
29/// Command-line argument parsers and command enums.
30pub mod args;
31/// Configuration helpers used to read and write local settings.
32pub mod config;
33/// Executors responsible for invoking Jira operations.
34pub mod executors;
35/// Runners that implement the actual Jira calls.
36pub mod runners;
37/// Shared utilities for printing and data handling.
38pub mod utils;
39
40#[cfg(test)]
41pub mod tests;
42
43/// Manages the loading of the CLI configuration
44///
45/// # Arguments
46/// * `config_file_path` - The path to the configuration file
47/// * `args` - The arguments passed to the CLI
48///
49/// # Returns
50/// * A tuple containing the configuration file and the command to execute
51///
52/// # Errors
53/// * If the configuration file is not found
54/// * If the configuration file is missing mandatory fields
55///
56/// # Examples
57///
58/// ```no_run
59/// use jirust_cli::manage_config;
60/// use jirust_cli::config::config_file::ConfigFile;
61/// use jirust_cli::args::commands::Commands;
62///
63/// # fn main() -> Result<(), std::io::Error> {
64/// let config_file_path = String::from("config.json");
65/// let cfg_data = manage_config(config_file_path)?;
66/// # Ok(())
67/// # }
68/// ```
69pub fn manage_config(config_file_path: String) -> Result<ConfigFile, Error> {
70    let cfg_data = match ConfigFile::read_from_file(config_file_path.as_str()) {
71        Ok(cfg) => cfg,
72        Err(_) => {
73            return Err(Error::new(
74                ErrorKind::NotFound,
75                "Missing basic configuration, setup mandatory!",
76            ));
77        }
78    };
79    if cfg_data.get_auth_key().is_empty() || cfg_data.get_jira_url().is_empty() {
80        Err(Error::new(
81            ErrorKind::NotFound,
82            "Missing basic configuration, setup mandatory!",
83        ))
84    } else {
85        Ok(cfg_data)
86    }
87}
88
89/// Processes the command passed to the CLI
90///
91/// # Arguments
92/// * `command` - The command to execute
93/// * `config_file_path` - The path to the configuration file (optional, for Jira commands but mandatory to setup config)
94/// * `cfg_data` - The configuration file data
95///
96/// # Returns
97/// * A Result containing the result of the command execution
98///
99/// # Errors
100/// * If the command execution fails
101///
102/// # Examples
103///
104/// ```no_run
105/// use jirust_cli::process_command;
106/// use jirust_cli::config::config_file::ConfigFile;
107/// use jirust_cli::args::commands::{Commands, VersionArgs, VersionActionValues, PaginationArgs, OutputArgs};
108///
109/// # fn main() -> Result<(), std::io::Error> {
110/// let args = VersionArgs {
111///   version_act: VersionActionValues::List,
112///   project_key: "project_key".to_string(),
113///   project_id: None,
114///   version_id: Some("97531".to_string()),
115///   version_name: Some("version_name".to_string()),
116///   version_description: Some("version_description".to_string()),
117///   version_start_date: None,
118///   version_release_date: None,
119///   version_archived: None,
120///   version_released: Some(true),
121///   changelog_file: None,
122///   pagination: PaginationArgs { page_size: Some(20), page_offset: None },
123///   output: OutputArgs { output_format: None, output_type: None },
124///   transition_assignee: None,
125///   transition_issues: None,
126/// };
127///
128/// let result = process_command(Commands::Version(args), None, ConfigFile::default());
129/// # Ok(())
130/// # }
131/// ```
132pub async fn process_command(
133    command: Commands,
134    config_file_path: Option<String>,
135    cfg_data: ConfigFile,
136) -> Result<Vec<PrintableData>, Box<dyn std::error::Error>> {
137    match command {
138        Commands::Config(args) => match config_file_path {
139            Some(path) => {
140                let config_executor = ConfigExecutor::new(path, args.cfg_act);
141                config_executor.exec_config_command(cfg_data).await
142            }
143            None => Err(Box::new(Error::new(
144                ErrorKind::NotFound,
145                "Missing config file path!",
146            ))),
147        },
148        Commands::Version(args) => {
149            let version_executor = VersionExecutor::new(cfg_data, args.version_act, args);
150            version_executor.exec_jira_command().await
151        }
152        Commands::Project(args) => {
153            let project_executor = ProjectExecutor::new(cfg_data, args.project_act, args);
154            project_executor.exec_jira_command().await
155        }
156        Commands::Issue(args) => {
157            let issue_executor = IssueExecutor::new(cfg_data, args.issue_act, args);
158            issue_executor.exec_jira_command().await
159        }
160        Commands::Transition(args) => {
161            let issue_transition_executor =
162                IssueTransitionExecutor::new(cfg_data, args.transition_act, args);
163            issue_transition_executor.exec_jira_command().await
164        }
165        Commands::Link(args) => {
166            let link_issue_executor = LinkIssueExecutor::new(cfg_data, args.link_act, args);
167            link_issue_executor.exec_jira_command().await
168        }
169    }
170}
171
172#[cfg(target_family = "wasm")]
173pub fn set_panic_hook() {
174    #[cfg(feature = "console_error_panic_hook")]
175    console_error_panic_hook::set_once();
176}
177
178#[cfg(target_family = "wasm")]
179#[wasm_bindgen]
180pub async fn run(js_args: js_sys::Array, js_cfg: JsValue) -> JsValue {
181    set_panic_hook();
182
183    // Initialize the command-line arguments vector
184    // The first argument is the program name in CLI, so to correctly manage the CLI arguments with `clap` crate we must add it to the head of the vector
185    let mut args: Vec<String> = vec!["jirust-cli".to_string()];
186
187    // Then we add the arguments from the JavaScript array to the args vector
188    args.append(&mut js_args.iter().filter_map(|el| el.as_string()).collect());
189
190    let opts = match JirustCliArgs::try_parse_from(args) {
191        Ok(opts) => opts,
192        Err(err) => {
193            let err_s = format!("Error: {err}");
194            return serde_wasm_bindgen::to_value(&err_s).unwrap_or(JsValue::NULL);
195        }
196    };
197
198    let cfg_data: ConfigFile = serde_wasm_bindgen::from_value(js_cfg).expect("Config must be set!");
199
200    let result = process_command(opts.subcmd, None, cfg_data).await;
201
202    match result {
203        Ok(data) => serde_wasm_bindgen::to_value(&data).unwrap_or(JsValue::NULL),
204        Err(err) => {
205            let err_s = format!("Error: {err}");
206            serde_wasm_bindgen::to_value(&err_s).unwrap_or(JsValue::NULL)
207        }
208    }
209}