// use std::{ collections::BTreeMap, net::SocketAddr, path::{Path, PathBuf}, str::FromStr, }; use kxio::fs::FileSystem; use secrecy::Secret; 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 { http: Http, webhook: InboundWebhook, notification: Notification, 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 notification(&self) -> &Notification { &self.notification } pub const fn inbound_webhook(&self) -> &InboundWebhook { &self.webhook } pub fn http(&self) -> Result { self.http.socket_addr() } } /// 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 ))?) } } /// 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, 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() } } /// Identifier for the type of Notification #[derive( Clone, Default, Debug, derive_more::From, PartialEq, Eq, PartialOrd, Ord, serde::Deserialize, Copy, )] pub enum NotificationType { #[default] None, Webhook, } /// 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, )] pub struct Notification { r#type: NotificationType, webhook: Option, } impl Notification { pub const fn none() -> Self { Self { r#type: NotificationType::None, webhook: None, } } pub const fn new_webhook(webhook: OutboundWebhook) -> Self { Self { r#type: NotificationType::Webhook, webhook: Some(webhook), } } pub const fn r#type(&self) -> NotificationType { self.r#type } 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) } } impl Default for Notification { fn default() -> Self { Self::none() } } #[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()) } }