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