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) = ¶ms.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}