diff --git a/Cargo.toml b/Cargo.toml index 2b1970a..2a77ae9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,6 +21,9 @@ nursery = { level = "warn", priority = -1 } unwrap_used = "warn" expect_used = "warn" +[workspace.lints.rust] +unexpected_cfgs = { level = "warn", check-cfg = ['cfg(tarpaulin_include)'] } + [workspace.dependencies] git-next-core = { path = "crates/core", version = "0.12" } git-next-forge-forgejo = { path = "crates/forge-forgejo", version = "0.12" } diff --git a/crates/cli/Cargo.toml b/crates/cli/Cargo.toml index 40fbe9a..e1a65e8 100644 --- a/crates/cli/Cargo.toml +++ b/crates/cli/Cargo.toml @@ -79,3 +79,6 @@ nursery = { level = "warn", priority = -1 } # pedantic = "warn" unwrap_used = "warn" expect_used = "warn" + +[lints.rust] +unexpected_cfgs = { level = "warn", check-cfg = ['cfg(tarpaulin_include)'] } diff --git a/crates/cli/README.md b/crates/cli/README.md index 607a542..ee6c42b 100644 --- a/crates/cli/README.md +++ b/crates/cli/README.md @@ -87,7 +87,9 @@ happen to have those same names. The server is configured by the `git-next-server.toml` file. -#### http +#### listen + +##### http The server needs to be able to receive webhook notifications from your forge, (e.g. github.com). You can do this via any method that suits your environment, @@ -100,17 +102,9 @@ This is the address and port that your reverse proxy should route traffic to. - **addr** - the IP address the server should bind to - **port** - the IP port the server should bind to -#### notification +##### url -The server should be able to notify the user when manual intervention is required. -Currently this is only available via sending a Webhook message. - -- **type** - one of `None` or `Webhook` -- **webhook** - the URL to POST the notification to - -See [Notifications](#notifications) for more details. - -#### webhook +The HTTPS URL for forges to send webhooks to. Your forges need to know where they should route webhooks to. This should be an address this is accessible to the forge. So, for github.com, it would need @@ -118,7 +112,16 @@ to be a publicly accessible HTTPS URL. For a self-hosted forge, e.g. ForgeJo, on your own network, then it only needs to be accessible from the server your forge is running on. -- **url** - the HTTPS URL for forges to send webhook to +#### shout + +The server should be able to notify the user when manual intervention is required. + +##### webhook + +- **url** - the URL to POST the notification to and the +- **secret** - the sync key used to sign the webhook payload + +See [Notifications](#notifications) for more details. #### storage @@ -214,13 +217,11 @@ Currently `git-next` can only use a `gitdir` if the forge and repo is the same one specified as the `origin` remote. Otherwise the behaviour is untested and undefined. -## Notifications +## Webhook Notifications -`git-next` can send a number of notification to the user when intervention is required. -Currently, only WebHooks are supported. - -Webhooks are sent using the Standard Webhooks format. That means all POST messages have -the following headers: +When sending a Webhook Notification to a user they are sent using the +Standard Webhooks format. That means all POST messages have the +following headers: - `Webhook-Id` - `Webhook-Signature` diff --git a/crates/cli/src/repo/handlers/register_webhook.rs b/crates/cli/src/repo/handlers/register_webhook.rs index b7cf44c..563024c 100644 --- a/crates/cli/src/repo/handlers/register_webhook.rs +++ b/crates/cli/src/repo/handlers/register_webhook.rs @@ -18,14 +18,16 @@ impl Handler for RepoActor { if self.webhook_id.is_none() { let forge_alias = self.repo_details.forge.forge_alias().clone(); let repo_alias = self.repo_details.repo_alias.clone(); - let webhook_url = self.webhook.url(&forge_alias, &repo_alias); + let repo_listen_url = self + .listen_url + .repo_url(forge_alias.clone(), repo_alias.clone()); let forge = self.forge.duplicate(); let addr = ctx.address(); let notify_user_recipient = self.notify_user_recipient.clone(); let log = self.log.clone(); debug!("registering webhook"); async move { - match forge.register_webhook(&webhook_url).await { + match forge.register_webhook(&repo_listen_url).await { Ok(registered_webhook) => { debug!(?registered_webhook, ""); do_send( diff --git a/crates/cli/src/repo/mod.rs b/crates/cli/src/repo/mod.rs index cb7e756..a7e0a0c 100644 --- a/crates/cli/src/repo/mod.rs +++ b/crates/cli/src/repo/mod.rs @@ -13,7 +13,8 @@ use git_next_core::{ repository::{factory::RepositoryFactory, open::OpenRepositoryLike}, UserNotification, }, - server, WebhookAuth, WebhookId, + server::ListenUrl, + WebhookAuth, WebhookId, }; mod branch; @@ -45,7 +46,7 @@ pub struct RepoActor { generation: git::Generation, message_token: messages::MessageToken, repo_details: git::RepoDetails, - webhook: server::InboundWebhook, + listen_url: ListenUrl, webhook_id: Option, // INFO: if [None] then no webhook is configured webhook_auth: Option, // INFO: if [None] then no webhook is configured last_main_commit: Option, @@ -63,7 +64,7 @@ impl RepoActor { pub fn new( repo_details: git::RepoDetails, forge: Box, - webhook: server::InboundWebhook, + listen_url: ListenUrl, generation: git::Generation, net: Network, repository_factory: Box, @@ -75,7 +76,7 @@ impl RepoActor { generation, message_token, repo_details, - webhook, + listen_url, webhook_id: None, webhook_auth: None, last_main_commit: None, diff --git a/crates/cli/src/repo/tests/given.rs b/crates/cli/src/repo/tests/given.rs index dddeb9f..22f7985 100644 --- a/crates/cli/src/repo/tests/given.rs +++ b/crates/cli/src/repo/tests/given.rs @@ -1,3 +1,5 @@ +use git_next_core::server::ListenUrl; + // use super::*; @@ -50,8 +52,8 @@ pub fn a_network() -> kxio::network::MockNetwork { kxio::network::MockNetwork::new() } -pub fn a_webhook_url(forge_alias: &ForgeAlias, repo_alias: &RepoAlias) -> WebhookUrl { - InboundWebhook::new(a_name()).url(forge_alias, repo_alias) +pub fn a_listen_url() -> ListenUrl { + ListenUrl::new(a_name()) } pub fn a_name() -> String { @@ -168,10 +170,6 @@ pub fn a_message_token() -> MessageToken { MessageToken::default() } -pub fn a_webhook(url: &WebhookUrl) -> InboundWebhook { - InboundWebhook::new(url.clone().into()) -} - pub fn a_forge() -> Box { Box::new(MockForgeLike::new()) } @@ -182,10 +180,7 @@ pub fn a_repo_actor( forge: Box, net: kxio::network::Network, ) -> (RepoActor, RepoActorLog) { - let forge_alias = repo_details.forge.forge_alias(); - let repo_alias = &repo_details.repo_alias; - let webhook_url = given::a_webhook_url(forge_alias, repo_alias); - let webhook = given::a_webhook(&webhook_url); + let listen_url = given::a_listen_url(); let generation = Generation::default(); let log = RepoActorLog::default(); let actors_log = log.clone(); @@ -193,7 +188,7 @@ pub fn a_repo_actor( RepoActor::new( repo_details, forge, - webhook, + listen_url, generation, net, repository_factory, diff --git a/crates/cli/src/repo/tests/mod.rs b/crates/cli/src/repo/tests/mod.rs index 31e0ffb..cf78919 100644 --- a/crates/cli/src/repo/tests/mod.rs +++ b/crates/cli/src/repo/tests/mod.rs @@ -21,7 +21,6 @@ use git_next_core::{ Commit, ForgeLike, Generation, MockForgeLike, RepoDetails, }, message, - server::{InboundWebhook, WebhookUrl}, webhook::{forge_notification::Body, Push}, BranchName, ForgeAlias, ForgeConfig, ForgeNotification, ForgeType, GitDir, Hostname, RegisteredWebhook, RemoteUrl, RepoAlias, RepoBranches, RepoConfig, RepoConfigSource, diff --git a/crates/cli/src/server/actor/handlers/notify_user.rs b/crates/cli/src/server/actor/handlers/notify_user.rs index cd5fb21..76f4aa3 100644 --- a/crates/cli/src/server/actor/handlers/notify_user.rs +++ b/crates/cli/src/server/actor/handlers/notify_user.rs @@ -16,10 +16,10 @@ impl Handler for ServerActor { let Some(server_config) = &self.server_config else { return; }; - let notification_config = server_config.notification().clone(); + let shout_config = server_config.shout().clone(); let net = self.net.clone(); async move { - if let Some(webhook_config) = notification_config.webhook() { + if let Some(webhook_config) = shout_config.webhook() { send_webhook(msg, webhook_config, net).await; } } diff --git a/crates/cli/src/server/actor/handlers/receive_server_config.rs b/crates/cli/src/server/actor/handlers/receive_server_config.rs index bddf8ac..b447ab8 100644 --- a/crates/cli/src/server/actor/handlers/receive_server_config.rs +++ b/crates/cli/src/server/actor/handlers/receive_server_config.rs @@ -11,7 +11,7 @@ impl Handler for ServerActor { #[allow(clippy::cognitive_complexity)] fn handle(&mut self, msg: ReceiveServerConfig, ctx: &mut Self::Context) -> Self::Result { tracing::info!("recieved server config"); - let Ok(socket_addr) = msg.http() else { + let Ok(socket_addr) = msg.listen_socket_addr() else { return self.abort(ctx, "Unable to parse http.addr"); }; @@ -19,7 +19,7 @@ impl Handler for ServerActor { return self.abort(ctx, "Server storage not available"); }; - if msg.inbound_webhook().base_url().ends_with('/') { + if msg.listen().url().ends_with('/') { return self.abort(ctx, "webhook.url must not end with a '/'"); } diff --git a/crates/cli/src/server/actor/handlers/receive_valid_server_config.rs b/crates/cli/src/server/actor/handlers/receive_valid_server_config.rs index 58cbe47..3c3862a 100644 --- a/crates/cli/src/server/actor/handlers/receive_valid_server_config.rs +++ b/crates/cli/src/server/actor/handlers/receive_valid_server_config.rs @@ -32,7 +32,7 @@ impl Handler for ServerActor { // Webhook Server info!("Starting Webhook Server..."); let webhook_router = WebhookRouter::default().start(); - let inbound_webhook = server_config.inbound_webhook(); + let listen_url = server_config.listen().url(); // Forge Actors for (forge_alias, forge_config) in server_config.forges() { let repo_actors = self @@ -40,7 +40,7 @@ impl Handler for ServerActor { forge_config, forge_alias.clone(), &server_storage, - inbound_webhook, + listen_url, ctx.address().recipient(), ) .into_iter() diff --git a/crates/cli/src/server/actor/mod.rs b/crates/cli/src/server/actor/mod.rs index fca7587..2677e06 100644 --- a/crates/cli/src/server/actor/mod.rs +++ b/crates/cli/src/server/actor/mod.rs @@ -20,7 +20,7 @@ use crate::{ use git_next_core::{ git::{repository::factory::RepositoryFactory, Generation, RepoDetails}, - server::{self, InboundWebhook, ServerConfig, ServerStorage}, + server::{self, ListenUrl, ServerConfig, ServerStorage}, ForgeAlias, ForgeConfig, GitDir, RepoAlias, ServerRepoConfig, StoragePathType, }; @@ -113,7 +113,7 @@ impl ServerActor { forge_config: &ForgeConfig, forge_name: ForgeAlias, server_storage: &ServerStorage, - webhook: &InboundWebhook, + listen_url: &ListenUrl, notify_user_recipient: Recipient, ) -> Vec<(ForgeAlias, RepoAlias, RepoActor)> { let span = @@ -122,7 +122,8 @@ impl ServerActor { let _guard = span.enter(); tracing::info!("Creating Forge"); let mut repos = vec![]; - let creator = self.create_actor(forge_name, forge_config.clone(), server_storage, webhook); + let creator = + self.create_actor(forge_name, forge_config.clone(), server_storage, listen_url); for (repo_alias, server_repo_config) in forge_config.repos() { let forge_repo = creator(( repo_alias, @@ -143,17 +144,16 @@ impl ServerActor { forge_name: ForgeAlias, forge_config: ForgeConfig, server_storage: &ServerStorage, - webhook: &InboundWebhook, + listen_url: &ListenUrl, ) -> impl Fn( (RepoAlias, &ServerRepoConfig, Recipient), ) -> (ForgeAlias, RepoAlias, RepoActor) { let server_storage = server_storage.clone(); - let webhook = webhook.clone(); + let listen_url = listen_url.clone(); let net = self.net.clone(); let repository_factory = self.repository_factory.duplicate(); let generation = self.generation; let sleep_duration = self.sleep_duration; - // let notify_user_recipient = server_addr.recipient(); move |(repo_alias, server_repo_config, notify_user_recipient)| { let span = tracing::info_span!("create_actor", alias = %repo_alias, config = %server_repo_config); let _guard = span.enter(); @@ -185,7 +185,7 @@ impl ServerActor { let actor = RepoActor::new( repo_details, forge, - webhook.clone(), + listen_url.clone(), generation, net.clone(), repository_factory.duplicate(), @@ -230,13 +230,13 @@ impl ServerActor { } /// Attempts to gracefully shutdown the server before stopping the system. - fn abort(&mut self, ctx: &mut ::Context, message: impl Into) { + fn abort(&self, ctx: &mut ::Context, message: impl Into) { tracing::error!("Aborting: {}", message.into()); self.do_send(crate::server::actor::messages::Shutdown, ctx); System::current().stop_with_code(1); } - fn do_send(&mut self, msg: M, _ctx: &mut ::Context) + fn do_send(&self, msg: M, _ctx: &mut ::Context) where M: actix::Message + Send + 'static + std::fmt::Debug, Self: actix::Handler, diff --git a/crates/cli/src/server/actor/tests/receive_server_config.rs b/crates/cli/src/server/actor/tests/receive_server_config.rs index b82436e..40cb277 100644 --- a/crates/cli/src/server/actor/tests/receive_server_config.rs +++ b/crates/cli/src/server/actor/tests/receive_server_config.rs @@ -4,7 +4,7 @@ use actix::prelude::*; use crate::server::actor::{tests::given, ReceiveServerConfig, ServerActor}; use git_next_core::{ git, - server::{Http, InboundWebhook, Notification, ServerConfig, ServerStorage}, + server::{Http, Listen, ListenUrl, ServerConfig, ServerStorage, Shout}, }; use std::{ @@ -25,9 +25,11 @@ async fn when_webhook_url_has_trailing_slash_should_not_send() { let server = ServerActor::new(fs.clone(), net.into(), repo, duration); // collaborators - let http = Http::new("0.0.0.0".to_string(), 80); - let webhook = InboundWebhook::new("http://localhost/".to_string()); // With trailing slash - let notifications = Notification::none(); + let listen = Listen::new( + Http::new("0.0.0.0".to_string(), 80), + ListenUrl::new("http://localhost/".to_string()), // with trailing slash + ); + let shout = Shout::default(); let server_storage = ServerStorage::new((fs.base()).to_path_buf()); let repos = BTreeMap::default(); @@ -39,9 +41,8 @@ async fn when_webhook_url_has_trailing_slash_should_not_send() { server .start() .do_send(ReceiveServerConfig::new(ServerConfig::new( - http, - webhook, - notifications, + listen, + shout, server_storage, repos, ))); diff --git a/crates/cli/src/server/server-default.toml b/crates/cli/src/server/server-default.toml index 6cee97e..5076ad4 100644 --- a/crates/cli/src/server/server-default.toml +++ b/crates/cli/src/server/server-default.toml @@ -1,11 +1,12 @@ -[http] # where to listen for incoming updates from forges -addr = "0.0.0.0" -port = 8080 +[listen] +# The address and port to listen to for incoming webhooks from forges. +http = { addr = "0.0.0.0", port = 8080 } -[webhook] # where forge should send updates to - should be route to 'http.addr:http.port' above (e.g. using a reverse proxy) +# The URL where forge should send updates to. +# This should be route to 'http.addr:http.port' above (e.g. using a reverse proxy) url = "https://localhost:8080" # don't include any query path or a trailing slash -[notification] # where updates from git-next should be sent to alert the user +[shout] # where updates from git-next should be sent to alert the user # webhook = { url = "https//localhost:9090", secret = "secret-password" } [storage] # where local copies of repositories will be cloned (bare) into @@ -22,3 +23,9 @@ path = "./data" # [forge.default.repos] # the repos at the forge to manage # hello = { repo = "bob/hello", branch = "main", gitdir = "/opt/git/projects/bob/hello.git" } # world = { repo = "bob/world", branch = "master", main = "master", next = "upcoming", "dev" = "develop" } +# hello = { repo = "bob/hello", branch = "main", gitdir = "/opt/git/projects/bob/hello.git" } +# world = { repo = "bob/world", branch = "master", main = "master", next = "upcoming", "dev" = "develop" } +# world = { repo = "bob/world", branch = "master", main = "master", next = "upcoming", "dev" = "develop" } +# world = { repo = "bob/world", branch = "master", main = "master", next = "upcoming", "dev" = "develop" } +# world = { repo = "bob/world", branch = "master", main = "master", next = "upcoming", "dev" = "develop" } +# world = { repo = "bob/world", branch = "master", main = "master", next = "upcoming", "dev" = "develop" } diff --git a/crates/core/Cargo.toml b/crates/core/Cargo.toml index 005669d..ab91cc6 100644 --- a/crates/core/Cargo.toml +++ b/crates/core/Cargo.toml @@ -61,3 +61,6 @@ nursery = { level = "warn", priority = -1 } # pedantic = "warn" unwrap_used = "warn" expect_used = "warn" + +[lints.rust] +unexpected_cfgs = { level = "warn", check-cfg = ['cfg(tarpaulin_include)'] } diff --git a/crates/core/src/config/server.rs b/crates/core/src/config/server.rs index cd60b5d..4486612 100644 --- a/crates/core/src/config/server.rs +++ b/crates/core/src/config/server.rs @@ -3,12 +3,15 @@ use std::{ collections::BTreeMap, net::SocketAddr, + ops::Deref, path::{Path, PathBuf}, str::FromStr, }; +use derive_more::Display; use kxio::fs::FileSystem; use secrecy::Secret; +use serde::{Deserialize, Serialize}; use tracing::info; use crate::{ @@ -44,9 +47,8 @@ type Result = core::result::Result; derive_more::Constructor, )] pub struct ServerConfig { - http: Http, - webhook: InboundWebhook, - notification: Notification, + listen: Listen, + shout: Shout, storage: ServerStorage, pub forge: BTreeMap, } @@ -69,16 +71,75 @@ impl ServerConfig { &self.storage } - pub const fn notification(&self) -> &Notification { - &self.notification + pub const fn shout(&self) -> &Shout { + &self.shout } - pub const fn inbound_webhook(&self) -> &InboundWebhook { - &self.webhook + pub const fn listen(&self) -> &Listen { + &self.listen } - pub fn http(&self) -> Result { - self.http.socket_addr() + pub fn listen_socket_addr(&self) -> Result { + self.listen.http.socket_addr() + } +} + +/// Defines how the server receives webhook notifications from forges. +#[derive( + Clone, + Debug, + derive_more::From, + PartialEq, + Eq, + PartialOrd, + Ord, + derive_more::AsRef, + serde::Deserialize, + derive_more::Constructor, +)] +pub struct Listen { + http: Http, + url: ListenUrl, +} +impl Listen { + /// Returns the URL a Repo will listen to for updates from the Forge + pub fn repo_url(&self, forge_alias: ForgeAlias, repo_alias: RepoAlias) -> RepoListenUrl { + self.url.repo_url(forge_alias, repo_alias) + } + + pub const fn url(&self) -> &ListenUrl { + &self.url + } +} + +newtype!( + ListenUrl: + String, Serialize, Deserialize, PartialOrd, Ord, Display: + "The base url for receiving all webhooks from all forges" +); +impl ListenUrl { + pub fn repo_url(&self, forge_alias: ForgeAlias, repo_alias: RepoAlias) -> RepoListenUrl { + RepoListenUrl::new((self.clone(), forge_alias, repo_alias)) + } +} + +newtype!(ForgeWebhookUrl: String: "Raw URL from a forge Webhook"); + +newtype!(RepoListenUrl: (ListenUrl, ForgeAlias, RepoAlias): "URL to listen for webhook from forges"); +impl Display for RepoListenUrl { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!( + f, + "{}/{}/{}", + self.deref().0, + self.deref().1, + self.deref().2 + ) + } +} +impl From for ForgeWebhookUrl { + fn from(value: RepoListenUrl) -> Self { + Self::new(value.to_string()) } } @@ -108,35 +169,6 @@ impl Http { } } -/// Defines the Webhook Forges should send updates to -/// Must be an address that is accessible from the remote forge -#[derive( - Clone, - Debug, - derive_more::From, - PartialEq, - Eq, - PartialOrd, - Ord, - derive_more::AsRef, - serde::Deserialize, - derive_more::Constructor, -)] -pub struct InboundWebhook { - url: String, -} -impl InboundWebhook { - pub fn url(&self, forge_alias: &ForgeAlias, repo_alias: &RepoAlias) -> WebhookUrl { - let base_url = &self.url; - WebhookUrl(format!("{base_url}/{forge_alias}/{repo_alias}")) - } - pub fn base_url(&self) -> &str { - &self.url - } -} - -newtype!(WebhookUrl: String, serde::Serialize: "The URL for the webhook where forges should send their updates"); - /// The directory to store server data, such as cloned repos #[derive( Clone, @@ -164,6 +196,7 @@ impl ServerStorage { #[derive( Clone, Debug, + Default, derive_more::From, PartialEq, Eq, @@ -172,13 +205,10 @@ impl ServerStorage { derive_more::AsRef, serde::Deserialize, )] -pub struct Notification { +pub struct Shout { webhook: Option, } -impl Notification { - pub const fn none() -> Self { - Self { webhook: None } - } +impl Shout { pub const fn new_webhook(webhook: OutboundWebhook) -> Self { Self { webhook: Some(webhook), @@ -197,11 +227,6 @@ impl Notification { self.webhook.clone().map(|x| x.secret).map(Secret::new) } } -impl Default for Notification { - fn default() -> Self { - Self::none() - } -} #[derive( Clone, diff --git a/crates/core/src/config/tests.rs b/crates/core/src/config/tests.rs index cecc8b5..ec42d62 100644 --- a/crates/core/src/config/tests.rs +++ b/crates/core/src/config/tests.rs @@ -7,7 +7,6 @@ use std::collections::BTreeMap; use std::path::PathBuf; use crate::server::Http; -use crate::server::InboundWebhook; use crate::server::ServerConfig; use crate::server::ServerStorage; use crate::webhook::push::Branch; @@ -465,14 +464,14 @@ mod server { server_config: &ServerConfig, fs: &kxio::fs::FileSystem, ) -> TestResult { - let http = &server_config.http()?; + let http = &server_config.listen_socket_addr()?; let http_addr = http.ip(); - let http_port = server_config.http()?.port(); - let webhook_url = server_config.inbound_webhook().base_url(); + let http_port = server_config.listen_socket_addr()?.port(); + let listen_url = server_config.listen().url(); let storage_path = server_config.storage().path(); - let notification = &server_config.notification(); - let notification_webhook_url = notification.webhook_url().unwrap_or_default(); - let notification_webhook_secret = notification + let shout = server_config.shout(); + let shout_webhook_url = shout.webhook_url().unwrap_or_default(); + let shout_webhook_secret = shout .webhook_secret() .map(|secret| secret.expose_secret().clone()) .unwrap_or_default(); @@ -515,20 +514,16 @@ mod server { let repos = repos.join("\n"); let file_contents = &format!( r#" -[http] -addr = "{http_addr}" -port = {http_port} +[listen] +http = {{ addr = "{http_addr}", port = {http_port} }} +url = "{listen_url}" -[webhook] -url = "{webhook_url}" +[shout] +webhook = {{ url = "{shout_webhook_url}", secret = "{shout_webhook_secret}" }} [storage] path = {storage_path:?} -[notification] -type = "Webhook" -webhook = {{ url = "{notification_webhook_url}", secret = "{notification_webhook_secret}" }} - [forge.{forge_alias}] forge_type = "{forge_type}" hostname = "{forge_hostname}" @@ -697,7 +692,7 @@ mod push { } mod given { - use crate::server::{Notification, OutboundWebhook}; + use crate::server::{Listen, ListenUrl, OutboundWebhook, Shout}; use super::*; use rand::Rng as _; @@ -720,8 +715,7 @@ mod given { } pub fn a_server_config() -> ServerConfig { ServerConfig::new( - an_http(), - an_inbound_webhook(), + a_listen(), a_notification_config(), a_server_storage(), some_forge_configs(), @@ -743,8 +737,11 @@ mod given { pub fn a_port() -> u16 { rand::thread_rng().gen() } - pub fn an_inbound_webhook() -> InboundWebhook { - InboundWebhook::new(a_name()) + pub fn a_listen() -> Listen { + Listen::new(an_http(), a_listen_url()) + } + pub fn a_listen_url() -> ListenUrl { + ListenUrl::new(a_name()) } pub fn an_outbound_webhook() -> OutboundWebhook { OutboundWebhook::new(a_name(), a_name()) @@ -752,8 +749,8 @@ mod given { pub fn a_server_storage() -> ServerStorage { ServerStorage::new(a_name().into()) } - pub fn a_notification_config() -> Notification { - Notification::new_webhook(an_outbound_webhook()) + pub fn a_notification_config() -> Shout { + Shout::new_webhook(an_outbound_webhook()) } pub fn some_forge_configs() -> BTreeMap { [(a_name(), a_forge_config())].into() diff --git a/crates/core/src/git/forge/like.rs b/crates/core/src/git/forge/like.rs index cdc44dc..a2f02d5 100644 --- a/crates/core/src/git/forge/like.rs +++ b/crates/core/src/git/forge/like.rs @@ -1,6 +1,7 @@ // use crate::{ - git, server::WebhookUrl, webhook, ForgeNotification, RegisteredWebhook, WebhookAuth, WebhookId, + git, server::RepoListenUrl, webhook, ForgeNotification, RegisteredWebhook, WebhookAuth, + WebhookId, }; #[mockall::automock] @@ -29,7 +30,10 @@ pub trait ForgeLike: std::fmt::Debug + Send + Sync { async fn commit_status(&self, commit: &git::Commit) -> git::forge::commit::Status; // Lists all the webhooks - async fn list_webhooks(&self, url: &WebhookUrl) -> git::forge::webhook::Result>; + async fn list_webhooks( + &self, + repo_listen_url: &RepoListenUrl, + ) -> git::forge::webhook::Result>; // Unregisters a webhook async fn unregister_webhook(&self, webhook: &WebhookId) -> git::forge::webhook::Result<()>; @@ -37,6 +41,6 @@ pub trait ForgeLike: std::fmt::Debug + Send + Sync { // Registers a webhook async fn register_webhook( &self, - webhook_url: &WebhookUrl, + repo_listen_url: &RepoListenUrl, ) -> git::forge::webhook::Result; } diff --git a/crates/forge-forgejo/Cargo.toml b/crates/forge-forgejo/Cargo.toml index 5ef0290..95fa646 100644 --- a/crates/forge-forgejo/Cargo.toml +++ b/crates/forge-forgejo/Cargo.toml @@ -49,3 +49,6 @@ nursery = { level = "warn", priority = -1 } # pedantic = "warn" unwrap_used = "warn" expect_used = "warn" + +[lints.rust] +unexpected_cfgs = { level = "warn", check-cfg = ['cfg(tarpaulin_include)'] } diff --git a/crates/forge-forgejo/src/lib.rs b/crates/forge-forgejo/src/lib.rs index 438a39f..e5f07fd 100644 --- a/crates/forge-forgejo/src/lib.rs +++ b/crates/forge-forgejo/src/lib.rs @@ -5,8 +5,10 @@ mod tests; mod webhook; use git_next_core::{ - self as core, git, git::forge::commit::Status, server, ForgeNotification, RegisteredWebhook, - WebhookAuth, WebhookId, + self as core, + git::{self, forge::commit::Status}, + server::RepoListenUrl, + ForgeNotification, RegisteredWebhook, WebhookAuth, WebhookId, }; use kxio::network::{self, Network}; @@ -93,9 +95,9 @@ impl git::ForgeLike for ForgeJo { async fn list_webhooks( &self, - webhook_url: &server::WebhookUrl, + repo_listen_url: &RepoListenUrl, ) -> git::forge::webhook::Result> { - webhook::list(&self.repo_details, webhook_url, &self.net).await + webhook::list(&self.repo_details, repo_listen_url, &self.net).await } async fn unregister_webhook(&self, webhook_id: &WebhookId) -> git::forge::webhook::Result<()> { @@ -104,9 +106,9 @@ impl git::ForgeLike for ForgeJo { async fn register_webhook( &self, - webhook_url: &server::WebhookUrl, + repo_listen_url: &RepoListenUrl, ) -> git::forge::webhook::Result { - webhook::register(&self.repo_details, webhook_url, &self.net).await + webhook::register(&self.repo_details, repo_listen_url, &self.net).await } } diff --git a/crates/forge-forgejo/src/tests.rs b/crates/forge-forgejo/src/tests.rs index 775a613..fbb0d61 100644 --- a/crates/forge-forgejo/src/tests.rs +++ b/crates/forge-forgejo/src/tests.rs @@ -2,7 +2,7 @@ use crate::ForgeJo; use git_next_core::{ git::{self, forge::commit::Status, ForgeLike as _}, - server::{InboundWebhook, WebhookUrl}, + server::ListenUrl, BranchName, ForgeAlias, ForgeConfig, ForgeNotification, ForgeType, GitDir, Hostname, RepoAlias, RepoBranches, RepoPath, ServerRepoConfig, StoragePathType, WebhookAuth, WebhookId, }; @@ -198,7 +198,7 @@ mod forgejo { async fn should_return_a_list_of_matching_webhooks() { let fs = given::a_filesystem(); let repo_details = given::repo_details(&fs); - let webhook_url = given::any_webhook_url(); + let repo_listen_url = given::a_repo_listen_url(&repo_details); let hostname = repo_details.forge.hostname(); let repo_path = &repo_details.repo_path; let token = repo_details.forge.token().expose_secret(); @@ -217,9 +217,9 @@ mod forgejo { with::get_webhooks_by_page( 1, &[ - with::ReturnedWebhook::new(hook_id_1, &webhook_url), - with::ReturnedWebhook::new(hook_id_2, &webhook_url), - with::ReturnedWebhook::new(hook_id_3, &given::any_webhook_url()), + with::ReturnedWebhook::new(hook_id_1, &repo_listen_url), + with::ReturnedWebhook::new(hook_id_2, &repo_listen_url), + with::ReturnedWebhook::new(hook_id_3, &given::a_repo_listen_url(&repo_details)), ], &mut args, ); @@ -228,7 +228,7 @@ mod forgejo { let forge = given::a_forgejo_forge(&repo_details, net); - let_assert!(Ok(result) = forge.list_webhooks(&webhook_url).await); + let_assert!(Ok(result) = forge.list_webhooks(&repo_listen_url).await); assert_eq!( result, vec![ @@ -242,7 +242,7 @@ mod forgejo { async fn should_return_any_network_error() { let fs = given::a_filesystem(); let repo_details = given::repo_details(&fs); - let webhook_url = given::a_webhook_url(&given::a_forge_alias(), &given::a_repo_alias()); + let repo_listen_url = given::a_repo_listen_url(&repo_details); let hostname = repo_details.forge.hostname(); let repo_path = &repo_details.repo_path; let token = repo_details.forge.token().expose_secret(); @@ -256,7 +256,7 @@ mod forgejo { let forge = given::a_forgejo_forge(&repo_details, net); - let_assert!(Err(_) = forge.list_webhooks(&webhook_url).await); + let_assert!(Err(_) = forge.list_webhooks(&repo_listen_url).await); } } @@ -326,8 +326,7 @@ mod forgejo { repo_details.repo_config.is_some(), "repo_details needs to have repo_config for this test" ); - let webhook_url = - given::a_webhook_url(repo_details.forge.forge_alias(), &repo_details.repo_alias); + let repo_listen_url = given::a_repo_listen_url(&repo_details); let hostname = repo_details.forge.hostname(); let repo_path = &repo_details.repo_path; let token = repo_details.forge.token().expose_secret(); @@ -352,7 +351,7 @@ mod forgejo { let forge = given::a_forgejo_forge(&repo_details, net); - let_assert!(Ok(registered_webhook) = forge.register_webhook(&webhook_url).await); + let_assert!(Ok(registered_webhook) = forge.register_webhook(&repo_listen_url).await); assert_eq!( registered_webhook.id(), &WebhookId::new(format!("{webhook_id}")) @@ -368,11 +367,10 @@ mod forgejo { repo_details.repo_config.is_none(), "repo_details needs to NOT have repo_config for this test" ); - let webhook_url = - given::a_webhook_url(repo_details.forge.forge_alias(), &repo_details.repo_alias); + let repo_listen_url = given::a_repo_listen_url(&repo_details); let net = given::a_network(); let forge = given::a_forgejo_forge(&repo_details, net); - let_assert!(Err(err) = forge.register_webhook(&webhook_url).await); + let_assert!(Err(err) = forge.register_webhook(&repo_listen_url).await); assert!( matches!(err, git::forge::webhook::Error::NoRepoConfig), "{err:?}" @@ -387,8 +385,7 @@ mod forgejo { repo_details.repo_config.is_some(), "repo_details needs to have repo_config for this test" ); - let webhook_url = - given::a_webhook_url(repo_details.forge.forge_alias(), &repo_details.repo_alias); + let repo_listen_url = given::a_repo_listen_url(&repo_details); let hostname = repo_details.forge.hostname(); let repo_path = &repo_details.repo_path; let token = repo_details.forge.token().expose_secret(); @@ -400,11 +397,11 @@ mod forgejo { repo_path, token, }; - let hook1 = with::ReturnedWebhook::new(given::a_forgejo_webhook_id(), &webhook_url); - let hook2 = with::ReturnedWebhook::new(given::a_forgejo_webhook_id(), &webhook_url); + let hook1 = with::ReturnedWebhook::new(given::a_forgejo_webhook_id(), &repo_listen_url); + let hook2 = with::ReturnedWebhook::new(given::a_forgejo_webhook_id(), &repo_listen_url); let hook3 = with::ReturnedWebhook::new( given::a_forgejo_webhook_id(), - &given::any_webhook_url(), + &given::a_repo_listen_url(&repo_details), ); let hooks = [hook1, hook2, hook3]; @@ -424,7 +421,7 @@ mod forgejo { let forge = given::a_forgejo_forge(&repo_details, net); - let_assert!(Ok(registered_webhook) = forge.register_webhook(&webhook_url).await); + let_assert!(Ok(registered_webhook) = forge.register_webhook(&repo_listen_url).await); assert_eq!( registered_webhook.id(), &WebhookId::new(format!("{webhook_id}")) @@ -439,8 +436,7 @@ mod forgejo { repo_details.repo_config.is_some(), "repo_details needs to have repo_config for this test" ); - let webhook_url = - given::a_webhook_url(repo_details.forge.forge_alias(), &repo_details.repo_alias); + let repo_listen_url = given::a_repo_listen_url(&repo_details); let hostname = repo_details.forge.hostname(); let repo_path = &repo_details.repo_path; let token = repo_details.forge.token().expose_secret(); @@ -464,7 +460,7 @@ mod forgejo { let forge = given::a_forgejo_forge(&repo_details, net); - let_assert!(Err(err) = forge.register_webhook(&webhook_url).await); + let_assert!(Err(err) = forge.register_webhook(&repo_listen_url).await); assert!( matches!(err, git::forge::webhook::Error::FailedToRegister(_)), "{err:?}" @@ -479,8 +475,7 @@ mod forgejo { repo_details.repo_config.is_some(), "repo_details needs to have repo_config for this test" ); - let webhook_url = - given::a_webhook_url(repo_details.forge.forge_alias(), &repo_details.repo_alias); + let repo_listen_url = given::a_repo_listen_url(&repo_details); let hostname = repo_details.forge.hostname(); let repo_path = &repo_details.repo_path; let token = repo_details.forge.token().expose_secret(); @@ -503,7 +498,7 @@ mod forgejo { let forge = given::a_forgejo_forge(&repo_details, net); - let_assert!(Err(err) = forge.register_webhook(&webhook_url).await); + let_assert!(Err(err) = forge.register_webhook(&repo_listen_url).await); assert!( matches!(err, git::forge::webhook::Error::FailedToRegister(_)), "{err:?}" @@ -511,6 +506,8 @@ mod forgejo { } } mod with { + use git_next_core::server::RepoListenUrl; + use super::*; pub fn get_webhooks_by_page( @@ -556,20 +553,25 @@ mod forgejo { pub config: Config, } impl ReturnedWebhook { - pub fn new(id: i64, url: &WebhookUrl) -> Self { + pub fn new(id: i64, url: &RepoListenUrl) -> Self { Self { id, - config: Config { url: url.clone() }, + config: Config { + url: url.to_string(), + }, } } } #[derive(Debug, Serialize)] pub struct Config { - pub url: WebhookUrl, + pub url: String, } } mod given { + use git::RepoDetails; + use git_next_core::server::RepoListenUrl; + use super::*; pub fn a_commit_state( @@ -674,14 +676,6 @@ mod forgejo { kxio::network::MockNetwork::new() } - pub fn a_webhook_url(forge_alias: &ForgeAlias, repo_alias: &RepoAlias) -> WebhookUrl { - InboundWebhook::new(a_name()).url(forge_alias, repo_alias) - } - - pub fn any_webhook_url() -> WebhookUrl { - a_webhook_url(&a_forge_alias(), &a_repo_alias()) - } - pub fn a_name() -> String { use rand::Rng; use std::iter; @@ -765,5 +759,16 @@ mod forgejo { gitdir, ) } + + pub fn a_repo_listen_url(repo_details: &RepoDetails) -> RepoListenUrl { + let listen_url = a_listen_url(); + let forge_alias = repo_details.forge.forge_alias().clone(); + let repo_alias = repo_details.repo_alias.clone(); + RepoListenUrl::new((listen_url, forge_alias, repo_alias)) + } + + fn a_listen_url() -> ListenUrl { + ListenUrl::new(a_name()) + } } } diff --git a/crates/forge-forgejo/src/webhook/list.rs b/crates/forge-forgejo/src/webhook/list.rs index 009fa1d..1282fd0 100644 --- a/crates/forge-forgejo/src/webhook/list.rs +++ b/crates/forge-forgejo/src/webhook/list.rs @@ -1,5 +1,5 @@ // -use git_next_core::{git, server::WebhookUrl, WebhookId}; +use git_next_core::{git, server::RepoListenUrl, WebhookId}; use kxio::network; @@ -7,7 +7,7 @@ use crate::webhook::Hook; pub async fn list( repo_details: &git::RepoDetails, - webhook_url: &WebhookUrl, + repo_listen_url: &RepoListenUrl, net: &network::Network, ) -> git::forge::webhook::Result> { let mut ids: Vec = vec![]; @@ -38,7 +38,7 @@ pub async fn list( } for hook in list { if let Some(existing_url) = hook.config.get("url") { - if existing_url.starts_with(webhook_url.as_ref()) { + if existing_url.starts_with(&repo_listen_url.to_string()) { ids.push(hook.id()); } } diff --git a/crates/forge-forgejo/src/webhook/register.rs b/crates/forge-forgejo/src/webhook/register.rs index 08a49f7..0d7a017 100644 --- a/crates/forge-forgejo/src/webhook/register.rs +++ b/crates/forge-forgejo/src/webhook/register.rs @@ -1,5 +1,6 @@ +use git_next_core::server::RepoListenUrl; // -use git_next_core::{git, server, RegisteredWebhook, WebhookAuth, WebhookId}; +use git_next_core::{git, RegisteredWebhook, WebhookAuth, WebhookId}; use kxio::network; use tracing::{info, warn}; @@ -10,7 +11,7 @@ use crate::webhook::Hook; #[tracing::instrument(skip_all)] pub async fn register( repo_details: &git::RepoDetails, - webhook_url: &server::WebhookUrl, + repo_listen_url: &RepoListenUrl, net: &network::Network, ) -> git::forge::webhook::Result { let Some(repo_config) = repo_details.repo_config.clone() else { @@ -18,7 +19,7 @@ pub async fn register( }; // remove any lingering webhooks for the same URL - let existing_webhook_ids = webhook::list(repo_details, webhook_url, net).await?; + let existing_webhook_ids = webhook::list(repo_details, repo_listen_url, net).await?; for webhook_id in existing_webhook_ids { webhook::unregister(&webhook_id, repo_details, net).await?; } @@ -30,7 +31,6 @@ pub async fn register( let url = network::NetUrl::new(format!( "https://{hostname}/api/v1/repos/{repo_path}/hooks?token={token}" )); - let repo_alias = &repo_details.repo_alias; let headers = network::NetRequestHeaders::new().with("Content-Type", "application/json"); let authorisation = WebhookAuth::generate(); let body = network::json!({ @@ -39,7 +39,7 @@ pub async fn register( "branch_filter": format!("{{{},{},{}}}", repo_config.branches().main(), repo_config.branches().next(), repo_config.branches().dev()), "config": { "content_type": "json", - "url": format!("{}/{}", webhook_url.as_ref(), repo_alias), + "url": repo_listen_url.to_string(), }, "events": [ "push" ], "type": "forgejo" diff --git a/crates/forge-github/Cargo.toml b/crates/forge-github/Cargo.toml index c62a5b3..981b451 100644 --- a/crates/forge-github/Cargo.toml +++ b/crates/forge-github/Cargo.toml @@ -57,3 +57,6 @@ nursery = { level = "warn", priority = -1 } # pedantic = "warn" unwrap_used = "warn" expect_used = "warn" + +[lints.rust] +unexpected_cfgs = { level = "warn", check-cfg = ['cfg(tarpaulin_include)'] } diff --git a/crates/forge-github/src/lib.rs b/crates/forge-github/src/lib.rs index 84e11a0..5bb410d 100644 --- a/crates/forge-github/src/lib.rs +++ b/crates/forge-github/src/lib.rs @@ -9,7 +9,8 @@ use crate as github; use git_next_core::{ self as core, git::{self, forge::commit::Status}, - server, ForgeNotification, RegisteredWebhook, WebhookAuth, WebhookId, + server::{self, RepoListenUrl}, + ForgeNotification, RegisteredWebhook, WebhookAuth, WebhookId, }; use derive_more::Constructor; @@ -57,9 +58,9 @@ impl git::ForgeLike for Github { async fn list_webhooks( &self, - webhook_url: &server::WebhookUrl, + repo_listen_url: &RepoListenUrl, ) -> git::forge::webhook::Result> { - github::webhook::list(self, webhook_url).await + github::webhook::list(self, repo_listen_url).await } async fn unregister_webhook(&self, webhook_id: &WebhookId) -> git::forge::webhook::Result<()> { @@ -69,9 +70,9 @@ impl git::ForgeLike for Github { // https://docs.github.com/en/rest/repos/webhooks?apiVersion=2022-11-28#create-a-repository-webhook async fn register_webhook( &self, - webhook_url: &server::WebhookUrl, + repo_listen_url: &RepoListenUrl, ) -> git::forge::webhook::Result { - github::webhook::register(self, webhook_url).await + github::webhook::register(self, repo_listen_url).await } } @@ -103,8 +104,8 @@ impl GithubHook { pub fn id(&self) -> WebhookId { WebhookId::new(format!("{}", self.id)) } - pub fn url(&self) -> server::WebhookUrl { - server::WebhookUrl::new(self.config.url.clone()) + pub fn url(&self) -> server::ListenUrl { + server::ListenUrl::new(self.config.url.clone()) } } #[derive(Debug, serde::Deserialize)] diff --git a/crates/forge-github/src/tests/mod.rs b/crates/forge-github/src/tests/mod.rs index 5c9c65b..7b96f0c 100644 --- a/crates/forge-github/src/tests/mod.rs +++ b/crates/forge-github/src/tests/mod.rs @@ -2,7 +2,7 @@ use crate::{Github, GithubState, GithubStatus}; use git_next_core::{ git::{self, forge::commit::Status, ForgeLike}, - server::{InboundWebhook, WebhookUrl}, + server::ListenUrl, webhook::{self, forge_notification::Body}, ForgeAlias, ForgeConfig, ForgeNotification, ForgeType, GitDir, Hostname, RepoAlias, RepoBranches, RepoPath, ServerRepoConfig, StoragePathType, WebhookAuth, WebhookId, @@ -158,7 +158,7 @@ mod github { #[tokio::test] async fn should_return_a_list_of_matching_webhooks() { let repo_details = given::repo_details(); - let webhook_url = given::any_webhook_url(); + let repo_listen_url = given::a_repo_listen_url(&repo_details); let hostname = repo_details.forge.hostname(); let repo_path = &repo_details.repo_path; let hook_id_1 = given::a_github_webhook_id(); @@ -174,8 +174,8 @@ mod github { with::get_webhooks_by_page( 1, &[ - with::ReturnedWebhook::new(hook_id_1, &webhook_url), - with::ReturnedWebhook::new(hook_id_2, &webhook_url), + with::ReturnedWebhook::new(hook_id_1, &repo_listen_url), + with::ReturnedWebhook::new(hook_id_2, &repo_listen_url), with::ReturnedWebhook::new(hook_id_3, &given::any_webhook_url()), ], &mut args, @@ -185,7 +185,7 @@ mod github { let forge = given::a_github_forge(&repo_details, net); - let_assert!(Ok(result) = forge.list_webhooks(&webhook_url).await); + let_assert!(Ok(result) = forge.list_webhooks(&repo_listen_url).await); assert_eq!( result, vec![ @@ -198,7 +198,7 @@ mod github { #[tokio::test] async fn should_return_any_network_error() { let repo_details = given::repo_details(); - let webhook_url = given::a_webhook_url(&given::a_forge_alias(), &given::a_repo_alias()); + let repo_listen_url = given::a_repo_listen_url(&repo_details); let hostname = repo_details.forge.hostname(); let repo_path = &repo_details.repo_path; let mut net = given::a_network(); @@ -209,7 +209,7 @@ mod github { let forge = given::a_github_forge(&repo_details, net); - let_assert!(Err(_) = forge.list_webhooks(&webhook_url).await); + let_assert!(Err(_) = forge.list_webhooks(&repo_listen_url).await); } } @@ -271,8 +271,7 @@ mod github { repo_details.repo_config.is_some(), "repo_details needs to have repo_config for this test" ); - let webhook_url = - given::a_webhook_url(repo_details.forge.forge_alias(), &repo_details.repo_alias); + let repo_listen_url = given::a_repo_listen_url(&repo_details); let hostname = repo_details.forge.hostname(); let repo_path = &repo_details.repo_path; let mut net = given::a_network(); @@ -288,14 +287,14 @@ mod github { net.add_post_response( format!("https://api.{hostname}/repos/{repo_path}/hooks").as_str(), StatusCode::OK, - json!({"id": webhook_id, "config":{"url": webhook_url}}) + json!({"id": webhook_id, "config":{"url": repo_listen_url.to_string()}}) .to_string() .as_str(), ); let forge = given::a_github_forge(&repo_details, net); - let_assert!(Ok(registered_webhook) = forge.register_webhook(&webhook_url).await); + let_assert!(Ok(registered_webhook) = forge.register_webhook(&repo_listen_url).await); assert_eq!( registered_webhook.id(), &WebhookId::new(format!("{webhook_id}")) @@ -310,11 +309,10 @@ mod github { repo_details.repo_config.is_none(), "repo_details needs to NOT have repo_config for this test" ); - let webhook_url = - given::a_webhook_url(repo_details.forge.forge_alias(), &repo_details.repo_alias); + let repo_listen_url = given::a_repo_listen_url(&repo_details); let net = given::a_network(); let forge = given::a_github_forge(&repo_details, net); - let_assert!(Err(err) = forge.register_webhook(&webhook_url).await); + let_assert!(Err(err) = forge.register_webhook(&repo_listen_url).await); assert!( matches!(err, git::forge::webhook::Error::NoRepoConfig), "{err:?}" @@ -328,8 +326,7 @@ mod github { repo_details.repo_config.is_some(), "repo_details needs to have repo_config for this test" ); - let webhook_url = - given::a_webhook_url(repo_details.forge.forge_alias(), &repo_details.repo_alias); + let repo_listen_url = given::a_repo_listen_url(&repo_details); let hostname = repo_details.forge.hostname(); let repo_path = &repo_details.repo_path; let mut net = given::a_network(); @@ -338,8 +335,8 @@ mod github { hostname, repo_path, }; - let hook1 = with::ReturnedWebhook::new(given::a_github_webhook_id(), &webhook_url); - let hook2 = with::ReturnedWebhook::new(given::a_github_webhook_id(), &webhook_url); + let hook1 = with::ReturnedWebhook::new(given::a_github_webhook_id(), &repo_listen_url); + let hook2 = with::ReturnedWebhook::new(given::a_github_webhook_id(), &repo_listen_url); let hook3 = with::ReturnedWebhook::new(given::a_github_webhook_id(), &given::any_webhook_url()); let hooks = [hook1, hook2, hook3]; @@ -354,14 +351,14 @@ mod github { net.add_post_response( format!("https://api.{hostname}/repos/{repo_path}/hooks").as_str(), StatusCode::OK, - json!({"id": webhook_id, "config":{"url": webhook_url}}) + json!({"id": webhook_id, "config":{"url": repo_listen_url.to_string()}}) .to_string() .as_str(), ); let forge = given::a_github_forge(&repo_details, net); - let_assert!(Ok(registered_webhook) = forge.register_webhook(&webhook_url).await); + let_assert!(Ok(registered_webhook) = forge.register_webhook(&repo_listen_url).await); assert_eq!( registered_webhook.id(), &WebhookId::new(format!("{webhook_id}")) @@ -375,8 +372,7 @@ mod github { repo_details.repo_config.is_some(), "repo_details needs to have repo_config for this test" ); - let webhook_url = - given::a_webhook_url(repo_details.forge.forge_alias(), &repo_details.repo_alias); + let repo_listen_url = given::a_repo_listen_url(&repo_details); let hostname = repo_details.forge.hostname(); let repo_path = &repo_details.repo_path; let mut net = given::a_network(); @@ -396,7 +392,7 @@ mod github { let forge = given::a_github_forge(&repo_details, net); - let_assert!(Err(err) = forge.register_webhook(&webhook_url).await); + let_assert!(Err(err) = forge.register_webhook(&repo_listen_url).await); assert!( matches!(err, git::forge::webhook::Error::FailedToRegister(_)), "{err:?}" @@ -410,8 +406,7 @@ mod github { repo_details.repo_config.is_some(), "repo_details needs to have repo_config for this test" ); - let webhook_url = - given::a_webhook_url(repo_details.forge.forge_alias(), &repo_details.repo_alias); + let repo_listen_url = given::a_repo_listen_url(&repo_details); let hostname = repo_details.forge.hostname(); let repo_path = &repo_details.repo_path; let mut net = given::a_network(); @@ -430,7 +425,7 @@ mod github { let forge = given::a_github_forge(&repo_details, net); - let_assert!(Err(err) = forge.register_webhook(&webhook_url).await); + let_assert!(Err(err) = forge.register_webhook(&repo_listen_url).await); assert!( matches!(err, git::forge::webhook::Error::FailedToRegister(_)), "{err:?}" @@ -439,6 +434,8 @@ mod github { } pub mod with { + use git_next_core::server::RepoListenUrl; + use super::*; pub fn get_webhooks_by_page( @@ -476,21 +473,26 @@ mod github { pub config: Config, } impl ReturnedWebhook { - pub fn new(id: i64, url: &WebhookUrl) -> Self { + pub fn new(id: i64, url: &RepoListenUrl) -> Self { Self { id, - config: Config { url: url.clone() }, + config: Config { + url: url.to_string(), + }, } } } #[derive(Debug, Serialize)] pub struct Config { - pub url: WebhookUrl, + pub url: String, } } mod given { + use git::RepoDetails; + use git_next_core::server::RepoListenUrl; + use super::*; pub fn commit_states( @@ -615,11 +617,15 @@ mod github { kxio::network::MockNetwork::new() } - pub fn a_webhook_url(forge_alias: &ForgeAlias, repo_alias: &RepoAlias) -> WebhookUrl { - InboundWebhook::new(a_name()).url(forge_alias, repo_alias) + pub fn a_repo_listen_url(repo_details: &RepoDetails) -> RepoListenUrl { + RepoListenUrl::new(( + ListenUrl::new(a_name()), + repo_details.forge.forge_alias().clone(), + repo_details.repo_alias.clone(), + )) } - pub fn any_webhook_url() -> WebhookUrl { - given::a_webhook_url(&given::a_forge_alias(), &given::a_repo_alias()) + pub fn any_webhook_url() -> RepoListenUrl { + given::a_repo_listen_url(&given::repo_details()) } pub fn a_name() -> String { diff --git a/crates/forge-github/src/webhook/list.rs b/crates/forge-github/src/webhook/list.rs index e3afd37..1aaf039 100644 --- a/crates/forge-github/src/webhook/list.rs +++ b/crates/forge-github/src/webhook/list.rs @@ -1,13 +1,13 @@ // use crate as github; -use git_next_core::{git, server, WebhookId}; +use git_next_core::{git, server::RepoListenUrl, WebhookId}; use kxio::network; // https://docs.github.com/en/rest/repos/webhooks?apiVersion=2022-11-28#list-repository-webhooks pub async fn list( github: &github::Github, - webhook_url: &server::WebhookUrl, + repo_listen_url: &RepoListenUrl, ) -> git::forge::webhook::Result> { let mut ids: Vec = vec![]; let repo_details = &github.repo_details; @@ -39,7 +39,11 @@ pub async fn list( return Ok(ids); } for hook in list { - if hook.url().as_ref().starts_with(webhook_url.as_ref()) { + if hook + .url() + .as_ref() + .starts_with(&repo_listen_url.to_string()) + { ids.push(hook.id()); } } diff --git a/crates/forge-github/src/webhook/register.rs b/crates/forge-github/src/webhook/register.rs index a702a25..f26c6b6 100644 --- a/crates/forge-github/src/webhook/register.rs +++ b/crates/forge-github/src/webhook/register.rs @@ -1,13 +1,13 @@ // use crate::{self as github, webhook}; -use git_next_core::{git, server, RegisteredWebhook, WebhookAuth, WebhookId}; +use git_next_core::{git, server::RepoListenUrl, RegisteredWebhook, WebhookAuth, WebhookId}; use kxio::network; // https://docs.github.com/en/rest/repos/webhooks?apiVersion=2022-11-28#create-a-repository-webhook pub async fn register( github: &github::Github, - webhook_url: &server::WebhookUrl, + repo_listen_url: &RepoListenUrl, ) -> git::forge::webhook::Result { let repo_details = &github.repo_details; if repo_details.repo_config.is_none() { @@ -15,7 +15,7 @@ pub async fn register( }; // remove any lingering webhooks for the same URL - let existing_webhook_ids = webhook::list(github, webhook_url).await?; + let existing_webhook_ids = webhook::list(github, repo_listen_url).await?; for webhook_id in existing_webhook_ids { webhook::unregister(github, &webhook_id).await?; } @@ -35,7 +35,7 @@ pub async fn register( "active": true, "events": ["push"], "config": { - "url": webhook_url.as_ref(), + "url": repo_listen_url.as_ref(), "content_type": "json", "secret": authorisation.to_string(), "insecure_ssl": "0",