init plugins
This commit is contained in:
@@ -0,0 +1,103 @@
|
||||
//! `plugin.toml` parsing + load-time validation (ABI v0).
|
||||
//!
|
||||
//! The manifest is the host's source of truth for *what to load and when to
|
||||
//! call it*. Validation fails closed: unknown sections/keys, a mismatched ABI,
|
||||
//! an unknown subscribed event, or any non-empty `[permissions]` (M0 grants
|
||||
//! none) all reject the plugin. A rejected plugin is skipped, never fatal.
|
||||
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
use crate::application::ports::plugin_ports::{EVENT_FILE_UPLOADED, OXICLOUD_PLUGIN_ABI};
|
||||
|
||||
/// Parsed `plugin.toml`. `#[serde(deny_unknown_fields)]` on every struct turns
|
||||
/// stray keys into load errors rather than silently ignored config.
|
||||
#[derive(Debug, Clone, serde::Deserialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct PluginManifest {
|
||||
pub plugin: PluginSection,
|
||||
pub events: EventsSection,
|
||||
/// M0: must be empty. Any key here rejects the plugin (no grantable
|
||||
/// permissions exist yet). Kept as a free map so future keys are *detected*,
|
||||
/// not parsed.
|
||||
#[serde(default)]
|
||||
pub permissions: BTreeMap<String, toml::Value>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, serde::Deserialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct PluginSection {
|
||||
/// Reverse-DNS, unique per instance.
|
||||
pub id: String,
|
||||
pub name: String,
|
||||
/// The plugin's own semver.
|
||||
pub version: String,
|
||||
/// Must equal [`OXICLOUD_PLUGIN_ABI`].
|
||||
pub abi: u32,
|
||||
/// Path to the `.wasm`, relative to the manifest.
|
||||
pub entrypoint: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, serde::Deserialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct EventsSection {
|
||||
/// Events this plugin wants. M0 accepts only `"file.uploaded"`.
|
||||
pub subscribe: Vec<String>,
|
||||
}
|
||||
|
||||
/// Why a manifest was rejected. `reason()` yields the stable, machine-readable
|
||||
/// key used in audit logs.
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum ManifestError {
|
||||
#[error("failed to parse plugin.toml: {0}")]
|
||||
Parse(String),
|
||||
#[error("plugin declares ABI {got}, host speaks {want}")]
|
||||
AbiMismatch { got: u32, want: u32 },
|
||||
#[error("events.subscribe must not be empty")]
|
||||
NoEvents,
|
||||
#[error("unknown event '{0}' in events.subscribe")]
|
||||
UnknownEvent(String),
|
||||
#[error("permissions must be empty in ABI v0 (found key '{0}')")]
|
||||
PermissionsNotEmpty(String),
|
||||
}
|
||||
|
||||
impl ManifestError {
|
||||
/// Stable key for `tracing` audit lines; never reworded across releases.
|
||||
pub fn reason(&self) -> &'static str {
|
||||
match self {
|
||||
ManifestError::Parse(_) => "parse_error",
|
||||
ManifestError::AbiMismatch { .. } => "abi_mismatch",
|
||||
ManifestError::NoEvents => "no_events",
|
||||
ManifestError::UnknownEvent(_) => "unknown_event",
|
||||
ManifestError::PermissionsNotEmpty(_) => "permissions_not_empty",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Parse and validate a `plugin.toml` body. Does not touch the `.wasm`; the
|
||||
/// caller probes `abi_version` separately after a successful parse.
|
||||
pub fn parse_and_validate(toml_str: &str) -> Result<PluginManifest, ManifestError> {
|
||||
let manifest: PluginManifest =
|
||||
toml::from_str(toml_str).map_err(|e| ManifestError::Parse(e.to_string()))?;
|
||||
|
||||
if manifest.plugin.abi != OXICLOUD_PLUGIN_ABI {
|
||||
return Err(ManifestError::AbiMismatch {
|
||||
got: manifest.plugin.abi,
|
||||
want: OXICLOUD_PLUGIN_ABI,
|
||||
});
|
||||
}
|
||||
|
||||
if manifest.events.subscribe.is_empty() {
|
||||
return Err(ManifestError::NoEvents);
|
||||
}
|
||||
for event in &manifest.events.subscribe {
|
||||
if event != EVENT_FILE_UPLOADED {
|
||||
return Err(ManifestError::UnknownEvent(event.clone()));
|
||||
}
|
||||
}
|
||||
|
||||
if let Some((key, _)) = manifest.permissions.iter().next() {
|
||||
return Err(ManifestError::PermissionsNotEmpty(key.clone()));
|
||||
}
|
||||
|
||||
Ok(manifest)
|
||||
}
|
||||
Reference in New Issue
Block a user