rustant_plugins/
loader.rs1use crate::{Plugin, PluginError};
6use std::path::{Path, PathBuf};
7
8pub struct NativePluginLoader {
10 search_dirs: Vec<PathBuf>,
11}
12
13impl NativePluginLoader {
14 pub fn new() -> Self {
16 Self {
17 search_dirs: Vec::new(),
18 }
19 }
20
21 pub fn add_search_dir(&mut self, dir: impl Into<PathBuf>) {
23 self.search_dirs.push(dir.into());
24 }
25
26 pub fn discover(&self) -> Vec<PathBuf> {
28 let mut plugins = Vec::new();
29 for dir in &self.search_dirs {
30 if let Ok(entries) = std::fs::read_dir(dir) {
31 for entry in entries.flatten() {
32 let path = entry.path();
33 if is_plugin_library(&path) {
34 plugins.push(path);
35 }
36 }
37 }
38 }
39 plugins
40 }
41
42 pub unsafe fn load(&self, path: &Path) -> Result<Box<dyn Plugin>, PluginError> {
48 let lib = libloading::Library::new(path)
49 .map_err(|e| PluginError::LoadFailed(format!("{}: {}", path.display(), e)))?;
50
51 let create_fn: libloading::Symbol<unsafe extern "C" fn() -> *mut dyn Plugin> =
53 lib.get(b"rustant_plugin_create").map_err(|e| {
54 PluginError::LoadFailed(format!(
55 "Symbol 'rustant_plugin_create' not found in {}: {}",
56 path.display(),
57 e
58 ))
59 })?;
60
61 let raw = create_fn();
62 if raw.is_null() {
63 return Err(PluginError::LoadFailed(
64 "Plugin creation function returned null".into(),
65 ));
66 }
67
68 let plugin = Box::from_raw(raw);
69
70 std::mem::forget(lib);
72
73 Ok(plugin)
74 }
75}
76
77impl Default for NativePluginLoader {
78 fn default() -> Self {
79 Self::new()
80 }
81}
82
83fn is_plugin_library(path: &Path) -> bool {
85 let ext = path.extension().and_then(|e| e.to_str()).unwrap_or("");
86 matches!(ext, "so" | "dll" | "dylib")
87}
88
89#[cfg(test)]
90mod tests {
91 use super::*;
92
93 #[test]
94 fn test_is_plugin_library() {
95 assert!(is_plugin_library(Path::new("libfoo.so")));
96 assert!(is_plugin_library(Path::new("foo.dll")));
97 assert!(is_plugin_library(Path::new("libfoo.dylib")));
98 assert!(!is_plugin_library(Path::new("foo.rs")));
99 assert!(!is_plugin_library(Path::new("foo.toml")));
100 assert!(!is_plugin_library(Path::new("foo")));
101 }
102
103 #[test]
104 fn test_native_loader_discover_empty() {
105 let dir = tempfile::TempDir::new().unwrap();
106 let mut loader = NativePluginLoader::new();
107 loader.add_search_dir(dir.path());
108 let plugins = loader.discover();
109 assert!(plugins.is_empty());
110 }
111
112 #[test]
113 fn test_native_loader_discover_finds_libs() {
114 let dir = tempfile::TempDir::new().unwrap();
115
116 std::fs::write(dir.path().join("libplugin.so"), b"fake").unwrap();
118 std::fs::write(dir.path().join("plugin.dll"), b"fake").unwrap();
119 std::fs::write(dir.path().join("README.md"), b"docs").unwrap();
120
121 let mut loader = NativePluginLoader::new();
122 loader.add_search_dir(dir.path());
123 let plugins = loader.discover();
124 assert_eq!(plugins.len(), 2);
125 }
126
127 #[test]
128 fn test_native_loader_discover_nonexistent_dir() {
129 let mut loader = NativePluginLoader::new();
130 loader.add_search_dir("/nonexistent/path");
131 let plugins = loader.discover();
132 assert!(plugins.is_empty());
133 }
134}