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 /// 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}