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 p_key = if let Some(key) = &params.project_key {
122                key
123            } else {
124                return Err(Box::new(std::io::Error::other(
125                    "Error linking issues: Empty project key".to_string(),
126                )));
127            };
128            let changelog_extractor = ChangelogExtractor::new(params.changelog_file.unwrap());
129            let version_data: Option<String> = Some(
130                changelog_extractor
131                    .extract_version_changelog()
132                    .unwrap_or_default(),
133            );
134            if let Some(version_data) = version_data {
135                let issues = changelog_extractor
136                    .extract_issues_from_changelog(&version_data, p_key)
137                    .unwrap_or_default();
138                link_requests = issues
139                    .iter()
140                    .map(|issue| LinkIssueRequestJsonBean {
141                        comment: None,
142                        inward_issue: Box::new(LinkedIssue {
143                            key: Some(params.origin_issue_key.clone()),
144                            id: None,
145                            fields: None,
146                            param_self: None,
147                        }),
148                        outward_issue: Box::new(LinkedIssue {
149                            key: Some(issue.clone()),
150                            id: None,
151                            fields: None,
152                            param_self: None,
153                        }),
154                        r#type: Box::new(IssueLinkType {
155                            name: Some(params.link_type.clone()),
156                            inward: None,
157                            outward: None,
158                            id: None,
159                            param_self: None,
160                        }),
161                    })
162                    .collect();
163            } else {
164                return Err(Box::new(std::io::Error::other(
165                    "Error linking issues: No destination issue key found in changelog".to_string(),
166                )));
167            }
168        };
169
170        let mut link_result: Value = Value::String("Linking OK".to_string());
171
172        for link_issue_request_json_bean in link_requests {
173            match link_issues(&self.cfg, link_issue_request_json_bean).await {
174                Ok(_) => {}
175                Err(Error::Serde(_)) => {}
176                Err(_) => {
177                    link_result = Value::String("Linking KO".to_string());
178                }
179            };
180        }
181        Ok(link_result)
182    }
183}
184
185/// Link issue command parameters
186///
187/// # Fields
188///
189/// * `project_key` - Jira project key
190/// * `origin_issue_key` - Jira origin issue key
191/// * `destination_issue_key` - Jira destination issue key
192/// * `link_type` - Jira link type
193/// * `changelog_file` - Changelog file
194pub struct LinkIssueCmdParams {
195    /// Jira project key
196    pub project_key: Option<String>,
197    /// Jira issue key
198    pub origin_issue_key: String,
199    /// Jira issue key
200    pub destination_issue_key: Option<String>,
201    /// Jira issue fields
202    pub link_type: String,
203    /// Jira issue transition
204    pub changelog_file: Option<String>,
205}
206
207/// Implementation of LinkIssueCmdParams struct
208///
209/// # Methods
210///
211/// * `new` - Creates a new LinkIssueCmdParams instance
212impl LinkIssueCmdParams {
213    /// Creates a new LinkIssueCmdParams instance
214    ///
215    /// # Returns
216    ///
217    /// * `LinkIssueCmdParams` - Issue command parameters
218    ///
219    /// # Examples
220    ///
221    /// ```
222    /// use jirust_cli::runners::jira_cmd_runners::link_issue_cmd_runner::LinkIssueCmdParams;
223    ///
224    /// let params = LinkIssueCmdParams::new();
225    /// ```
226    pub fn new() -> LinkIssueCmdParams {
227        LinkIssueCmdParams {
228            project_key: None,
229            origin_issue_key: "".to_string(),
230            destination_issue_key: None,
231            link_type: "".to_string(),
232            changelog_file: None,
233        }
234    }
235}
236
237/// Implementation of From trait for LinkIssueCmdParams struct
238/// to convert LinkIssueArgs struct to LinkIssueCmdParams struct
239impl From<&LinkIssueArgs> for LinkIssueCmdParams {
240    /// Converts LinkIssueArgs struct to LinkIssueCmdParams struct
241    /// to create a new LinkIssueCmdParams instance
242    ///
243    /// # Arguments
244    ///
245    /// * `value` - LinkIssueArgs struct
246    ///
247    /// # Returns
248    ///
249    /// * `LinkIssueArgs` - Link issue command parameters
250    ///
251    /// # Examples
252    ///
253    /// ```
254    /// use jirust_cli::runners::jira_cmd_runners::link_issue_cmd_runner::LinkIssueCmdParams;
255    /// use jirust_cli::args::commands::{LinkIssueArgs, LinkIssueActionValues};
256    /// use std::collections::HashMap;
257    /// use serde_json::Value;
258    ///
259    /// let link_issue_args = LinkIssueArgs {
260    ///    link_act: LinkIssueActionValues::Create,
261    ///    project_key: Some("project_key".to_string()),
262    ///    origin_issue_key: "origin_issue_key".to_string(),
263    ///    destination_issue_key: Some("destination_issue_key".to_string()),
264    ///    link_type: "link_type".to_string(),
265    ///    changelog_file: None,
266    /// };
267    ///
268    /// let params = LinkIssueCmdParams::from(&link_issue_args);
269    ///
270    /// assert_eq!(params.project_key, Some("project_key".to_string()));
271    /// assert_eq!(params.origin_issue_key, "origin_issue_key".to_string());
272    /// assert_eq!(params.destination_issue_key, Some("destination_issue_key".to_string()));
273    /// assert_eq!(params.link_type, "link_type".to_string());
274    /// assert_eq!(params.changelog_file, None);
275    ///
276    /// ```
277    fn from(value: &LinkIssueArgs) -> Self {
278        if (value.destination_issue_key.is_none() && value.changelog_file.is_none())
279            || (value.destination_issue_key.is_some() && value.changelog_file.is_some())
280        {
281            panic!("Either destination issue key or changelog file is required");
282        }
283        if value.changelog_file.is_some() && value.project_key.is_none() {
284            panic!("Project key is required when changelog file is provided");
285        }
286        LinkIssueCmdParams {
287            project_key: value.project_key.clone(),
288            origin_issue_key: value.origin_issue_key.clone(),
289            destination_issue_key: value.destination_issue_key.clone(),
290            link_type: value.link_type.clone(),
291            changelog_file: value.changelog_file.clone(),
292        }
293    }
294}
295
296/// Default implementation for IssueCmdParams struct
297impl Default for LinkIssueCmdParams {
298    /// Creates a default LinkIssueCmdParams instance
299    ///
300    /// # Returns
301    ///
302    /// A LinkIssueCmdParams instance with default values
303    ///
304    /// # Examples
305    ///
306    /// ```
307    /// use jirust_cli::runners::jira_cmd_runners::link_issue_cmd_runner::LinkIssueCmdParams;
308    ///
309    /// let params = LinkIssueCmdParams::default();
310    ///
311    /// assert_eq!(params.project_key, None);
312    /// assert_eq!(params.origin_issue_key, "".to_string());
313    /// assert_eq!(params.destination_issue_key, None);
314    /// assert_eq!(params.link_type, "".to_string());
315    /// assert_eq!(params.changelog_file, None);
316    /// ```
317    fn default() -> Self {
318        LinkIssueCmdParams::new()
319    }
320}