Skip to main content

rust_web_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//! use rust_web_server::di::Container;
12//!
13//! struct EmailService { host: String }
14//!
15//! let mut c = Container::new();
16//! c.register(EmailService { host: "smtp.example.com".into() });
17//!
18//! let svc = c.get::<EmailService>().unwrap();
19//! assert_eq!(svc.host, "smtp.example.com");
20//! ```
21//!
22//! # Trait-object services
23//!
24//! ```rust
25//! use std::sync::Arc;
26//! use rust_web_server::di::Container;
27//!
28//! pub trait Mailer: Send + Sync {
29//!     fn send(&self, to: &str);
30//! }
31//! struct SmtpMailer;
32//! impl Mailer for SmtpMailer {
33//!     fn send(&self, _to: &str) {}
34//! }
35//!
36//! let mut c = Container::new();
37//! c.provide::<dyn Mailer>(Arc::new(SmtpMailer));
38//! let mailer = c.get::<dyn Mailer>().unwrap();
39//! mailer.send("user@example.com");
40//! ```
41//!
42//! # Named services
43//!
44//! Register multiple instances of the same type under distinct string names:
45//!
46//! ```rust
47//! use rust_web_server::di::Container;
48//!
49//! let mut c = Container::new();
50//! c.register_named("primary",  5432u16)
51//!  .register_named("replica",  5433u16);
52//!
53//! assert_eq!(*c.get_named::<u16>("primary").unwrap(), 5432);
54//! assert_eq!(*c.get_named::<u16>("replica").unwrap(), 5433);
55//! ```
56//!
57//! # Usage with `App::with_state` / `App::with_async_state`
58//!
59//! `Container` needs no special-cased integration — it's `Send + Sync +
60//! 'static` like any other state type, so it plugs directly into
61//! [`App::with_state`](crate::app::App::with_state) as `S`. Pass the
62//! container itself, **not** `container.into_arc()`: `with_state` already
63//! wraps `S` in an `Arc` internally, so calling `into_arc()` first just
64//! double-wraps it (`Arc<Arc<Container>>`) for no benefit — handlers then
65//! receive `&Container` directly instead of `&Arc<Container>`. `into_arc()`
66//! exists for call sites that need to share one container across multiple
67//! `Application`s built by hand, outside of `App::with_state`/`with_async_state`.
68//!
69//! ```rust,no_run
70//! use rust_web_server::app::App;
71//! use rust_web_server::di::Container;
72//! use rust_web_server::request::Request;
73//! use rust_web_server::router::PathParams;
74//! use rust_web_server::server::ConnectionInfo;
75//! use rust_web_server::response::{Response, STATUS_CODE_REASON_PHRASE};
76//! use rust_web_server::routes;
77//!
78//! struct Config { version: &'static str }
79//!
80//! fn get_version(
81//!     _req: &Request,
82//!     _p: &PathParams,
83//!     _c: &ConnectionInfo,
84//!     state: &Container,
85//! ) -> Response {
86//!     let _cfg = state.get::<Config>().unwrap();
87//!     Response::get_response(&STATUS_CODE_REASON_PHRASE.n200_ok, None, None)
88//! }
89//!
90//! let mut container = Container::new();
91//! container.register(Config { version: "1.0" });
92//!
93//! let app = routes! {
94//!     App::with_state(container),
95//!     GET "/version" => get_version,
96//! };
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    /// # use std::sync::Arc;
151    /// # use rust_web_server::di::Container;
152    /// # trait Greeter: Send + Sync { fn greet(&self) -> &str; }
153    /// # struct Hello; impl Greeter for Hello { fn greet(&self) -> &str { "hi" } }
154    /// let mut c = Container::new();
155    /// c.provide::<dyn Greeter>(Arc::new(Hello));
156    /// assert_eq!(c.get::<dyn Greeter>().unwrap().greet(), "hi");
157    /// ```
158    ///
159    /// Also usable for concrete types when you already have an `Arc`.
160    pub fn provide<T>(&mut self, service: Arc<T>) -> &mut Self
161    where
162        T: ?Sized + Send + Sync + 'static,
163    {
164        // Arc<T> is always Sized (fat pointer for DSTs), 'static when T: 'static,
165        // Send + Sync when T: Send + Sync, and Any via blanket impl for all 'static.
166        let boxed: Box<dyn Any + Send + Sync> = Box::new(service);
167        self.services.insert(TypeId::of::<T>(), boxed);
168        self
169    }
170
171    /// Register a named concrete service.
172    ///
173    /// Use this when you need multiple instances of the same type
174    /// (e.g., primary vs. replica database pools).
175    pub fn register_named<T: Send + Sync + 'static>(
176        &mut self,
177        name: impl Into<String>,
178        service: T,
179    ) -> &mut Self {
180        self.named.insert(
181            (TypeId::of::<T>(), name.into()),
182            Box::new(Arc::new(service)),
183        );
184        self
185    }
186
187    /// Register a pre-built `Arc<T>` under a string name.
188    ///
189    /// Use for named trait-object registrations.
190    pub fn provide_named<T>(
191        &mut self,
192        name: impl Into<String>,
193        service: Arc<T>,
194    ) -> &mut Self
195    where
196        T: ?Sized + Send + Sync + 'static,
197    {
198        let boxed: Box<dyn Any + Send + Sync> = Box::new(service);
199        self.named.insert((TypeId::of::<T>(), name.into()), boxed);
200        self
201    }
202
203    // ── Resolution ──────────────────────────────────────────────────────────────
204
205    /// Resolve a service by type.
206    ///
207    /// Works for both **concrete types** (`get::<MyService>()`) and **trait
208    /// objects** (`get::<dyn MyTrait>()`).  Returns `None` if no service of
209    /// that type has been registered.
210    pub fn get<T>(&self) -> Option<Arc<T>>
211    where
212        T: ?Sized + 'static,
213        Arc<T>: Clone,
214    {
215        self.services
216            .get(&TypeId::of::<T>())
217            .and_then(|b| b.downcast_ref::<Arc<T>>())
218            .cloned()
219    }
220
221    /// Resolve a named service by type and name.
222    ///
223    /// Returns `None` if no service of that type and name has been registered.
224    pub fn get_named<T>(&self, name: &str) -> Option<Arc<T>>
225    where
226        T: ?Sized + 'static,
227        Arc<T>: Clone,
228    {
229        self.named
230            .get(&(TypeId::of::<T>(), name.to_string()))
231            .and_then(|b| b.downcast_ref::<Arc<T>>())
232            .cloned()
233    }
234
235    // ── Inspection ──────────────────────────────────────────────────────────────
236
237    /// Returns `true` if a service of type `T` has been registered (unnamed).
238    pub fn contains<T: ?Sized + 'static>(&self) -> bool {
239        self.services.contains_key(&TypeId::of::<T>())
240    }
241
242    /// Returns `true` if a named service of type `T` with `name` exists.
243    pub fn contains_named<T: ?Sized + 'static>(&self, name: &str) -> bool {
244        self.named
245            .contains_key(&(TypeId::of::<T>(), name.to_string()))
246    }
247
248    /// Total number of unnamed registrations.
249    pub fn len(&self) -> usize {
250        self.services.len()
251    }
252
253    /// Returns `true` if no services have been registered.
254    pub fn is_empty(&self) -> bool {
255        self.services.is_empty()
256    }
257
258    // ── Sharing ─────────────────────────────────────────────────────────────────
259
260    /// Seal this container into an `Arc<Container>` for sharing across threads
261    /// and handler closures.
262    ///
263    /// Typical usage with `App::with_state`:
264    /// ```rust,no_run
265    /// # use rust_web_server::di::Container;
266    /// # use rust_web_server::app::App;
267    /// # use rust_web_server::routes;
268    /// let mut c = Container::new();
269    /// // c.register(...);
270    /// let app = routes! { App::with_state(c.into_arc()), };
271    /// ```
272    pub fn into_arc(self) -> Arc<Self> {
273        Arc::new(self)
274    }
275}
276
277#[cfg(test)]
278mod tests;