use crate::{Plugin, PluginError};
use std::path::{Path, PathBuf};
pub struct NativePluginLoader {
search_dirs: Vec<PathBuf>,
}
impl NativePluginLoader {
pub fn new() -> Self {
Self {
search_dirs: Vec::new(),
}
}
pub fn add_search_dir(&mut self, dir: impl Into<PathBuf>) {
self.search_dirs.push(dir.into());
}
pub fn discover(&self) -> Vec<PathBuf> {
let mut plugins = Vec::new();
for dir in &self.search_dirs {
if let Ok(entries) = std::fs::read_dir(dir) {
for entry in entries.flatten() {
let path = entry.path();
if is_plugin_library(&path) {
plugins.push(path);
}
}
}
}
plugins
}
pub unsafe fn load(&self, path: &Path) -> Result<Box<dyn Plugin>, PluginError> {
let lib = unsafe {
libloading::Library::new(path)
.map_err(|e| PluginError::LoadFailed(format!("{}: {}", path.display(), e)))?
};
let create_fn: libloading::Symbol<unsafe extern "C" fn() -> *mut dyn Plugin> = unsafe {
lib.get(b"rustant_plugin_create").map_err(|e| {
PluginError::LoadFailed(format!(
"Symbol 'rustant_plugin_create' not found in {}: {}",
path.display(),
e
))
})?
};
let raw = unsafe { create_fn() };
if raw.is_null() {
return Err(PluginError::LoadFailed(
"Plugin creation function returned null".into(),
));
}
let plugin = unsafe { Box::from_raw(raw) };
std::mem::forget(lib);
Ok(plugin)
}
}
impl Default for NativePluginLoader {
fn default() -> Self {
Self::new()
}
}
fn is_plugin_library(path: &Path) -> bool {
let ext = path.extension().and_then(|e| e.to_str()).unwrap_or("");
matches!(ext, "so" | "dll" | "dylib")
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_is_plugin_library() {
assert!(is_plugin_library(Path::new("libfoo.so")));
assert!(is_plugin_library(Path::new("foo.dll")));
assert!(is_plugin_library(Path::new("libfoo.dylib")));
assert!(!is_plugin_library(Path::new("foo.rs")));
assert!(!is_plugin_library(Path::new("foo.toml")));
assert!(!is_plugin_library(Path::new("foo")));
}
#[test]
fn test_native_loader_discover_empty() {
let dir = tempfile::TempDir::new().unwrap();
let mut loader = NativePluginLoader::new();
loader.add_search_dir(dir.path());
let plugins = loader.discover();
assert!(plugins.is_empty());
}
#[test]
fn test_native_loader_discover_finds_libs() {
let dir = tempfile::TempDir::new().unwrap();
std::fs::write(dir.path().join("libplugin.so"), b"fake").unwrap();
std::fs::write(dir.path().join("plugin.dll"), b"fake").unwrap();
std::fs::write(dir.path().join("README.md"), b"docs").unwrap();
let mut loader = NativePluginLoader::new();
loader.add_search_dir(dir.path());
let plugins = loader.discover();
assert_eq!(plugins.len(), 2);
}
#[test]
fn test_native_loader_discover_nonexistent_dir() {
let mut loader = NativePluginLoader::new();
loader.add_search_dir("/nonexistent/path");
let plugins = loader.discover();
assert!(plugins.is_empty());
}
}