// #[cfg(test)] mod tests; mod webhook; use git::forge::commit::Status; use git_next_config as config; use git_next_git as git; use kxio::network::{self, Network}; use tracing::warn; #[derive(Clone, Debug)] pub struct ForgeJo { repo_details: git::RepoDetails, net: Network, } impl ForgeJo { pub const fn new(repo_details: git::RepoDetails, net: Network) -> Self { Self { repo_details, net } } } #[async_trait::async_trait] impl git::ForgeLike for ForgeJo { fn name(&self) -> String { "forgejo".to_string() } fn is_message_authorised( &self, msg: &config::WebhookMessage, expected: &config::WebhookAuth, ) -> bool { let authorization = msg.header("authorization"); tracing::info!(?authorization, %expected, "is message authorised?"); authorization .and_then(|header| header.strip_prefix("Basic ").map(|v| v.to_owned())) .and_then(|value| config::WebhookAuth::new(value.as_str()).ok()) .map(|auth| &auth == expected) .unwrap_or(false) } fn parse_webhook_body( &self, body: &config::webhook::message::Body, ) -> git::forge::webhook::Result { webhook::parse_body(body) } async fn commit_status(&self, commit: &git::Commit) -> Status { let repo_details = &self.repo_details; let hostname = &repo_details.forge.hostname(); let repo_path = &repo_details.repo_path; let api_token = &repo_details.forge.token(); use secrecy::ExposeSecret; let token = api_token.expose_secret(); let url = network::NetUrl::new(format!( "https://{hostname}/api/v1/repos/{repo_path}/commits/{commit}/status?token={token}" )); let request = network::NetRequest::new( network::RequestMethod::Get, url, network::NetRequestHeaders::new(), network::RequestBody::None, network::ResponseType::Json, None, network::NetRequestLogging::None, ); let result = self.net.get::(request).await; match result { Ok(response) => { match response.response_body() { Some(status) => match status.state { ForgejoState::Success => Status::Pass, ForgejoState::Pending => Status::Pending, ForgejoState::Failure => Status::Fail, ForgejoState::Error => Status::Fail, ForgejoState::Blank => Status::Pending, }, None => { warn!("No status found for commit"); Status::Pending // assume issue is transient and allow retry } } } Err(e) => { warn!(?e, "Failed to get commit status"); Status::Pending // assume issue is transient and allow retry } } } async fn list_webhooks( &self, webhook_url: &config::server::WebhookUrl, ) -> git::forge::webhook::Result> { webhook::list(&self.repo_details, webhook_url, &self.net).await } async fn unregister_webhook( &self, webhook_id: &config::WebhookId, ) -> git::forge::webhook::Result<()> { webhook::unregister(webhook_id, &self.repo_details, &self.net).await } async fn register_webhook( &self, webhook_url: &config::server::WebhookUrl, ) -> git::forge::webhook::Result { webhook::register(&self.repo_details, webhook_url, &self.net).await } } #[derive(Debug, serde::Deserialize)] struct CombinedStatus { pub state: ForgejoState, } #[derive(Debug, serde::Deserialize)] enum ForgejoState { #[serde(rename = "success")] Success, #[serde(rename = "pending")] Pending, #[serde(rename = "failure")] Failure, #[serde(rename = "error")] Error, #[serde(rename = "")] Blank, }