git-gemini-forge 0.2.0

A simple Gemini server that serves a read-only view of public repositories from a Git forge.
mod config;
mod handlers;
mod network;

use config::config;
use url::Url;
use windmark::response::Response;

// TODO: Somehow ensure sure we never panic except in expected places

#[windmark::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
	let cfg: config::Config = config()?; // Panics if misconfigured
	let version: &'static str = cfg.crate_version;

	return windmark::router::Router::new()
		.set_private_key_file(format!("{}/key.pem", cfg.certs_dir))
		.set_certificate_file(format!("{}/cert.pem", cfg.certs_dir))
		.add_header(|_| String::from("# Forgejo Proxy"))
		.add_footer(move |ctx: windmark::context::RouteContext| {
			let base: &str = ctx.url.path().split("/").collect::<Vec<&str>>()[1];
			println!("base: '{base}'");
			let mode_dest: &str;
			if base == "users" {
				mode_dest = "/ Recent Activity";
			} else {
				mode_dest = "/users Users";
			}
			return format!("\n=> {mode_dest}\n\nPowered by git-gemini-forge v{version}\n=> https://git.average.name/AverageHelper/git-gemini-forge View source")
		})
		.mount("/", |_| {
			async {
				let cfg: config::Config = config().unwrap(); // Remake the config, since Rust is weird about borrow symantics
				match handlers::root::handler(&cfg) {
					Ok(res) => res,
					Err(network::error::Error::NetworkFailure) => Response::temporary_failure("Couldn't communicate with the forge."),
					Err(network::error::Error::UnexpectedResponse) => Response::temporary_failure("The upstream gave us a response we don't understand"),
				}
			}
		})
		.mount("/users", |_| {
			async {
				let cfg: config::Config = config().unwrap(); // Remake the config, since Rust is weird about borrow symantics
				match handlers::users::handler(&cfg) {
					Ok(res) => res,
					Err(network::error::Error::NetworkFailure) => Response::temporary_failure("Couldn't communicate with the forge."),
					Err(network::error::Error::UnexpectedResponse) => Response::temporary_failure("The upstream gave us a response we don't understand"),
				}
			}
		})
		.mount("/:user", |ctx| {
			async move {
				let user: &str = ctx.url.path().split("/").last().unwrap_or("");
				let cfg: config::Config = config().unwrap(); // Remake the config, since Rust is weird about borrow symantics
				match handlers::user::handler(&cfg, &user) {
					Ok(res) => res,
					Err(network::error::Error::NetworkFailure) => Response::temporary_failure("Couldn't communicate with the forge."),
					Err(network::error::Error::UnexpectedResponse) => Response::temporary_failure("The upstream gave us a response we don't understand"),
				}
			}
		})
		.mount("/:user/:repo", |ctx| {
			async move {
				let user: &str = ctx.url.path().split("/").collect::<Vec<&str>>()[1];
				let repo: &str = ctx.url.path().split("/").collect::<Vec<&str>>()[2];
				let cfg: config::Config = config().unwrap(); // Remake the config, since Rust is weird about borrow symantics
				match handlers::repo::handler(&cfg, &user, &repo) {
					Ok(res) => res,
					Err(network::error::Error::NetworkFailure) => Response::temporary_failure("Couldn't communicate with the forge."),
					Err(network::error::Error::UnexpectedResponse) => Response::temporary_failure("The upstream gave us a response we don't understand"),
				}
			}
		})
		// .mount("/*", windmark::not_found!("Nothing to be found here!"))
		.set_error_handler(|error| {
			let failure_addr: Url = error.url;
			println!("Failed to serve {}", failure_addr.as_str());
			return Response::temporary_failure("Something went wrong!");
		})
		.run()
		.await
}