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 hyper::Method;
use reinhardt_core::endpoint::EndpointInfo;
use reinhardt_http::{Handler, Request, Response};
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()
}
struct UserDetail;
struct HealthCheck;
impl EndpointInfo for UserDetail {
fn path() -> &'static str {
"/users/{id}/"
}
fn method() -> Method {
Method::GET
}
fn name() -> &'static str {
"user-detail"
}
}
impl EndpointInfo for HealthCheck {
fn path() -> &'static str {
"/health/"
}
fn method() -> Method {
Method::GET
}
fn name() -> &'static str {
"health-check"
}
}
#[async_trait::async_trait]
impl Handler for UserDetail {
async fn handle(&self, _req: Request) -> reinhardt_core::exception::Result<Response> {
Ok(Response::ok())
}
}
#[async_trait::async_trait]
impl Handler for HealthCheck {
async fn handle(&self, _req: Request) -> reinhardt_core::exception::Result<Response> {
Ok(Response::ok())
}
}
#[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().endpoint(|| UserDetail);
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().endpoint(|| HealthCheck);
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().endpoint(|| UserDetail);
register_router(router);
let router = get_router().unwrap();
let url = router.reverse("user-detail", &[("id", "42")]);
assert_eq!(url, Some("/users/42/".to_string()));
}
}