jirust_cli/runners/jira_cmd_runners/
issue_cmd_runner.rs

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