Skip to main content

astrid_plugins/
error.rs

1//! Plugin error types.
2
3use std::path::PathBuf;
4
5use crate::PluginId;
6
7/// Errors from plugin operations.
8#[derive(Debug, thiserror::Error)]
9pub enum PluginError {
10    /// The requested plugin was not found in the registry.
11    #[error("plugin not found: {0}")]
12    NotFound(PluginId),
13
14    /// A plugin with this ID is already registered.
15    #[error("plugin already registered: {0}")]
16    AlreadyRegistered(PluginId),
17
18    /// Failed to parse a plugin manifest file.
19    #[error("manifest parse error in {path}: {message}")]
20    ManifestParseError {
21        /// Path to the manifest file.
22        path: PathBuf,
23        /// Parse error message.
24        message: String,
25    },
26
27    /// Plugin failed to load.
28    #[error("plugin load failed: {plugin_id} - {message}")]
29    LoadFailed {
30        /// The plugin that failed to load.
31        plugin_id: PluginId,
32        /// Failure reason.
33        message: String,
34    },
35
36    /// Plugin tool execution failed.
37    #[error("plugin execution failed: {0}")]
38    ExecutionFailed(String),
39
40    /// The requested tool was not found.
41    #[error("tool not found: {0}")]
42    ToolNotFound(String),
43
44    /// The plugin ID is invalid.
45    #[error("invalid plugin id: {0}")]
46    InvalidId(String),
47
48    /// Storage operation failed.
49    #[error("storage error: {0}")]
50    Storage(String),
51
52    /// The MCP server for a plugin failed.
53    #[error("MCP server failed for plugin {plugin_id}: {message}")]
54    McpServerFailed {
55        /// The plugin whose MCP server failed.
56        plugin_id: PluginId,
57        /// Failure reason.
58        message: String,
59    },
60
61    /// An MCP client is required but was not provided.
62    #[error("MCP client required for MCP plugin entry point")]
63    McpClientRequired,
64
65    /// The plugin entry point type is not supported by this factory.
66    #[error("unsupported entry point type: {0}")]
67    UnsupportedEntryPoint(String),
68
69    /// Sandbox profile error.
70    #[error("sandbox error: {0}")]
71    SandboxError(String),
72
73    /// I/O error.
74    #[error("I/O error: {0}")]
75    Io(#[from] std::io::Error),
76
77    /// WASM runtime error (Extism/Wasmtime).
78    #[error("WASM error: {0}")]
79    WasmError(String),
80
81    /// npm registry API failure.
82    #[error("registry error: {message}")]
83    RegistryError {
84        /// Description of the registry failure.
85        message: String,
86    },
87
88    /// SHA-512 SRI integrity verification failed.
89    #[error("integrity mismatch for {package}: expected {expected}")]
90    IntegrityError {
91        /// Package that failed verification.
92        package: String,
93        /// Expected SRI hash string.
94        expected: String,
95    },
96
97    /// Tarball extraction failure.
98    #[error("extraction error: {message}")]
99    ExtractionError {
100        /// Description of the extraction failure.
101        message: String,
102    },
103
104    /// Unsafe entry type in archive (e.g. symlink, hardlink, device node).
105    #[error("unsafe archive entry type '{entry_type}' at {path}")]
106    UnsafeEntryType {
107        /// The entry type that was rejected.
108        entry_type: String,
109        /// The path of the entry.
110        path: String,
111    },
112
113    /// Path traversal detected in archive entry.
114    #[error("path traversal detected: {path}")]
115    PathTraversal {
116        /// The offending path.
117        path: String,
118    },
119
120    /// Tarball exceeds maximum allowed size.
121    #[error("package too large: {size} bytes (limit: {limit} bytes)")]
122    PackageTooLarge {
123        /// Actual size in bytes.
124        size: u64,
125        /// Maximum allowed size in bytes.
126        limit: u64,
127    },
128
129    /// Package is not an `OpenClaw` plugin (missing `openclaw.plugin.json`).
130    #[error("not an OpenClaw plugin: missing openclaw.plugin.json")]
131    NotOpenClawPlugin,
132
133    /// Invalid npm package name.
134    #[error("invalid package name '{name}': {reason}")]
135    InvalidPackageName {
136        /// The invalid name.
137        name: String,
138        /// Why the name is invalid.
139        reason: String,
140    },
141
142    /// SSRF attempt blocked — tarball URL doesn't match registry.
143    #[error("SSRF blocked: tarball URL {url} does not match registry")]
144    SsrfBlocked {
145        /// The blocked URL.
146        url: String,
147    },
148
149    /// Security gate denied the operation.
150    #[error("security denied: {0}")]
151    SecurityDenied(String),
152
153    /// WASM module hash verification failed.
154    #[error("hash mismatch: expected {expected}, got {actual}")]
155    HashMismatch {
156        /// Expected blake3 hex digest.
157        expected: String,
158        /// Actual blake3 hex digest.
159        actual: String,
160    },
161
162    /// Lockfile read/write/parse error.
163    #[error("lockfile error at {path}: {message}")]
164    LockfileError {
165        /// Path to the lockfile.
166        path: PathBuf,
167        /// Error description.
168        message: String,
169    },
170}
171
172impl From<astrid_storage::StorageError> for PluginError {
173    fn from(e: astrid_storage::StorageError) -> Self {
174        Self::Storage(e.to_string())
175    }
176}
177
178/// Result type for plugin operations.
179pub type PluginResult<T> = Result<T, PluginError>;