fusabi_plugin_runtime/
error.rs

1//! Error types for plugin runtime operations.
2
3use thiserror::Error;
4
5/// Result type alias using [`Error`].
6pub type Result<T> = std::result::Result<T, Error>;
7
8/// Errors that can occur during plugin operations.
9#[derive(Error, Debug)]
10pub enum Error {
11    /// Plugin not found.
12    #[error("plugin not found: {0}")]
13    PluginNotFound(String),
14
15    /// Plugin already loaded.
16    #[error("plugin already loaded: {0}")]
17    PluginAlreadyLoaded(String),
18
19    /// Invalid manifest.
20    #[error("invalid manifest: {0}")]
21    InvalidManifest(String),
22
23    /// Missing required field in manifest.
24    #[error("missing required manifest field: {0}")]
25    MissingManifestField(String),
26
27    /// API version mismatch.
28    #[error("API version mismatch: plugin requires {required}, host provides {provided}")]
29    ApiVersionMismatch {
30        /// Version required by plugin.
31        required: String,
32        /// Version provided by host.
33        provided: String,
34    },
35
36    /// Missing required capability.
37    #[error("missing required capability: {0}")]
38    MissingCapability(String),
39
40    /// Capability not declared in manifest.
41    #[error("capability not declared in manifest: {0}")]
42    UndeclaredCapability(String),
43
44    /// Dependency not satisfied.
45    #[error("dependency not satisfied: {name} requires {version}")]
46    DependencyNotSatisfied {
47        /// Dependency name.
48        name: String,
49        /// Required version.
50        version: String,
51    },
52
53    /// Plugin initialization failed.
54    #[error("plugin initialization failed: {0}")]
55    InitializationFailed(String),
56
57    /// Plugin execution failed.
58    #[error("plugin execution failed: {0}")]
59    ExecutionFailed(String),
60
61    /// Plugin already in invalid state for operation.
62    #[error("invalid plugin state: expected {expected}, got {actual}")]
63    InvalidState {
64        /// Expected state.
65        expected: String,
66        /// Actual state.
67        actual: String,
68    },
69
70    /// Function not found in plugin.
71    #[error("function not found: {0}")]
72    FunctionNotFound(String),
73
74    /// Compilation error.
75    #[error("compilation error: {0}")]
76    Compilation(String),
77
78    /// IO error.
79    #[error("io error: {0}")]
80    Io(#[from] std::io::Error),
81
82    /// Host error.
83    #[error("host error: {0}")]
84    Host(#[from] fusabi_host::Error),
85
86    /// Manifest parse error.
87    #[cfg(feature = "serde")]
88    #[error("manifest parse error: {0}")]
89    ManifestParse(String),
90
91    /// Watch error.
92    #[cfg(feature = "watch")]
93    #[error("watch error: {0}")]
94    Watch(String),
95
96    /// Plugin was unloaded.
97    #[error("plugin was unloaded")]
98    PluginUnloaded,
99
100    /// Plugin reload failed.
101    #[error("plugin reload failed: {0}")]
102    ReloadFailed(String),
103
104    /// Registry error.
105    #[error("registry error: {0}")]
106    Registry(String),
107}
108
109impl Error {
110    /// Create a plugin not found error.
111    pub fn plugin_not_found(name: impl Into<String>) -> Self {
112        Self::PluginNotFound(name.into())
113    }
114
115    /// Create an invalid manifest error.
116    pub fn invalid_manifest(msg: impl Into<String>) -> Self {
117        Self::InvalidManifest(msg.into())
118    }
119
120    /// Create a missing manifest field error.
121    pub fn missing_field(field: impl Into<String>) -> Self {
122        Self::MissingManifestField(field.into())
123    }
124
125    /// Create an API version mismatch error.
126    pub fn api_version_mismatch(required: impl Into<String>, provided: impl Into<String>) -> Self {
127        Self::ApiVersionMismatch {
128            required: required.into(),
129            provided: provided.into(),
130        }
131    }
132
133    /// Create a missing capability error.
134    pub fn missing_capability(cap: impl Into<String>) -> Self {
135        Self::MissingCapability(cap.into())
136    }
137
138    /// Create a dependency not satisfied error.
139    pub fn dependency_not_satisfied(name: impl Into<String>, version: impl Into<String>) -> Self {
140        Self::DependencyNotSatisfied {
141            name: name.into(),
142            version: version.into(),
143        }
144    }
145
146    /// Create an initialization failed error.
147    pub fn init_failed(msg: impl Into<String>) -> Self {
148        Self::InitializationFailed(msg.into())
149    }
150
151    /// Create an execution failed error.
152    pub fn execution_failed(msg: impl Into<String>) -> Self {
153        Self::ExecutionFailed(msg.into())
154    }
155
156    /// Create an invalid state error.
157    pub fn invalid_state(expected: impl Into<String>, actual: impl Into<String>) -> Self {
158        Self::InvalidState {
159            expected: expected.into(),
160            actual: actual.into(),
161        }
162    }
163
164    /// Returns true if this error is recoverable.
165    pub fn is_recoverable(&self) -> bool {
166        matches!(
167            self,
168            Self::PluginNotFound(_)
169                | Self::FunctionNotFound(_)
170                | Self::InvalidState { .. }
171        )
172    }
173
174    /// Returns true if this error should trigger a reload.
175    pub fn should_reload(&self) -> bool {
176        matches!(
177            self,
178            Self::Compilation(_) | Self::ExecutionFailed(_) | Self::ReloadFailed(_)
179        )
180    }
181}
182
183#[cfg(test)]
184mod tests {
185    use super::*;
186
187    #[test]
188    fn test_error_display() {
189        let err = Error::plugin_not_found("my-plugin");
190        assert_eq!(err.to_string(), "plugin not found: my-plugin");
191
192        let err = Error::api_version_mismatch("0.2.0", "0.1.0");
193        assert!(err.to_string().contains("0.2.0"));
194        assert!(err.to_string().contains("0.1.0"));
195    }
196
197    #[test]
198    fn test_error_classification() {
199        assert!(Error::plugin_not_found("test").is_recoverable());
200        assert!(!Error::init_failed("test").is_recoverable());
201
202        assert!(Error::Compilation("test".into()).should_reload());
203        assert!(!Error::plugin_not_found("test").should_reload());
204    }
205}