use crate::routers::reverse::{clear_global_reverser, set_global_reverser};
use crate::routers::server_router::ServerRouter;
use once_cell::sync::OnceCell;
use std::sync::Arc;
use std::sync::PoisonError;
use std::sync::RwLock as StdRwLock;
static GLOBAL_ROUTER: OnceCell<StdRwLock<Option<Arc<ServerRouter>>>> = OnceCell::new();
static GLOBAL_DI_REGISTRATIONS: OnceCell<StdRwLock<Option<reinhardt_di::DiRegistrationList>>> =
OnceCell::new();
pub fn register_router(mut router: ServerRouter) {
let errors = router.register_all_routes();
for error in &errors {
tracing::warn!("{}", error);
}
register_router_arc(Arc::new(router));
}
pub fn register_router_arc(router: Arc<ServerRouter>) {
set_global_reverser(router.reverser.clone());
let cell = GLOBAL_ROUTER.get_or_init(|| StdRwLock::new(None));
let mut guard = cell.write().unwrap_or_else(PoisonError::into_inner);
*guard = Some(router);
}
pub fn get_router() -> Option<Arc<ServerRouter>> {
GLOBAL_ROUTER
.get()
.and_then(|cell| cell.read().unwrap_or_else(PoisonError::into_inner).clone())
}
pub fn is_router_registered() -> bool {
GLOBAL_ROUTER
.get()
.map(|cell| {
cell.read()
.unwrap_or_else(PoisonError::into_inner)
.is_some()
})
.unwrap_or(false)
}
pub fn register_di_registrations(list: reinhardt_di::DiRegistrationList) {
let cell = GLOBAL_DI_REGISTRATIONS.get_or_init(|| StdRwLock::new(None));
let mut guard = cell.write().unwrap_or_else(PoisonError::into_inner);
match guard.as_mut() {
Some(existing) => existing.merge(list),
None => *guard = Some(list),
}
}
pub fn take_di_registrations() -> Option<reinhardt_di::DiRegistrationList> {
GLOBAL_DI_REGISTRATIONS
.get()
.and_then(|cell| cell.write().unwrap_or_else(PoisonError::into_inner).take())
}
pub fn get_router_di_context() -> Option<Arc<reinhardt_di::InjectionContext>> {
get_router().and_then(|router| router.di_context().cloned())
}
pub fn clear_router() {
if let Some(cell) = GLOBAL_ROUTER.get() {
let mut guard = cell.write().unwrap_or_else(PoisonError::into_inner);
*guard = None;
}
if let Some(cell) = GLOBAL_DI_REGISTRATIONS.get() {
let mut guard = cell.write().unwrap_or_else(PoisonError::into_inner);
*guard = None;
}
clear_global_reverser();
}
#[cfg(test)]
mod tests {
use super::*;
use reinhardt_testkit::resource::{TeardownGuard, TestResource};
use rstest::{fixture, rstest};
struct CleanGlobalRouter;
impl TestResource for CleanGlobalRouter {
fn setup() -> Self {
clear_router();
Self
}
fn teardown(&mut self) {
clear_router();
}
}
#[fixture]
fn env() -> TeardownGuard<CleanGlobalRouter> {
TeardownGuard::new()
}
#[rstest]
#[serial_test::serial(global_router)]
fn returns_none_when_no_router_registered(_env: TeardownGuard<CleanGlobalRouter>) {
let result = get_router_di_context();
assert!(result.is_none());
}
#[rstest]
#[serial_test::serial(global_router)]
fn returns_none_when_router_has_no_di_context(_env: TeardownGuard<CleanGlobalRouter>) {
let router = crate::routers::ServerRouter::new();
register_router(router);
let result = get_router_di_context();
assert!(result.is_none());
}
#[rstest]
#[serial_test::serial(global_router)]
fn returns_context_when_router_has_di_context(_env: TeardownGuard<CleanGlobalRouter>) {
let singleton_scope = Arc::new(reinhardt_di::SingletonScope::new());
singleton_scope.set(42u32);
let di_ctx = Arc::new(reinhardt_di::InjectionContext::builder(singleton_scope).build());
let router = crate::routers::ServerRouter::new().with_di_context(di_ctx);
register_router(router);
let result = get_router_di_context();
assert!(result.is_some());
let ctx = result.unwrap();
let value = ctx.singleton_scope().get::<u32>();
assert!(value.is_some());
assert_eq!(*value.unwrap(), 42);
}
#[rstest]
#[serial_test::serial(global_router)]
fn global_reverser_populated_after_register_router(_env: TeardownGuard<CleanGlobalRouter>) {
let router = crate::routers::ServerRouter::new().function_named(
"/users/{id}/",
hyper::Method::GET,
"user-detail",
dummy_handler,
);
register_router(router);
let reverser = crate::routers::UrlReverser::try_from_global();
assert!(reverser.is_some());
let reverser = reverser.unwrap();
assert!(reverser.has_route("user-detail"));
}
#[rstest]
#[serial_test::serial(global_router)]
fn global_reverser_cleared_on_clear_router(_env: TeardownGuard<CleanGlobalRouter>) {
let router = crate::routers::ServerRouter::new().function_named(
"/health/",
hyper::Method::GET,
"health-check",
dummy_handler,
);
register_router(router);
assert!(crate::routers::UrlReverser::try_from_global().is_some());
clear_router();
assert!(crate::routers::UrlReverser::try_from_global().is_none());
}
#[rstest]
#[serial_test::serial(global_router)]
fn server_router_reverse_works_after_global_population(_env: TeardownGuard<CleanGlobalRouter>) {
let router = crate::routers::ServerRouter::new().function_named(
"/users/{id}/",
hyper::Method::GET,
"user-detail",
dummy_handler,
);
register_router(router);
let router = get_router().unwrap();
let url = router.reverse("user-detail", &[("id", "42")]);
assert_eq!(url, Some("/users/42/".to_string()));
}
async fn dummy_handler(
_req: reinhardt_http::Request,
) -> reinhardt_core::exception::Result<reinhardt_http::Response> {
Ok(reinhardt_http::Response::ok())
}
}