crates\nfarms-plugin\src\plugin.rs¶
源码路径:
crates\nfarms-plugin\src\plugin.rs· 145 行
//! 插件 trait 与错误类型。
use std::fmt;
use crate::context::Context;
/// 一个功能单元。
///
/// 实现者通常是个**无状态**的结构体(要存状态就放进自己提供的服务里),
/// 这样 [`Plugin::apply`] 可以安全地反复调用(cordis 的插件也要求幂等)。
pub trait Plugin: Send + Sync + 'static {
/// 插件名。界面上要展示,而且同名插件只装一个 —— 取个能认出来的名字。
fn name(&self) -> &str;
/// 版本。
///
/// **默认值是 `nfarms-plugin` 自己的版本,不是插件所在包的版本。**
/// `env!("CARGO_PKG_VERSION")` 在展开它的 crate 里求值,而这行代码编译进
/// `nfarms-plugin`;所以插件若在别的包里,默认值报的是框架的版本。
/// 现在五个 crate 都是 `version.workspace = true`,取值恰好相同,现象看不出来 ——
/// 一旦有包单独升版本,界面上的插件版本就会串。
///
/// 因此插件**只要不在 `nfarms-plugin` 包里,就应该自己实现这个函数**
/// (一行 `env!("CARGO_PKG_VERSION")`,那个 `env!` 在插件自己的包里展开)。
/// 反过来,测试桩和同包内的实现用默认值是合适的。
///
/// # 例子
///
/// ```
/// use nfarms_plugin::{Context, Plugin, Result};
///
/// /// 测试桩 / 同包实现:什么都不写,版本 = `nfarms-plugin` 的版本。
/// struct Stub;
/// impl Plugin for Stub {
/// fn name(&self) -> &str {
/// "stub"
/// }
/// fn apply(&self, _ctx: &Context) -> Result<()> {
/// Ok(())
/// }
/// }
/// assert_eq!(Stub.version(), env!("CARGO_PKG_VERSION"));
///
/// /// 跨包插件:必须自己实现,否则上面那个值会冒充本插件的版本。
/// struct Elsewhere;
/// impl Plugin for Elsewhere {
/// fn name(&self) -> &str {
/// "elsewhere"
/// }
/// fn version(&self) -> &str {
/// "0.2.0" // 真实写法是 env!("CARGO_PKG_VERSION"),在这里展开
/// }
/// fn apply(&self, _ctx: &Context) -> Result<()> {
/// Ok(())
/// }
/// }
/// assert_eq!(Elsewhere.version(), "0.2.0");
/// ```
fn version(&self) -> &str {
env!("CARGO_PKG_VERSION")
}
/// 依赖的服务名。**没满足不会报错**:插件先挂起(Pending),
/// 等哪天有插件把缺的服务 `provide` 出来了,内核自动把它装上。
///
/// 这正是「加功能不用改老代码」的关键 —— 老插件可以先加载,
/// 它依赖的服务由更晚注册的插件提供。
fn inject(&self) -> &[&'static str] {
&[]
}
/// 装载。在这里 `provide` 自己的服务、注册自己的页面、订阅事件。
fn apply(&self, ctx: &Context) -> Result<()>;
/// 卸载。默认什么都不做。
///
/// 注意:内核**不会**替你摘掉已 provide 的服务(cordis 里作用域销毁会
/// 连带清理,这里没做那么细)。所以插件自己 provide 的服务,自己负责
/// 在 `dispose` 里置空或标记失效。
fn dispose(&self, _ctx: &Context) -> Result<()> {
Ok(())
}
}
/// 插件返回的错误。带上插件名,免得界面上只看到一句没头没尾的话。
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct PluginError {
pub plugin: String,
pub message: String,
}
impl PluginError {
pub fn new(plugin: impl Into<String>, message: impl Into<String>) -> Self {
Self {
plugin: plugin.into(),
message: message.into(),
}
}
}
impl fmt::Display for PluginError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "[{}] {}", self.plugin, self.message)
}
}
impl std::error::Error for PluginError {}
/// 插件 API 的结果类型。
pub type Result<T> = std::result::Result<T, PluginError>;
#[cfg(test)]
mod tests {
use super::*;
struct Noop;
impl Plugin for Noop {
fn name(&self) -> &str {
"noop"
}
fn apply(&self, _ctx: &Context) -> Result<()> {
Ok(())
}
}
/// 默认实现只保证「有版本号」,不保证「是插件自己那个包的版本」——
/// 它返回的是 `nfarms-plugin` 的版本(见 [`Plugin::version`] 的文档)。
/// 这里断言的是这条**已写明的语义**,而不是拿它去证明跨包一致:
/// 跨包的版本对齐只能在插件自己的包里断言(例如在 `nfarms-gui` 里
/// 断 `PagePlugin::version() == env!("CARGO_PKG_VERSION")`),本 crate 测不到。
#[test]
fn default_version_is_the_framework_version() {
let p = Noop;
assert_eq!(p.name(), "noop");
assert!(p.inject().is_empty());
assert_eq!(p.version(), env!("CARGO_PKG_VERSION"));
}
#[test]
fn error_message_carries_plugin_name() {
let e = PluginError::new("weather", "取不到数据");
assert_eq!(e.to_string(), "[weather] 取不到数据");
}
}