git_next_forge_forgejo/
lib.rs1#[cfg(test)]
3mod tests;
4
5mod webhook;
6
7use std::borrow::ToOwned;
8
9use git_next_core::{
10 self as core,
11 git::{self, forge::commit::Status},
12 server::RepoListenUrl,
13 ForgeNotification, RegisteredWebhook, WebhookAuth, WebhookId,
14};
15
16use kxio::net::Net;
17
18#[derive(Clone, Debug)]
19pub struct ForgeJo {
20 repo_details: git::RepoDetails,
21 net: Net,
22}
23impl ForgeJo {
24 #[must_use]
25 pub const fn new(repo_details: git::RepoDetails, net: Net) -> Self {
26 Self { repo_details, net }
27 }
28}
29#[async_trait::async_trait]
30impl git::ForgeLike for ForgeJo {
31 fn duplicate(&self) -> Box<dyn git::ForgeLike> {
32 Box::new(self.clone())
33 }
34 fn name(&self) -> String {
35 "forgejo".to_string()
36 }
37
38 fn is_message_authorised(&self, msg: &ForgeNotification, expected: &WebhookAuth) -> bool {
39 let authorization = msg.header("authorization");
40 tracing::info!(?authorization, %expected, "is message authorised?");
41 authorization
42 .and_then(|header| header.strip_prefix("Basic ").map(ToOwned::to_owned))
43 .and_then(|value| WebhookAuth::try_new(value.as_str()).ok())
44 .is_some_and(|auth| &auth == expected)
45 }
46
47 fn parse_webhook_body(
48 &self,
49 body: &core::webhook::forge_notification::Body,
50 ) -> git::forge::webhook::Result<core::webhook::Push> {
51 webhook::parse_body(body)
52 }
53
54 async fn commit_status(&self, commit: &git::Commit) -> git::forge::webhook::Result<Status> {
55 let repo_details = &self.repo_details;
56 let hostname = &repo_details.forge.hostname();
57 let repo_path = &repo_details.repo_path;
58 let api_token = &repo_details.forge.token();
59 use secrecy::ExposeSecret;
60 let token = api_token.expose_secret();
61 let url = format!(
62 "https://{hostname}/api/v1/repos/{repo_path}/commits/{commit}/status?token={token}"
63 );
64
65 let Ok(response) = self.net.get(url).send().await else {
66 return Ok(Status::Pending);
67 };
68 let combined_status = response.json::<CombinedStatus>().await.unwrap_or_default();
69 let status = match combined_status.state {
70 ForgejoState::Success => Status::Pass,
71 ForgejoState::Pending | ForgejoState::Blank => Status::Pending,
72 ForgejoState::Failure | ForgejoState::Error => Status::Fail,
73 };
74 Ok(status)
75 }
76
77 async fn list_webhooks(
78 &self,
79 repo_listen_url: &RepoListenUrl,
80 ) -> git::forge::webhook::Result<Vec<WebhookId>> {
81 webhook::list(&self.repo_details, repo_listen_url, &self.net).await
82 }
83
84 async fn unregister_webhook(&self, webhook_id: &WebhookId) -> git::forge::webhook::Result<()> {
85 webhook::unregister(webhook_id, &self.repo_details, &self.net).await
86 }
87
88 async fn register_webhook(
89 &self,
90 repo_listen_url: &RepoListenUrl,
91 ) -> git::forge::webhook::Result<RegisteredWebhook> {
92 webhook::register(&self.repo_details, repo_listen_url, &self.net).await
93 }
94}
95
96#[derive(Debug, Default, serde::Deserialize)]
97struct CombinedStatus {
98 pub state: ForgejoState,
99}
100
101#[derive(Debug, Default, serde::Deserialize)]
102enum ForgejoState {
103 #[serde(rename = "success")]
104 Success,
105 #[serde(rename = "pending")]
106 #[default]
107 Pending,
108 #[serde(rename = "failure")]
109 Failure,
110 #[serde(rename = "error")]
111 Error,
112 #[serde(rename = "")]
113 Blank,
114}