use libloading::{Library, Symbol};
use std::ffi::CStr;
use std::os::raw::c_char;
use std::path::Path;
use wavecraft_protocol::ParameterInfo;
#[derive(Debug)]
pub enum PluginLoaderError {
LibraryLoad(libloading::Error),
SymbolNotFound(String),
NullPointer(&'static str),
JsonParse(serde_json::Error),
InvalidUtf8(std::str::Utf8Error),
}
impl std::fmt::Display for PluginLoaderError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::LibraryLoad(e) => write!(f, "Failed to load plugin library: {}", e),
Self::SymbolNotFound(name) => write!(f, "Symbol not found: {}", name),
Self::NullPointer(func) => write!(f, "FFI function {} returned null", func),
Self::JsonParse(e) => write!(f, "Failed to parse parameter JSON: {}", e),
Self::InvalidUtf8(e) => write!(f, "Invalid UTF-8 in FFI response: {}", e),
}
}
}
impl std::error::Error for PluginLoaderError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
Self::LibraryLoad(e) => Some(e),
Self::JsonParse(e) => Some(e),
Self::InvalidUtf8(e) => Some(e),
_ => None,
}
}
}
type GetParamsJsonFn = unsafe extern "C" fn() -> *mut c_char;
type FreeStringFn = unsafe extern "C" fn(*mut c_char);
pub struct PluginParamLoader {
_library: Library,
parameters: Vec<ParameterInfo>,
}
impl PluginParamLoader {
pub fn load<P: AsRef<Path>>(dylib_path: P) -> Result<Self, PluginLoaderError> {
let library =
unsafe { Library::new(dylib_path.as_ref()) }.map_err(PluginLoaderError::LibraryLoad)?;
let get_params_json: Symbol<GetParamsJsonFn> = unsafe {
library.get(b"wavecraft_get_params_json\0").map_err(|e| {
PluginLoaderError::SymbolNotFound(format!("wavecraft_get_params_json: {}", e))
})?
};
let free_string: Symbol<FreeStringFn> = unsafe {
library.get(b"wavecraft_free_string\0").map_err(|e| {
PluginLoaderError::SymbolNotFound(format!("wavecraft_free_string: {}", e))
})?
};
let params = unsafe {
let json_ptr = get_params_json();
if json_ptr.is_null() {
return Err(PluginLoaderError::NullPointer("wavecraft_get_params_json"));
}
let c_str = CStr::from_ptr(json_ptr);
let json_str = c_str.to_str().map_err(PluginLoaderError::InvalidUtf8)?;
let params: Vec<ParameterInfo> =
serde_json::from_str(json_str).map_err(PluginLoaderError::JsonParse)?;
free_string(json_ptr);
params
};
Ok(Self {
_library: library,
parameters: params,
})
}
pub fn parameters(&self) -> &[ParameterInfo] {
&self.parameters
}
#[allow(dead_code)]
pub fn get_parameter(&self, id: &str) -> Option<&ParameterInfo> {
self.parameters.iter().find(|p| p.id == id)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_error_display() {
let err = PluginLoaderError::SymbolNotFound("test_symbol".to_string());
assert!(err.to_string().contains("test_symbol"));
}
#[test]
fn test_null_pointer_error() {
let err = PluginLoaderError::NullPointer("wavecraft_get_params_json");
assert!(err.to_string().contains("null"));
}
}