// 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::{ config::{ForgeAlias, ForgeConfig, RepoAlias}, newtype, }; #[derive(Debug, thiserror::Error)] pub enum Error { #[error("fs: {0}")] KxioFs(#[from] kxio::fs::Error), #[error("deserialise toml: {0}")] TomlDe(#[from] toml::de::Error), #[error("parse IP addres/port: {0}")] AddressParse(#[from] std::net::AddrParseError), } type Result = core::result::Result; /// Mapped from the `git-next-server.toml` file #[derive( Clone, Debug, derive_more::From, PartialEq, Eq, PartialOrd, Ord, derive_more::AsRef, serde::Deserialize, derive_more::Constructor, )] pub struct ServerConfig { listen: Listen, shout: Shout, storage: ServerStorage, pub forge: BTreeMap, } impl ServerConfig { #[tracing::instrument(skip_all)] pub fn load(fs: &FileSystem) -> Result { let file = fs.base().join("git-next-server.toml"); info!(?file, ""); let str = fs.file_read_to_string(&file)?; Ok(toml::from_str(&str)?) } pub fn forges(&self) -> impl Iterator { self.forge .iter() .map(|(alias, forge)| (ForgeAlias::new(alias.clone()), forge)) } pub const fn storage(&self) -> &ServerStorage { &self.storage } pub const fn shout(&self) -> &Shout { &self.shout } pub const fn listen(&self) -> &Listen { &self.listen } 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()) } } /// Defines the port the server will listen to for incoming webhooks messages #[derive( Clone, Debug, derive_more::From, PartialEq, Eq, PartialOrd, Ord, derive_more::AsRef, serde::Deserialize, derive_more::Constructor, )] pub struct Http { addr: String, port: u16, } impl Http { fn socket_addr(&self) -> Result { Ok(SocketAddr::from_str(&format!( "{}:{}", self.addr, self.port ))?) } } /// The directory to store server data, such as cloned repos #[derive( Clone, Debug, derive_more::From, PartialEq, Eq, PartialOrd, Ord, derive_more::AsRef, serde::Deserialize, derive_more::Constructor, )] pub struct ServerStorage { path: PathBuf, } impl ServerStorage { pub fn path(&self) -> &Path { self.path.as_path() } } /// Defines the Webhook Forges should send updates to /// Must be an address that is accessible from the remote forge #[derive( Clone, Debug, Default, derive_more::From, PartialEq, Eq, PartialOrd, Ord, derive_more::AsRef, serde::Deserialize, )] pub struct Shout { webhook: Option, } impl Shout { pub const fn new_webhook(webhook: OutboundWebhook) -> Self { Self { webhook: Some(webhook), } } pub const fn webhook(&self) -> Option<&OutboundWebhook> { self.webhook.as_ref() } pub fn webhook_url(&self) -> Option { self.webhook.clone().map(|x| x.url) } pub fn webhook_secret(&self) -> Option> { self.webhook.clone().map(|x| x.secret).map(Secret::new) } } #[derive( Clone, Debug, derive_more::From, PartialEq, Eq, PartialOrd, Ord, serde::Deserialize, derive_more::Constructor, )] pub struct OutboundWebhook { url: String, secret: String, } impl OutboundWebhook { pub fn url(&self) -> &str { self.url.as_ref() } pub fn secret(&self) -> Secret { Secret::new(self.secret.clone()) } }