use async_trait::async_trait;
use jira_v3_openapi::apis::Error;
use jira_v3_openapi::apis::configuration::Configuration;
use jira_v3_openapi::apis::issue_links_api::link_issues;
use jira_v3_openapi::models::{IssueLinkType, LinkIssueRequestJsonBean, LinkedIssue};
use serde_json::Value;
use crate::args::commands::LinkIssueArgs;
use crate::config::config_file::{AuthData, ConfigFile};
use crate::utils::changelog_extractor::ChangelogExtractor;
#[cfg(test)]
use mockall::automock;
pub struct LinkIssueCmdRunner {
cfg: Configuration,
}
impl LinkIssueCmdRunner {
pub fn new(cfg_file: &ConfigFile) -> LinkIssueCmdRunner {
let mut config = Configuration::new();
let auth_data = AuthData::from_base64(cfg_file.get_auth_key());
config.base_path = cfg_file.get_jira_url().to_string();
config.basic_auth = Some((auth_data.0, Some(auth_data.1)));
LinkIssueCmdRunner { cfg: config }
}
pub async fn link_jira_issues(
&self,
params: LinkIssueCmdParams,
) -> Result<Value, Box<dyn std::error::Error>> {
let mut link_requests: Vec<LinkIssueRequestJsonBean> = Vec::new();
if params.destination_issue_key.is_some() {
let link_request = LinkIssueRequestJsonBean {
comment: None,
inward_issue: Box::new(LinkedIssue {
key: Some(params.origin_issue_key),
id: None,
fields: None,
param_self: None,
}),
outward_issue: Box::new(LinkedIssue {
key: params.destination_issue_key,
id: None,
fields: None,
param_self: None,
}),
r#type: Box::new(IssueLinkType {
name: Some(params.link_type),
inward: None,
outward: None,
id: None,
param_self: None,
}),
};
link_requests.push(link_request);
} else {
let p_key = if let Some(key) = ¶ms.project_key {
key
} else {
return Err(Box::new(std::io::Error::other(
"Error linking issues: Empty project key".to_string(),
)));
};
let changelog_extractor = ChangelogExtractor::new(params.changelog_file.unwrap());
let version_data: Option<String> = Some(
changelog_extractor
.extract_version_changelog()
.unwrap_or_default(),
);
if let Some(version_data) = version_data {
let issues = changelog_extractor
.extract_issues_from_changelog(&version_data, p_key)
.unwrap_or_default();
link_requests = issues
.iter()
.map(|issue| LinkIssueRequestJsonBean {
comment: None,
inward_issue: Box::new(LinkedIssue {
key: Some(params.origin_issue_key.clone()),
id: None,
fields: None,
param_self: None,
}),
outward_issue: Box::new(LinkedIssue {
key: Some(issue.clone()),
id: None,
fields: None,
param_self: None,
}),
r#type: Box::new(IssueLinkType {
name: Some(params.link_type.clone()),
inward: None,
outward: None,
id: None,
param_self: None,
}),
})
.collect();
} else {
return Err(Box::new(std::io::Error::other(
"Error linking issues: No destination issue key found in changelog".to_string(),
)));
}
};
let mut link_result: Value = Value::String("Linking OK".to_string());
for link_issue_request_json_bean in link_requests {
match link_issues(&self.cfg, link_issue_request_json_bean).await {
Ok(_) => {}
Err(Error::Serde(_)) => {}
Err(_) => {
link_result = Value::String("Linking KO".to_string());
}
};
}
Ok(link_result)
}
}
pub struct LinkIssueCmdParams {
pub project_key: Option<String>,
pub origin_issue_key: String,
pub destination_issue_key: Option<String>,
pub link_type: String,
pub changelog_file: Option<String>,
}
impl LinkIssueCmdParams {
pub fn new() -> LinkIssueCmdParams {
LinkIssueCmdParams {
project_key: None,
origin_issue_key: "".to_string(),
destination_issue_key: None,
link_type: "".to_string(),
changelog_file: None,
}
}
}
impl From<&LinkIssueArgs> for LinkIssueCmdParams {
fn from(value: &LinkIssueArgs) -> Self {
if (value.destination_issue_key.is_none() && value.changelog_file.is_none())
|| (value.destination_issue_key.is_some() && value.changelog_file.is_some())
{
panic!("Either destination issue key or changelog file is required");
}
if value.changelog_file.is_some() && value.project_key.is_none() {
panic!("Project key is required when changelog file is provided");
}
LinkIssueCmdParams {
project_key: value.project_key.clone(),
origin_issue_key: value.origin_issue_key.clone(),
destination_issue_key: value.destination_issue_key.clone(),
link_type: value.link_type.clone(),
changelog_file: value.changelog_file.clone(),
}
}
}
impl Default for LinkIssueCmdParams {
fn default() -> Self {
LinkIssueCmdParams::new()
}
}
#[cfg_attr(test, automock)]
#[async_trait(?Send)]
pub trait LinkIssueCmdRunnerApi: Send + Sync {
async fn link_jira_issues(
&self,
params: LinkIssueCmdParams,
) -> Result<Value, Box<dyn std::error::Error>>;
}
#[async_trait(?Send)]
impl LinkIssueCmdRunnerApi for LinkIssueCmdRunner {
async fn link_jira_issues(
&self,
params: LinkIssueCmdParams,
) -> Result<Value, Box<dyn std::error::Error>> {
LinkIssueCmdRunner::link_jira_issues(self, params).await
}
}