1use axum::{
8 extract::FromRequestParts,
9 http::{StatusCode, request::Parts},
10};
11use std::any::{Any, TypeId};
12use std::collections::HashMap;
13use std::ops::Deref;
14use std::sync::Arc;
15
16#[derive(Debug, thiserror::Error, Clone, PartialEq, Eq)]
18pub enum DiError {
19 #[error("Dependency of type '{0}' is not registered in the DI Container")]
21 NotRegistered(&'static str),
22
23 #[error("Failed to downcast dependency of type '{0}'")]
25 DowncastFailed(&'static str),
26
27 #[error("Dependency injection failed: {0}")]
29 InjectionFailed(String),
30}
31
32pub trait Injectable: Sized + Send + Sync + 'static {
34 fn inject(container: &Container) -> Result<Self, DiError>;
36}
37
38#[derive(Clone, Default)]
40pub struct Container {
41 services: HashMap<TypeId, Arc<dyn Any + Send + Sync>>,
42}
43
44impl Container {
45 pub fn new() -> Self {
47 Self {
48 services: HashMap::new(),
49 }
50 }
51
52 pub fn register<T: Send + Sync + 'static>(&mut self, service: T) {
54 self.services.insert(TypeId::of::<T>(), Arc::new(service));
55 }
56
57 pub fn register_arc<T: Send + Sync + 'static>(&mut self, service: Arc<T>) {
59 self.services.insert(TypeId::of::<T>(), service);
60 }
61
62 pub fn resolve<T: Send + Sync + 'static>(&self) -> Result<Arc<T>, DiError> {
64 let type_id = TypeId::of::<T>();
65 if let Some(service) = self.services.get(&type_id) {
66 if let Ok(downcasted) = service.clone().downcast::<T>() {
67 return Ok(downcasted);
68 }
69 return Err(DiError::DowncastFailed(std::any::type_name::<T>()));
70 }
71 Err(DiError::NotRegistered(std::any::type_name::<T>()))
72 }
73}
74
75pub struct Inject<T: Send + Sync + 'static>(pub Arc<T>);
95
96impl<T: Send + Sync + 'static> Deref for Inject<T> {
97 type Target = T;
98
99 fn deref(&self) -> &Self::Target {
100 &self.0
101 }
102}
103
104impl<T, S> FromRequestParts<S> for Inject<T>
105where
106 T: Send + Sync + 'static,
107 S: Send + Sync,
108{
109 type Rejection = (StatusCode, String);
110
111 async fn from_request_parts(parts: &mut Parts, _state: &S) -> Result<Self, Self::Rejection> {
112 if let Some(container) = parts.extensions.get::<Arc<Container>>() {
113 match container.resolve::<T>() {
114 Ok(service) => Ok(Inject(service)),
115 Err(err) => Err((StatusCode::INTERNAL_SERVER_ERROR, err.to_string())),
116 }
117 } else if let Some(service) = parts.extensions.get::<Arc<T>>() {
118 Ok(Inject(service.clone()))
119 } else {
120 Err((
121 StatusCode::INTERNAL_SERVER_ERROR,
122 format!(
123 "Rullst DI Container or Extension for type '{}' not found in request state",
124 std::any::type_name::<T>()
125 ),
126 ))
127 }
128 }
129}
130
131#[cfg(test)]
132#[allow(clippy::unwrap_used, clippy::expect_used)]
133mod tests {
134 use super::*;
135 use axum::extract::FromRequestParts;
136 use axum::http::Request;
137
138 struct DatabaseService {
139 connection_string: String,
140 }
141
142 struct UserService {
143 db: Arc<DatabaseService>,
144 }
145
146 #[test]
147 fn test_di_container_registration_and_resolution() {
148 let mut container = Container::new();
149 container.register(DatabaseService {
150 connection_string: "sqlite::memory:".to_string(),
151 });
152
153 let db = container.resolve::<DatabaseService>().unwrap();
154 assert_eq!(db.connection_string, "sqlite::memory:");
155
156 let user_svc = UserService { db: db.clone() };
157 container.register(user_svc);
158
159 let resolved_user_svc = container.resolve::<UserService>().unwrap();
160 assert_eq!(resolved_user_svc.db.connection_string, "sqlite::memory:");
161 }
162
163 #[tokio::test]
164 async fn extractor_resolves_container_direct_extension_and_missing_state() {
165 let mut container = Container::new();
166 container.register(DatabaseService {
167 connection_string: "sqlite://container".to_string(),
168 });
169 let mut parts = Request::new(()).into_parts().0;
170 parts.extensions.insert(Arc::new(container));
171
172 let injected = Inject::<DatabaseService>::from_request_parts(&mut parts, &())
173 .await
174 .expect("registered container service");
175 assert_eq!(injected.connection_string, "sqlite://container");
176
177 let direct = Arc::new(DatabaseService {
178 connection_string: "sqlite://extension".to_string(),
179 });
180 let mut direct_parts = Request::new(()).into_parts().0;
181 direct_parts.extensions.insert(direct.clone());
182 let injected = Inject::<DatabaseService>::from_request_parts(&mut direct_parts, &())
183 .await
184 .expect("direct typed extension");
185 assert!(Arc::ptr_eq(&injected.0, &direct));
186
187 let mut missing_parts = Request::new(()).into_parts().0;
188 let Err((status, message)) =
189 Inject::<DatabaseService>::from_request_parts(&mut missing_parts, &()).await
190 else {
191 panic!("missing dependency must fail closed");
192 };
193 assert_eq!(status, StatusCode::INTERNAL_SERVER_ERROR);
194 assert!(message.contains(std::any::type_name::<DatabaseService>()));
195 assert!(!message.contains("sqlite://"));
196 }
197
198 #[test]
199 fn typed_errors_do_not_hide_the_requested_dependency() {
200 let missing = DiError::NotRegistered(std::any::type_name::<DatabaseService>());
201 assert!(missing.to_string().contains("DatabaseService"));
202
203 let injection = DiError::InjectionFailed("invalid service graph".to_string());
204 assert_eq!(
205 injection.to_string(),
206 "Dependency injection failed: invalid service graph"
207 );
208 }
209}