Skip to main content

drasi_host_sdk/
plugin_types.rs

1// Copyright 2025 The Drasi Authors.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15//! Shared plugin management types used by the host-sdk lifecycle layer
16//! and consumed by host applications like drasi-server.
17
18use std::path::PathBuf;
19
20/// Category of a plugin descriptor.
21#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
22pub enum PluginCategory {
23    Source,
24    Reaction,
25    Bootstrap,
26}
27
28impl std::fmt::Display for PluginCategory {
29    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
30        match self {
31            PluginCategory::Source => write!(f, "source"),
32            PluginCategory::Reaction => write!(f, "reaction"),
33            PluginCategory::Bootstrap => write!(f, "bootstrap"),
34        }
35    }
36}
37
38/// Lightweight representation of a single descriptor kind provided by a plugin.
39#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
40#[serde(rename_all = "camelCase")]
41pub struct PluginKindEntry {
42    pub category: PluginCategory,
43    pub kind: String,
44    pub config_version: String,
45    pub config_schema_name: String,
46}
47
48/// Lifecycle status of a loaded plugin.
49#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
50pub enum PluginStatus {
51    /// Library loaded, descriptors registered, no instances yet.
52    Loaded,
53    /// Has running component instances.
54    Active,
55    /// Load or initialization failed.
56    Failed,
57}
58
59impl std::fmt::Display for PluginStatus {
60    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
61        match self {
62            PluginStatus::Loaded => write!(f, "Loaded"),
63            PluginStatus::Active => write!(f, "Active"),
64            PluginStatus::Failed => write!(f, "Failed"),
65        }
66    }
67}
68
69/// Events emitted by the plugin lifecycle layer.
70///
71/// These are broadcast through a `tokio::sync::broadcast` channel and can be
72/// consumed by server-level logging or UI updates.
73#[derive(Debug, Clone)]
74pub enum PluginEvent {
75    /// A new plugin was loaded.
76    Loaded {
77        plugin_id: String,
78        version: String,
79        kinds: Vec<PluginKindEntry>,
80    },
81    /// A plugin failed to load.
82    LoadFailed { path: PathBuf, error: String },
83}
84
85/// Raw filesystem events emitted by the `PluginWatcher`.
86///
87/// These are policy-neutral: the watcher does not decide whether a file change
88/// means load or reload. That decision belongs to the host application's
89/// orchestrator layer.
90#[derive(Debug, Clone)]
91pub enum PluginFileEvent {
92    Added(PathBuf),
93    Changed(PathBuf),
94    Removed(PathBuf),
95}
96
97#[cfg(test)]
98mod tests {
99    use super::*;
100
101    #[test]
102    fn plugin_category_display() {
103        assert_eq!(PluginCategory::Source.to_string(), "source");
104        assert_eq!(PluginCategory::Reaction.to_string(), "reaction");
105        assert_eq!(PluginCategory::Bootstrap.to_string(), "bootstrap");
106    }
107
108    #[test]
109    fn plugin_status_display() {
110        assert_eq!(PluginStatus::Loaded.to_string(), "Loaded");
111        assert_eq!(PluginStatus::Active.to_string(), "Active");
112        assert_eq!(PluginStatus::Failed.to_string(), "Failed");
113    }
114
115    #[test]
116    fn plugin_kind_entry_serde_roundtrip() {
117        let entry = PluginKindEntry {
118            category: PluginCategory::Source,
119            kind: "postgres".to_string(),
120            config_version: "1.0.0".to_string(),
121            config_schema_name: "PostgresSourceConfig".to_string(),
122        };
123        let json = serde_json::to_string(&entry).expect("serialize");
124        let deserialized: PluginKindEntry = serde_json::from_str(&json).expect("deserialize");
125        assert_eq!(entry, deserialized);
126    }
127}