diff --git a/.idea/acrate.iml b/.idea/acrate.iml
index 8ef4dcf..01a6e70 100644
--- a/.idea/acrate.iml
+++ b/.idea/acrate.iml
@@ -12,6 +12,7 @@
+
diff --git a/Cargo.toml b/Cargo.toml
index d528923..1e44dab 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,3 +1,3 @@
[workspace]
resolver = "2"
-members = ["acrate_database", "acrate_rd", "acrate_nodeinfo", "acrate_rdserver"]
+members = ["acrate_database", "acrate_rd", "acrate_nodeinfo", "acrate_rdserver", "acrate_utils"]
diff --git a/acrate_rdserver/Cargo.toml b/acrate_rdserver/Cargo.toml
index ac749de..fe2421e 100644
--- a/acrate_rdserver/Cargo.toml
+++ b/acrate_rdserver/Cargo.toml
@@ -12,12 +12,12 @@ categories = ["web-programming"]
[dependencies]
acrate_database = { path = "../acrate_database", features = ["connect"] }
acrate_rd = { path = "../acrate_rd" }
+acrate_utils = { path = "../acrate_utils" }
anyhow = "1.0.93"
axum = { version = "0.7.7", features = ["macros"] }
axum-extra = { version = "0.9.4", features = ["query"] }
log = { version = "0.4.22", features = ["std", "max_level_trace", "release_max_level_debug"] }
micronfig = "0.3.0"
-minijinja = "2.5.0"
pretty_env_logger = "0.5.0"
quick-xml = { version = "0.37.0", features = ["serialize"] }
serde = { version = "1.0.215", features = ["derive"] }
diff --git a/acrate_rdserver/src/config.rs b/acrate_rdserver/src/config.rs
index 535a5c4..786b401 100644
--- a/acrate_rdserver/src/config.rs
+++ b/acrate_rdserver/src/config.rs
@@ -1,3 +1,3 @@
micronfig::config!(
- ACRATE_WEBFINGER_BIND_ADDRESS: String,
+ ACRATE_WEBFINGER_BIND_ADDRESS: String > std::net::SocketAddr,
);
diff --git a/acrate_rdserver/src/main.rs b/acrate_rdserver/src/main.rs
index 60dffed..f926ac2 100644
--- a/acrate_rdserver/src/main.rs
+++ b/acrate_rdserver/src/main.rs
@@ -1,42 +1,19 @@
-use std::sync::Arc;
-use anyhow::Context;
-use axum::Extension;
+use axum::routing::get;
+use acrate_utils::web_server;
mod config;
mod route;
-
#[tokio::main]
-async fn main() -> anyhow::Result {
- pretty_env_logger::init();
- log::debug!("Logging initialized!");
-
- log::trace!("Creating Minijinja environment...");
- let mut mj = minijinja::Environment::<'static>::new();
-
- log::trace!("Adding webfinger page to the Minijinja environment...");
- mj.add_template("rd.html.j2", include_str!("rd.html.j2"))
- .expect("rd.html.j2 to be a valid Minijinja template");
-
- log::trace!("Creating Axum router...");
- let app = axum::Router::new()
- .route("/*path", axum::routing::get(route::webfinger_handler))
- .route("/.healthcheck", axum::routing::get(route::healthcheck_handler))
- .layer(Extension(Arc::new(mj)));
- log::trace!("Axum router created successfully!");
-
- log::trace!("Creating Tokio listener...");
- let bind_address = config::ACRATE_WEBFINGER_BIND_ADDRESS();
- let listener = tokio::net::TcpListener::bind(bind_address)
- .await
- .context("failed to bind listener to address")?;
- log::trace!("Tokio listener bound to: {bind_address}");
-
- log::info!("Starting server...");
- axum::serve(listener, app)
- .await
- .context("server exited with error")?;
-
- log::error!("Server exited with no error, panicking.");
- panic!("server exited with no error");
+async fn main() {
+ web_server!(
+ on: *config::ACRATE_WEBFINGER_BIND_ADDRESS(),
+ templates: [
+ "rd.html.j2"
+ ],
+ routes: {
+ "/*path" => get(route::webfinger_handler),
+ "/.healthcheck" => get(route::healthcheck_handler)
+ }
+ );
}
diff --git a/acrate_rdserver/src/route.rs b/acrate_rdserver/src/route.rs
index a601283..34fa201 100644
--- a/acrate_rdserver/src/route.rs
+++ b/acrate_rdserver/src/route.rs
@@ -1,5 +1,4 @@
use std::iter::IntoIterator;
-use std::sync::Arc;
use axum::Extension;
use axum::extract::Path;
use axum::http::{HeaderMap, Response, StatusCode};
@@ -11,6 +10,7 @@ use acrate_database::diesel::GroupedBy;
use acrate_database::meta::{MetaAlias, MetaLink, MetaLinkProperty, MetaLinkTitle, MetaProperty, MetaSubject};
use acrate_rd::jrd::ResourceDescriptorLinkJRD;
use acrate_rd::xrd::{ResourceDescriptorLinkXRD, ResourceDescriptorPropertyXRD, ResourceDescriptorTitleXRD};
+use acrate_utils::ext::{minijinja, ExtMj};
pub async fn healthcheck_handler() -> Result {
log::debug!("Handling an healthcheck request!");
@@ -36,7 +36,7 @@ pub async fn webfinger_handler(
Path(path): Path,
Query(WebfingerQuery {resource, rel}): Query,
headers: HeaderMap,
- Extension(mj): Extension>>,
+ Extension(mj): ExtMj,
) -> Result, StatusCode> {
log::debug!("Handling a WebFinger request!");
diff --git a/acrate_utils/Cargo.toml b/acrate_utils/Cargo.toml
new file mode 100644
index 0000000..1497ca2
--- /dev/null
+++ b/acrate_utils/Cargo.toml
@@ -0,0 +1,22 @@
+[package]
+name = "acrate_utils"
+version = "0.3.0"
+authors = ["Stefano Pigozzi "]
+edition = "2021"
+description = "Utilities for the acrate project"
+repository = "https://forge.steffo.eu/unimore/tirocinio-canali-steffo-acrate"
+license = "EUPL-1.2"
+keywords = []
+categories = []
+
+[dependencies]
+axum = { version = "0.7.7", features = ["macros"] }
+axum-extra = { version = "0.9.4", features = ["query"] }
+log = { version = "0.4.22", features = ["std", "max_level_trace", "release_max_level_debug"] }
+pretty_env_logger = "0.5.0"
+mediatype = { version = "0.19.18", features = ["serde"] }
+minijinja = "2.5.0"
+tokio = { version = "1.41.1", features = ["net"] }
+
+[lints.clippy]
+tabs-in-doc-comments = "allow"
diff --git a/acrate_utils/src/ext.rs b/acrate_utils/src/ext.rs
new file mode 100644
index 0000000..99fef0c
--- /dev/null
+++ b/acrate_utils/src/ext.rs
@@ -0,0 +1,6 @@
+use std::sync::Arc;
+use axum::Extension;
+
+pub use minijinja;
+
+pub type ExtMj = Extension>>;
diff --git a/acrate_utils/src/init.rs b/acrate_utils/src/init.rs
new file mode 100644
index 0000000..e5c73d2
--- /dev/null
+++ b/acrate_utils/src/init.rs
@@ -0,0 +1,49 @@
+use std::net::SocketAddr;
+use std::sync::Arc;
+use axum::Extension;
+
+/// Initialize logging with [`pretty_env_logger::init`].
+pub fn init_logging() {
+ log::trace!("Initializing logging...");
+ pretty_env_logger::init();
+ log::trace!("Initialized logging!");
+}
+
+/// Initialize a [`tokio::net::TcpListener`] bound to the given [`SocketAddr`].
+///
+/// # Panics
+///
+/// If unable to bind to the given [`SocketAddr`].
+///
+pub async fn init_listener(bind: SocketAddr) -> tokio::net::TcpListener {
+ log::trace!("Creating Tokio listener bound to: {bind:#?}");
+ let listener = tokio::net::TcpListener::bind(bind)
+ .await
+ .unwrap_or_else(|_| panic!("Failed to bind to: {bind:#?}"));
+ log::trace!("Created Tokio listener: {listener:#?}");
+
+ listener
+}
+
+/// Initialize a static [`minijinja::Environment`].
+pub fn init_minijinja() -> minijinja::Environment<'static> {
+ log::trace!("Creating Minijinja environment...");
+ let mj = minijinja::Environment::<'static>::new();
+ log::trace!("Created Minijinja environment: {mj:#?}");
+
+ mj
+}
+
+/// Initialize an [`axum::Router`] with the given [`minijinja::Environment`] available.
+pub fn init_router(mj: minijinja::Environment<'static>) -> axum::Router {
+ log::trace!("Creating Arc for the minijinja Environment...");
+ let mj = Arc::new(mj);
+ log::trace!("Created Arc for the minijinja Environment: {mj:#?}");
+
+ log::trace!("Creating Axum router...");
+ let router = axum::Router::new()
+ .layer(Extension(mj));
+ log::trace!("Created Axum router: {router:#?}");
+
+ router
+}
diff --git a/acrate_utils/src/lib.rs b/acrate_utils/src/lib.rs
new file mode 100644
index 0000000..1187539
--- /dev/null
+++ b/acrate_utils/src/lib.rs
@@ -0,0 +1,50 @@
+pub mod ext;
+pub mod init;
+pub mod run;
+
+
+/// Add the template file at the given path to the given [`minijinja::Environment`].
+#[macro_export]
+macro_rules! add_minijinja_template {
+ ($mj:ident, $path:literal) => {
+ log::trace!("Adding template to minijinja Environment: {:?} ← {:#?}", $mj, $path);
+ $mj.add_template($path, include_str!($path))
+ .expect(concat!("Invalid Minijinja template: ", $path));
+ log::trace!("Added template to minijinja Environment: {:?} ← {:#?}", $mj, $path);
+ };
+}
+
+/// Add the given route to the [`axum::Router`].
+#[macro_export]
+macro_rules! add_axum_route {
+ ($router:ident, $path:literal, $route:expr) => {
+ log::trace!("Adding route to axum Router: {:?} ← {:#?}", $router, $path);
+ let $router = $router.route($path, $route);
+ log::trace!("Added route to axum Router: {:?} ← {:#?}", $router, $path);
+ };
+}
+
+#[macro_export]
+macro_rules! web_server {
+ (
+ on: $socket_addr:expr,
+ templates: [
+ $( $template_path:literal ),+
+ ],
+ routes: {
+ $( $path:literal => $route:expr ),+
+ }
+ ) => {
+ $crate::init::init_logging();
+ let listener = $crate::init::init_listener($socket_addr).await;
+ let mut mj = $crate::init::init_minijinja();
+ $(
+ $crate::add_minijinja_template!(mj, $template_path);
+ )+
+ let router = $crate::init::init_router(mj);
+ $(
+ $crate::add_axum_route!(router, $path, $route);
+ )+
+ $crate::run::run_server(listener, router).await
+ };
+}
diff --git a/acrate_utils/src/run.rs b/acrate_utils/src/run.rs
new file mode 100644
index 0000000..3185be8
--- /dev/null
+++ b/acrate_utils/src/run.rs
@@ -0,0 +1,25 @@
+/// Run a web server with [`axum::serve`], using the given [`tokio::net::TcpListener`] and [`axum::Router`].
+///
+/// # Exits
+///
+/// Once the server is terminated, the process will exit with either:
+///
+/// - `0`, if [`axum::serve`] returned [`Ok`];
+/// - `1`, if [`axum::serve`] returned [`Err`].
+///
+pub async fn run_server(listener: tokio::net::TcpListener, application: axum::Router) -> std::convert::Infallible {
+ log::trace!("Serving application: {listener:#?} → {application:#?}");
+ let result = axum::serve(listener, application)
+ .await;
+
+ match result {
+ Ok(()) => {
+ log::error!("Server exited gracefully.");
+ std::process::exit(0);
+ }
+ Err(e) => {
+ log::error!("Server exited with an error: {e:#?}");
+ std::process::exit(1);
+ }
+ }
+}