Skip to main content

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    /// Lifecycle hooks, in registration order (18.35.0).
116    hooks: Vec<Hook>,
117    /// How many hooks [`start`](Container::start) ran; [`stop`](Container::stop) undoes those.
118    /// Behind `Arc`s: a container stays `Freeze` (no interior mutability inline).
119    started: Arc<std::sync::atomic::AtomicUsize>,
120    running: Arc<std::sync::atomic::AtomicBool>,
121}
122
123type StartHook = Box<dyn Fn(&Container) -> Result<(), String> + Send + Sync>;
124type StopHook = Box<dyn Fn(&Container) + Send + Sync>;
125
126struct Hook {
127    name: String,
128    start: Option<StartHook>,
129    stop: Option<StopHook>,
130}
131
132/// A service with a start and a stop: a connection pool to warm, a
133/// background thread to join (18.35.0). Register it with
134/// [`Container::register_managed`]; both default to doing nothing.
135pub trait Lifecycle: Send + Sync {
136    /// Called by [`Container::start`], in registration order.
137    fn start(&self) -> Result<(), String> {
138        Ok(())
139    }
140
141    /// Called by [`Container::stop`], in reverse order — only if `start`
142    /// succeeded.
143    fn stop(&self) {}
144}
145
146impl Default for Container {
147    fn default() -> Self {
148        Self::new()
149    }
150}
151
152impl Container {
153    /// Create an empty container.
154    pub fn new() -> Self {
155        Container {
156            services: HashMap::new(),
157            named: HashMap::new(),
158            hooks: Vec::new(),
159            started: Arc::new(std::sync::atomic::AtomicUsize::new(0)),
160            running: Arc::new(std::sync::atomic::AtomicBool::new(false)),
161        }
162    }
163
164    // ── Registration ────────────────────────────────────────────────────────────
165
166    /// Register a concrete service by value.
167    ///
168    /// Wraps `service` in `Arc<T>` and stores it under `TypeId::of::<T>()`.
169    /// A subsequent registration for the same `T` replaces the previous one.
170    pub fn register<T: Send + Sync + 'static>(&mut self, service: T) -> &mut Self {
171        self.services
172            .insert(TypeId::of::<T>(), Box::new(Arc::new(service)));
173        self
174    }
175
176    /// Register a pre-built `Arc<T>`.
177    ///
178    /// Required when registering **trait objects**, because the concrete type
179    /// must be erased before storage:
180    ///
181    /// ```rust
182    /// # extern crate rws_server as rust_web_server;
183    /// # use std::sync::Arc;
184    /// # use rust_web_server::di::Container;
185    /// # trait Greeter: Send + Sync { fn greet(&self) -> &str; }
186    /// # struct Hello; impl Greeter for Hello { fn greet(&self) -> &str { "hi" } }
187    /// let mut c = Container::new();
188    /// c.provide::<dyn Greeter>(Arc::new(Hello));
189    /// assert_eq!(c.get::<dyn Greeter>().unwrap().greet(), "hi");
190    /// ```
191    ///
192    /// Also usable for concrete types when you already have an `Arc`.
193    pub fn provide<T>(&mut self, service: Arc<T>) -> &mut Self
194    where
195        T: ?Sized + Send + Sync + 'static,
196    {
197        // Arc<T> is always Sized (fat pointer for DSTs), 'static when T: 'static,
198        // Send + Sync when T: Send + Sync, and Any via blanket impl for all 'static.
199        let boxed: Box<dyn Any + Send + Sync> = Box::new(service);
200        self.services.insert(TypeId::of::<T>(), boxed);
201        self
202    }
203
204    /// Register a named concrete service.
205    ///
206    /// Use this when you need multiple instances of the same type
207    /// (e.g., primary vs. replica database pools).
208    pub fn register_named<T: Send + Sync + 'static>(
209        &mut self,
210        name: impl Into<String>,
211        service: T,
212    ) -> &mut Self {
213        self.named.insert(
214            (TypeId::of::<T>(), name.into()),
215            Box::new(Arc::new(service)),
216        );
217        self
218    }
219
220    /// Register a pre-built `Arc<T>` under a string name.
221    ///
222    /// Use for named trait-object registrations.
223    pub fn provide_named<T>(
224        &mut self,
225        name: impl Into<String>,
226        service: Arc<T>,
227    ) -> &mut Self
228    where
229        T: ?Sized + Send + Sync + 'static,
230    {
231        let boxed: Box<dyn Any + Send + Sync> = Box::new(service);
232        self.named.insert((TypeId::of::<T>(), name.into()), boxed);
233        self
234    }
235
236    // ── Resolution ──────────────────────────────────────────────────────────────
237
238    /// Resolve a service by type.
239    ///
240    /// Works for both **concrete types** (`get::<MyService>()`) and **trait
241    /// objects** (`get::<dyn MyTrait>()`).  Returns `None` if no service of
242    /// that type has been registered.
243    pub fn get<T>(&self) -> Option<Arc<T>>
244    where
245        T: ?Sized + 'static,
246        Arc<T>: Clone,
247    {
248        self.services
249            .get(&TypeId::of::<T>())
250            .and_then(|b| b.downcast_ref::<Arc<T>>())
251            .cloned()
252    }
253
254    /// Resolve a named service by type and name.
255    ///
256    /// Returns `None` if no service of that type and name has been registered.
257    pub fn get_named<T>(&self, name: &str) -> Option<Arc<T>>
258    where
259        T: ?Sized + 'static,
260        Arc<T>: Clone,
261    {
262        self.named
263            .get(&(TypeId::of::<T>(), name.to_string()))
264            .and_then(|b| b.downcast_ref::<Arc<T>>())
265            .cloned()
266    }
267
268    // ── Inspection ──────────────────────────────────────────────────────────────
269
270    /// Returns `true` if a service of type `T` has been registered (unnamed).
271    pub fn contains<T: ?Sized + 'static>(&self) -> bool {
272        self.services.contains_key(&TypeId::of::<T>())
273    }
274
275    /// Returns `true` if a named service of type `T` with `name` exists.
276    pub fn contains_named<T: ?Sized + 'static>(&self, name: &str) -> bool {
277        self.named
278            .contains_key(&(TypeId::of::<T>(), name.to_string()))
279    }
280
281    /// Total number of unnamed registrations.
282    pub fn len(&self) -> usize {
283        self.services.len()
284    }
285
286    /// Returns `true` if no services have been registered.
287    pub fn is_empty(&self) -> bool {
288        self.services.is_empty()
289    }
290
291    // ── Lifecycle (18.35.0) ──────────────────────────────────────────────────────
292
293    /// Run `hook` at [`start`](Self::start), after the hooks registered
294    /// before it. An `Err` stops the start.
295    pub fn on_start(&mut self, name: &str, hook: impl Fn(&Container) -> Result<(), String> + Send + Sync + 'static) -> &mut Self {
296        self.hooks.push(Hook { name: name.to_string(), start: Some(Box::new(hook)), stop: None });
297        self
298    }
299
300    /// Run `hook` at [`stop`](Self::stop), before the hooks registered
301    /// before it (stops run in reverse).
302    pub fn on_stop(&mut self, name: &str, hook: impl Fn(&Container) + Send + Sync + 'static) -> &mut Self {
303        self.hooks.push(Hook { name: name.to_string(), start: None, stop: Some(Box::new(hook)) });
304        self
305    }
306
307    /// Register `service` (as [`register`](Self::register)) with its
308    /// [`Lifecycle`] `start`/`stop` as hooks.
309    pub fn register_managed<T: Lifecycle + 'static>(&mut self, service: T) -> &mut Self {
310        self.register(service);
311        let name = std::any::type_name::<T>().to_string();
312        self.hooks.push(Hook {
313            name,
314            start: Some(Box::new(|c: &Container| c.get::<T>().map_or(Ok(()), |s| s.start()))),
315            stop: Some(Box::new(|c: &Container| {
316                if let Some(s) = c.get::<T>() {
317                    s.stop();
318                }
319            })),
320        });
321        self
322    }
323
324    /// Run the start hooks in registration order. When one fails, the stop
325    /// hooks of those before it run (in reverse) and the error names the
326    /// hook: `"<name>: <error>"`. Calling it again after a success does
327    /// nothing until [`stop`](Self::stop).
328    pub fn start(&self) -> Result<(), String> {
329        use std::sync::atomic::Ordering;
330        if self.running.load(Ordering::SeqCst) {
331            return Ok(());
332        }
333        for (i, hook) in self.hooks.iter().enumerate() {
334            if let Some(start) = &hook.start {
335                if let Err(e) = start(self) {
336                    self.stop_first(i);
337                    return Err(format!("{}: {e}", hook.name));
338                }
339            }
340        }
341        self.started.store(self.hooks.len(), Ordering::SeqCst);
342        self.running.store(true, Ordering::SeqCst);
343        Ok(())
344    }
345
346    /// Run the stop hooks of everything [`start`](Self::start) started, in
347    /// reverse order. Does nothing when not started; safe to call twice.
348    pub fn stop(&self) {
349        if self.running.swap(false, std::sync::atomic::Ordering::SeqCst) {
350            let n = self.started.swap(0, std::sync::atomic::Ordering::SeqCst);
351            self.stop_first(n);
352        }
353    }
354
355    fn stop_first(&self, n: usize) {
356        for hook in self.hooks[..n.min(self.hooks.len())].iter().rev() {
357            if let Some(stop) = &hook.stop {
358                stop(self);
359            }
360        }
361    }
362
363    /// Whether [`start`](Self::start) succeeded and [`stop`](Self::stop)
364    /// hasn't run since.
365    pub fn is_started(&self) -> bool {
366        self.running.load(std::sync::atomic::Ordering::SeqCst)
367    }
368
369    // ── Sharing ─────────────────────────────────────────────────────────────────
370
371    /// Seal this container into an `Arc<Container>` for sharing across threads
372    /// and handler closures.
373    ///
374    /// Typical usage with `App::with_state`:
375    /// ```rust,no_run
376    /// # extern crate rws_server as rust_web_server;
377    /// # use rust_web_server::di::Container;
378    /// # use rust_web_server::app::App;
379    /// let mut c = Container::new();
380    /// // c.register(...);
381    /// let app = App::with_state(c.into_arc());
382    /// ```
383    pub fn into_arc(self) -> Arc<Self> {
384        Arc::new(self)
385    }
386}
387
388#[cfg(test)]
389mod tests;