jirust_cli/runners/jira_cmd_runners/
project_cmd_runner.rs

1use std::io::Error;
2
3use crate::args::commands::ProjectArgs;
4use crate::config::config_file::{AuthData, ConfigFile};
5use async_trait::async_trait;
6use jira_v3_openapi::apis::configuration::Configuration;
7use jira_v3_openapi::apis::issues_api::{
8    get_create_issue_meta_issue_type_id, get_create_issue_meta_issue_types,
9};
10use jira_v3_openapi::apis::projects_api::{create_project, search_projects};
11use jira_v3_openapi::models::create_project_details::{AssigneeType, ProjectTypeKey};
12use jira_v3_openapi::models::{CreateProjectDetails, ProjectIdentifiers};
13use jira_v3_openapi::models::{
14    FieldCreateMetadata, IssueTypeIssueCreateMetadata, project::Project,
15};
16
17#[cfg(test)]
18use mockall::automock;
19
20/// Project command runner struct.
21///
22/// This struct is responsible for holding the project commands parameters
23/// and it is used to pass the parameters to the project commands runner.
24pub struct ProjectCmdRunner {
25    cfg: Configuration,
26}
27
28/// Project command runner implementation.
29///
30/// # Methods
31///
32/// * `new` - Creates a new instance of the ProjectCmdRunner struct.
33/// * `list_jira_projects` - Lists Jira projects.
34/// * `get_jira_project_issue_types` - Gets Jira project issue types.
35/// * `get_jira_project_issue_type_id` - Gets Jira project issue fields by issue type ID.
36impl ProjectCmdRunner {
37    /// Creates a new instance of the ProjectCmdRunner struct.
38    ///
39    /// # Arguments
40    ///
41    /// * `cfg_file` - A ConfigFile struct.
42    ///
43    /// # Returns
44    ///
45    /// * A new instance of the ProjectCmdRunner struct.
46    ///
47    /// # Examples
48    ///
49    /// ```
50    /// use jirust_cli::config::config_file::ConfigFile;
51    /// use jirust_cli::runners::jira_cmd_runners::project_cmd_runner::ProjectCmdRunner;
52    /// use toml::Table;
53    ///
54    /// let cfg_file = ConfigFile::new("dXNlcm5hbWU6YXBpX2tleQ==".to_string(), "jira_url".to_string(), "standard_resolution".to_string(), "standard_resolution_comment".to_string(), Table::new());
55    ///
56    /// let project_cmd_runner = ProjectCmdRunner::new(&cfg_file);
57    /// ```
58    pub fn new(cfg_file: &ConfigFile) -> ProjectCmdRunner {
59        let mut config = Configuration::new();
60        let auth_data = AuthData::from_base64(cfg_file.get_auth_key());
61        config.base_path = cfg_file.get_jira_url().to_string();
62        config.basic_auth = Some((auth_data.0, Some(auth_data.1)));
63        ProjectCmdRunner { cfg: config }
64    }
65
66    /// Create a new Jira project using the provided parameters.
67    ///
68    /// # Arguments
69    /// * `params` - The parameters for creating the project.
70    ///
71    /// # Returns
72    /// A `Result` containing the project identifiers if successful, or an error if failed.
73    ///
74    /// # Examples
75    ///
76    /// ```no_run
77    /// use jirust_cli::runners::jira_cmd_runners::project_cmd_runner::{ProjectCmdRunner, ProjectCmdParams};
78    /// use jirust_cli::config::config_file::ConfigFile;
79    /// use toml::Table;
80    ///
81    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
82    /// # tokio_test::block_on(async {
83    /// let cfg_file = ConfigFile::new("auth_token".to_string(), "jira_url".to_string(), "standard_resolution".to_string(), "standard_resolution_comment".to_string(), Table::new());
84    /// let project_cmd_runner = ProjectCmdRunner::new(&cfg_file);
85    ///
86    /// let mut params = ProjectCmdParams::new();
87    /// params.project_key = Some("TEST".to_string());
88    /// params.project_name = Some("Test Project".to_string());
89    /// params.project_description = Some("This is a test project".to_string());
90    /// params.project_field_configuration_id = Some(12345);
91    /// params.project_issue_security_scheme_id = Some(67890);
92    /// params.project_issue_type_scheme_id = Some(54321);
93    ///
94    /// let projects = project_cmd_runner.create_jira_project(params).await?;
95    ///
96    /// # Ok(())
97    /// # })
98    /// # }
99    /// ```
100    pub async fn create_jira_project(
101        &self,
102        params: ProjectCmdParams,
103    ) -> Result<ProjectIdentifiers, Box<dyn std::error::Error>> {
104        let p_key = params
105            .project_key
106            .as_deref()
107            .ok_or_else(|| Box::new(Error::other("Error creating project: Empty project key")))?;
108        let p_name = params
109            .project_name
110            .as_deref()
111            .ok_or_else(|| Box::new(Error::other("Error creating project: Empty project name")))?;
112
113        let mut project_data = CreateProjectDetails::new(p_key.to_string(), p_name.to_string());
114        project_data.description = params.project_description;
115        project_data.field_configuration_scheme = params.project_field_configuration_id;
116        project_data.issue_security_scheme = params.project_issue_security_scheme_id;
117        project_data.issue_type_scheme = params.project_issue_type_scheme_id;
118        project_data.issue_type_screen_scheme = params.project_issue_type_screen_scheme_id;
119        project_data.notification_scheme = params.project_notification_scheme_id;
120        project_data.permission_scheme = params.project_permission_scheme_id;
121        project_data.workflow_scheme = params.project_workflow_scheme_id;
122        project_data.lead_account_id = params.project_lead_account_id;
123        project_data.assignee_type = if params
124            .project_assignee_type
125            .unwrap_or("unassigned".to_string())
126            == "lead"
127        {
128            Some(AssigneeType::ProjectLead)
129        } else {
130            Some(AssigneeType::Unassigned)
131        };
132        project_data.project_type_key = Some(ProjectTypeKey::Software);
133        Ok(create_project(&self.cfg, project_data).await?)
134    }
135
136    /// Lists Jira projects.
137    ///
138    /// # Arguments
139    ///
140    /// * `params` - A ProjectCmdParams struct.
141    ///
142    /// # Returns
143    ///
144    /// * A Result with a vector of Project structs or an error message.
145    ///
146    /// # Examples
147    ///
148    /// ```no_run
149    /// use jirust_cli::runners::jira_cmd_runners::project_cmd_runner::{ProjectCmdRunner, ProjectCmdParams};
150    /// use jirust_cli::config::config_file::ConfigFile;
151    /// use toml::Table;
152    ///
153    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
154    /// # tokio_test::block_on(async {
155    /// let cfg_file = ConfigFile::new("auth_token".to_string(), "jira_url".to_string(), "standard_resolution".to_string(), "standard_resolution_comment".to_string(), Table::new());
156    /// let project_cmd_runner = ProjectCmdRunner::new(&cfg_file);
157    /// let params = ProjectCmdParams::new();
158    ///
159    /// let projects = project_cmd_runner.list_jira_projects(params).await?;
160    /// # Ok(())
161    /// # })
162    /// # }
163    /// ```
164    pub async fn list_jira_projects(
165        &self,
166        params: ProjectCmdParams,
167    ) -> Result<Vec<Project>, Box<dyn std::error::Error>> {
168        let page_size = Some(params.projects_page_size.unwrap_or(10));
169        let page_offset = Some(i64::from(params.projects_page_offset.unwrap_or(0)));
170        match search_projects(
171            &self.cfg,
172            page_offset,
173            page_size,
174            None,
175            None,
176            None,
177            None,
178            None,
179            None,
180            None,
181            None,
182            None,
183            None,
184            None,
185        )
186        .await?
187        .values
188        {
189            Some(values) => Ok(values),
190            None => Ok(vec![]),
191        }
192    }
193
194    /// Gets Jira project issue types.
195    ///
196    /// # Arguments
197    ///
198    /// * `params` - A ProjectCmdParams struct.
199    ///
200    /// # Returns
201    /// * A Result with a vector of IssueTypeIssueCreateMetadata structs or an error message.
202    ///
203    /// # Examples
204    ///
205    /// ```no_run
206    /// use jirust_cli::runners::jira_cmd_runners::project_cmd_runner::{ProjectCmdRunner, ProjectCmdParams};
207    /// use jirust_cli::config::config_file::ConfigFile;
208    /// use toml::Table;
209    ///
210    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
211    /// # tokio_test::block_on(async {
212    /// let cfg_file = ConfigFile::new("auth_token".to_string(), "jira_url".to_string(), "standard_resolution".to_string(), "standard_resolution_comment".to_string(), Table::new());
213    /// let project_cmd_runner = ProjectCmdRunner::new(&cfg_file);
214    /// let params = ProjectCmdParams::new();
215    ///
216    /// let issue_types = project_cmd_runner.get_jira_project_issue_types(params).await?;
217    /// # Ok(())
218    /// # })
219    /// # }
220    /// ```
221    pub async fn get_jira_project_issue_types(
222        &self,
223        params: ProjectCmdParams,
224    ) -> Result<Vec<IssueTypeIssueCreateMetadata>, Box<dyn std::error::Error>> {
225        let p_key = if let Some(key) = &params.project_key {
226            key.as_str()
227        } else {
228            return Err(Box::new(Error::other(
229                "Error retrieving project issue types: Empty project key".to_string(),
230            )));
231        };
232        let page_size = Some(params.projects_page_size.unwrap_or(10));
233        let page_offset = Some(params.projects_page_offset.unwrap_or(0));
234        match get_create_issue_meta_issue_types(&self.cfg, p_key, page_offset, page_size)
235            .await?
236            .issue_types
237        {
238            Some(issue_types) => Ok(issue_types),
239            None => Ok(vec![]),
240        }
241    }
242
243    /// Gets Jira project issue fields by issue type id.
244    ///
245    /// # Arguments
246    ///
247    /// * `params` - A ProjectCmdParams struct.
248    ///
249    /// # Returns
250    ///
251    /// * A Result with a vector of FieldCreateMetadata structs or an error message.
252    ///
253    /// # Examples
254    ///
255    /// ```no_run
256    /// use jirust_cli::runners::jira_cmd_runners::project_cmd_runner::{ProjectCmdRunner, ProjectCmdParams};
257    /// use jirust_cli::config::config_file::ConfigFile;
258    /// use toml::Table;
259    ///
260    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
261    /// # tokio_test::block_on(async {
262    /// let cfg_file = ConfigFile::new("auth_token".to_string(), "jira_url".to_string(), "standard_resolution".to_string(), "standard_resolution_comment".to_string(), Table::new());
263    /// let project_cmd_runner = ProjectCmdRunner::new(&cfg_file);
264    /// let params = ProjectCmdParams::new();
265    ///
266    /// let issue_fields = project_cmd_runner.get_jira_project_issue_type_id(params).await?;
267    /// # Ok(())
268    /// # })
269    /// # }
270    /// ```
271    pub async fn get_jira_project_issue_type_id(
272        &self,
273        params: ProjectCmdParams,
274    ) -> Result<Vec<FieldCreateMetadata>, Box<dyn std::error::Error>> {
275        let p_key = if let Some(key) = &params.project_key {
276            key.as_str()
277        } else {
278            return Err(Box::new(Error::other(
279                "Error retrieving project issue types ids: Empty project key".to_string(),
280            )));
281        };
282        let issue_type = if let Some(key) = &params.project_issue_type {
283            key.as_str()
284        } else {
285            return Err(Box::new(Error::other(
286                "Error retrieving project issue types ids: Empty project issue type key"
287                    .to_string(),
288            )));
289        };
290        let page_size = Some(params.projects_page_size.unwrap_or(10));
291        let page_offset = Some(params.projects_page_offset.unwrap_or(0));
292        match get_create_issue_meta_issue_type_id(
293            &self.cfg,
294            p_key,
295            issue_type,
296            page_offset,
297            page_size,
298        )
299        .await?
300        .fields
301        {
302            Some(id) => Ok(id),
303            None => Ok(vec![]),
304        }
305    }
306}
307
308/// This struct defines the parameters for the Project commands
309///
310/// # Fields
311///
312/// * `project_key` - The project key, **required** for get project issue types and issue fields commands.
313/// * `project_issue_type` - The project issue type, **required** for get issue fields command.
314/// * `projects_page_size` - The page size for the project command, optional.
315/// * `projects_page_offset` - The page offset for the project command, optional.
316pub struct ProjectCmdParams {
317    pub project_key: Option<String>,
318    pub project_issue_type: Option<String>,
319    pub project_name: Option<String>,
320    pub project_description: Option<String>,
321    pub project_field_configuration_id: Option<i64>,
322    pub project_issue_security_scheme_id: Option<i64>,
323    pub project_issue_type_scheme_id: Option<i64>,
324    pub project_issue_type_screen_scheme_id: Option<i64>,
325    pub project_notification_scheme_id: Option<i64>,
326    pub project_permission_scheme_id: Option<i64>,
327    pub project_workflow_scheme_id: Option<i64>,
328    pub project_lead_account_id: Option<String>,
329    pub project_assignee_type: Option<String>,
330    pub projects_page_size: Option<i32>,
331    pub projects_page_offset: Option<i32>,
332}
333
334/// Implementation of the ProjectCmdParams struct
335///
336/// # Methods
337///
338/// * `new` - Creates a new ProjectCmdParams struct.
339///
340impl ProjectCmdParams {
341    /// Creates a new ProjectCmdParams struct instance.
342    ///
343    /// # Returns
344    ///
345    /// * A ProjectCmdParams struct instance.
346    ///
347    /// # Examples
348    ///
349    /// ```
350    /// use jirust_cli::runners::jira_cmd_runners::project_cmd_runner::ProjectCmdParams;
351    ///
352    /// let params = ProjectCmdParams::new();
353    /// ```
354    pub fn new() -> ProjectCmdParams {
355        ProjectCmdParams {
356            project_key: None,
357            project_issue_type: None,
358            project_name: None,
359            project_description: None,
360            project_field_configuration_id: None,
361            project_issue_security_scheme_id: None,
362            project_issue_type_scheme_id: None,
363            project_issue_type_screen_scheme_id: None,
364            project_notification_scheme_id: None,
365            project_permission_scheme_id: None,
366            project_workflow_scheme_id: None,
367            project_lead_account_id: None,
368            project_assignee_type: None,
369            projects_page_size: None,
370            projects_page_offset: None,
371        }
372    }
373}
374
375/// Implementation of the From trait for the ProjectCmdParams struct
376/// to convert from ProjectArgs to ProjectCmdParams.
377impl From<&ProjectArgs> for ProjectCmdParams {
378    /// Converts from ProjectArgs to ProjectCmdParams.
379    ///
380    /// # Arguments
381    ///
382    /// * `value` - A ProjectArgs struct.
383    ///
384    /// # Returns
385    ///
386    /// * A ProjectCmdParams struct instance.
387    ///
388    /// # Examples
389    ///
390    /// ```
391    /// use jirust_cli::runners::jira_cmd_runners::project_cmd_runner::ProjectCmdParams;
392    /// use jirust_cli::args::commands::{ProjectArgs, ProjectActionValues, PaginationArgs, OutputArgs};
393    ///
394    /// let project_args = ProjectArgs {
395    ///     project_act: ProjectActionValues::GetIssueTypeFields,
396    ///     project_key: Some("project_key".to_string()),
397    ///     project_issue_type: Some("project_issue_type".to_string()),
398    ///     project_name: None,
399    ///     project_description: None,
400    ///     project_field_configuration_id: None,
401    ///     project_issue_security_scheme_id: None,
402    ///     project_issue_type_scheme_id: None,
403    ///     project_issue_type_screen_scheme_id: None,
404    ///     project_notification_scheme_id: None,
405    ///     project_permission_scheme_id: None,
406    ///     project_workflow_scheme_id: None,
407    ///     project_lead_account_id: None,
408    ///     project_assignee_type: None,
409    ///     pagination: PaginationArgs { page_size: Some(10), page_offset: None },
410    ///     output: OutputArgs { output_format: None, output_type: None },
411    /// };
412    ///
413    /// let params = ProjectCmdParams::from(&project_args);
414    ///
415    /// assert_eq!(params.project_key, Some("project_key".to_string()));
416    /// assert_eq!(params.project_issue_type, Some("project_issue_type".to_string()));
417    /// assert_eq!(params.projects_page_size, Some(10));
418    /// assert_eq!(params.projects_page_offset, Some(0));
419    /// ```
420    fn from(value: &ProjectArgs) -> Self {
421        ProjectCmdParams {
422            project_key: value.project_key.clone(),
423            project_issue_type: value.project_issue_type.clone(),
424            project_name: value.project_name.clone(),
425            project_description: value.project_description.clone(),
426            project_field_configuration_id: value.project_field_configuration_id,
427            project_issue_security_scheme_id: value.project_issue_security_scheme_id,
428            project_issue_type_scheme_id: value.project_issue_type_scheme_id,
429            project_issue_type_screen_scheme_id: value.project_issue_type_screen_scheme_id,
430            project_notification_scheme_id: value.project_notification_scheme_id,
431            project_permission_scheme_id: value.project_permission_scheme_id,
432            project_workflow_scheme_id: value.project_workflow_scheme_id,
433            project_lead_account_id: value.project_lead_account_id.clone(),
434            project_assignee_type: value.project_assignee_type.clone(),
435            projects_page_size: value.pagination.page_size,
436            projects_page_offset: Some(
437                i32::try_from(value.pagination.page_offset.unwrap_or(0))
438                    .expect("Invalid page offset, should fit an i32!"),
439            ),
440        }
441    }
442}
443
444/// Implementation of the Default trait for the ProjectCmdParams struct
445impl Default for ProjectCmdParams {
446    /// Creates a default ProjectCmdParams struct instance.
447    ///
448    /// # Returns
449    ///
450    /// * A ProjectCmdParams struct instance with default values.
451    ///
452    /// # Examples
453    ///
454    /// ```
455    /// use jirust_cli::runners::jira_cmd_runners::project_cmd_runner::ProjectCmdParams;
456    ///
457    /// let params = ProjectCmdParams::default();
458    ///
459    /// assert_eq!(params.project_key, None);
460    /// assert_eq!(params.project_issue_type, None);
461    /// assert_eq!(params.projects_page_size, None);
462    /// assert_eq!(params.projects_page_offset, None);
463    /// ```
464    fn default() -> Self {
465        ProjectCmdParams::new()
466    }
467}
468
469#[cfg_attr(test, automock)]
470#[async_trait(?Send)]
471pub trait ProjectCmdRunnerApi: Send + Sync {
472    async fn create_jira_project(
473        &self,
474        params: ProjectCmdParams,
475    ) -> Result<ProjectIdentifiers, Box<dyn std::error::Error>>;
476
477    async fn list_jira_projects(
478        &self,
479        params: ProjectCmdParams,
480    ) -> Result<Vec<Project>, Box<dyn std::error::Error>>;
481
482    async fn get_jira_project_issue_types(
483        &self,
484        params: ProjectCmdParams,
485    ) -> Result<Vec<IssueTypeIssueCreateMetadata>, Box<dyn std::error::Error>>;
486
487    async fn get_jira_project_issue_type_id(
488        &self,
489        params: ProjectCmdParams,
490    ) -> Result<Vec<FieldCreateMetadata>, Box<dyn std::error::Error>>;
491}
492
493#[async_trait(?Send)]
494impl ProjectCmdRunnerApi for ProjectCmdRunner {
495    async fn create_jira_project(
496        &self,
497        params: ProjectCmdParams,
498    ) -> Result<ProjectIdentifiers, Box<dyn std::error::Error>> {
499        ProjectCmdRunner::create_jira_project(self, params).await
500    }
501
502    async fn list_jira_projects(
503        &self,
504        params: ProjectCmdParams,
505    ) -> Result<Vec<Project>, Box<dyn std::error::Error>> {
506        ProjectCmdRunner::list_jira_projects(self, params).await
507    }
508
509    async fn get_jira_project_issue_types(
510        &self,
511        params: ProjectCmdParams,
512    ) -> Result<Vec<IssueTypeIssueCreateMetadata>, Box<dyn std::error::Error>> {
513        ProjectCmdRunner::get_jira_project_issue_types(self, params).await
514    }
515
516    async fn get_jira_project_issue_type_id(
517        &self,
518        params: ProjectCmdParams,
519    ) -> Result<Vec<FieldCreateMetadata>, Box<dyn std::error::Error>> {
520        ProjectCmdRunner::get_jira_project_issue_type_id(self, params).await
521    }
522}