jirust_cli/runners/jira_cmd_runners/
link_issue_cmd_runner.rs

1use jira_v3_openapi::apis::Error;
2use jira_v3_openapi::apis::configuration::Configuration;
3use jira_v3_openapi::apis::issue_links_api::link_issues;
4use jira_v3_openapi::models::{IssueLinkType, LinkIssueRequestJsonBean, LinkedIssue};
5use serde_json::Value;
6
7use crate::args::commands::LinkIssueArgs;
8use crate::config::config_file::{AuthData, ConfigFile};
9use crate::utils::changelog_extractor::ChangelogExtractor;
10
11/// Link issue command runner
12/// This struct is responsible for running the link issue command
13/// It uses the Jira API to perform the operations
14///
15/// # Fields
16///
17/// * `cfg` - Configuration object
18pub struct LinkIssueCmdRunner {
19    /// Configuration object
20    cfg: Configuration,
21}
22
23/// Implementation of IssueCmdRunner
24///
25/// # Methods
26///
27/// * `new` - Creates a new instance of LinkIssueCmdRunner
28/// * `link_jira_issues` - Links Jira issues
29impl LinkIssueCmdRunner {
30    /// Creates a new instance of LinkIssueCmdRunner
31    ///
32    /// # Arguments
33    ///
34    /// * `cfg_file` - Configuration file
35    ///
36    /// # Returns
37    ///
38    /// * `LinkIssueCmdRunner` - Instance of LinkIssueCmdRunner
39    ///
40    /// # Examples
41    ///
42    /// ```
43    /// use jirust_cli::config::config_file::ConfigFile;
44    /// use jirust_cli::runners::jira_cmd_runners::link_issue_cmd_runner::LinkIssueCmdRunner;
45    /// use toml::Table;
46    ///
47    /// let cfg_file = ConfigFile::new("dXNlcm5hbWU6YXBpX2tleQ==".to_string(), "jira_url".to_string(), "standard_resolution".to_string(), "standard_resolution_comment".to_string(), Table::new());
48    ///
49    /// let link_issue_cmd_runner = LinkIssueCmdRunner::new(cfg_file);
50    /// ```
51    pub fn new(cfg_file: ConfigFile) -> LinkIssueCmdRunner {
52        let mut config = Configuration::new();
53        let auth_data = AuthData::from_base64(cfg_file.get_auth_key());
54        config.base_path = cfg_file.get_jira_url().to_string();
55        config.basic_auth = Some((auth_data.0, Some(auth_data.1)));
56        LinkIssueCmdRunner { cfg: config }
57    }
58
59    /// Links Jira issues
60    ///
61    /// # Arguments
62    ///
63    /// * `params` - LinkIssueCmdParams struct
64    ///
65    /// # Returns
66    ///
67    /// * `Result<Value, Box<dyn std::error::Error>>` - Result of the operation
68    ///
69    /// # Examples
70    ///
71    /// ```no_run
72    /// use jirust_cli::runners::jira_cmd_runners::link_issue_cmd_runner::{LinkIssueCmdRunner, LinkIssueCmdParams};
73    /// use jirust_cli::config::config_file::ConfigFile;
74    /// use toml::Table;
75    /// # use std::error::Error;
76    ///
77    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
78    /// # tokio_test::block_on(async {
79    /// let cfg_file = ConfigFile::new("dXNlcm5hbWU6YXBpX2tleQ==".to_string(), "jira_url".to_string(), "standard_resolution".to_string(), "standard_resolution_comment".to_string(), Table::new());
80    /// let link_issue_cmd_runner = LinkIssueCmdRunner::new(cfg_file);
81    /// let mut params = LinkIssueCmdParams::new();
82    /// params.origin_issue_key = "ISSUE-1".to_string();
83    /// params.destination_issue_key = Some("ISSUE-2".to_string());
84    /// params.link_type = "Blocks".to_string();
85    ///
86    /// let result = link_issue_cmd_runner.link_jira_issues(params).await?;
87    /// # Ok(())
88    /// # })
89    /// # }
90    /// ```
91    pub async fn link_jira_issues(
92        &self,
93        params: LinkIssueCmdParams,
94    ) -> Result<Value, Box<dyn std::error::Error>> {
95        let mut link_requests: Vec<LinkIssueRequestJsonBean> = Vec::new();
96        if params.destination_issue_key.is_some() {
97            let link_request = LinkIssueRequestJsonBean {
98                comment: None,
99                inward_issue: Box::new(LinkedIssue {
100                    key: Some(params.origin_issue_key),
101                    id: None,
102                    fields: None,
103                    param_self: None,
104                }),
105                outward_issue: Box::new(LinkedIssue {
106                    key: params.destination_issue_key,
107                    id: None,
108                    fields: None,
109                    param_self: None,
110                }),
111                r#type: Box::new(IssueLinkType {
112                    name: Some(params.link_type),
113                    inward: None,
114                    outward: None,
115                    id: None,
116                    param_self: None,
117                }),
118            };
119            link_requests.push(link_request);
120        } else {
121            let changelog_extractor = ChangelogExtractor::new(params.changelog_file.unwrap());
122            let version_data: Option<String> = Some(
123                changelog_extractor
124                    .extract_version_changelog()
125                    .unwrap_or_default(),
126            );
127            if version_data.is_some() {
128                let issues = changelog_extractor
129                    .extract_issues_from_changelog(
130                        version_data.clone().unwrap(),
131                        params.project_key.clone().expect("Project key is required"),
132                    )
133                    .unwrap_or_default();
134                link_requests = issues
135                    .iter()
136                    .map(|issue| LinkIssueRequestJsonBean {
137                        comment: None,
138                        inward_issue: Box::new(LinkedIssue {
139                            key: Some(params.origin_issue_key.clone()),
140                            id: None,
141                            fields: None,
142                            param_self: None,
143                        }),
144                        outward_issue: Box::new(LinkedIssue {
145                            key: Some(issue.clone()),
146                            id: None,
147                            fields: None,
148                            param_self: None,
149                        }),
150                        r#type: Box::new(IssueLinkType {
151                            name: Some(params.link_type.clone()),
152                            inward: None,
153                            outward: None,
154                            id: None,
155                            param_self: None,
156                        }),
157                    })
158                    .collect();
159            }
160        };
161
162        let mut link_result: Value = Value::String("Linking OK".to_string());
163
164        for link_issue_request_json_bean in link_requests {
165            match link_issues(&self.cfg, link_issue_request_json_bean).await {
166                Ok(_) => {}
167                Err(Error::Serde(_)) => {}
168                Err(_) => {
169                    link_result = Value::String("Linking KO".to_string());
170                }
171            };
172        }
173        Ok(link_result)
174    }
175}
176
177/// Link issue command parameters
178///
179/// # Fields
180///
181/// * `project_key` - Jira project key
182/// * `origin_issue_key` - Jira origin issue key
183/// * `destination_issue_key` - Jira destination issue key
184/// * `link_type` - Jira link type
185/// * `changelog_file` - Changelog file
186pub struct LinkIssueCmdParams {
187    /// Jira project key
188    pub project_key: Option<String>,
189    /// Jira issue key
190    pub origin_issue_key: String,
191    /// Jira issue key
192    pub destination_issue_key: Option<String>,
193    /// Jira issue fields
194    pub link_type: String,
195    /// Jira issue transition
196    pub changelog_file: Option<String>,
197}
198
199/// Implementation of LinkIssueCmdParams struct
200///
201/// # Methods
202///
203/// * `new` - Creates a new LinkIssueCmdParams instance
204impl LinkIssueCmdParams {
205    /// Creates a new LinkIssueCmdParams instance
206    ///
207    /// # Returns
208    ///
209    /// * `LinkIssueCmdParams` - Issue command parameters
210    ///
211    /// # Examples
212    ///
213    /// ```
214    /// use jirust_cli::runners::jira_cmd_runners::link_issue_cmd_runner::LinkIssueCmdParams;
215    ///
216    /// let params = LinkIssueCmdParams::new();
217    /// ```
218    pub fn new() -> LinkIssueCmdParams {
219        LinkIssueCmdParams {
220            project_key: None,
221            origin_issue_key: "".to_string(),
222            destination_issue_key: None,
223            link_type: "".to_string(),
224            changelog_file: None,
225        }
226    }
227}
228
229/// Implementation of From trait for LinkIssueCmdParams struct
230/// to convert LinkIssueArgs struct to LinkIssueCmdParams struct
231impl From<&LinkIssueArgs> for LinkIssueCmdParams {
232    /// Converts LinkIssueArgs struct to LinkIssueCmdParams struct
233    /// to create a new LinkIssueCmdParams instance
234    ///
235    /// # Arguments
236    ///
237    /// * `value` - LinkIssueArgs struct
238    ///
239    /// # Returns
240    ///
241    /// * `LinkIssueArgs` - Link issue command parameters
242    ///
243    /// # Examples
244    ///
245    /// ```
246    /// use jirust_cli::runners::jira_cmd_runners::link_issue_cmd_runner::LinkIssueCmdParams;
247    /// use jirust_cli::args::commands::{LinkIssueArgs, LinkIssueActionValues};
248    /// use std::collections::HashMap;
249    /// use serde_json::Value;
250    ///
251    /// let link_issue_args = LinkIssueArgs {
252    ///    link_act: LinkIssueActionValues::Create,
253    ///    project_key: Some("project_key".to_string()),
254    ///    origin_issue_key: "origin_issue_key".to_string(),
255    ///    destination_issue_key: Some("destination_issue_key".to_string()),
256    ///    link_type: "link_type".to_string(),
257    ///    changelog_file: None,
258    /// };
259    ///
260    /// let params = LinkIssueCmdParams::from(&link_issue_args);
261    ///
262    /// assert_eq!(params.project_key, Some("project_key".to_string()));
263    /// assert_eq!(params.origin_issue_key, "origin_issue_key".to_string());
264    /// assert_eq!(params.destination_issue_key, Some("destination_issue_key".to_string()));
265    /// assert_eq!(params.link_type, "link_type".to_string());
266    /// assert_eq!(params.changelog_file, None);
267    ///
268    /// ```
269    fn from(value: &LinkIssueArgs) -> Self {
270        if (value.destination_issue_key.is_none() && value.changelog_file.is_none())
271            || (value.destination_issue_key.is_some() && value.changelog_file.is_some())
272        {
273            panic!("Either destination issue key or changelog file is required");
274        }
275        if value.changelog_file.is_some() && value.project_key.is_none() {
276            panic!("Project key is required when changelog file is provided");
277        }
278        LinkIssueCmdParams {
279            project_key: value.project_key.clone(),
280            origin_issue_key: value.origin_issue_key.clone(),
281            destination_issue_key: value.destination_issue_key.clone(),
282            link_type: value.link_type.clone(),
283            changelog_file: value.changelog_file.clone(),
284        }
285    }
286}
287
288/// Default implementation for IssueCmdParams struct
289impl Default for LinkIssueCmdParams {
290    /// Creates a default LinkIssueCmdParams instance
291    ///
292    /// # Returns
293    ///
294    /// A LinkIssueCmdParams instance with default values
295    ///
296    /// # Examples
297    ///
298    /// ```
299    /// use jirust_cli::runners::jira_cmd_runners::link_issue_cmd_runner::LinkIssueCmdParams;
300    ///
301    /// let params = LinkIssueCmdParams::default();
302    ///
303    /// assert_eq!(params.project_key, None);
304    /// assert_eq!(params.origin_issue_key, "".to_string());
305    /// assert_eq!(params.destination_issue_key, None);
306    /// assert_eq!(params.link_type, "".to_string());
307    /// assert_eq!(params.changelog_file, None);
308    /// ```
309    fn default() -> Self {
310        LinkIssueCmdParams::new()
311    }
312}