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    /// Jira project key when acting on an existing project.
318    pub project_key: Option<String>,
319    /// Issue type identifier used when querying issue type fields.
320    pub project_issue_type: Option<String>,
321    /// Name for the project being created.
322    pub project_name: Option<String>,
323    /// Description for the project being created.
324    pub project_description: Option<String>,
325    /// Field configuration id to associate to the project.
326    pub project_field_configuration_id: Option<i64>,
327    /// Issue security scheme id linked to the project.
328    pub project_issue_security_scheme_id: Option<i64>,
329    /// Issue type scheme id to apply to the project.
330    pub project_issue_type_scheme_id: Option<i64>,
331    /// Issue type screen scheme id to apply to the project.
332    pub project_issue_type_screen_scheme_id: Option<i64>,
333    /// Notification scheme id to attach to the project.
334    pub project_notification_scheme_id: Option<i64>,
335    /// Permission scheme id to attach to the project.
336    pub project_permission_scheme_id: Option<i64>,
337    /// Workflow scheme id to attach to the project.
338    pub project_workflow_scheme_id: Option<i64>,
339    /// Account id for the project lead.
340    pub project_lead_account_id: Option<String>,
341    /// Default assignee type for the project.
342    pub project_assignee_type: Option<String>,
343    /// Page size used when listing projects.
344    pub projects_page_size: Option<i32>,
345    /// Page offset used when listing projects.
346    pub projects_page_offset: Option<i32>,
347}
348
349/// Implementation of the ProjectCmdParams struct
350///
351/// # Methods
352///
353/// * `new` - Creates a new ProjectCmdParams struct.
354///
355impl ProjectCmdParams {
356    /// Creates a new ProjectCmdParams struct instance.
357    ///
358    /// # Returns
359    ///
360    /// * A ProjectCmdParams struct instance.
361    ///
362    /// # Examples
363    ///
364    /// ```
365    /// use jirust_cli::runners::jira_cmd_runners::project_cmd_runner::ProjectCmdParams;
366    ///
367    /// let params = ProjectCmdParams::new();
368    /// ```
369    pub fn new() -> ProjectCmdParams {
370        ProjectCmdParams {
371            project_key: None,
372            project_issue_type: None,
373            project_name: None,
374            project_description: None,
375            project_field_configuration_id: None,
376            project_issue_security_scheme_id: None,
377            project_issue_type_scheme_id: None,
378            project_issue_type_screen_scheme_id: None,
379            project_notification_scheme_id: None,
380            project_permission_scheme_id: None,
381            project_workflow_scheme_id: None,
382            project_lead_account_id: None,
383            project_assignee_type: None,
384            projects_page_size: None,
385            projects_page_offset: None,
386        }
387    }
388}
389
390/// Implementation of the From trait for the ProjectCmdParams struct
391/// to convert from ProjectArgs to ProjectCmdParams.
392impl From<&ProjectArgs> for ProjectCmdParams {
393    /// Converts from ProjectArgs to ProjectCmdParams.
394    ///
395    /// # Arguments
396    ///
397    /// * `value` - A ProjectArgs struct.
398    ///
399    /// # Returns
400    ///
401    /// * A ProjectCmdParams struct instance.
402    ///
403    /// # Examples
404    ///
405    /// ```
406    /// use jirust_cli::runners::jira_cmd_runners::project_cmd_runner::ProjectCmdParams;
407    /// use jirust_cli::args::commands::{ProjectArgs, ProjectActionValues, PaginationArgs, OutputArgs};
408    ///
409    /// let project_args = ProjectArgs {
410    ///     project_act: ProjectActionValues::GetIssueTypeFields,
411    ///     project_key: Some("project_key".to_string()),
412    ///     project_issue_type: Some("project_issue_type".to_string()),
413    ///     project_name: None,
414    ///     project_description: None,
415    ///     project_field_configuration_id: None,
416    ///     project_issue_security_scheme_id: None,
417    ///     project_issue_type_scheme_id: None,
418    ///     project_issue_type_screen_scheme_id: None,
419    ///     project_notification_scheme_id: None,
420    ///     project_permission_scheme_id: None,
421    ///     project_workflow_scheme_id: None,
422    ///     project_lead_account_id: None,
423    ///     project_assignee_type: None,
424    ///     pagination: PaginationArgs { page_size: Some(10), page_offset: None },
425    ///     output: OutputArgs { output_format: None, output_type: None },
426    /// };
427    ///
428    /// let params = ProjectCmdParams::from(&project_args);
429    ///
430    /// assert_eq!(params.project_key, Some("project_key".to_string()));
431    /// assert_eq!(params.project_issue_type, Some("project_issue_type".to_string()));
432    /// assert_eq!(params.projects_page_size, Some(10));
433    /// assert_eq!(params.projects_page_offset, Some(0));
434    /// ```
435    fn from(value: &ProjectArgs) -> Self {
436        ProjectCmdParams {
437            project_key: value.project_key.clone(),
438            project_issue_type: value.project_issue_type.clone(),
439            project_name: value.project_name.clone(),
440            project_description: value.project_description.clone(),
441            project_field_configuration_id: value.project_field_configuration_id,
442            project_issue_security_scheme_id: value.project_issue_security_scheme_id,
443            project_issue_type_scheme_id: value.project_issue_type_scheme_id,
444            project_issue_type_screen_scheme_id: value.project_issue_type_screen_scheme_id,
445            project_notification_scheme_id: value.project_notification_scheme_id,
446            project_permission_scheme_id: value.project_permission_scheme_id,
447            project_workflow_scheme_id: value.project_workflow_scheme_id,
448            project_lead_account_id: value.project_lead_account_id.clone(),
449            project_assignee_type: value.project_assignee_type.clone(),
450            projects_page_size: value.pagination.page_size,
451            projects_page_offset: Some(
452                i32::try_from(value.pagination.page_offset.unwrap_or(0))
453                    .expect("Invalid page offset, should fit an i32!"),
454            ),
455        }
456    }
457}
458
459/// Implementation of the Default trait for the ProjectCmdParams struct
460impl Default for ProjectCmdParams {
461    /// Creates a default ProjectCmdParams struct instance.
462    ///
463    /// # Returns
464    ///
465    /// * A ProjectCmdParams struct instance with default values.
466    ///
467    /// # Examples
468    ///
469    /// ```
470    /// use jirust_cli::runners::jira_cmd_runners::project_cmd_runner::ProjectCmdParams;
471    ///
472    /// let params = ProjectCmdParams::default();
473    ///
474    /// assert_eq!(params.project_key, None);
475    /// assert_eq!(params.project_issue_type, None);
476    /// assert_eq!(params.projects_page_size, None);
477    /// assert_eq!(params.projects_page_offset, None);
478    /// ```
479    fn default() -> Self {
480        ProjectCmdParams::new()
481    }
482}
483
484/// API contract for performing Jira project operations.
485#[cfg_attr(test, automock)]
486#[async_trait(?Send)]
487pub trait ProjectCmdRunnerApi: Send + Sync {
488    /// Creates a Jira project with the provided parameters.
489    async fn create_jira_project(
490        &self,
491        params: ProjectCmdParams,
492    ) -> Result<ProjectIdentifiers, Box<dyn std::error::Error>>;
493
494    /// Lists Jira projects using pagination if provided.
495    async fn list_jira_projects(
496        &self,
497        params: ProjectCmdParams,
498    ) -> Result<Vec<Project>, Box<dyn std::error::Error>>;
499
500    /// Retrieves issue types available for a project key.
501    async fn get_jira_project_issue_types(
502        &self,
503        params: ProjectCmdParams,
504    ) -> Result<Vec<IssueTypeIssueCreateMetadata>, Box<dyn std::error::Error>>;
505
506    /// Retrieves fields for a specific project issue type.
507    async fn get_jira_project_issue_type_id(
508        &self,
509        params: ProjectCmdParams,
510    ) -> Result<Vec<FieldCreateMetadata>, Box<dyn std::error::Error>>;
511}
512
513#[async_trait(?Send)]
514impl ProjectCmdRunnerApi for ProjectCmdRunner {
515    async fn create_jira_project(
516        &self,
517        params: ProjectCmdParams,
518    ) -> Result<ProjectIdentifiers, Box<dyn std::error::Error>> {
519        ProjectCmdRunner::create_jira_project(self, params).await
520    }
521
522    async fn list_jira_projects(
523        &self,
524        params: ProjectCmdParams,
525    ) -> Result<Vec<Project>, Box<dyn std::error::Error>> {
526        ProjectCmdRunner::list_jira_projects(self, params).await
527    }
528
529    async fn get_jira_project_issue_types(
530        &self,
531        params: ProjectCmdParams,
532    ) -> Result<Vec<IssueTypeIssueCreateMetadata>, Box<dyn std::error::Error>> {
533        ProjectCmdRunner::get_jira_project_issue_types(self, params).await
534    }
535
536    async fn get_jira_project_issue_type_id(
537        &self,
538        params: ProjectCmdParams,
539    ) -> Result<Vec<FieldCreateMetadata>, Box<dyn std::error::Error>> {
540        ProjectCmdRunner::get_jira_project_issue_type_id(self, params).await
541    }
542}