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    /// 严格 scope 校验下从 root provider 解析 Scoped 服务。
35    ///
36    /// 等价于 MEDI `ValidateScopes` 的
37    /// `InvalidOperationException("Cannot resolve scoped service from root provider.")`。
38    #[error(
39        "scoped service '{service}' cannot be resolved from the root provider; \
40         create a scope with provider.scope() and resolve it there, or build with \
41         ServiceProviderOptions{{ validate_scopes: false }} to restore 0.7 root-scope behavior"
42    )]
43    ScopedFromRootProvider { service: String },
44    /// provider 已被显式 [`dispose`](crate::ServiceProvider::dispose) 或已销毁。
45    #[error("the service provider has been disposed; resolution is no longer allowed")]
46    ProviderDisposed,
47    /// scope 已被显式 [`dispose`](crate::Scope::dispose) 或 `Drop` 释放。
48    #[error("the scope has been disposed; resolution is no longer allowed")]
49    ScopeDisposed,
50}
51
52impl RdiError {
53    /// Build a [`ServiceNotFound`](Self::ServiceNotFound) with an optional consumer context.
54    pub fn not_found(ty: &'static str, context: impl Into<Option<String>>) -> Self {
55        Self::ServiceNotFound {
56            ty,
57            context: context.into(),
58        }
59    }
60
61    /// Build a [`KeyedServiceNotFound`](Self::KeyedServiceNotFound) with optional context.
62    pub fn keyed_not_found(
63        ty: &'static str,
64        key: impl Into<String>,
65        context: impl Into<Option<String>>,
66    ) -> Self {
67        Self::KeyedServiceNotFound {
68            key: key.into(),
69            ty,
70            context: context.into(),
71        }
72    }
73
74    /// Build a [`ScopedFromRootProvider`](Self::ScopedFromRootProvider) for `service`.
75    pub fn scoped_from_root(service: &'static str) -> Self {
76        Self::ScopedFromRootProvider {
77            service: service.to_string(),
78        }
79    }
80
81    /// Convert a `panic::catch_unwind` payload into [`RdiError`].
82    ///
83    /// Prefers a panicked [`RdiError`] (from inject constructors via `panic_any`),
84    /// then string payloads, otherwise wraps as [`FactoryPanic`](Self::FactoryPanic).
85    pub fn from_panic_payload(service: &str, payload: Box<dyn Any + Send>) -> Self {
86        match payload.downcast::<RdiError>() {
87            Ok(err) => *err,
88            Err(payload) => {
89                let msg = payload
90                    .downcast_ref::<&'static str>()
91                    .map(|s| (*s).to_string())
92                    .or_else(|| payload.downcast_ref::<String>().cloned())
93                    .unwrap_or_else(|| "<non-string panic payload>".to_string());
94                Self::FactoryPanic {
95                    service: service.to_string(),
96                    payload: msg,
97                }
98            }
99        }
100    }
101}
102
103/// Convert an inject `Result` into `Arc<T>`, panicking with [`RdiError`] as the
104/// payload so runtime `catch_unwind` can recover a typed error.
105///
106/// Used by generated `__rdi_construct_*` wrappers and inventory factories.
107#[doc(hidden)]
108pub fn inject_arc<T: Send + Sync + 'static>(
109    result: Result<std::sync::Arc<T>, RdiError>,
110) -> std::sync::Arc<T> {
111    match result {
112        Ok(v) => v,
113        Err(e) => std::panic::panic_any(e),
114    }
115}