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 by wrapping in `Arc<Container>` and using
6//! `App::with_state(container.into_arc())`.
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`
58//!
59//! ```rust,no_run
60//! use std::sync::Arc;
61//! use rust_web_server::app::App;
62//! use rust_web_server::di::Container;
63//! use rust_web_server::request::Request;
64//! use rust_web_server::router::PathParams;
65//! use rust_web_server::server::ConnectionInfo;
66//! use rust_web_server::response::{Response, STATUS_CODE_REASON_PHRASE};
67//! use rust_web_server::routes;
68//!
69//! struct Config { version: &'static str }
70//!
71//! fn get_version(
72//!     _req: &Request,
73//!     _p: &PathParams,
74//!     _c: &ConnectionInfo,
75//!     state: &Arc<Container>,
76//! ) -> Response {
77//!     let cfg = state.get::<Config>().unwrap();
78//!     let mut r = Response::new();
79//!     r.status_code = *STATUS_CODE_REASON_PHRASE.n200_ok.status_code;
80//!     r.reason_phrase = STATUS_CODE_REASON_PHRASE.n200_ok.reason_phrase.to_string();
81//!     r
82//! }
83//!
84//! let mut container = Container::new();
85//! container.register(Config { version: "1.0" });
86//!
87//! let app = routes! {
88//!     App::with_state(container.into_arc()),
89//!     GET "/version" => get_version,
90//! };
91//! ```
92
93use std::any::{Any, TypeId};
94use std::collections::HashMap;
95use std::sync::Arc;
96
97/// A type-keyed service container for dependency injection.
98///
99/// See the [module documentation][self] for usage examples.
100pub struct Container {
101    /// Unnamed services: key = TypeId of T, value = Box<dyn Any> holding Arc<T>.
102    services: HashMap<TypeId, Box<dyn Any + Send + Sync>>,
103    /// Named services: key = (TypeId of T, name), value = Box<dyn Any> holding Arc<T>.
104    named: HashMap<(TypeId, String), Box<dyn Any + Send + Sync>>,
105}
106
107impl Default for Container {
108    fn default() -> Self {
109        Self::new()
110    }
111}
112
113impl Container {
114    /// Create an empty container.
115    pub fn new() -> Self {
116        Container {
117            services: HashMap::new(),
118            named: HashMap::new(),
119        }
120    }
121
122    // ── Registration ────────────────────────────────────────────────────────────
123
124    /// Register a concrete service by value.
125    ///
126    /// Wraps `service` in `Arc<T>` and stores it under `TypeId::of::<T>()`.
127    /// A subsequent registration for the same `T` replaces the previous one.
128    pub fn register<T: Send + Sync + 'static>(&mut self, service: T) -> &mut Self {
129        self.services
130            .insert(TypeId::of::<T>(), Box::new(Arc::new(service)));
131        self
132    }
133
134    /// Register a pre-built `Arc<T>`.
135    ///
136    /// Required when registering **trait objects**, because the concrete type
137    /// must be erased before storage:
138    ///
139    /// ```rust
140    /// # use std::sync::Arc;
141    /// # use rust_web_server::di::Container;
142    /// # trait Greeter: Send + Sync { fn greet(&self) -> &str; }
143    /// # struct Hello; impl Greeter for Hello { fn greet(&self) -> &str { "hi" } }
144    /// let mut c = Container::new();
145    /// c.provide::<dyn Greeter>(Arc::new(Hello));
146    /// assert_eq!(c.get::<dyn Greeter>().unwrap().greet(), "hi");
147    /// ```
148    ///
149    /// Also usable for concrete types when you already have an `Arc`.
150    pub fn provide<T>(&mut self, service: Arc<T>) -> &mut Self
151    where
152        T: ?Sized + Send + Sync + 'static,
153    {
154        // Arc<T> is always Sized (fat pointer for DSTs), 'static when T: 'static,
155        // Send + Sync when T: Send + Sync, and Any via blanket impl for all 'static.
156        let boxed: Box<dyn Any + Send + Sync> = Box::new(service);
157        self.services.insert(TypeId::of::<T>(), boxed);
158        self
159    }
160
161    /// Register a named concrete service.
162    ///
163    /// Use this when you need multiple instances of the same type
164    /// (e.g., primary vs. replica database pools).
165    pub fn register_named<T: Send + Sync + 'static>(
166        &mut self,
167        name: impl Into<String>,
168        service: T,
169    ) -> &mut Self {
170        self.named.insert(
171            (TypeId::of::<T>(), name.into()),
172            Box::new(Arc::new(service)),
173        );
174        self
175    }
176
177    /// Register a pre-built `Arc<T>` under a string name.
178    ///
179    /// Use for named trait-object registrations.
180    pub fn provide_named<T>(
181        &mut self,
182        name: impl Into<String>,
183        service: Arc<T>,
184    ) -> &mut Self
185    where
186        T: ?Sized + Send + Sync + 'static,
187    {
188        let boxed: Box<dyn Any + Send + Sync> = Box::new(service);
189        self.named.insert((TypeId::of::<T>(), name.into()), boxed);
190        self
191    }
192
193    // ── Resolution ──────────────────────────────────────────────────────────────
194
195    /// Resolve a service by type.
196    ///
197    /// Works for both **concrete types** (`get::<MyService>()`) and **trait
198    /// objects** (`get::<dyn MyTrait>()`).  Returns `None` if no service of
199    /// that type has been registered.
200    pub fn get<T>(&self) -> Option<Arc<T>>
201    where
202        T: ?Sized + 'static,
203        Arc<T>: Clone,
204    {
205        self.services
206            .get(&TypeId::of::<T>())
207            .and_then(|b| b.downcast_ref::<Arc<T>>())
208            .cloned()
209    }
210
211    /// Resolve a named service by type and name.
212    ///
213    /// Returns `None` if no service of that type and name has been registered.
214    pub fn get_named<T>(&self, name: &str) -> Option<Arc<T>>
215    where
216        T: ?Sized + 'static,
217        Arc<T>: Clone,
218    {
219        self.named
220            .get(&(TypeId::of::<T>(), name.to_string()))
221            .and_then(|b| b.downcast_ref::<Arc<T>>())
222            .cloned()
223    }
224
225    // ── Inspection ──────────────────────────────────────────────────────────────
226
227    /// Returns `true` if a service of type `T` has been registered (unnamed).
228    pub fn contains<T: ?Sized + 'static>(&self) -> bool {
229        self.services.contains_key(&TypeId::of::<T>())
230    }
231
232    /// Returns `true` if a named service of type `T` with `name` exists.
233    pub fn contains_named<T: ?Sized + 'static>(&self, name: &str) -> bool {
234        self.named
235            .contains_key(&(TypeId::of::<T>(), name.to_string()))
236    }
237
238    /// Total number of unnamed registrations.
239    pub fn len(&self) -> usize {
240        self.services.len()
241    }
242
243    /// Returns `true` if no services have been registered.
244    pub fn is_empty(&self) -> bool {
245        self.services.is_empty()
246    }
247
248    // ── Sharing ─────────────────────────────────────────────────────────────────
249
250    /// Seal this container into an `Arc<Container>` for sharing across threads
251    /// and handler closures.
252    ///
253    /// Typical usage with `App::with_state`:
254    /// ```rust,no_run
255    /// # use rust_web_server::di::Container;
256    /// # use rust_web_server::app::App;
257    /// # use rust_web_server::routes;
258    /// let mut c = Container::new();
259    /// // c.register(...);
260    /// let app = routes! { App::with_state(c.into_arc()), };
261    /// ```
262    pub fn into_arc(self) -> Arc<Self> {
263        Arc::new(self)
264    }
265}
266
267#[cfg(test)]
268mod tests;