Skip to main content

sh_layer4/plugin_loader/
abi.rs

1//! Stable ABI for Plugin Interface
2//!
3//! Uses abi_stable to provide a stable ABI across Rust versions.
4
5use abi_stable::std_types::RString;
6use abi_stable::StableAbi;
7use serde::{Deserialize, Serialize};
8
9/// Plugin metadata with stable ABI
10#[repr(C)]
11#[derive(Debug, Clone, StableAbi, Serialize, Deserialize)]
12pub struct StablePluginMeta {
13    pub name: RString,
14    pub version: RString,
15    pub author: RString,
16    pub description: RString,
17}
18
19impl Default for StablePluginMeta {
20    fn default() -> Self {
21        Self {
22            name: RString::from("unknown"),
23            version: RString::from("0.1.0"),
24            author: RString::from("unknown"),
25            description: RString::new(),
26        }
27    }
28}
29
30impl From<super::PluginMeta> for StablePluginMeta {
31    fn from(meta: super::PluginMeta) -> Self {
32        Self {
33            name: RString::from(meta.name),
34            version: RString::from(meta.version),
35            author: RString::from(meta.author),
36            description: RString::from(meta.description),
37        }
38    }
39}
40
41impl From<StablePluginMeta> for super::PluginMeta {
42    fn from(meta: StablePluginMeta) -> Self {
43        Self {
44            name: meta.name.into_string(),
45            version: meta.version.into_string(),
46            author: meta.author.into_string(),
47            description: meta.description.into_string(),
48            ..Default::default()
49        }
50    }
51}
52
53/// Plugin entry point (exported by plugin)
54#[repr(C)]
55pub struct PluginEntryPoint {
56    /// Create plugin instance
57    pub create: extern "C" fn() -> *mut std::ffi::c_void,
58    /// Destroy plugin instance
59    pub destroy: extern "C" fn(*mut std::ffi::c_void),
60    /// Get plugin metadata
61    pub meta: extern "C" fn() -> StablePluginMeta,
62}
63
64/// Macro for creating stable ABI plugin exports
65#[macro_export]
66macro_rules! declare_stable_plugin {
67    ($plugin_type:ty, $name:expr, $version:expr) => {
68        static mut PLUGIN_INSTANCE: Option<Box<$plugin_type>> = None;
69
70        #[no_mangle]
71        pub extern "C" fn plugin_create() -> *mut std::ffi::c_void {
72            unsafe {
73                PLUGIN_INSTANCE = Some(Box::new(<$plugin_type>::default()));
74                PLUGIN_INSTANCE.as_mut().unwrap().as_mut() as *mut _ as *mut std::ffi::c_void
75            }
76        }
77
78        #[no_mangle]
79        pub extern "C" fn plugin_destroy(_ptr: *mut std::ffi::c_void) {
80            unsafe {
81                PLUGIN_INSTANCE = None;
82            }
83        }
84
85        #[no_mangle]
86        pub extern "C" fn plugin_meta() -> $crate::plugin_loader::abi::StablePluginMeta {
87            $crate::plugin_loader::abi::StablePluginMeta {
88                name: abi_stable::std_types::RString::from($name),
89                version: abi_stable::std_types::RString::from($version),
90                ..Default::default()
91            }
92        }
93    };
94}
95
96#[cfg(test)]
97mod tests {
98    use super::*;
99
100    #[test]
101    fn test_stable_plugin_meta_default() {
102        let meta = StablePluginMeta::default();
103        assert_eq!(meta.name.as_str(), "unknown");
104        assert_eq!(meta.version.as_str(), "0.1.0");
105    }
106
107    #[test]
108    fn test_stable_plugin_meta_conversion() {
109        let original = super::super::PluginMeta {
110            name: "test".to_string(),
111            version: "1.0.0".to_string(),
112            author: "tester".to_string(),
113            description: "test plugin".to_string(),
114            ..Default::default()
115        };
116
117        let stable: StablePluginMeta = original.clone().into();
118        assert_eq!(stable.name.as_str(), "test");
119
120        let back: super::super::PluginMeta = stable.into();
121        assert_eq!(back.name, "test");
122        assert_eq!(back.version, "1.0.0");
123    }
124}