rust-dix 1.0.0

rust-dix: A Rust dependency injection framework inspired by Microsoft.Extensions.DependencyInjection
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
use crate::error::RdiError;
use crate::key::ServiceKey;
use crate::provider::ServiceProvider;
use std::any::{Any, TypeId};
use std::collections::HashMap;
use std::future::Future;
use std::pin::Pin;
use std::sync::Arc;

/// Service lifetime enum — defines how instances are cached.
///
/// - `Transient`: new instance every resolution (never cached)
/// - `Scoped`: one instance per scope (cached within the scope)
/// - `Singleton`: one instance shared across all scopes (cached globally)
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum ServiceLifetime {
    Transient,
    Scoped,
    Singleton,
}

/// Service descriptor holds registration metadata for a service.
///
/// Created during `ServiceCollection` building and passed to `ServiceStore`
/// to create `ServiceEntry`.
#[derive(Clone)]
pub struct ServiceDescriptor {
    pub type_id: TypeId,
    pub type_name: &'static str,
    pub key: Option<ServiceKey>,
    pub factory: ServiceFactory,
    /// Factory used by `AnyKey` fallback registrations; receives the requested key.
    pub keyed_factory: Option<KeyedServiceFactory>,
    pub async_factory: Option<AsyncServiceFactory>,
    pub lifetime: ServiceLifetime,
}

/// ServiceStore is the root registry — maps `TypeId` to all `ServiceEntry` entries.
///
/// Multiple entries per type exist when keyed variants are registered.
pub type ServiceStore = HashMap<TypeId, Vec<ServiceEntry>>;

/// Service resolver trait — the core DI resolution interface.
///
/// Provides both type-erased and generic resolution methods.
///
/// # Panic contract of `Option`-returning lookups
///
/// [`Self::get_any`], [`Self::get_keyed_any`], [`Self::get_by_type_id`] and
/// [`Self::get_keyed_by_type_id`] return `Option` because they answer "found or
/// not". For a *registered* service that must not be resolved at this point
/// (root scope validation under `validate_scopes`, a disposed provider/scope,
/// circular dependency) implementations panic with a **typed [`RdiError`]**
/// payload instead of returning `None`, so required-resolution APIs
/// ([`Self::get`], `ServiceProvider::get`, …) can catch it and surface a precise
/// `Err`. Constructor injection relies on this to propagate the real cause
/// through user factories.
///
/// Infallible probing must use [`Self::try_get_any_result`] /
/// [`Self::try_get_keyed_any_result`] (or `try_get` / `try_get_keyed`), which
/// map such failures to `None`/`Err` without panicking.
pub trait IServiceResolver: Send + Sync {
    fn get_any(&self, key: &str) -> Option<Arc<dyn Any + Send + Sync>>;
    fn get_keyed_any(&self, key: &str, variant: &str) -> Option<Arc<dyn Any + Send + Sync>>;

    /// Typed-keyed resolution by type name (used by `#[inject(key = <expr>)]`).
    ///
    /// The default keeps external implementors source-compatible by falling back
    /// to the string path **only for string-like keys**
    /// ([`ServiceKey::as_str`]); typed keys require an implementor override
    /// (`ServiceProvider`, `Scope`, `ServiceProviderWrapper` and the build-time
    /// validation resolver all support typed keys).
    fn get_keyed_any_key(
        &self,
        key: &str,
        variant: &ServiceKey,
    ) -> Option<Arc<dyn Any + Send + Sync>> {
        self.get_keyed_any(key, variant.as_str()?)
    }

    /// Error-reporting typed-keyed lookup (used by required/optional inject
    /// fields). Defaults to [`Self::get_keyed_any_key`].
    fn try_get_keyed_any_result_key(
        &self,
        key: &str,
        variant: &ServiceKey,
    ) -> Result<Option<Arc<dyn Any + Send + Sync>>, RdiError> {
        Ok(self.get_keyed_any_key(key, variant))
    }

    /// Typed-keyed owned resolution (bypasses caches; non-singleton only).
    ///
    /// Default keeps external implementors working for string-like keys.
    fn get_keyed_owned_any_key(
        &self,
        key: &str,
        variant: &ServiceKey,
    ) -> Option<Arc<dyn Any + Send + Sync>> {
        self.get_keyed_owned_any(key, variant.as_str()?)
    }

    /// Error-reporting typed-keyed owned lookup. Defaults to
    /// [`Self::get_keyed_owned_any_key`].
    fn try_get_keyed_owned_any_key(
        &self,
        key: &str,
        variant: &ServiceKey,
    ) -> Result<Option<Arc<dyn Any + Send + Sync>>, RdiError> {
        Ok(self.get_keyed_owned_any_key(key, variant))
    }

    /// TypeId 直查(默认 `None`,`ServiceProvider` 等覆盖为 O(1) 直查)。
    ///
    /// 外部实现者若需优化可覆盖;不覆盖则 [`Self::get`] 默认实现 fallback
    /// 到 [`Self::get_any`] 字符串路径。
    ///
    /// 覆盖实现须遵循 trait 级 panic 契约(注册但当前不可解析 → typed
    /// [`RdiError`] payload panic,供 required API 捕获;可选探测请用
    /// [`Self::try_get_any_result`])。
    fn get_by_type_id(&self, _tid: TypeId) -> Option<Arc<dyn Any + Send + Sync>> {
        None
    }

    /// TypeId 直查 keyed 版本(默认 `None`)。
    ///
    /// 覆盖实现须遵循 trait 级 panic 契约(见 [`Self::get_by_type_id`])。
    fn get_keyed_by_type_id(&self, _tid: TypeId, _key: &str) -> Option<Arc<dyn Any + Send + Sync>> {
        None
    }

    /// Type-erased polymorphic resolution: return all **unkeyed** registered
    /// instances of the given type, in registration order.
    ///
    /// Equivalent to C# MEDI `GetServices<T>()` / `IEnumerable<IFoo>`
    /// constructor injection, which also excludes keyed services.
    /// Keyed implementations are enumerated with [`Self::get_all_keyed_any`].
    ///
    /// Default empty `Vec` keeps external implementors backward-compatible.
    fn get_all_any(&self, _key: &str) -> Vec<Arc<dyn Any + Send + Sync>> {
        Vec::new()
    }

    /// Type-erased polymorphic resolution over **keyed** registrations only
    /// (MEDI `GetKeyedServices<T>(KeyedService.AnyKey)`).
    ///
    /// Default empty `Vec` keeps external implementors backward-compatible.
    fn get_all_keyed_any(&self, _key: &str) -> Vec<Arc<dyn Any + Send + Sync>> {
        Vec::new()
    }

    /// Error-reporting variant of [`Self::get_all_any`], used by the inject
    /// macros for `Vec<Arc<T>>` fields.
    ///
    /// Containers that can fail (scope validation, disposal, factory errors)
    /// override it so constructor injection surfaces a typed [`RdiError`]
    /// instead of silently returning a partial/empty list. The default keeps
    /// external implementors source-compatible.
    fn get_all_any_result(&self, key: &str) -> Result<Vec<Arc<dyn Any + Send + Sync>>, RdiError> {
        Ok(self.get_all_any(key))
    }

    /// Return an `Arc<ServiceProvider>` for on-demand service resolution.
    ///
    /// Used by `#[inject(provider)]` to give a service access to the DI container
    /// so it can resolve scoped/owned instances on-demand (e.g., per-request
    /// `DbContext` in a web handler, or per-tick scopes in a singleton worker).
    ///
    /// Available during `build()` as well: validation executes singleton
    /// factories against the real provider, so `#[inject(provider)]` works in
    /// eagerly built singletons too.
    ///
    /// Returns `None` only for resolvers not backed by a `ServiceProvider`.
    ///
    /// Default `None` keeps external implementors backward-compatible.
    fn provider_arc(&self) -> Option<Arc<ServiceProvider>> {
        None
    }

    /// Resolve a service by type (concrete or `dyn Trait`).
    /// Returns `Err(RdiError::ServiceNotFound)` if not registered.
    ///
    /// 优先走 `get_by_type_id`(O(1) TypeId 直查),未覆盖时 fallback
    /// 到 `get_any` 字符串路径。
    fn get<T: ?Sized + Sync + Send + 'static>(&self) -> Result<Arc<T>, RdiError>
    where
        Self: Sized,
    {
        let type_name = std::any::type_name::<T>();
        let tid = TypeId::of::<T>();
        self.get_by_type_id(tid)
            .or_else(|| self.get_any(type_name))
            .and_then(|a| a.downcast::<Arc<T>>().ok().map(|d| Arc::clone(&*d)))
            .ok_or_else(|| RdiError::not_found(type_name, None))
    }

    /// Resolve a service by type, returning `None` if it is not registered or
    /// not resolvable at this point (e.g. a scoped service under root scope
    /// validation, or a disposed provider/scope).
    ///
    /// Uses [`Self::try_get_any_result`]; containers override it with an
    /// infallible lookup so `try_get` never panics for framework-level failures.
    fn try_get<T: ?Sized + Sync + Send + 'static>(&self) -> Option<Arc<T>>
    where
        Self: Sized,
    {
        let type_name = std::any::type_name::<T>();
        self.try_get_any_result(type_name)
            .ok()
            .flatten()
            .and_then(|a| a.downcast::<Arc<T>>().ok().map(|d| Arc::clone(&*d)))
    }

    /// Fallible, type-erased lookup used by [`Self::try_get`].
    ///
    /// Default implementation delegates to [`Self::get_any`] (so external
    /// resolvers keep working); containers that can fail override it so
    /// infallible lookups can distinguish "absent" from "resolution error"
    /// without panicking.
    fn try_get_any_result(
        &self,
        key: &str,
    ) -> Result<Option<Arc<dyn Any + Send + Sync>>, RdiError> {
        Ok(self.get_any(key))
    }

    /// Resolve a keyed service by type and key. Returns `Err` if not found.
    fn get_keyed<T: ?Sized + Sync + Send + 'static>(
        &self,
        variant: &str,
    ) -> Result<Arc<T>, RdiError>
    where
        Self: Sized,
    {
        let type_name = std::any::type_name::<T>();
        let tid = TypeId::of::<T>();
        self.get_keyed_by_type_id(tid, variant)
            .or_else(|| self.get_keyed_any(type_name, variant))
            .and_then(|a| a.downcast::<Arc<T>>().ok().map(|d| Arc::clone(&*d)))
            .ok_or_else(|| RdiError::keyed_not_found(type_name, variant, None))
    }

    /// Resolve a keyed service by type and key, returning `None` if not found or
    /// not resolvable at this point (e.g. root scope validation, disposed
    /// provider/scope). Never panics for framework-level failures.
    fn try_get_keyed<T: ?Sized + Sync + Send + 'static>(&self, variant: &str) -> Option<Arc<T>>
    where
        Self: Sized,
    {
        let type_name = std::any::type_name::<T>();
        self.try_get_keyed_any_result(type_name, variant)
            .ok()
            .flatten()
            .and_then(|a| a.downcast::<Arc<T>>().ok().map(|d| Arc::clone(&*d)))
    }

    /// Fallible, type-erased keyed lookup used by [`Self::try_get_keyed`].
    ///
    /// Default implementation delegates to [`Self::get_keyed_any`]; containers
    /// override it to return `None`/`Err` without panicking.
    fn try_get_keyed_any_result(
        &self,
        key: &str,
        variant: &str,
    ) -> Result<Option<Arc<dyn Any + Send + Sync>>, RdiError> {
        Ok(self.get_keyed_any(key, variant))
    }

    /// Resolve all **unkeyed** registered instances of `T`, in registration
    /// order.
    ///
    /// Equivalent to C# MEDI `GetServices<T>()` (which also excludes keyed
    /// services). Types implementing `IServiceResolver` get this for free via
    /// `get_all_any`; types with an inherent `get_all<T>` method (e.g.
    /// `ServiceProvider`, `Scope`) use that instead — Rust's method resolution
    /// gives inherent methods priority over trait methods.
    fn get_all<T: ?Sized + Sync + Send + 'static>(&self) -> Vec<Arc<T>>
    where
        Self: Sized,
    {
        self.get_all_any(std::any::type_name::<T>())
            .into_iter()
            .filter_map(|a| a.downcast::<Arc<T>>().ok().map(|d| Arc::clone(&*d)))
            .collect()
    }

    /// Type-erased owned resolution: bypass cache, call factory fresh, return
    /// `Arc<dyn Any + Send + Sync>` (concrete `Arc<Arc<T>>`).
    ///
    /// - Singleton → `None` (shared singleton cannot be owned).
    /// - Scoped/Transient → factory invoked directly (no cache read/write),
    ///   caller downcasts + `Arc::try_unwrap` to obtain owned `T`.
    ///
    /// Default `None` keeps external implementors backward-compatible.
    fn get_owned_any(&self, _key: &str) -> Option<Arc<dyn Any + Send + Sync>> {
        None
    }
    fn get_keyed_owned_any(
        &self,
        _key: &str,
        _variant: &str,
    ) -> Option<Arc<dyn Any + Send + Sync>> {
        None
    }

    /// Resolve owned `T` (bypass cache, fresh each call).
    /// Returns `Err` if not registered or the service is Singleton.
    fn get_owned<T: Send + Sync + 'static>(&self) -> Result<T, RdiError>
    where
        Self: Sized,
    {
        let type_name = std::any::type_name::<T>();
        let arc_any = self
            .get_owned_any(type_name)
            .ok_or_else(|| RdiError::not_found(type_name, None))?;
        let arc_t: Arc<T> = arc_any
            .downcast::<Arc<T>>()
            .ok()
            .map(|d| Arc::clone(&*d))
            .ok_or_else(|| RdiError::not_found(type_name, None))?;
        Arc::try_unwrap(arc_t).map_err(|_| RdiError::OwnedNotSupported(type_name))
    }

    /// Resolve owned `T`, returning `None` if not registered or Singleton.
    fn try_get_owned<T: Send + Sync + 'static>(&self) -> Option<T>
    where
        Self: Sized,
    {
        let arc_any = self.get_owned_any(std::any::type_name::<T>())?;
        let arc_t: Arc<T> = arc_any.downcast::<Arc<T>>().ok().map(|d| Arc::clone(&*d))?;
        Arc::try_unwrap(arc_t).ok()
    }

    /// Resolve owned keyed `T`.
    /// Returns `Err` if not found or Singleton.
    fn get_keyed_owned<T: Send + Sync + 'static>(&self, variant: &str) -> Result<T, RdiError>
    where
        Self: Sized,
    {
        let type_name = std::any::type_name::<T>();
        let arc_any = self
            .get_keyed_owned_any(type_name, variant)
            .ok_or_else(|| RdiError::keyed_not_found(type_name, variant, None))?;
        let arc_t: Arc<T> = arc_any
            .downcast::<Arc<T>>()
            .ok()
            .map(|d| Arc::clone(&*d))
            .ok_or_else(|| RdiError::keyed_not_found(type_name, variant, None))?;
        Arc::try_unwrap(arc_t).map_err(|_| RdiError::OwnedNotSupported(type_name))
    }

    /// Resolve owned keyed `T`, returning `None` if not found or Singleton.
    fn try_get_keyed_owned<T: Send + Sync + 'static>(&self, variant: &str) -> Option<T>
    where
        Self: Sized,
    {
        let arc_any = self.get_keyed_owned_any(std::any::type_name::<T>(), variant)?;
        let arc_t: Arc<T> = arc_any.downcast::<Arc<T>>().ok().map(|d| Arc::clone(&*d))?;
        Arc::try_unwrap(arc_t).ok()
    }
}

/// Typed resolution helpers for factory closures.
///
/// Factories are passed `&dyn IServiceResolver`; the generic methods on
/// [`IServiceResolver`] cannot be called on the trait object, so this extension
/// restores type safety inside factories (no stringified type names, no manual
/// `downcast`). Bring it into scope with `use rust_dix::IServiceResolverExt as _;`
/// (or `use rust_dix::*;`).
///
/// Sync factory closures return `Arc<T>`, so `?` belongs in a fallible helper
/// (or use `.unwrap()`/`.expect()`); the helper result is then wrapped with
/// [`inject_arc`](crate::inject_arc) to keep the typed error:
///
/// ```
/// use rust_dix::*;
/// use std::sync::Arc;
///
/// struct Config;
/// struct Handler {
///     config: Arc<Config>,
/// }
///
/// fn build_handler(resolver: &dyn IServiceResolver) -> Result<Arc<Handler>, RdiError> {
///     let config: Arc<Config> = resolver.resolve_arc()?; // `?` works here
///     Ok(Arc::new(Handler { config }))
/// }
///
/// let collection = ServiceCollection::new()
///     .singleton(|_| Arc::new(Config))
///     .singleton(|r| inject_arc(build_handler(r)));
/// let _ = collection;
/// ```
pub trait IServiceResolverExt {
    /// Resolve `T` (concrete or `dyn Trait`) as a shared instance.
    ///
    /// Intended for factory closures — the only place where a resolver is seen
    /// as `&dyn IServiceResolver`. On a container (`ServiceProvider`, `Scope`)
    /// prefer the inherent `get`/`get_optional`, which are the primary API and
    /// avoid the extra dyn-aware path.
    ///
    /// Returns `Err(RdiError::ServiceNotFound)` when nothing is registered.
    /// Services that are registered but not resolvable *at this point*
    /// (strict-scope validation, disposed container/scope, circular dependency)
    /// are also reported as `Err` — the typed `RdiError` payload of the
    /// [`IServiceResolver::get_by_type_id`] panic contract is caught and
    /// converted, so this method never panics for framework-level failures.
    fn resolve_arc<T: ?Sized + Send + Sync + 'static>(&self) -> Result<Arc<T>, RdiError>;
}

impl<R: IServiceResolver + ?Sized> IServiceResolverExt for R {
    fn resolve_arc<T: ?Sized + Send + Sync + 'static>(&self) -> Result<Arc<T>, RdiError> {
        let ty = std::any::type_name::<T>();
        let tid = TypeId::of::<T>();
        // `get_by_type_id` follows the trait panic contract (registered but not
        // resolvable → typed `RdiError` payload panic). Catch it here so the
        // typed API surfaces an ordinary `Err`, matching `IServiceResolver::get`.
        let found = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
            self.get_by_type_id(tid).or_else(|| self.get_any(ty))
        }))
        .map_err(|payload| RdiError::from_panic_payload(ty, payload))?;
        found
            .and_then(|a| a.downcast::<Arc<T>>().ok().map(|d| Arc::clone(&*d)))
            .ok_or_else(|| RdiError::not_found(ty, None))
    }
}

pub struct ServiceEntry {
    pub cache_key: usize,
    pub key: Option<ServiceKey>,
    pub type_name: &'static str, // kept for IServiceResolver string-based resolution
    /// Primary sync factory (typed, resolver-only).
    pub factory: ServiceFactory,
    /// `AnyKey` fallback factory; receives the requested key.
    pub keyed_factory: Option<KeyedServiceFactory>,
    pub async_factory: Option<AsyncServiceFactory>,
    pub lifetime: ServiceLifetime,
}

impl ServiceEntry {
    /// Whether this registration requires async initialization.
    pub(crate) fn is_async(&self) -> bool {
        self.async_factory.is_some()
    }

    /// Whether this is an `AnyKey` fallback registration.
    pub(crate) fn is_any_key(&self) -> bool {
        self.key.as_ref().is_some_and(ServiceKey::is_any)
    }
}

pub type ServiceFactory =
    Arc<dyn Fn(&dyn IServiceResolver) -> Arc<dyn Any + Send + Sync> + Send + Sync>;

/// Factory for `AnyKey` fallback registrations: receives the requested key.
pub type KeyedServiceFactory =
    Arc<dyn Fn(&dyn IServiceResolver, &ServiceKey) -> Arc<dyn Any + Send + Sync> + Send + Sync>;

/// Async variant of [`ServiceFactory`] — for services that require async
/// initialization (e.g., database connections, remote config loading).
///
/// Used with [`ServiceCollection::build_async`](crate::collection::ServiceCollection::build_async).
/// The factory receives an `Arc<ServiceProvider>` (instead of `&dyn IServiceResolver`)
/// because async closures cannot borrow from the caller's stack frame.
pub type AsyncServiceFactory = Arc<
    dyn Fn(Arc<ServiceProvider>) -> Pin<Box<dyn Future<Output = Arc<dyn Any + Send + Sync>> + Send>>
        + Send
        + Sync,
>;