1use std::any::type_name;
27use std::ops::Deref;
28use std::sync::Arc;
29
30use axum::extract::{FromRef, FromRequestParts};
31use axum::http::request::Parts;
32
33use crate::state::AroState;
34
35pub struct Dep<T: ?Sized + 'static>(pub Arc<T>);
40
41impl<T: ?Sized + 'static> Deref for Dep<T> {
42 type Target = T;
43
44 fn deref(&self) -> &T {
45 &self.0
46 }
47}
48
49impl<T: ?Sized + 'static> Clone for Dep<T> {
50 fn clone(&self) -> Self {
51 Self(Arc::clone(&self.0))
52 }
53}
54
55impl<T: ?Sized + 'static> std::fmt::Debug for Dep<T> {
56 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
57 write!(f, "Dep<{}>", type_name::<T>())
58 }
59}
60
61impl<T: ?Sized + 'static> FromRef<AroState> for Dep<T>
62where
63 Arc<T>: Send + Sync + Clone + 'static,
64{
65 #[expect(
66 clippy::panic,
67 reason = "intentional boot-time crash for missing dependency registration"
68 )]
69 fn from_ref(state: &AroState) -> Self {
70 Self(state.get::<T>().unwrap_or_else(|| {
71 panic!(
72 "Dep<{}> not registered in AroState. \
73 Call AroState::builder().register::<{}>(...) at startup.",
74 type_name::<T>(),
75 type_name::<T>(),
76 )
77 }))
78 }
79}
80
81impl<T: ?Sized + 'static> FromRequestParts<AroState> for Dep<T>
82where
83 Arc<T>: Send + Sync + Clone + 'static,
84{
85 type Rejection = std::convert::Infallible;
86
87 async fn from_request_parts(
88 _parts: &mut Parts,
89 state: &AroState,
90 ) -> Result<Self, Self::Rejection> {
91 Ok(Self::from_ref(state))
92 }
93}
94
95#[cfg(test)]
96mod tests {
97 use super::*;
98 use crate::state::AroState;
99
100 trait UserRepo: Send + Sync {
101 fn find_name(&self, id: u64) -> Option<String>;
102 }
103
104 struct MockUserRepo {
105 users: Vec<(u64, String)>,
106 }
107
108 impl UserRepo for MockUserRepo {
109 fn find_name(&self, id: u64) -> Option<String> {
110 self.users
111 .iter()
112 .find(|(uid, _)| *uid == id)
113 .map(|(_, name)| name.clone())
114 }
115 }
116
117 trait Notifier: Send + Sync {
118 fn notify(&self, msg: &str) -> bool;
119 }
120
121 struct MockNotifier;
122 impl Notifier for MockNotifier {
123 fn notify(&self, _msg: &str) -> bool {
124 true
125 }
126 }
127
128 #[test]
129 fn dep_extracts_from_aro_state() {
130 let state = AroState::builder()
131 .register::<dyn UserRepo>(Arc::new(MockUserRepo {
132 users: vec![(1, "Alice".into())],
133 }))
134 .build();
135
136 let dep = Dep::<dyn UserRepo>::from_ref(&state);
137 assert_eq!(dep.find_name(1), Some("Alice".into()));
138 }
139
140 #[test]
141 #[should_panic(expected = "not registered in AroState")]
142 fn missing_dep_panics_with_clear_message() {
143 let state = AroState::builder().build();
144 let _dep = Dep::<dyn UserRepo>::from_ref(&state);
145 }
146
147 #[test]
148 fn dep_deref_calls_trait_methods() {
149 let dep = Dep::<dyn UserRepo>(Arc::new(MockUserRepo {
150 users: vec![(1, "Bob".into())],
151 }));
152 assert_eq!(dep.find_name(1), Some("Bob".into()));
153 }
154
155 #[test]
156 fn dep_direct_construction_for_testing() {
157 let repo: Arc<dyn UserRepo> = Arc::new(MockUserRepo {
158 users: vec![(42, "TestUser".into())],
159 });
160 let dep = Dep(repo);
161 assert_eq!(dep.find_name(42), Some("TestUser".into()));
162 assert!(dep.find_name(99).is_none());
163 }
164
165 #[test]
166 fn dep_is_clone() {
167 let repo: Arc<dyn UserRepo> = Arc::new(MockUserRepo {
168 users: vec![(1, "Alice".into())],
169 });
170 let dep = Dep(repo);
171 let cloned = dep.clone();
172 assert_eq!(cloned.find_name(1), Some("Alice".into()));
173 }
174
175 #[test]
176 fn multiple_deps_resolve_from_same_state() {
177 let state = AroState::builder()
178 .register::<dyn UserRepo>(Arc::new(MockUserRepo {
179 users: vec![(1, "Alice".into())],
180 }))
181 .register::<dyn Notifier>(Arc::new(MockNotifier))
182 .build();
183
184 let user_dep = Dep::<dyn UserRepo>::from_ref(&state);
185 let notifier_dep = Dep::<dyn Notifier>::from_ref(&state);
186
187 assert_eq!(user_dep.find_name(1), Some("Alice".into()));
188 assert!(notifier_dep.notify("test"));
189 }
190}