zenoh_plugin_trait/
plugin.rs

1//
2// Copyright (c) 2023 ZettaScale Technology
3//
4// This program and the accompanying materials are made available under the
5// terms of the Eclipse Public License 2.0 which is available at
6// http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
7// which is available at https://www.apache.org/licenses/LICENSE-2.0.
8//
9// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
10//
11// Contributors:
12//   ZettaScale Zenoh Team, <zenoh@zettascale.tech>
13//
14use std::{borrow::Cow, ops::BitOrAssign};
15
16use serde::{Deserialize, Serialize};
17use zenoh_keyexpr::keyexpr;
18use zenoh_result::ZResult;
19
20use crate::StructVersion;
21
22/// The diff in the configuration when plugins are updated:
23/// - Delete: the plugin has been removed from the configuration at runtime
24/// - Start: the plugin has been added to the configuration at runtime
25#[derive(Debug, Clone)]
26pub enum PluginDiff {
27    Delete(String),
28    Start(zenoh_config::PluginLoad),
29}
30
31/// The plugin can be in one of these states:
32/// - Declared: the plugin is declared in the configuration file, but not loaded yet or failed to load
33/// - Loaded: the plugin is loaded, but not started yet or failed to start
34/// - Started: the plugin is started and running
35#[derive(Copy, Clone, Debug, PartialEq, Eq, Serialize, Deserialize, PartialOrd, Ord)]
36pub enum PluginState {
37    Declared,
38    Loaded,
39    Started,
40}
41
42/// The severity level of a plugin report messages
43/// - Normal: the message(s) are just notifications
44/// - Warning: at least one warning is reported
45/// - Error: at least one error is reported
46#[derive(Copy, Clone, Debug, PartialEq, Eq, Default, Serialize, Deserialize, PartialOrd, Ord)]
47pub enum PluginReportLevel {
48    #[default]
49    Info,
50    Warning,
51    Error,
52}
53
54/// Allow using the `|=` operator to update the severity level of a report
55impl BitOrAssign for PluginReportLevel {
56    fn bitor_assign(&mut self, rhs: Self) {
57        if *self < rhs {
58            *self = rhs;
59        }
60    }
61}
62
63/// A plugin report contains a severity level and a list of messages
64/// describing the plugin's situation (for the Declared state - dynamic library loading errors, for the Loaded state - plugin start errors, etc)
65#[derive(Clone, Debug, PartialEq, Eq, Serialize, Default, Deserialize)]
66pub struct PluginReport {
67    level: PluginReportLevel,
68    #[serde(skip_serializing_if = "Vec::is_empty")]
69    messages: Vec<Cow<'static, str>>,
70}
71
72/// Trait allowing getting all information about the plugin
73pub trait PluginStatus {
74    /// Returns the name of the plugin
75    fn name(&self) -> &str;
76    /// Returns the ID of the plugin
77    fn id(&self) -> &str;
78    /// Returns the version of the loaded plugin (usually the version of the plugin's crate)
79    fn version(&self) -> Option<&str>;
80    /// Returns the long version of the loaded plugin (usually the version of the plugin's crate + git commit hash)
81    fn long_version(&self) -> Option<&str>;
82    /// Returns the path of the loaded plugin
83    fn path(&self) -> &str;
84    /// Returns the plugin's state (Declared, Loaded, Started)
85    fn state(&self) -> PluginState;
86    /// Returns the plugin's current report: a list of messages and the severity level
87    /// When the status is changed, the report is cleared
88    fn report(&self) -> PluginReport;
89}
90
91/// The structure which contains all information about the plugin status in a single cloneable structure
92#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
93pub struct PluginStatusRec<'a> {
94    pub name: Cow<'a, str>,
95    pub id: Cow<'a, str>,
96    #[serde(skip_serializing_if = "Option::is_none")]
97    pub version: Option<Cow<'a, str>>,
98    pub long_version: Option<Cow<'a, str>>,
99    pub path: Cow<'a, str>,
100    pub state: PluginState,
101    pub report: PluginReport,
102}
103
104impl PluginStatus for PluginStatusRec<'_> {
105    fn name(&self) -> &str {
106        &self.name
107    }
108
109    fn id(&self) -> &str {
110        &self.id
111    }
112
113    fn version(&self) -> Option<&str> {
114        self.version.as_deref()
115    }
116    fn long_version(&self) -> Option<&str> {
117        self.long_version.as_deref()
118    }
119    fn path(&self) -> &str {
120        &self.path
121    }
122    fn state(&self) -> PluginState {
123        self.state
124    }
125    fn report(&self) -> PluginReport {
126        self.report.clone()
127    }
128}
129
130impl<'a> PluginStatusRec<'a> {
131    /// Construct the status structure from the getter interface
132    pub fn new<T: PluginStatus + ?Sized>(plugin: &'a T) -> Self {
133        Self {
134            name: Cow::Borrowed(plugin.name()),
135            id: Cow::Borrowed(plugin.id()),
136            version: plugin.version().map(Cow::Borrowed),
137            long_version: plugin.long_version().map(Cow::Borrowed),
138            path: Cow::Borrowed(plugin.path()),
139            state: plugin.state(),
140            report: plugin.report(),
141        }
142    }
143    /// Convert the status structure to the owned version
144    pub fn into_owned(self) -> PluginStatusRec<'static> {
145        PluginStatusRec {
146            name: Cow::Owned(self.name.into_owned()),
147            id: Cow::Owned(self.id.into_owned()),
148            version: self.version.map(|v| Cow::Owned(v.into_owned())),
149            long_version: self.long_version.map(|v| Cow::Owned(v.into_owned())),
150            path: Cow::Owned(self.path.into_owned()),
151            state: self.state,
152            report: self.report,
153        }
154    }
155    pub(crate) fn prepend_name(self, prefix: &str) -> Self {
156        Self {
157            name: Cow::Owned(format!("{}/{}", prefix, self.name)),
158            ..self
159        }
160    }
161}
162
163/// This trait allows getting information about the plugin status and the status of its subplugins, if any.
164pub trait PluginControl {
165    /// Returns the current state of the running plugin. By default, the state is `PluginReportLevel::Normal` and the list of messages is empty.
166    /// This can be overridden by the plugin implementation if the plugin is able to report its status: no connection to the database, etc.
167    fn report(&self) -> PluginReport {
168        PluginReport::default()
169    }
170    /// Collects information of sub-plugins matching the `_names` key expression. The information is richer than the one returned by `report()`: it contains external information about the running plugin, such as its name, path on disk, load status, etc.
171    /// Returns an empty list by default.
172    fn plugins_status(&self, _names: &keyexpr) -> Vec<PluginStatusRec<'_>> {
173        Vec::new()
174    }
175}
176
177pub trait PluginStartArgs: StructVersion {}
178
179pub trait PluginInstance: PluginControl + Send + Sync {}
180
181/// Base plugin trait. The loaded plugin
182pub trait Plugin: Sized + 'static {
183    type StartArgs: PluginStartArgs;
184    type Instance: PluginInstance;
185    /// Plugins' default name when statically linked.
186    const DEFAULT_NAME: &'static str;
187    /// Plugin's version. Used only for information purposes. It's recommended to use [plugin_version!](crate::plugin_version!) macro to generate this string.
188    const PLUGIN_VERSION: &'static str;
189    /// Plugin's long version (with git commit hash). Used only for information purposes. It's recommended to use [plugin_version!](crate::plugin_version!) macro to generate this string.
190    const PLUGIN_LONG_VERSION: &'static str;
191    /// Starts your plugin. Use `Ok` to return your plugin's control structure
192    fn start(name: &str, args: &Self::StartArgs) -> ZResult<Self::Instance>;
193}
194
195#[macro_export]
196macro_rules! plugin_version {
197    () => {
198        env!("CARGO_PKG_VERSION")
199    };
200}
201
202#[macro_export]
203macro_rules! plugin_long_version {
204    () => {
205        $crate::export::git_version::git_version!(prefix = "v", cargo_prefix = "v")
206    };
207}
208
209impl PluginReport {
210    pub fn new() -> Self {
211        Self::default()
212    }
213    pub fn clear(&mut self) {
214        *self = Self::default();
215    }
216    pub fn get_level(&self) -> PluginReportLevel {
217        self.level
218    }
219    pub fn add_error<S: Into<Cow<'static, str>>>(&mut self, error: S) {
220        self.level |= PluginReportLevel::Error;
221        self.messages.push(error.into());
222    }
223    pub fn add_warning<S: Into<Cow<'static, str>>>(&mut self, warning: S) {
224        self.level |= PluginReportLevel::Warning;
225        self.messages.push(warning.into());
226    }
227    pub fn add_info<S: Into<Cow<'static, str>>>(&mut self, message: S) {
228        self.level |= PluginReportLevel::Info;
229        self.messages.push(message.into());
230    }
231    pub fn messages(&self) -> &[Cow<'static, str>] {
232        &self.messages
233    }
234}
235
236pub trait PluginConditionSetter {
237    fn add_error(self, report: &mut PluginReport) -> Self;
238    fn add_warning(self, report: &mut PluginReport) -> Self;
239    fn add_info(self, report: &mut PluginReport) -> Self;
240}
241
242impl<T, E: ToString> PluginConditionSetter for core::result::Result<T, E> {
243    fn add_error(self, report: &mut PluginReport) -> Self {
244        if let Err(e) = &self {
245            report.add_error(e.to_string());
246        }
247        self
248    }
249    fn add_warning(self, report: &mut PluginReport) -> Self {
250        if let Err(e) = &self {
251            report.add_warning(e.to_string());
252        }
253        self
254    }
255    fn add_info(self, report: &mut PluginReport) -> Self {
256        if let Err(e) = &self {
257            report.add_info(e.to_string());
258        }
259        self
260    }
261}