diff --git a/src/debug.rs b/src/debug.rs index 6a9f8b2..790abea 100644 --- a/src/debug.rs +++ b/src/debug.rs @@ -1,17 +1,21 @@ -use url::Url; use crate::config::Config; use crate::error::GeneralError; use crate::gitea::issue::Issue; use crate::openproject::client::Client; use crate::openproject::root::RootClient; use crate::openproject::user::GetMe; +use url::Url; pub(crate) async fn debug(config: Config) -> Result<(), GeneralError> { println!("test"); - let gitea_client = crate::gitea::client::Client::from_config(config.gitea.first().unwrap()); - let issue: Issue = gitea_client.get(Url::parse("https://gitea.champs-libres.be/api/v1/repos/julienfastre/test/issues/6").unwrap()).await?; + let issue: Issue = gitea_client + .get( + Url::parse("https://gitea.champs-libres.be/api/v1/repos/julienfastre/test/issues/6") + .unwrap(), + ) + .await?; println!("issue: {:?}", issue); diff --git a/src/gitea/client.rs b/src/gitea/client.rs index a3abd12..cfce202 100644 --- a/src/gitea/client.rs +++ b/src/gitea/client.rs @@ -1,9 +1,10 @@ +use crate::config::{Config, GiteaConfig, GitlabConfig}; use crate::error::GeneralError; -use reqwest::header::{HeaderMap, AUTHORIZATION, ACCEPT}; +use gitlab::AsyncGitlab; +use reqwest::header::{HeaderMap, ACCEPT, AUTHORIZATION}; use reqwest::{ClientBuilder, StatusCode}; use serde::de::DeserializeOwned; use url::Url; -use crate::config::GiteaConfig; #[derive(Debug)] pub struct Client { @@ -12,7 +13,6 @@ pub struct Client { } impl Client { - pub fn from_config(config: &GiteaConfig) -> Self { Self::new(&config.token, &config.domain) } @@ -40,7 +40,7 @@ impl Client { StatusCode::OK => { let result: T = response.json().await?; - return Ok(result); + Ok(result) } _ => Err(GeneralError { description: format!( diff --git a/src/gitea/issue.rs b/src/gitea/issue.rs index ad7fc57..416365c 100644 --- a/src/gitea/issue.rs +++ b/src/gitea/issue.rs @@ -1,6 +1,6 @@ use crate::gitea::client::Client; -use serde::Deserialize; use crate::gitea::repository::Repository; +use serde::Deserialize; #[derive(Debug, Deserialize)] pub struct Issue { @@ -8,7 +8,7 @@ pub struct Issue { number: u64, title: String, body: String, - repository: Repository + repository: Repository, } pub trait IssueClient { diff --git a/src/gitlab/action.rs b/src/gitlab/action.rs new file mode 100644 index 0000000..1b9822f --- /dev/null +++ b/src/gitlab/action.rs @@ -0,0 +1,139 @@ +use crate::cli::Issue2Work; +use crate::config::Config; +use crate::error::GeneralError; +use crate::gitlab::client::{client_for_url, has_client_for_url}; +use crate::gitlab::issue::IssueBundle; +use crate::openproject::client::Client; +use crate::openproject::user::{GetMe, User}; +use crate::openproject::work::WorkPackageWriterAssignee; +use crate::planning::Issue2WorkActionTrait; +use gitlab::api::{issues, projects, AsyncQuery}; +use gitlab::{Issue, Project}; +use url::Url; + +#[derive(Debug)] +struct IssueInfo { + project: String, + iid: u64, +} + +/// details on how to create a work package from various informations +#[derive(Debug)] +struct Issue2WorkPackageDTO { + pub issue: IssueBundle, + pub assign_to: Option, +} + +pub(crate) struct GitlabAction {} + +impl Issue2WorkActionTrait for GitlabAction { + async fn run(&self, url: &Url, config: &Config, args: &Issue2Work) -> Result<(), GeneralError> { + let client = client_for_url(&url, &config).await?; + let data = extract_issue_info(&url).unwrap(); + + let endpoint = issues::ProjectIssues::builder() + .iid(data.iid) + .project(String::from(data.project)) + .build() + .unwrap(); + + let issues: Vec = endpoint.query_async(&client).await.unwrap(); + let issue = issues.first().unwrap(); + + let project_endpoint = projects::Project::builder() + .project(issue.project_id.value()) + .build() + .unwrap(); + + let project: Project = project_endpoint.query_async(&client).await.unwrap(); + + let issue_bundle = IssueBundle::new(&issue, &project); + + let open_project_client = Client::from_config(&config.openproject); + + let dto = Issue2WorkPackageDTO { + issue: issue_bundle, + assign_to: match args.assign_to_me { + true => { + let u = open_project_client.me().await?; + Some(u) + } + false => None, + }, + }; + + let work_package = open_project_client + .create_work_package(&(&dto).into(), &args.project_id) + .await?; + + println!( + "new work package created: {:?}, edit at {}/projects/{}/work_packages/{}", + work_package.subject, config.openproject.base_url, args.project_id, work_package.id + ); + + Ok(()) + } + + fn supports(&self, url: &Url, config: &Config, args: &Issue2Work) -> bool { + has_client_for_url(&url, &config) + } +} + +fn extract_issue_info(url: &Url) -> Option { + let parts = url + .path_segments() + .expect("Could not parse path segment of given url"); + let mut project_url: Vec = Vec::with_capacity(3); + let mut iid: Option = None; + + let mut project_found = false; + + for el in parts { + if el == "-" { + project_found = true; + continue; + } + if el == "issues" { + continue; + } + if !project_found { + project_url.push(String::from(el)); + } else { + // must be the id + iid = Some(String::from(el)); + break; + } + } + + Some(IssueInfo { + project: project_url.join("/"), + iid: iid + .expect("iid of the issue not found") + .parse() + .expect("could not transform issue id to u64"), + }) +} + +impl From<&Issue2WorkPackageDTO> for crate::openproject::work::WorkPackageWriter { + fn from(value: &Issue2WorkPackageDTO) -> Self { + crate::openproject::work::WorkPackageWriter { + subject: format!( + "{} ({}/{})", + value.issue.issue.title, + value.issue.project.name_with_namespace, + value.issue.issue.iid + ), + work_type: "TASK".into(), + description: crate::openproject::work::DescriptionWriter { + format: "markdown".into(), + raw: format!("From gitlab: {}", value.issue.issue.web_url), + }, + assignee: WorkPackageWriterAssignee { + href: match &value.assign_to { + None => None, + Some(w) => Some(w.clone().d_links.d_self.href), + }, + }, + } + } +} diff --git a/src/gitlab/client.rs b/src/gitlab/client.rs index d84979b..11a3fd0 100644 --- a/src/gitlab/client.rs +++ b/src/gitlab/client.rs @@ -1,35 +1,48 @@ -use crate::config::Config; +use crate::config::{Config, GitlabConfig}; use crate::error::GeneralError; -use gitlab::AsyncGitlab; -use gitlab::GitlabBuilder; +use gitlab::{AsyncGitlab, GitlabBuilder}; use url::Url; -pub trait ClientProviderTrait { - async fn client_for_url(url: &Url, config: &Config) -> Result; -} - pub struct ClientProvider {} -impl ClientProviderTrait for ClientProvider { - async fn client_for_url(url: &Url, config: &Config) -> Result { - for c in &config.gitlab { - if url.domain() == Some(c.domain.as_str()) { - let client = GitlabBuilder::new("gitlab.com", c.token.clone()) - .build_async() - .await; +impl ClientProvider {} - return match client { - Ok(new_client) => Ok(new_client), - Err(e) => { - let new_error = e.into(); - Err(new_error) - } - }; - } - } - - Err(GeneralError { - description: format!("No client available for this domain: {:?}", url.domain()), - }) +fn is_client_for_url(url: &Url, config: &GitlabConfig) -> bool { + if url.domain() == Some(config.domain.as_str()) { + return true; } + + false +} + +pub async fn client_for_url(url: &Url, config: &Config) -> Result { + for c in &config.gitlab { + if is_client_for_url(url, c) { + let client = GitlabBuilder::new("gitlab.com", c.token.clone()) + .build_async() + .await; + + return match client { + Ok(new_client) => Ok(new_client), + Err(e) => { + let new_error = e.into(); + Err(new_error) + } + }; + } + } + + Err(GeneralError { + description: format!("No client available for this domain: {:?}", url.domain()), + }) +} + +pub fn has_client_for_url(url: &Url, config: &Config) -> bool { + for c in &config.gitlab { + if (is_client_for_url(url, c)) { + return true; + } + } + + false } diff --git a/src/gitlab/mod.rs b/src/gitlab/mod.rs index e278a81..b26b243 100644 --- a/src/gitlab/mod.rs +++ b/src/gitlab/mod.rs @@ -1,3 +1,4 @@ +pub mod action; pub mod client; pub mod issue; diff --git a/src/planning/issue2work.rs b/src/planning/issue2work.rs index 7c19cc5..695728c 100644 --- a/src/planning/issue2work.rs +++ b/src/planning/issue2work.rs @@ -1,132 +1,37 @@ use crate::cli::Issue2Work; use crate::config::Config; use crate::error::GeneralError; -use crate::gitlab::client::{ClientProvider, ClientProviderTrait}; -use crate::gitlab::issue::IssueBundle; -use crate::openproject::client::Client; -use crate::openproject::user::{GetMe, User}; -use crate::openproject::work::WorkPackageWriterAssignee; -use gitlab::api::{issues, projects, AsyncQuery}; -use gitlab::{Issue, Project}; +use crate::gitlab::action::GitlabAction; +use crate::planning::Issue2WorkActionTrait; use url::Url; -#[derive(Debug)] -struct IssueInfo { - project: String, - iid: u64, +struct App { + gitlab_issue2work_action: GitlabAction, } -/// details on how to create a work package from various informations -#[derive(Debug)] -struct Issue2WorkPackageDTO { - pub issue: IssueBundle, - pub assign_to: Option, -} - -impl From<&Issue2WorkPackageDTO> for crate::openproject::work::WorkPackageWriter { - fn from(value: &Issue2WorkPackageDTO) -> Self { - crate::openproject::work::WorkPackageWriter { - subject: format!( - "{} ({}/{})", - value.issue.issue.title, - value.issue.project.name_with_namespace, - value.issue.issue.iid - ), - work_type: "TASK".into(), - description: crate::openproject::work::DescriptionWriter { - format: "markdown".into(), - raw: format!("From gitlab: {}", value.issue.issue.web_url), - }, - assignee: WorkPackageWriterAssignee { - href: match &value.assign_to { - None => None, - Some(w) => Some(w.clone().d_links.d_self.href), - }, - }, - } - } +trait Issue2WorkPackageAction { + fn issue2work_package_action( + &self, + config: &Config, + args: &Issue2Work, + ) -> Result<(), GeneralError>; } pub(crate) async fn issue2work(config: Config, args: &Issue2Work) -> Result<(), GeneralError> { let url = Url::parse(&*args.issue_url).expect("issue_url is not valid"); - let data = extract_issue_info(&url).unwrap(); - - let client = ClientProvider::client_for_url(&url, &config).await?; - - let endpoint = issues::ProjectIssues::builder() - .iid(data.iid) - .project(String::from(data.project)) - .build() - .unwrap(); - - let issues: Vec = endpoint.query_async(&client).await.unwrap(); - let issue = issues.first().unwrap(); - - let project_endpoint = projects::Project::builder() - .project(issue.project_id.value()) - .build() - .unwrap(); - - let project: Project = project_endpoint.query_async(&client).await.unwrap(); - - let issue_bundle = IssueBundle::new(&issue, &project); - - let open_project_client = Client::from_config(&config.openproject); - - let dto = Issue2WorkPackageDTO { - issue: issue_bundle, - assign_to: match args.assign_to_me { - true => { - let u = open_project_client.me().await?; - Some(u) - } - false => None, - }, + let app = App { + gitlab_issue2work_action: GitlabAction {}, }; - let work_package = open_project_client - .create_work_package(&(&dto).into(), &args.project_id) - .await?; - - println!( - "new work package created: {:?}, edit at {}/projects/{}/work_packages/{}", - work_package.subject, config.openproject.base_url, args.project_id, work_package.id - ); - - Ok(()) -} - -fn extract_issue_info(url: &Url) -> Option { - let parts = url - .path_segments() - .expect("Could not parse path segment of given url"); - let mut project_url: Vec = Vec::with_capacity(3); - let mut iid: Option = None; - - let mut project_found = false; - - for el in parts { - if el == "-" { - project_found = true; - continue; - } - if el == "issues" { - continue; - } - if !project_found { - project_url.push(String::from(el)); - } else { - // must be the id - iid = Some(String::from(el)); - break; - } + if (app + .gitlab_issue2work_action + .supports(&url, &config, args) + ) + { + app.gitlab_issue2work_action.run(&url, &config, args).await + } else { + Err(GeneralError { + description: format!("This action is not supported for this url: {}", url), + }) } - - Some(IssueInfo { - project: project_url.join("/"), - iid: iid - .expect("iid of the issue not found") - .parse() - .expect("could not transform issue id to u64"), - }) } diff --git a/src/planning/mod.rs b/src/planning/mod.rs index dd9b3f9..23561e9 100644 --- a/src/planning/mod.rs +++ b/src/planning/mod.rs @@ -1 +1,14 @@ +use crate::cli::Issue2Work; +use crate::config::Config; +use crate::error::GeneralError; +use crate::gitlab::client::ClientProvider; +use gitlab::{AsyncGitlab, GitlabBuilder}; +use url::Url; + pub(crate) mod issue2work; + +pub trait Issue2WorkActionTrait { + async fn run(&self, url: &Url, config: &Config, args: &Issue2Work) -> Result<(), GeneralError>; + + fn supports(&self, url: &Url, config: &Config, args: &Issue2Work) -> bool; +}