use std::any::type_name;
use std::ops::Deref;
use std::sync::Arc;
use axum::extract::{FromRef, FromRequestParts};
use axum::http::request::Parts;
use crate::state::AroState;
pub struct Dep<T: ?Sized + 'static>(pub Arc<T>);
impl<T: ?Sized + 'static> Deref for Dep<T> {
type Target = T;
fn deref(&self) -> &T {
&self.0
}
}
impl<T: ?Sized + 'static> Clone for Dep<T> {
fn clone(&self) -> Self {
Self(Arc::clone(&self.0))
}
}
impl<T: ?Sized + 'static> std::fmt::Debug for Dep<T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "Dep<{}>", type_name::<T>())
}
}
impl<T: ?Sized + 'static> FromRef<AroState> for Dep<T>
where
Arc<T>: Send + Sync + Clone + 'static,
{
#[expect(
clippy::panic,
reason = "intentional boot-time crash for missing dependency registration"
)]
fn from_ref(state: &AroState) -> Self {
Self(state.get::<T>().unwrap_or_else(|| {
panic!(
"Dep<{}> not registered in AroState. \
Call AroState::builder().register::<{}>(...) at startup.",
type_name::<T>(),
type_name::<T>(),
)
}))
}
}
impl<T: ?Sized + 'static> FromRequestParts<AroState> for Dep<T>
where
Arc<T>: Send + Sync + Clone + 'static,
{
type Rejection = std::convert::Infallible;
async fn from_request_parts(
_parts: &mut Parts,
state: &AroState,
) -> Result<Self, Self::Rejection> {
Ok(Self::from_ref(state))
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::state::AroState;
trait UserRepo: Send + Sync {
fn find_name(&self, id: u64) -> Option<String>;
}
struct MockUserRepo {
users: Vec<(u64, String)>,
}
impl UserRepo for MockUserRepo {
fn find_name(&self, id: u64) -> Option<String> {
self.users
.iter()
.find(|(uid, _)| *uid == id)
.map(|(_, name)| name.clone())
}
}
trait Notifier: Send + Sync {
fn notify(&self, msg: &str) -> bool;
}
struct MockNotifier;
impl Notifier for MockNotifier {
fn notify(&self, _msg: &str) -> bool {
true
}
}
#[test]
fn dep_extracts_from_aro_state() {
let state = AroState::builder()
.register::<dyn UserRepo>(Arc::new(MockUserRepo {
users: vec![(1, "Alice".into())],
}))
.build();
let dep = Dep::<dyn UserRepo>::from_ref(&state);
assert_eq!(dep.find_name(1), Some("Alice".into()));
}
#[test]
#[should_panic(expected = "not registered in AroState")]
fn missing_dep_panics_with_clear_message() {
let state = AroState::builder().build();
let _dep = Dep::<dyn UserRepo>::from_ref(&state);
}
#[test]
fn dep_deref_calls_trait_methods() {
let dep = Dep::<dyn UserRepo>(Arc::new(MockUserRepo {
users: vec![(1, "Bob".into())],
}));
assert_eq!(dep.find_name(1), Some("Bob".into()));
}
#[test]
fn dep_direct_construction_for_testing() {
let repo: Arc<dyn UserRepo> = Arc::new(MockUserRepo {
users: vec![(42, "TestUser".into())],
});
let dep = Dep(repo);
assert_eq!(dep.find_name(42), Some("TestUser".into()));
assert!(dep.find_name(99).is_none());
}
#[test]
fn dep_is_clone() {
let repo: Arc<dyn UserRepo> = Arc::new(MockUserRepo {
users: vec![(1, "Alice".into())],
});
let dep = Dep(repo);
let cloned = dep.clone();
assert_eq!(cloned.find_name(1), Some("Alice".into()));
}
#[test]
fn multiple_deps_resolve_from_same_state() {
let state = AroState::builder()
.register::<dyn UserRepo>(Arc::new(MockUserRepo {
users: vec![(1, "Alice".into())],
}))
.register::<dyn Notifier>(Arc::new(MockNotifier))
.build();
let user_dep = Dep::<dyn UserRepo>::from_ref(&state);
let notifier_dep = Dep::<dyn Notifier>::from_ref(&state);
assert_eq!(user_dep.find_name(1), Some("Alice".into()));
assert!(notifier_dep.notify("test"));
}
}