Skip to main content

rust_dix/
error.rs

1use std::any::Any;
2use thiserror::Error;
3
4/// Errors returned by the container.
5///
6/// The enum is `#[non_exhaustive]`: future releases may add variants without a
7/// semver-major break. Match with a wildcard arm (`_ => …`) when handling errors.
8#[derive(Error, Debug, Clone)]
9#[non_exhaustive]
10pub enum RdiError {
11    #[error("service not registered: {ty}{}", context.as_ref().map(|c| format!(" (while resolving {c})")).unwrap_or_default())]
12    ServiceNotFound {
13        ty: &'static str,
14        context: Option<String>,
15    },
16    #[error("keyed service not registered: key={key}, type={ty}{}", context.as_ref().map(|c| format!(" (while resolving {c})")).unwrap_or_default())]
17    KeyedServiceNotFound {
18        key: String,
19        ty: &'static str,
20        context: Option<String>,
21    },
22    #[error("circular dependency detected: {0}")]
23    CircularDependency(String),
24    #[error("singleton '{singleton}' cannot depend on scoped service '{scoped}'")]
25    SingletonDependsOnScoped { singleton: String, scoped: String },
26    #[error("factory panicked while resolving service '{service}': {payload}")]
27    FactoryPanic { service: String, payload: String },
28    #[error("cannot resolve owned instance of '{0}': Singleton services cannot be owned")]
29    OwnedNotSupported(&'static str),
30    #[error(
31        "async service '{service}' cannot be built with build(); use ServiceCollection::build_async() instead"
32    )]
33    AsyncServiceRequiresBuildAsync { service: String },
34    #[error(
35        "async service '{service}' must be resolved with get_async()/get_keyed_async(); \
36         synchronous resolution is not available before its async initialization completes"
37    )]
38    AsyncServiceRequiresGetAsync { service: String },
39    /// 同步 singleton 依赖 async singleton(build 期检测)。
40    ///
41    /// 同步构造函数无法 `await`,因此该依赖方向在架构上不成立:请把依赖方也改为
42    /// async singleton,或在方法内用 `ScopeFactory` / `get_async` 延迟解析。
43    #[error(
44        "synchronous singleton '{singleton}' depends on async service '{async_service}': \
45         a sync constructor cannot await; make '{singleton}' an async singleton or resolve \
46         '{async_service}' lazily in a method (ScopeFactory / get_async)"
47    )]
48    SyncSingletonDependsOnAsyncService {
49        singleton: String,
50        async_service: String,
51    },
52    /// 严格 scope 校验下从 root provider 解析 Scoped 服务。
53    ///
54    /// 等价于 MEDI `ValidateScopes` 的
55    /// `InvalidOperationException("Cannot resolve scoped service from root provider.")`。
56    #[error(
57        "scoped service '{service}' cannot be resolved from the root provider; \
58         create a scope with provider.scope() and resolve it there, or build with \
59         ServiceProviderOptions{{ validate_scopes: false }} to restore 0.7 root-scope behavior"
60    )]
61    ScopedFromRootProvider { service: String },
62    /// provider 已被显式 [`dispose`](crate::ServiceProvider::dispose) 或已销毁。
63    #[error("the service provider has been disposed; resolution is no longer allowed")]
64    ProviderDisposed,
65    /// scope 已被显式 [`dispose`](crate::Scope::dispose) 或 `Drop` 释放。
66    #[error("the scope has been disposed; resolution is no longer allowed")]
67    ScopeDisposed,
68}
69
70impl RdiError {
71    /// Build a [`ServiceNotFound`](Self::ServiceNotFound) with an optional consumer context.
72    pub fn not_found(ty: &'static str, context: impl Into<Option<String>>) -> Self {
73        Self::ServiceNotFound {
74            ty,
75            context: context.into(),
76        }
77    }
78
79    /// Build a [`KeyedServiceNotFound`](Self::KeyedServiceNotFound) with optional context.
80    pub fn keyed_not_found(
81        ty: &'static str,
82        key: impl Into<String>,
83        context: impl Into<Option<String>>,
84    ) -> Self {
85        Self::KeyedServiceNotFound {
86            key: key.into(),
87            ty,
88            context: context.into(),
89        }
90    }
91
92    /// Build a [`ScopedFromRootProvider`](Self::ScopedFromRootProvider) for `service`.
93    pub fn scoped_from_root(service: &'static str) -> Self {
94        Self::ScopedFromRootProvider {
95            service: service.to_string(),
96        }
97    }
98
99    /// Whether this error (or a wrapping [`FactoryPanic`](Self::FactoryPanic))
100    /// represents a dependency cycle.
101    ///
102    /// When a user factory `unwrap()`s a nested `CircularDependency`, the
103    /// top-level build error is a `FactoryPanic` whose payload contains the
104    /// `CircularDependency` rendering. This helper is a **heuristic**: it also
105    /// matches a `FactoryPanic` whose (user-provided) payload happens to contain
106    /// that text. Prefer matching the typed variants directly when available.
107    pub fn is_circular(&self) -> bool {
108        match self {
109            Self::CircularDependency(_) => true,
110            Self::FactoryPanic { payload, .. } => payload.contains("CircularDependency"),
111            _ => false,
112        }
113    }
114
115    /// Convert a `panic::catch_unwind` payload into [`RdiError`].
116    ///
117    /// Prefers a panicked [`RdiError`] (from inject constructors via `panic_any`),
118    /// then string payloads, otherwise wraps as [`FactoryPanic`](Self::FactoryPanic).
119    pub fn from_panic_payload(service: &str, payload: Box<dyn Any + Send>) -> Self {
120        match payload.downcast::<RdiError>() {
121            Ok(err) => *err,
122            Err(payload) => {
123                let msg = payload
124                    .downcast_ref::<&'static str>()
125                    .map(|s| (*s).to_string())
126                    .or_else(|| payload.downcast_ref::<String>().cloned())
127                    .unwrap_or_else(|| "<non-string panic payload>".to_string());
128                Self::FactoryPanic {
129                    service: service.to_string(),
130                    payload: msg,
131                }
132            }
133        }
134    }
135}
136
137/// Convert an inject `Result` into `Arc<T>`, panicking with [`RdiError`] as the
138/// payload so runtime `catch_unwind` can recover a typed error.
139///
140/// Used by generated `__rdi_construct_*` wrappers and inventory factories.
141#[doc(hidden)]
142pub fn inject_arc<T: Send + Sync + 'static>(
143    result: Result<std::sync::Arc<T>, RdiError>,
144) -> std::sync::Arc<T> {
145    match result {
146        Ok(v) => v,
147        Err(e) => std::panic::panic_any(e),
148    }
149}