git-next/crates/forge-forgejo/src/lib.rs
Paul Campbell 538728c491 feat!: restructured server config into listen & shout sections
Groups 'http' and 'webhook' sections under 'listen'.

Renames 'notification' section as 'shout'.
2024-08-01 07:56:31 +01:00

132 lines
4.1 KiB
Rust

//
#[cfg(test)]
mod tests;
mod webhook;
use git_next_core::{
self as core,
git::{self, forge::commit::Status},
server::RepoListenUrl,
ForgeNotification, RegisteredWebhook, WebhookAuth, WebhookId,
};
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 duplicate(&self) -> Box<dyn git::ForgeLike> {
Box::new(self.clone())
}
fn name(&self) -> String {
"forgejo".to_string()
}
fn is_message_authorised(&self, msg: &ForgeNotification, expected: &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| WebhookAuth::try_new(value.as_str()).ok())
.map(|auth| &auth == expected)
.unwrap_or(false)
}
fn parse_webhook_body(
&self,
body: &core::webhook::forge_notification::Body,
) -> git::forge::webhook::Result<core::webhook::Push> {
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::<CombinedStatus>(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 => {
#[cfg(not(tarpaulin_include))]
unreachable!(); // response.response_body() is always Some when
// request responseType::Json
}
},
Err(e) => {
warn!(?e, "Failed to get commit status");
Status::Pending // assume issue is transient and allow retry
}
}
}
async fn list_webhooks(
&self,
repo_listen_url: &RepoListenUrl,
) -> git::forge::webhook::Result<Vec<WebhookId>> {
webhook::list(&self.repo_details, repo_listen_url, &self.net).await
}
async fn unregister_webhook(&self, webhook_id: &WebhookId) -> git::forge::webhook::Result<()> {
webhook::unregister(webhook_id, &self.repo_details, &self.net).await
}
async fn register_webhook(
&self,
repo_listen_url: &RepoListenUrl,
) -> git::forge::webhook::Result<RegisteredWebhook> {
webhook::register(&self.repo_details, repo_listen_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,
}