bamboo_plugin/error.rs
1//! Error type shared by manifest validation, provenance I/O, and the
2//! installer trait.
3
4/// Result alias used throughout this crate.
5pub type PluginResult<T> = Result<T, PluginError>;
6
7#[derive(Debug, thiserror::Error)]
8pub enum PluginError {
9 /// `plugin.json` failed structural validation (see
10 /// [`crate::manifest::PluginManifest::validate`]) — includes bad ids,
11 /// bad semver shape, path-traversal attempts, duplicate ids, etc.
12 #[error("invalid plugin manifest: {0}")]
13 InvalidManifest(String),
14
15 /// No installed plugin with this id (uninstall/lookup).
16 #[error("plugin not found: {0}")]
17 NotFound(String),
18
19 /// A plugin with this id is already installed and `install` was called
20 /// with [`crate::installer::InstallDisposition::FailIfInstalled`] (the
21 /// `bamboo plugin install` verb). Re-run as an upgrade
22 /// (`bamboo plugin update` / [`crate::installer::InstallDisposition::Upgrade`])
23 /// to replace it in place.
24 #[error("plugin already installed: {0} (use `update` to upgrade in place)")]
25 AlreadyInstalled(String),
26
27 /// A capability the plugin declares collides with an existing entry in a
28 /// shared store that is NOT owned by this plugin (a user's own entry, or
29 /// another plugin's). For MCP servers and workflows the install REFUSES
30 /// rather than clobbering — an MCP server id / workflow filename is
31 /// referenced elsewhere, so silently overwriting (and later deleting on
32 /// uninstall) would destroy the user's entry. `kind` is a short label
33 /// such as `"mcp server"` or `"workflow"`.
34 #[error(
35 "{kind} '{name}' already exists and is not owned by plugin '{plugin_id}'; \
36 refusing to overwrite — rename the conflicting entry or the plugin's, then retry"
37 )]
38 Conflict {
39 kind: &'static str,
40 name: String,
41 plugin_id: String,
42 },
43
44 /// The manifest's `platforms` gate excludes the current OS.
45 #[error("plugin '{plugin_id}' does not support platform '{platform}'")]
46 UnsupportedPlatform { plugin_id: String, platform: String },
47
48 /// A step this foundation crate deliberately leaves for a later agent
49 /// (capability-registration wiring — see `PLUGIN_PLAN.md`). Returned
50 /// instead of panicking so a partially-stacked branch fails a request
51 /// cleanly rather than crashing the process.
52 #[error("not yet implemented: {0}")]
53 NotImplemented(String),
54
55 /// A capability failed to register/deregister against `AppState` for a
56 /// reason OTHER than an ownership conflict (e.g. `config.json` couldn't
57 /// be persisted, a network fetch during source-staging failed). Kept
58 /// distinct from [`Self::Conflict`] (a deliberate REFUSAL, not a
59 /// failure) so callers/HTTP status mapping can tell "your plugin
60 /// collides with something" apart from "something broke while trying to
61 /// register/fetch it".
62 #[error("plugin registration failed: {0}")]
63 Registration(String),
64
65 /// A downloaded artifact's sha256 did not match the manifest's declared
66 /// hash. Checked BEFORE unpacking (supply-chain: a URL-installed plugin
67 /// ships a binary that will be executed) — never surfaced as a generic
68 /// `Registration`/`InvalidManifest` error so callers can distinguish "the
69 /// author's manifest is malformed" from "the bytes served at that URL do
70 /// not match what the manifest promised".
71 #[error("artifact verification failed: {0}")]
72 ArtifactVerificationFailed(String),
73
74 #[error("io error: {0}")]
75 Io(#[from] std::io::Error),
76
77 #[error("json error: {0}")]
78 Json(#[from] serde_json::Error),
79}