use axum::http::Method;
use axum::response::Response;
use std::future::Future;
use std::pin::Pin;
pub type CustomViewFuture = Pin<Box<dyn Future<Output = Response> + Send + 'static>>;
pub type CustomViewHandler =
fn(crate::sql::Pool, axum::http::Request<axum::body::Body>) -> CustomViewFuture;
pub struct AdminCustomView {
pub table: &'static str,
pub suffix: &'static str,
pub method: Method,
pub label: &'static str,
pub handler: CustomViewHandler,
}
inventory::collect!(AdminCustomView);
#[must_use]
pub fn for_table(table: &str) -> Vec<&'static AdminCustomView> {
inventory::iter::<AdminCustomView>
.into_iter()
.filter(|v| v.table == table)
.collect()
}
pub(crate) const RESERVED_SUFFIXES: &[&str] = &["", "new", "__action", "__autocomplete"];
#[must_use]
pub(crate) fn is_reserved(suffix: &str) -> bool {
let trimmed = suffix.trim_matches('/');
if RESERVED_SUFFIXES.iter().any(|r| *r == trimmed) {
return true;
}
matches!(trimmed, "{pk}" | "{pk}/edit" | "{pk}/delete")
}
#[macro_export]
macro_rules! register_admin_view {
($table:expr, $suffix:expr, $method:expr, $label:expr, $handler:expr $(,)?) => {
$crate::inventory::submit! {
$crate::admin::custom_views::AdminCustomView {
table: $table,
suffix: $suffix,
method: $method,
label: $label,
handler: {
fn __rustango_admin_view_handler(
pool: $crate::sql::Pool,
req: ::axum::http::Request<::axum::body::Body>,
) -> $crate::admin::custom_views::CustomViewFuture {
::std::boxed::Box::pin(($handler)(pool, req))
}
__rustango_admin_view_handler
},
}
}
};
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn reserved_suffixes_includes_built_in_routes() {
assert!(is_reserved(""));
assert!(is_reserved("new"));
assert!(is_reserved("__action"));
assert!(is_reserved("__autocomplete"));
assert!(is_reserved("{pk}"));
assert!(is_reserved("{pk}/edit"));
assert!(is_reserved("{pk}/delete"));
}
#[test]
fn reserved_suffixes_strips_surrounding_slashes() {
assert!(is_reserved("/new"));
assert!(is_reserved("/new/"));
assert!(is_reserved("new/"));
}
#[test]
fn user_suffixes_are_not_reserved() {
assert!(!is_reserved("duplicate"));
assert!(!is_reserved("copy/{id}"));
assert!(!is_reserved("export.csv"));
assert!(!is_reserved("preview"));
}
#[test]
fn for_table_returns_empty_when_no_registrations() {
let v = for_table("nonexistent_table");
assert!(v.is_empty());
}
}