jirust_cli/runners/jira_cmd_runners/
issue_cmd_runner.rs

1use jira_v3_openapi::apis::issues_api::*;
2use jira_v3_openapi::models::user::AccountType;
3use jira_v3_openapi::models::{CreatedIssue, IssueBean, IssueTransition, Transitions, User};
4use jira_v3_openapi::{apis::configuration::Configuration, models::IssueUpdateDetails};
5use serde_json::Value;
6use std::collections::HashMap;
7use std::io::Error;
8
9use crate::args::commands::TransitionArgs;
10use crate::{
11    args::commands::IssueArgs,
12    config::config_file::{AuthData, ConfigFile},
13};
14
15/// Issue command runner
16/// This struct is responsible for running the issue command
17/// It uses the Jira API to perform the operations
18///
19/// # Fields
20///
21/// * `cfg` - Configuration object
22pub struct IssueCmdRunner {
23    /// Configuration object
24    cfg: Configuration,
25}
26
27/// Implementation of IssueCmdRunner
28///
29/// # Methods
30///
31/// * `new` - Creates a new instance of IssueCmdRunner
32/// * `assign_jira_issue` - Assigns a Jira issue to a user
33/// * `create_jira_issue` - Creates a Jira issue
34/// * `delete_jira_issue` - Deletes a Jira issue
35/// * `get_jira_issue` - Gets a Jira issue
36/// * `transition_jira_issue` - Transitions a Jira issue
37/// * `update_jira_issue` - Updates a Jira issue
38/// * `get_issue_available_transitions` - Gets available transitions for a Jira issue
39impl IssueCmdRunner {
40    /// Creates a new instance of IssueCmdRunner
41    ///
42    /// # Arguments
43    ///
44    /// * `cfg_file` - Configuration file
45    ///
46    /// # Returns
47    ///
48    /// * `IssueCmdRunner` - Instance of IssueCmdRunner
49    ///
50    /// # Examples
51    ///
52    /// ```
53    /// use jirust_cli::config::config_file::ConfigFile;
54    /// use jirust_cli::runners::jira_cmd_runners::issue_cmd_runner::IssueCmdRunner;
55    /// use toml::Table;
56    ///
57    /// let cfg_file = ConfigFile::new("dXNlcm5hbWU6YXBpX2tleQ==".to_string(), "jira_url".to_string(), "standard_resolution".to_string(), "standard_resolution_comment".to_string(), Table::new());
58    ///
59    /// let issue_cmd_runner = IssueCmdRunner::new(&cfg_file);
60    /// ```
61    pub fn new(cfg_file: &ConfigFile) -> IssueCmdRunner {
62        let mut config = Configuration::new();
63        let auth_data = AuthData::from_base64(cfg_file.get_auth_key());
64        config.base_path = cfg_file.get_jira_url().to_string();
65        config.basic_auth = Some((auth_data.0, Some(auth_data.1)));
66        IssueCmdRunner { cfg: config }
67    }
68
69    /// Assigns a Jira issue to a user
70    ///
71    /// # Arguments
72    ///
73    /// * `params` - Issue command parameters
74    ///
75    /// # Returns
76    ///
77    /// * `Value` - JSON value
78    ///
79    /// # Examples
80    ///
81    /// ```no_run
82    /// use jirust_cli::runners::jira_cmd_runners::issue_cmd_runner::{IssueCmdRunner, IssueCmdParams};
83    /// use jirust_cli::config::config_file::ConfigFile;
84    /// use toml::Table;
85    ///
86    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
87    /// # tokio_test::block_on(async {
88    /// let cfg_file = ConfigFile::new("dXNlcm5hbWU6YXBpX2tleQ==".to_string(), "jira_url".to_string(), "standard_resolution".to_string(), "standard_resolution_comment".to_string(), Table::new());
89    /// let issue_cmd_runner = IssueCmdRunner::new(&cfg_file);
90    /// let mut params = IssueCmdParams::new();
91    /// params.assignee = Some("assignee".to_string());
92    ///
93    /// let result = issue_cmd_runner.assign_jira_issue(params).await?;
94    /// # Ok(())
95    /// # })
96    /// # }
97    /// ```
98    pub async fn assign_jira_issue(
99        &self,
100        params: IssueCmdParams,
101    ) -> Result<Value, Box<dyn std::error::Error>> {
102        let user_data = User {
103            account_id: Some(params.assignee.expect("Assignee is required")),
104            account_type: Some(AccountType::Atlassian),
105            ..Default::default()
106        };
107
108        let i_key = if let Some(key) = &params.issue_key {
109            key.as_str()
110        } else {
111            return Err(Box::new(Error::other(
112                "Error assigning issue: Empty issue key".to_string(),
113            )));
114        };
115
116        Ok(assign_issue(&self.cfg, i_key, user_data).await?)
117    }
118
119    /// Creates a Jira issue
120    ///
121    /// # Arguments
122    ///
123    /// * `params` - Issue command parameters
124    ///
125    /// # Returns
126    ///
127    /// * `CreatedIssue` - Created issue
128    ///
129    /// # Examples
130    ///
131    /// ```no_run
132    /// use jirust_cli::runners::jira_cmd_runners::issue_cmd_runner::{IssueCmdRunner, IssueCmdParams};
133    /// use jirust_cli::config::config_file::ConfigFile;
134    /// use toml::Table;
135    ///
136    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
137    /// # tokio_test::block_on(async {
138    /// let cfg_file = ConfigFile::new("dXNlcm5hbWU6YXBpX2tleQ==".to_string(), "jira_url".to_string(), "standard_resolution".to_string(), "standard_resolution_comment".to_string(), Table::new());
139    /// let issue_cmd_runner = IssueCmdRunner::new(&cfg_file);
140    /// let params = IssueCmdParams::new();
141    ///
142    /// let result = issue_cmd_runner.create_jira_issue(params).await?;
143    /// # Ok(())
144    /// # })
145    /// # }
146    /// ```
147    pub async fn create_jira_issue(
148        &self,
149        params: IssueCmdParams,
150    ) -> Result<CreatedIssue, Box<dyn std::error::Error>> {
151        let mut issue_fields = params.issue_fields.unwrap_or_default();
152        issue_fields.insert(
153            "project".to_string(),
154            serde_json::json!({"key": params.project_key}),
155        );
156        let issue_data = IssueUpdateDetails {
157            fields: Some(issue_fields),
158            history_metadata: None,
159            properties: None,
160            transition: None,
161            update: None,
162        };
163        Ok(create_issue(&self.cfg, issue_data, None).await?)
164    }
165
166    /// Deletes a Jira issue
167    ///
168    /// # Arguments
169    ///
170    /// * `params` - Issue command parameters
171    ///
172    /// # Returns
173    ///
174    /// * `()` - Empty tuple
175    ///
176    /// # Examples
177    ///
178    /// ```no_run
179    /// use jirust_cli::runners::jira_cmd_runners::issue_cmd_runner::{IssueCmdRunner, IssueCmdParams};
180    /// use jirust_cli::config::config_file::ConfigFile;
181    /// use toml::Table;
182    ///
183    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
184    /// # tokio_test::block_on(async {
185    /// let cfg_file = ConfigFile::new("dXNlcm5hbWU6YXBpX2tleQ==".to_string(), "jira_url".to_string(), "standard_resolution".to_string(), "standard_resolution_comment".to_string(), Table::new());
186    /// let issue_cmd_runner = IssueCmdRunner::new(&cfg_file);
187    /// let mut params = IssueCmdParams::new();
188    /// params.issue_key = Some("issue_key".to_string());
189    ///
190    /// let result = issue_cmd_runner.delete_jira_issue(params).await?;
191    /// # Ok(())
192    /// # })
193    /// # }
194    /// ```
195    pub async fn delete_jira_issue(
196        &self,
197        params: IssueCmdParams,
198    ) -> Result<(), Box<dyn std::error::Error>> {
199        let i_key = if let Some(key) = &params.issue_key {
200            key.as_str()
201        } else {
202            return Err(Box::new(Error::other(
203                "Error deleting issue: Empty issue key".to_string(),
204            )));
205        };
206
207        Ok(delete_issue(&self.cfg, i_key, Some("true")).await?)
208    }
209
210    /// Gets a Jira issue
211    ///
212    /// # Arguments
213    ///
214    /// * `params` - Issue command parameters
215    ///
216    /// # Returns
217    ///
218    /// * `IssueBean` - Jira issue
219    ///
220    /// # Examples
221    ///
222    /// ```no_run
223    /// use jirust_cli::runners::jira_cmd_runners::issue_cmd_runner::{IssueCmdRunner, IssueCmdParams};
224    /// use jirust_cli::config::config_file::ConfigFile;
225    /// use toml::Table;
226    ///
227    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
228    /// # tokio_test::block_on(async {
229    /// let cfg_file = ConfigFile::new("dXNlcm5hbWU6YXBpX2tleQ==".to_string(), "jira_url".to_string(), "standard_resolution".to_string(), "standard_resolution_comment".to_string(), Table::new());
230    /// let issue_cmd_runner = IssueCmdRunner::new(&cfg_file);
231    /// let mut params = IssueCmdParams::new();
232    /// params.issue_key = Some("issue_key".to_string());
233    ///
234    /// let result = issue_cmd_runner.get_jira_issue(params).await?;
235    /// # Ok(())
236    /// # })
237    /// # }
238    /// ```
239    pub async fn get_jira_issue(
240        &self,
241        params: IssueCmdParams,
242    ) -> Result<IssueBean, Box<dyn std::error::Error>> {
243        let i_key = if let Some(key) = &params.issue_key {
244            key.as_str()
245        } else {
246            return Err(Box::new(Error::other(
247                "Error retrieving issue: Empty issue key".to_string(),
248            )));
249        };
250        Ok(get_issue(&self.cfg, i_key, None, None, None, None, None, None).await?)
251    }
252
253    /// Transitions a Jira issue
254    ///
255    /// # Arguments
256    ///
257    /// * `params` - Issue command parameters
258    ///
259    /// # Returns
260    ///
261    /// * `Value` - Jira issue transition
262    ///
263    /// # Examples
264    ///
265    /// ```no_run
266    /// use jirust_cli::runners::jira_cmd_runners::issue_cmd_runner::{IssueCmdRunner, IssueCmdParams};
267    /// use jirust_cli::config::config_file::ConfigFile;
268    /// use toml::Table;
269    ///
270    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
271    /// # tokio_test::block_on(async {
272    /// let cfg_file = ConfigFile::new("dXNlcm5hbWU6YXBpX2tleQ==".to_string(), "jira_url".to_string(), "standard_resolution".to_string(), "standard_resolution_comment".to_string(), Table::new());
273    /// let issue_cmd_runner = IssueCmdRunner::new(&cfg_file);
274    ///
275    /// let mut params = IssueCmdParams::new();
276    /// params.transition = Some("transition_id".to_string());
277    ///
278    /// let result = issue_cmd_runner.transition_jira_issue(params).await?;
279    /// # Ok(())
280    /// # })
281    /// # }
282    /// ```
283    pub async fn transition_jira_issue(
284        &self,
285        params: IssueCmdParams,
286    ) -> Result<Value, Box<dyn std::error::Error>> {
287        let i_key = if let Some(key) = &params.issue_key {
288            key.as_str()
289        } else {
290            return Err(Box::new(Error::other(
291                "Error with issue transition: Empty issue key".to_string(),
292            )));
293        };
294
295        let trans = if let Some(transition) = &params.transition {
296            transition.as_str()
297        } else {
298            return Err(Box::new(Error::other(
299                "Error with issue transition: Empty transition".to_string(),
300            )));
301        };
302
303        let transition = IssueTransition {
304            id: Some(trans.to_string()),
305            ..Default::default()
306        };
307        let issue_data = IssueUpdateDetails {
308            fields: params.issue_fields,
309            history_metadata: None,
310            properties: None,
311            transition: Some(transition),
312            update: None,
313        };
314        Ok(do_transition(&self.cfg, i_key, issue_data).await?)
315    }
316
317    /// Updates a Jira issue
318    ///
319    /// # Arguments
320    ///
321    /// * `params` - Issue command parameters
322    ///
323    /// # Returns
324    ///
325    /// * `Value` - Jira issue update
326    ///
327    /// # Examples
328    ///
329    /// ```no_run
330    /// use jirust_cli::runners::jira_cmd_runners::issue_cmd_runner::{IssueCmdRunner, IssueCmdParams};
331    /// use jirust_cli::config::config_file::ConfigFile;
332    /// use toml::Table;
333    ///
334    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
335    /// # tokio_test::block_on(async {
336    /// let cfg_file = ConfigFile::new("dXNlcm5hbWU6YXBpX2tleQ==".to_string(), "jira_url".to_string(), "standard_resolution".to_string(), "standard_resolution_comment".to_string(), Table::new());
337    /// let issue_cmd_runner = IssueCmdRunner::new(&cfg_file);
338    /// let params = IssueCmdParams::new();
339    ///
340    /// let result = issue_cmd_runner.update_jira_issue(params).await?;
341    /// # Ok(())
342    /// # })
343    /// # }
344    /// ```
345    pub async fn update_jira_issue(
346        &self,
347        params: IssueCmdParams,
348    ) -> Result<Value, Box<dyn std::error::Error>> {
349        let i_key = if let Some(key) = &params.issue_key {
350            key.as_str()
351        } else {
352            return Err(Box::new(Error::other(
353                "Error updating issue: Empty issue key".to_string(),
354            )));
355        };
356
357        let issue_data = IssueUpdateDetails {
358            fields: params.issue_fields,
359            history_metadata: None,
360            properties: None,
361            transition: None,
362            update: None,
363        };
364        Ok(edit_issue(
365            &self.cfg,
366            i_key,
367            issue_data,
368            None,
369            None,
370            None,
371            Some(true),
372            None,
373        )
374        .await?)
375    }
376
377    /// Gets available transitions for a Jira issue
378    ///
379    /// # Arguments
380    ///
381    /// * `params` - Issue command parameters
382    ///
383    /// # Returns
384    ///
385    /// * `Transitions` - Jira issue transitions
386    ///
387    /// # Examples
388    ///
389    /// ```no_run
390    /// use jirust_cli::runners::jira_cmd_runners::issue_cmd_runner::{IssueCmdRunner, IssueTransitionCmdParams};
391    /// use jirust_cli::config::config_file::ConfigFile;
392    /// use toml::Table;
393    ///
394    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
395    /// # tokio_test::block_on(async {
396    /// let cfg_file = ConfigFile::new("dXNlcm5hbWU6YXBpX2tleQ==".to_string(), "jira_url".to_string(), "standard_resolution".to_string(), "standard_resolution_comment".to_string(), Table::new());
397    /// let issue_cmd_runner = IssueCmdRunner::new(&cfg_file);
398    /// let mut params = IssueTransitionCmdParams::new();
399    /// params.issue_key = "issue_key".to_string();
400    ///
401    /// let result = issue_cmd_runner.get_issue_available_transitions(params).await?;
402    /// # Ok(())
403    /// # })
404    /// # }
405    /// ```
406    pub async fn get_issue_available_transitions(
407        &self,
408        params: IssueTransitionCmdParams,
409    ) -> Result<Transitions, Box<dyn std::error::Error>> {
410        Ok(get_transitions(
411            &self.cfg,
412            &params.issue_key,
413            None,
414            None,
415            None,
416            Some(false),
417            None,
418        )
419        .await?)
420    }
421}
422
423/// Issue command parameters
424///
425/// # Fields
426///
427/// * `project_key` - Jira project key
428/// * `issue_key` - Jira issue key
429/// * `issue_fields` - Jira issue fields
430/// * `transition` - Jira issue transition
431/// * `assignee` - Jira issue assignee
432pub struct IssueCmdParams {
433    /// Jira project key
434    pub project_key: String,
435    /// Jira issue key
436    pub issue_key: Option<String>,
437    /// Jira issue fields
438    pub issue_fields: Option<HashMap<String, Value>>,
439    /// Jira issue transition
440    pub transition: Option<String>,
441    /// Jira issue assignee
442    pub assignee: Option<String>,
443}
444
445/// Implementation of IssueCmdParams struct
446///
447/// # Methods
448///
449/// * `new` - Creates a new IssueCmdParams instance
450impl IssueCmdParams {
451    /// Creates a new IssueCmdParams instance
452    ///
453    /// # Returns
454    ///
455    /// * `IssueCmdParams` - Issue command parameters
456    ///
457    /// # Examples
458    ///
459    /// ```
460    /// use jirust_cli::runners::jira_cmd_runners::issue_cmd_runner::IssueCmdParams;
461    ///
462    /// let params = IssueCmdParams::new();
463    /// ```
464    pub fn new() -> IssueCmdParams {
465        IssueCmdParams {
466            project_key: "".to_string(),
467            issue_key: None,
468            issue_fields: None,
469            transition: None,
470            assignee: None,
471        }
472    }
473}
474
475/// Implementation of From trait for IssueCmdParams struct
476/// to convert IssueArgs struct to IssueCmdParams struct
477impl From<&IssueArgs> for IssueCmdParams {
478    /// Converts IssueArgs struct to IssueCmdParams struct
479    /// to create a new IssueCmdParams instance
480    ///
481    /// # Arguments
482    ///
483    /// * `value` - IssueArgs struct
484    ///
485    /// # Returns
486    ///
487    /// * `IssueCmdParams` - Issue command parameters
488    ///
489    /// # Examples
490    ///
491    /// ```
492    /// use jirust_cli::runners::jira_cmd_runners::issue_cmd_runner::IssueCmdParams;
493    /// use jirust_cli::args::commands::{IssueArgs, PaginationArgs, OutputArgs, IssueActionValues};
494    /// use std::collections::HashMap;
495    /// use serde_json::Value;
496    ///
497    /// let issue_args = IssueArgs {
498    ///    issue_act: IssueActionValues::Get,
499    ///    project_key: "project_key".to_string(),
500    ///    issue_key: Some("issue_key".to_string()),
501    ///    issue_fields: Some(vec![("key".to_string(), r#"{ "key": "value" }"#.to_string())]),
502    ///    transition_to: Some("transition_to".to_string()),
503    ///    assignee: Some("assignee".to_string()),
504    ///    pagination: PaginationArgs { page_size: Some(20), page_offset: None },
505    ///    output: OutputArgs { output_format: None, output_type: None },
506    /// };
507    ///
508    /// let params = IssueCmdParams::from(&issue_args);
509    ///
510    /// assert_eq!(params.project_key, "project_key".to_string());
511    /// assert_eq!(params.issue_key.unwrap(), "issue_key".to_string());
512    /// assert_eq!(params.transition.unwrap(), "transition_to".to_string());
513    /// assert_eq!(params.assignee.unwrap(), "assignee".to_string());
514    /// ```
515    fn from(value: &IssueArgs) -> Self {
516        IssueCmdParams {
517            project_key: value.project_key.clone(),
518            issue_key: value.issue_key.clone(),
519            issue_fields: Some(
520                value
521                    .issue_fields
522                    .clone()
523                    .unwrap_or_default()
524                    .iter()
525                    .map(|elem| {
526                        (
527                            elem.0.clone(),
528                            serde_json::from_str(elem.1.clone().as_str()).unwrap_or(Value::Null),
529                        )
530                    })
531                    .collect::<HashMap<_, _>>(),
532            ),
533            transition: value.transition_to.clone(),
534            assignee: value.assignee.clone(),
535        }
536    }
537}
538
539/// Issue transition command parameters
540///
541/// # Fields
542///
543/// * `issue_key` - Jira issue key
544pub struct IssueTransitionCmdParams {
545    /// Jira issue key
546    pub issue_key: String,
547}
548
549/// Implementation of IssueTransitionCmdParams struct
550///
551/// # Methods
552///
553/// * `new` - Creates a new IssueTransitionCmdParams instance
554impl IssueTransitionCmdParams {
555    /// Creates a new IssueTransitionCmdParams instance
556    ///
557    /// # Returns
558    ///
559    /// * `IssueTransitionCmdParams` - Issue transition command parameters
560    ///
561    /// # Examples
562    ///
563    /// ```
564    /// use jirust_cli::runners::jira_cmd_runners::issue_cmd_runner::IssueTransitionCmdParams;
565    ///
566    /// let params = IssueTransitionCmdParams::new();
567    /// ```
568    pub fn new() -> IssueTransitionCmdParams {
569        IssueTransitionCmdParams {
570            issue_key: "".to_string(),
571        }
572    }
573}
574
575/// Implementation of From trait for IssueTransitionCmdParams struct
576/// to convert TransitionArgs struct to IssueTransitionCmdParams struct
577impl From<&TransitionArgs> for IssueTransitionCmdParams {
578    /// Converts TransitionArgs struct to IssueTransitionCmdParams struct
579    /// to create a new IssueTransitionCmdParams instance
580    ///
581    /// # Arguments
582    ///
583    /// * `value` - TransitionArgs struct
584    ///
585    /// # Returns
586    ///
587    /// * `IssueTransitionCmdParams` - Issue transition command parameters
588    ///
589    /// # Examples
590    ///
591    /// ```
592    /// use jirust_cli::runners::jira_cmd_runners::issue_cmd_runner::IssueTransitionCmdParams;
593    /// use jirust_cli::args::commands::{TransitionArgs, TransitionActionValues, OutputArgs};
594    ///
595    /// let transition_args = TransitionArgs {
596    ///    transition_act: TransitionActionValues::List,
597    ///    issue_key: "issue_key".to_string(),
598    ///    output: OutputArgs { output_format: None, output_type: None },
599    /// };
600    ///
601    /// let params = IssueTransitionCmdParams::from(&transition_args);
602    ///
603    /// assert_eq!(params.issue_key, "issue_key".to_string());
604    /// ```
605    fn from(value: &TransitionArgs) -> Self {
606        IssueTransitionCmdParams {
607            issue_key: value.issue_key.clone(),
608        }
609    }
610}
611
612/// Default implementation for IssueCmdParams struct
613impl Default for IssueTransitionCmdParams {
614    /// Creates a default IssueTransitionCmdParams instance
615    ///
616    /// # Returns
617    ///
618    /// A IssueTransitionCmdParams instance with default values
619    ///
620    /// # Examples
621    ///
622    /// ```
623    /// use jirust_cli::runners::jira_cmd_runners::issue_cmd_runner::IssueTransitionCmdParams;
624    ///
625    /// let params = IssueTransitionCmdParams::default();
626    ///
627    /// assert_eq!(params.issue_key, "".to_string());
628    /// ```
629    fn default() -> Self {
630        IssueTransitionCmdParams::new()
631    }
632}
633
634/// Default implementation for IssueCmdParams struct
635impl Default for IssueCmdParams {
636    /// Creates a default IssueCmdParams instance
637    ///
638    /// # Returns
639    ///
640    /// A IssueCmdParams instance with default values
641    ///
642    /// # Examples
643    ///
644    /// ```
645    /// use jirust_cli::runners::jira_cmd_runners::issue_cmd_runner::IssueCmdParams;
646    ///
647    /// let params = IssueCmdParams::default();
648    ///
649    /// assert_eq!(params.project_key, "".to_string());
650    /// assert_eq!(params.issue_key, None);
651    /// assert_eq!(params.issue_fields, None);
652    /// assert_eq!(params.transition, None);
653    /// assert_eq!(params.assignee, None);
654    /// ```
655    fn default() -> Self {
656        IssueCmdParams::new()
657    }
658}