Skip to main content

rust_dix/
error.rs

1use std::any::Any;
2use thiserror::Error;
3
4#[derive(Error, Debug, Clone)]
5pub enum RdiError {
6    #[error("service not registered: {ty}{}", context.as_ref().map(|c| format!(" (while resolving {c})")).unwrap_or_default())]
7    ServiceNotFound {
8        ty: &'static str,
9        context: Option<String>,
10    },
11    #[error("keyed service not registered: key={key}, type={ty}{}", context.as_ref().map(|c| format!(" (while resolving {c})")).unwrap_or_default())]
12    KeyedServiceNotFound {
13        key: String,
14        ty: &'static str,
15        context: Option<String>,
16    },
17    #[error("circular dependency detected: {0}")]
18    CircularDependency(String),
19    #[error("singleton '{singleton}' cannot depend on scoped service '{scoped}'")]
20    SingletonDependsOnScoped { singleton: String, scoped: String },
21    #[error("factory panicked while resolving service '{service}': {payload}")]
22    FactoryPanic { service: String, payload: String },
23    #[error("cannot resolve owned instance of '{0}': Singleton services cannot be owned")]
24    OwnedNotSupported(&'static str),
25    #[error(
26        "async service '{service}' cannot be built with build(); use ServiceCollection::build_async() instead"
27    )]
28    AsyncServiceRequiresBuildAsync { service: String },
29}
30
31impl RdiError {
32    /// Build a [`ServiceNotFound`](Self::ServiceNotFound) with an optional consumer context.
33    pub fn not_found(ty: &'static str, context: impl Into<Option<String>>) -> Self {
34        Self::ServiceNotFound {
35            ty,
36            context: context.into(),
37        }
38    }
39
40    /// Build a [`KeyedServiceNotFound`](Self::KeyedServiceNotFound) with optional context.
41    pub fn keyed_not_found(
42        ty: &'static str,
43        key: impl Into<String>,
44        context: impl Into<Option<String>>,
45    ) -> Self {
46        Self::KeyedServiceNotFound {
47            key: key.into(),
48            ty,
49            context: context.into(),
50        }
51    }
52
53    /// Convert a `panic::catch_unwind` payload into [`RdiError`].
54    ///
55    /// Prefers a panicked [`RdiError`] (from inject constructors via `panic_any`),
56    /// then string payloads, otherwise wraps as [`FactoryPanic`](Self::FactoryPanic).
57    pub fn from_panic_payload(service: &str, payload: Box<dyn Any + Send>) -> Self {
58        match payload.downcast::<RdiError>() {
59            Ok(err) => *err,
60            Err(payload) => {
61                let msg = payload
62                    .downcast_ref::<&'static str>()
63                    .map(|s| (*s).to_string())
64                    .or_else(|| payload.downcast_ref::<String>().cloned())
65                    .unwrap_or_else(|| "<non-string panic payload>".to_string());
66                Self::FactoryPanic {
67                    service: service.to_string(),
68                    payload: msg,
69                }
70            }
71        }
72    }
73}
74
75/// Convert an inject `Result` into `Arc<T>`, panicking with [`RdiError`] as the
76/// payload so runtime `catch_unwind` can recover a typed error.
77///
78/// Used by generated `__rdi_construct_*` wrappers and inventory factories.
79#[doc(hidden)]
80pub fn inject_arc<T: Send + Sync + 'static>(result: Result<std::sync::Arc<T>, RdiError>) -> std::sync::Arc<T> {
81    match result {
82        Ok(v) => v,
83        Err(e) => std::panic::panic_any(e),
84    }
85}