vanguard_plugin/
dylib.rs

1use std::{
2    ffi::OsStr,
3    path::{Path, PathBuf},
4    sync::Arc,
5};
6
7use libloading::{Library, Symbol};
8use thiserror::Error;
9
10use crate::{CreatePluginFn, VanguardPlugin, CREATE_PLUGIN_SYMBOL};
11
12/// Errors that can occur during dynamic library operations
13#[derive(Error, Debug)]
14pub enum DylibError {
15    /// Failed to load library
16    #[error("Failed to load library: {0}")]
17    LoadError(#[from] libloading::Error),
18
19    /// Failed to find plugin entry point
20    #[error("Failed to find plugin entry point: {0}")]
21    EntryPointNotFound(String),
22
23    /// Invalid plugin library
24    #[error("Invalid plugin library: {0}")]
25    InvalidLibrary(String),
26}
27
28/// A loaded dynamic library containing a plugin
29pub struct PluginLibrary {
30    _lib: Library, // Keep library loaded while we have the plugin
31    plugin: Arc<dyn VanguardPlugin>,
32}
33
34impl PluginLibrary {
35    /// Load a plugin from a dynamic library
36    ///
37    /// # Safety
38    /// This function is unsafe because it loads a dynamic library and calls functions from it.
39    /// The caller must ensure that:
40    /// - The library path points to a valid dynamic library file
41    /// - The library implements the required plugin interface correctly
42    /// - The library is compatible with the current platform and architecture
43    /// - The library's create_plugin function returns a valid plugin instance
44    /// - The library remains loaded for the lifetime of the returned plugin
45    pub unsafe fn load<P: AsRef<OsStr>>(path: P) -> Result<Self, DylibError> {
46        // Load the dynamic library
47        let lib = Library::new(path).map_err(DylibError::LoadError)?;
48
49        // Get the plugin creation function
50        let create_plugin: Symbol<CreatePluginFn> = lib
51            .get(CREATE_PLUGIN_SYMBOL)
52            .map_err(|_| DylibError::EntryPointNotFound("create_plugin".into()))?;
53
54        // Create the plugin instance and get the raw pointer
55        let raw_plugin = create_plugin();
56        if raw_plugin.is_null() {
57            return Err(DylibError::InvalidLibrary(
58                "Plugin creation returned null".into(),
59            ));
60        }
61
62        // Convert the raw plugin back to a Box and extract the instance
63        let raw_plugin = Box::from_raw(raw_plugin);
64        let plugin = Arc::from(raw_plugin.instance);
65
66        Ok(Self { _lib: lib, plugin })
67    }
68
69    /// Get a reference to the loaded plugin
70    pub fn plugin(&self) -> Arc<dyn VanguardPlugin> {
71        self.plugin.clone()
72    }
73}
74
75/// Helper to get the dynamic library name for the current platform
76pub fn get_dylib_name(name: &str) -> String {
77    #[cfg(target_os = "windows")]
78    {
79        format!("{}.dll", name)
80    }
81    #[cfg(target_os = "linux")]
82    {
83        format!("lib{}.so", name)
84    }
85    #[cfg(target_os = "macos")]
86    {
87        format!("lib{}.dylib", name)
88    }
89}
90
91/// Helper to get the dynamic library path for a plugin
92pub fn get_dylib_path(plugin_dir: &Path, name: &str) -> PathBuf {
93    plugin_dir.join("lib").join(get_dylib_name(name))
94}
95
96#[cfg(test)]
97mod tests {
98    use super::*;
99    use tempfile::TempDir;
100
101    #[test]
102    fn test_dylib_name() {
103        let name = get_dylib_name("test-plugin");
104        #[cfg(target_os = "windows")]
105        assert_eq!(name, "test-plugin.dll");
106        #[cfg(target_os = "linux")]
107        assert_eq!(name, "libtest-plugin.so");
108        #[cfg(target_os = "macos")]
109        assert_eq!(name, "libtest-plugin.dylib");
110    }
111
112    #[test]
113    fn test_dylib_path() {
114        let temp_dir = TempDir::new().unwrap();
115        let path = get_dylib_path(temp_dir.path(), "test-plugin");
116        let expected = temp_dir
117            .path()
118            .join("lib")
119            .join(get_dylib_name("test-plugin"));
120        assert_eq!(path, expected);
121    }
122}