git-gemini-forge 0.5.2

A simple Gemini server that serves a read-only view of public repositories from a Git forge.
use crate::network::{forgejo::ForgejoApi, gitlab::GitLabApi, ForgeApi, ForgeApiKind};
use secrecy::SecretString;
use std::sync::OnceLock;
use url::Url;

#[derive(Clone, Debug)]
pub struct Config {
	/// The version of this software. (e.g. "1.3.0")
	pub crate_version: &'static str,

	/// Whether we're permitted to list users. (e.g. true)
	pub forge_list_users: bool,

	/// The kind of git forge software we're dealing with. (e.g. "forgejo")
	pub forge_kind: ForgeApiKind,

	/// The URL where the forge lives. (e.g. "https://git.average.name")
	pub forge_url: Url,

	/// A value that grants us permission to call the forge's API.
	pub forge_secret_key: Option<SecretString>,

	/// The directory to look for certificate files. (e.g. ".certs")
	pub certs_dir: String,
}

impl Default for Config {
	fn default() -> Self {
		Self {
			crate_version: "x.x.x",
			forge_list_users: false,
			forge_kind: ForgeApiKind::Forgejo,
			forge_url: Url::parse("http://localhost:3000").expect("Valid default URL"),
			forge_secret_key: None,
			certs_dir: String::from(".certs"),
		}
	}
}

impl Config {
	/// Constructs a forge API sentinel.
	pub fn forge_api(&self) -> &'static Box<dyn ForgeApi> {
		// Only instantiate the ForgeApi once.
		// NOTE: This assumes there is only ever one instance of Config.
		static API: OnceLock<Box<dyn ForgeApi>> = OnceLock::new();
		return API.get_or_init(|| {
			let kind = self.forge_kind;
			let forge_url = self.forge_url.clone();
			let private_token = &self.forge_secret_key;
			println!("Preparing {kind} API");

			match kind {
				ForgeApiKind::Forgejo | ForgeApiKind::Gitea => {
					// Forgejo == Gitea as far as our use of the APIs is concerned
					return Box::new(ForgejoApi::from_url(forge_url, kind));
				}
				ForgeApiKind::GitLab => {
					return Box::new(GitLabApi::from_url(forge_url, kind, private_token));
				}
			};
		});
	}
}

#[cfg(test)]
mod tests {
	use super::*;

	#[test]
	fn test_forge_api_same_each_time() {
		let mut config = Config::default();
		let api1 = config.forge_api();

		config.forge_kind = ForgeApiKind::Gitea;
		let api2 = config.forge_api();

		assert_eq!(api1.kind(), api2.kind());
	}
}