use thiserror::Error;
pub type Result<T> = std::result::Result<T, Error>;
#[derive(Error, Debug)]
pub enum Error {
#[error("plugin not found: {0}")]
PluginNotFound(String),
#[error("plugin already loaded: {0}")]
PluginAlreadyLoaded(String),
#[error("invalid manifest: {0}")]
InvalidManifest(String),
#[error("missing required manifest field: {0}")]
MissingManifestField(String),
#[error("API version mismatch: plugin requires {required}, host provides {provided}")]
ApiVersionMismatch {
required: String,
provided: String,
},
#[error("missing required capability: {0}")]
MissingCapability(String),
#[error("capability not declared in manifest: {0}")]
UndeclaredCapability(String),
#[error("dependency not satisfied: {name} requires {version}")]
DependencyNotSatisfied {
name: String,
version: String,
},
#[error("plugin initialization failed: {0}")]
InitializationFailed(String),
#[error("plugin execution failed: {0}")]
ExecutionFailed(String),
#[error("invalid plugin state: expected {expected}, got {actual}")]
InvalidState {
expected: String,
actual: String,
},
#[error("function not found: {0}")]
FunctionNotFound(String),
#[error("compilation error: {0}")]
Compilation(String),
#[error("io error: {0}")]
Io(#[from] std::io::Error),
#[error("host error: {0}")]
Host(#[from] fusabi_host::Error),
#[cfg(feature = "serde")]
#[error("manifest parse error: {0}")]
ManifestParse(String),
#[cfg(feature = "watch")]
#[error("watch error: {0}")]
Watch(String),
#[error("plugin was unloaded")]
PluginUnloaded,
#[error("plugin reload failed: {0}")]
ReloadFailed(String),
#[error("registry error: {0}")]
Registry(String),
}
impl Error {
pub fn plugin_not_found(name: impl Into<String>) -> Self {
Self::PluginNotFound(name.into())
}
pub fn invalid_manifest(msg: impl Into<String>) -> Self {
Self::InvalidManifest(msg.into())
}
pub fn missing_field(field: impl Into<String>) -> Self {
Self::MissingManifestField(field.into())
}
pub fn api_version_mismatch(required: impl Into<String>, provided: impl Into<String>) -> Self {
Self::ApiVersionMismatch {
required: required.into(),
provided: provided.into(),
}
}
pub fn missing_capability(cap: impl Into<String>) -> Self {
Self::MissingCapability(cap.into())
}
pub fn dependency_not_satisfied(name: impl Into<String>, version: impl Into<String>) -> Self {
Self::DependencyNotSatisfied {
name: name.into(),
version: version.into(),
}
}
pub fn init_failed(msg: impl Into<String>) -> Self {
Self::InitializationFailed(msg.into())
}
pub fn execution_failed(msg: impl Into<String>) -> Self {
Self::ExecutionFailed(msg.into())
}
pub fn invalid_state(expected: impl Into<String>, actual: impl Into<String>) -> Self {
Self::InvalidState {
expected: expected.into(),
actual: actual.into(),
}
}
pub fn is_recoverable(&self) -> bool {
matches!(
self,
Self::PluginNotFound(_)
| Self::FunctionNotFound(_)
| Self::InvalidState { .. }
)
}
pub fn should_reload(&self) -> bool {
matches!(
self,
Self::Compilation(_) | Self::ExecutionFailed(_) | Self::ReloadFailed(_)
)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_error_display() {
let err = Error::plugin_not_found("my-plugin");
assert_eq!(err.to_string(), "plugin not found: my-plugin");
let err = Error::api_version_mismatch("0.2.0", "0.1.0");
assert!(err.to_string().contains("0.2.0"));
assert!(err.to_string().contains("0.1.0"));
}
#[test]
fn test_error_classification() {
assert!(Error::plugin_not_found("test").is_recoverable());
assert!(!Error::init_failed("test").is_recoverable());
assert!(Error::Compilation("test".into()).should_reload());
assert!(!Error::plugin_not_found("test").should_reload());
}
}