Skip to main content

injectium_core/
lib.rs

1//! A minimal dependency-injection container for Rust.
2//!
3//! Injectium provides a runtime DI container built around providers.
4//!
5//! Providers can be closure-backed providers, shared values registered as
6//! `Arc<T>`, or explicit value providers created with [`cloned`] / [`copied`].
7//!
8//! # Quick Start
9//!
10//! ```
11//! use injectium_core::{Container, container, copied};
12//!
13//! // Build a container with providers
14//! let c = container! {
15//!     providers: [
16//!         copied(42_u32),
17//!         |c: &Container| format!("value is {}", c.get::<u32>()),
18//!     ],
19//! };
20//!
21//! assert_eq!(c.get::<u32>(), 42);
22//! assert_eq!(c.get::<String>(), "value is 42");
23//! ```
24//!
25//! # Using `#[derive(Injectable)]`
26//!
27//! The [`injectium`](https://docs.rs/injectium) crate (not this one) re-exports
28//! the `#[derive(Injectable)]` macro which automatically implements the
29//! [`Injectable`] trait for your structs:
30//!
31//! ```ignore
32//! use injectium::{Injectable, container};
33//! use std::sync::Arc;
34//!
35//! #[derive(Clone, Injectable)]
36//! struct Db {
37//!     conn: Arc<Connection>,
38//! }
39//!
40//! #[derive(Clone, Injectable)]
41//! struct Config {
42//!     url: String,
43//! }
44//!
45//! #[derive(Injectable)]
46//! struct Service {
47//!     db: Db,
48//!     config: Config,
49//! }
50//!
51//! // At application startup:
52//! let c = container! {
53//!     providers: [
54//!         Arc::new(Connection::new()),
55//!         Config { url: "localhost".into() },
56//!     ],
57//! };
58//!
59//! // Anywhere in your code, resolve a fully-constructed Service:
60//! let svc = Service::from_container(&c);
61//! ```
62//!
63//! # Validation
64//!
65//! Call [`Container::validate`] at startup to ensure every
66//! `#[derive(Injectable)]` struct's dependencies are actually registered:
67//!
68//! ```ignore
69//! let c = container! { /* ... */ };
70//! c.validate(); // panics with a helpful message if something is missing
71//! ```
72//!
73//! This catches misconfiguration immediately rather than failing on first use.
74
75mod container;
76mod inject;
77mod provider;
78mod types;
79
80use cfg_block::cfg_block;
81pub use container::{Container, ContainerBuilder};
82pub use inject::Injectable;
83pub use provider::{CloneProvider, CopyProvider, Provider, cloned, copied};
84pub use types::SyncBounds;
85
86/// Declarative macro for building a container from providers.
87///
88/// # Example
89///
90/// ```ignore
91/// let container = container! {
92///     providers: [
93///         Arc::new(db),
94///         config,
95///         jwt_secret,
96///         |c: &Container| MyService::new(c.get::<Arc<Db>>()),
97///         |c: &Container| AnotherService::new(c.get::<Config>()),
98///     ]
99/// };
100/// ```
101#[macro_export]
102macro_rules! container {
103    (@replace_expr $_expr:expr, $sub:expr) => {
104        $sub
105    };
106    (@count_exprs $( $expr:expr ),* $(,)?) => {
107        <[()]>::len(&[$($crate::container!(@replace_expr $expr, ())),*])
108    };
109    (providers: [$( $provider:expr ),* $(,)?] $(,)?) => {{
110        $crate::Container::builder_with_capacity(
111            $crate::container!(@count_exprs $($provider),*),
112        )
113            $(.provider($provider))*
114            .build()
115    }};
116}
117
118cfg_block! {
119    if #[cfg(feature = "validation")] {
120        pub use container::DeclaredDependency;
121        pub use inventory;
122
123        /// Declare that a type `$ty` must be present in the [`Container`].
124        ///
125        /// Registers a [`DeclaredDependency`] entry collected at link time by
126        /// [`inventory`]. Call [`Container::validate`] at startup to assert all
127        /// declared types are registered.
128        ///
129        /// `#[derive(Injectable)]` calls this automatically for every field type, so
130        /// manual use is only needed when calling `container.get::<T>()` directly
131        /// without going through [`Injectable`].
132        ///
133        /// # Example
134        ///
135        /// ```ignore
136        /// injectium::declare_dependency!(MyService);
137        /// ```
138        #[macro_export]
139        macro_rules! declare_dependency {
140            ($ty:ty) => {
141                $crate::inventory::submit! {
142                    $crate::DeclaredDependency {
143                        type_id: ::std::any::TypeId::of::<$ty>,
144                        type_name: ::std::stringify!($ty),
145                    }
146                }
147            };
148        }
149    } else {
150        /// No-op when validation is disabled.
151        #[macro_export]
152        macro_rules! declare_dependency {
153            ($ty:ty) => {};
154        }
155    }
156}
157
158#[cfg(test)]
159mod tests {
160    use std::sync::Arc;
161
162    use crate::{Container, cloned, copied};
163
164    #[test]
165    fn empty_providers() {
166        let c = container! {
167            providers: []
168        };
169        assert_eq!(c.provider_count(), 0);
170    }
171
172    #[test]
173    fn only_closure_providers() {
174        let c = container! {
175            providers: [|_c: &Container| 1_u32, |_c: &Container| 2_u64]
176        };
177        assert_eq!(c.provider_count(), 2);
178    }
179
180    #[test]
181    fn cloned_and_closure_providers() {
182        let c = container! {
183            providers: [
184                cloned(41_u16),
185                |_c: &Container| 42_u32,
186                |_c: &Container| "hello",
187            ]
188        };
189        assert_eq!(c.provider_count(), 3);
190    }
191
192    #[test]
193    fn providers_with_trailing_comma() {
194        let c = container! {
195            providers: [Arc::new(1_u32), |_c: &Container| 2_u64,]
196        };
197        assert_eq!(c.provider_count(), 2);
198    }
199
200    #[test]
201    fn only_providers_empty() {
202        let c = container! {
203            providers: []
204        };
205        assert_eq!(c.provider_count(), 0);
206    }
207
208    #[test]
209    fn get_from_arc_and_closure_providers() {
210        let c = container! {
211            providers: [Arc::new(1_u32), |_c: &Container| 99_u64]
212        };
213        assert_eq!(c.provider_count(), 2);
214        assert_eq!(*c.get::<Arc<u32>>(), 1);
215        assert_eq!(c.get::<u64>(), 99);
216    }
217
218    #[test]
219    fn with_capacity_builder() {
220        let c = Container::builder_with_capacity(3)
221            .provider(Arc::new(1_u32))
222            .provider(|_c: &Container| 2_u64)
223            .provider(|_c: &Container| 3_u8)
224            .build();
225
226        assert_eq!(c.provider_count(), 3);
227        assert_eq!(c.get::<u8>(), 3);
228    }
229
230    #[test]
231    fn get_clones_explicit_clone_providers() {
232        let c = container! { providers: [cloned(String::from("shared"))] };
233
234        assert_eq!(c.get::<String>(), "shared");
235    }
236
237    #[test]
238    fn get_copies_explicit_copy_providers() {
239        let c = container! { providers: [copied(7_u32)] };
240
241        assert_eq!(c.get::<u32>(), 7);
242    }
243
244    #[test]
245    fn get_executes_closure_providers() {
246        let c = container! {
247            providers: [|_c: &Container| String::from("factory")]
248        };
249
250        assert_eq!(c.get::<String>(), "factory");
251    }
252
253    #[test]
254    fn builder_contains_registered_output_type() {
255        let builder = Container::builder().provider(Arc::new(String::from("shared")));
256
257        assert!(builder.contains::<Arc<String>>());
258        assert!(builder.contains_provider::<Arc<String>>());
259        assert!(!builder.contains::<u32>());
260    }
261
262    #[test]
263    #[should_panic(
264        expected = "provider already registered for `alloc::sync::Arc<alloc::string::String>`"
265    )]
266    fn duplicate_arc_provider_panics() {
267        let _ = Container::builder()
268            .provider(Arc::new(String::from("first")))
269            .provider(Arc::new(String::from("second")));
270    }
271
272    #[test]
273    #[should_panic(
274        expected = "provider already registered for `alloc::sync::Arc<alloc::string::String>`"
275    )]
276    fn closure_and_arc_provider_conflict_panics() {
277        let _ = Container::builder()
278            .provider(Arc::new(String::from("shared")))
279            .provider(|_c: &Container| Arc::new(String::from("factory")));
280    }
281
282    #[test]
283    #[should_panic(expected = "provider already registered for `alloc::string::String`")]
284    fn duplicate_closure_provider_panics() {
285        let _ = Container::builder()
286            .provider(|_c: &Container| String::from("first"))
287            .provider(|_c: &Container| String::from("second"));
288    }
289}