rws_server/di/mod.rs
1//! Lightweight type-keyed dependency injection container.
2//!
3//! [`Container`] stores services keyed by their Rust type (`TypeId`). Register
4//! services at startup; resolve them anywhere that has access to the container.
5//! Share across request handlers with `App::with_state(container)` — see
6//! "Usage with `App::with_state`" below.
7//!
8//! # Concrete services
9//!
10//! ```rust
11//! # extern crate rws_server as rust_web_server;
12//! use rust_web_server::di::Container;
13//!
14//! struct EmailService { host: String }
15//!
16//! let mut c = Container::new();
17//! c.register(EmailService { host: "smtp.example.com".into() });
18//!
19//! let svc = c.get::<EmailService>().unwrap();
20//! assert_eq!(svc.host, "smtp.example.com");
21//! ```
22//!
23//! # Trait-object services
24//!
25//! ```rust
26//! # extern crate rws_server as rust_web_server;
27//! use std::sync::Arc;
28//! use rust_web_server::di::Container;
29//!
30//! pub trait Mailer: Send + Sync {
31//! fn send(&self, to: &str);
32//! }
33//! struct SmtpMailer;
34//! impl Mailer for SmtpMailer {
35//! fn send(&self, _to: &str) {}
36//! }
37//!
38//! let mut c = Container::new();
39//! c.provide::<dyn Mailer>(Arc::new(SmtpMailer));
40//! let mailer = c.get::<dyn Mailer>().unwrap();
41//! mailer.send("user@example.com");
42//! ```
43//!
44//! # Named services
45//!
46//! Register multiple instances of the same type under distinct string names:
47//!
48//! ```rust
49//! # extern crate rws_server as rust_web_server;
50//! use rust_web_server::di::Container;
51//!
52//! let mut c = Container::new();
53//! c.register_named("primary", 5432u16)
54//! .register_named("replica", 5433u16);
55//!
56//! assert_eq!(*c.get_named::<u16>("primary").unwrap(), 5432);
57//! assert_eq!(*c.get_named::<u16>("replica").unwrap(), 5433);
58//! ```
59//!
60//! # Usage with `App::with_state` / `App::with_async_state`
61//!
62//! `Container` needs no special-cased integration — it's `Send + Sync +
63//! 'static` like any other state type, so it plugs directly into
64//! [`App::with_state`](crate::app::App::with_state) as `S`. Pass the
65//! container itself, **not** `container.into_arc()`: `with_state` already
66//! wraps `S` in an `Arc` internally, so calling `into_arc()` first just
67//! double-wraps it (`Arc<Arc<Container>>`) for no benefit — handlers then
68//! receive `&Container` directly instead of `&Arc<Container>`. `into_arc()`
69//! exists for call sites that need to share one container across multiple
70//! `Application`s built by hand, outside of `App::with_state`/`with_async_state`.
71//!
72//! ```rust,no_run
73//! # extern crate rws_server as rust_web_server;
74//! use rust_web_server::app::App;
75//! use rust_web_server::di::Container;
76//! use rust_web_server::request::Request;
77//! use rust_web_server::router::PathParams;
78//! use rust_web_server::server::ConnectionInfo;
79//! use rust_web_server::response::{Response, STATUS_CODE_REASON_PHRASE};
80//!
81//! struct Config { version: &'static str }
82//!
83//! fn get_version(
84//! _req: &Request,
85//! _p: &PathParams,
86//! _c: &ConnectionInfo,
87//! state: &Container,
88//! ) -> Response {
89//! let _cfg = state.get::<Config>().unwrap();
90//! Response::get_response(&STATUS_CODE_REASON_PHRASE.n200_ok, None, None)
91//! }
92//!
93//! let mut container = Container::new();
94//! container.register(Config { version: "1.0" });
95//!
96//! let app = App::with_state(container).get("/version", get_version);
97//! ```
98//!
99//! The async counterpart works the same way with [`App::with_async_state`](crate::app::App::with_async_state)
100//! (requires the `http2` feature) — handlers are `async fn`s that receive
101//! `&Container` and can `.await` inside the closure while resolving services.
102
103use std::any::{Any, TypeId};
104use std::collections::HashMap;
105use std::sync::Arc;
106
107/// A type-keyed service container for dependency injection.
108///
109/// See the [module documentation][self] for usage examples.
110pub struct Container {
111 /// Unnamed services: key = TypeId of T, value = Box<dyn Any> holding Arc<T>.
112 services: HashMap<TypeId, Box<dyn Any + Send + Sync>>,
113 /// Named services: key = (TypeId of T, name), value = Box<dyn Any> holding Arc<T>.
114 named: HashMap<(TypeId, String), Box<dyn Any + Send + Sync>>,
115}
116
117impl Default for Container {
118 fn default() -> Self {
119 Self::new()
120 }
121}
122
123impl Container {
124 /// Create an empty container.
125 pub fn new() -> Self {
126 Container {
127 services: HashMap::new(),
128 named: HashMap::new(),
129 }
130 }
131
132 // ── Registration ────────────────────────────────────────────────────────────
133
134 /// Register a concrete service by value.
135 ///
136 /// Wraps `service` in `Arc<T>` and stores it under `TypeId::of::<T>()`.
137 /// A subsequent registration for the same `T` replaces the previous one.
138 pub fn register<T: Send + Sync + 'static>(&mut self, service: T) -> &mut Self {
139 self.services
140 .insert(TypeId::of::<T>(), Box::new(Arc::new(service)));
141 self
142 }
143
144 /// Register a pre-built `Arc<T>`.
145 ///
146 /// Required when registering **trait objects**, because the concrete type
147 /// must be erased before storage:
148 ///
149 /// ```rust
150 /// # extern crate rws_server as rust_web_server;
151 /// # use std::sync::Arc;
152 /// # use rust_web_server::di::Container;
153 /// # trait Greeter: Send + Sync { fn greet(&self) -> &str; }
154 /// # struct Hello; impl Greeter for Hello { fn greet(&self) -> &str { "hi" } }
155 /// let mut c = Container::new();
156 /// c.provide::<dyn Greeter>(Arc::new(Hello));
157 /// assert_eq!(c.get::<dyn Greeter>().unwrap().greet(), "hi");
158 /// ```
159 ///
160 /// Also usable for concrete types when you already have an `Arc`.
161 pub fn provide<T>(&mut self, service: Arc<T>) -> &mut Self
162 where
163 T: ?Sized + Send + Sync + 'static,
164 {
165 // Arc<T> is always Sized (fat pointer for DSTs), 'static when T: 'static,
166 // Send + Sync when T: Send + Sync, and Any via blanket impl for all 'static.
167 let boxed: Box<dyn Any + Send + Sync> = Box::new(service);
168 self.services.insert(TypeId::of::<T>(), boxed);
169 self
170 }
171
172 /// Register a named concrete service.
173 ///
174 /// Use this when you need multiple instances of the same type
175 /// (e.g., primary vs. replica database pools).
176 pub fn register_named<T: Send + Sync + 'static>(
177 &mut self,
178 name: impl Into<String>,
179 service: T,
180 ) -> &mut Self {
181 self.named.insert(
182 (TypeId::of::<T>(), name.into()),
183 Box::new(Arc::new(service)),
184 );
185 self
186 }
187
188 /// Register a pre-built `Arc<T>` under a string name.
189 ///
190 /// Use for named trait-object registrations.
191 pub fn provide_named<T>(
192 &mut self,
193 name: impl Into<String>,
194 service: Arc<T>,
195 ) -> &mut Self
196 where
197 T: ?Sized + Send + Sync + 'static,
198 {
199 let boxed: Box<dyn Any + Send + Sync> = Box::new(service);
200 self.named.insert((TypeId::of::<T>(), name.into()), boxed);
201 self
202 }
203
204 // ── Resolution ──────────────────────────────────────────────────────────────
205
206 /// Resolve a service by type.
207 ///
208 /// Works for both **concrete types** (`get::<MyService>()`) and **trait
209 /// objects** (`get::<dyn MyTrait>()`). Returns `None` if no service of
210 /// that type has been registered.
211 pub fn get<T>(&self) -> Option<Arc<T>>
212 where
213 T: ?Sized + 'static,
214 Arc<T>: Clone,
215 {
216 self.services
217 .get(&TypeId::of::<T>())
218 .and_then(|b| b.downcast_ref::<Arc<T>>())
219 .cloned()
220 }
221
222 /// Resolve a named service by type and name.
223 ///
224 /// Returns `None` if no service of that type and name has been registered.
225 pub fn get_named<T>(&self, name: &str) -> Option<Arc<T>>
226 where
227 T: ?Sized + 'static,
228 Arc<T>: Clone,
229 {
230 self.named
231 .get(&(TypeId::of::<T>(), name.to_string()))
232 .and_then(|b| b.downcast_ref::<Arc<T>>())
233 .cloned()
234 }
235
236 // ── Inspection ──────────────────────────────────────────────────────────────
237
238 /// Returns `true` if a service of type `T` has been registered (unnamed).
239 pub fn contains<T: ?Sized + 'static>(&self) -> bool {
240 self.services.contains_key(&TypeId::of::<T>())
241 }
242
243 /// Returns `true` if a named service of type `T` with `name` exists.
244 pub fn contains_named<T: ?Sized + 'static>(&self, name: &str) -> bool {
245 self.named
246 .contains_key(&(TypeId::of::<T>(), name.to_string()))
247 }
248
249 /// Total number of unnamed registrations.
250 pub fn len(&self) -> usize {
251 self.services.len()
252 }
253
254 /// Returns `true` if no services have been registered.
255 pub fn is_empty(&self) -> bool {
256 self.services.is_empty()
257 }
258
259 // ── Sharing ─────────────────────────────────────────────────────────────────
260
261 /// Seal this container into an `Arc<Container>` for sharing across threads
262 /// and handler closures.
263 ///
264 /// Typical usage with `App::with_state`:
265 /// ```rust,no_run
266 /// # extern crate rws_server as rust_web_server;
267 /// # use rust_web_server::di::Container;
268 /// # use rust_web_server::app::App;
269 /// let mut c = Container::new();
270 /// // c.register(...);
271 /// let app = App::with_state(c.into_arc());
272 /// ```
273 pub fn into_arc(self) -> Arc<Self> {
274 Arc::new(self)
275 }
276}
277
278#[cfg(test)]
279mod tests;