git-next/crates/core/src/config/server.rs
Paul Campbell 8c19680056
Some checks failed
Rust / build (push) Successful in 1m15s
ci/woodpecker/push/cron-docker-builder Pipeline was successful
ci/woodpecker/push/push-next Pipeline was successful
ci/woodpecker/push/tag-created Pipeline was successful
Release Please / Release-plz (push) Failing after 10m22s
ci/woodpecker/cron/cron-docker-builder Pipeline was successful
ci/woodpecker/cron/push-next Pipeline was successful
ci/woodpecker/cron/tag-created Pipeline was successful
refactor: macros use a more common syntax
Parameters were separated by ':', but are now separated by ','.
2024-08-06 20:06:39 +01:00

351 lines
7 KiB
Rust

//
use std::{
collections::BTreeMap,
net::SocketAddr,
ops::Deref,
path::{Path, PathBuf},
str::FromStr,
};
use derive_more::{Constructor, 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<T> = core::result::Result<T, Error>;
/// 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 AppConfig {
listen: Listen,
shout: Shout,
storage: Storage,
pub forge: BTreeMap<String, ForgeConfig>,
}
impl AppConfig {
#[tracing::instrument(skip_all)]
pub fn load(fs: &FileSystem) -> Result<Self> {
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<Item = (ForgeAlias, &ForgeConfig)> {
self.forge
.iter()
.map(|(alias, forge)| (ForgeAlias::new(alias.clone()), forge))
}
#[must_use]
pub const fn storage(&self) -> &Storage {
&self.storage
}
#[must_use]
pub const fn shout(&self) -> &Shout {
&self.shout
}
#[must_use]
pub const fn listen(&self) -> &Listen {
&self.listen
}
/// Returns the `SocketAddr` to listen to for incoming webhooks.
///
/// # Errors
///
/// Will return an `Err` if the IP address or port from the config file are invalid.
pub fn listen_socket_addr(&self) -> Result<SocketAddr> {
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)
// }
#[must_use]
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 {
#[must_use]
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<RepoListenUrl> 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<SocketAddr> {
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 Storage {
path: PathBuf,
}
impl Storage {
#[must_use]
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,
Constructor,
)]
pub struct Shout {
webhook: Option<OutboundWebhook>,
email: Option<EmailConfig>,
desktop: Option<bool>,
}
impl Shout {
#[must_use]
pub const fn webhook(&self) -> Option<&OutboundWebhook> {
self.webhook.as_ref()
}
#[cfg(test)]
pub(crate) fn webhook_url(&self) -> Option<String> {
self.webhook.clone().map(|x| x.url)
}
pub fn webhook_secret(&self) -> Option<Secret<String>> {
self.webhook.clone().map(|x| x.secret).map(Secret::new)
}
#[must_use]
pub const fn email(&self) -> Option<&EmailConfig> {
self.email.as_ref()
}
#[must_use]
pub const fn desktop(&self) -> Option<bool> {
self.desktop
}
}
#[derive(
Clone,
Debug,
derive_more::From,
PartialEq,
Eq,
PartialOrd,
Ord,
serde::Deserialize,
derive_more::Constructor,
)]
pub struct OutboundWebhook {
url: String,
secret: String,
}
impl OutboundWebhook {
#[must_use]
pub fn url(&self) -> &str {
self.url.as_ref()
}
#[must_use]
pub fn secret(&self) -> Secret<String> {
Secret::new(self.secret.clone())
}
}
#[derive(
Clone,
Debug,
derive_more::From,
PartialEq,
Eq,
PartialOrd,
Ord,
serde::Deserialize,
derive_more::Constructor,
)]
pub struct EmailConfig {
from: String,
to: String,
// email will be sent via sendmail, unless smtp is specified
smtp: Option<SmtpConfig>,
}
impl EmailConfig {
#[must_use]
pub fn from(&self) -> &str {
&self.from
}
#[must_use]
pub fn to(&self) -> &str {
&self.to
}
#[must_use]
pub const fn smtp(&self) -> Option<&SmtpConfig> {
self.smtp.as_ref()
}
}
#[derive(
Clone,
Debug,
derive_more::From,
PartialEq,
Eq,
PartialOrd,
Ord,
serde::Deserialize,
derive_more::Constructor,
)]
pub struct SmtpConfig {
hostname: String,
username: String,
password: String,
}
impl SmtpConfig {
#[must_use]
pub fn username(&self) -> &str {
&self.username
}
#[must_use]
pub fn password(&self) -> &str {
&self.password
}
#[must_use]
pub fn hostname(&self) -> &str {
&self.hostname
}
}