use std::ffi::c_void;
use std::ptr;
use serde_json::Value;
use shape_abi_v1::OutputSinkVTable;
use shape_ast::error::{Result, ShapeError};
pub struct PluginOutputSink {
name: String,
vtable: &'static OutputSinkVTable,
instance: *mut c_void,
handled_tags: Vec<String>,
}
impl PluginOutputSink {
pub fn new(name: String, vtable: &'static OutputSinkVTable, config: &Value) -> Result<Self> {
let config_bytes = rmp_serde::to_vec(config).map_err(|e| ShapeError::RuntimeError {
message: format!("Failed to serialize plugin config: {}", e),
location: None,
})?;
let init_fn = vtable.init.ok_or_else(|| ShapeError::RuntimeError {
message: format!("Plugin '{}' has no init function", name),
location: None,
})?;
let instance = unsafe { init_fn(config_bytes.as_ptr(), config_bytes.len()) };
if instance.is_null() {
return Err(ShapeError::RuntimeError {
message: format!("Plugin '{}' init returned null", name),
location: None,
});
}
let handled_tags = Self::get_handled_tags_from_vtable(vtable, instance)?;
Ok(Self {
name,
vtable,
instance,
handled_tags,
})
}
pub fn name(&self) -> &str {
&self.name
}
pub fn handled_tags(&self) -> &[String] {
&self.handled_tags
}
pub fn send(&self, alert: &Value) -> Result<()> {
let send_fn = self.vtable.send.ok_or_else(|| ShapeError::RuntimeError {
message: format!("Plugin '{}' has no send function", self.name),
location: None,
})?;
let alert_bytes = rmp_serde::to_vec(alert).map_err(|e| ShapeError::RuntimeError {
message: format!("Failed to serialize alert: {}", e),
location: None,
})?;
let result = unsafe { send_fn(self.instance, alert_bytes.as_ptr(), alert_bytes.len()) };
if result != 0 {
return Err(ShapeError::RuntimeError {
message: format!("Plugin '{}' send failed with code {}", self.name, result),
location: None,
});
}
Ok(())
}
pub fn flush(&self) -> Result<()> {
let flush_fn = match self.vtable.flush {
Some(f) => f,
None => return Ok(()), };
let result = unsafe { flush_fn(self.instance) };
if result != 0 {
return Err(ShapeError::RuntimeError {
message: format!("Plugin '{}' flush failed with code {}", self.name, result),
location: None,
});
}
Ok(())
}
fn get_handled_tags_from_vtable(
vtable: &OutputSinkVTable,
instance: *mut c_void,
) -> Result<Vec<String>> {
let get_tags_fn = match vtable.get_handled_tags {
Some(f) => f,
None => return Ok(Vec::new()), };
let mut out_ptr: *mut u8 = ptr::null_mut();
let mut out_len: usize = 0;
unsafe { get_tags_fn(instance, &mut out_ptr, &mut out_len) };
if out_ptr.is_null() || out_len == 0 {
return Ok(Vec::new());
}
let data_slice = unsafe { std::slice::from_raw_parts(out_ptr, out_len) };
let tags: Vec<String> = rmp_serde::from_slice(data_slice).unwrap_or_else(|_| Vec::new());
if let Some(free_fn) = vtable.free_buffer {
unsafe { free_fn(out_ptr, out_len) };
}
Ok(tags)
}
}
impl Drop for PluginOutputSink {
fn drop(&mut self) {
let _ = self.flush();
if let Some(drop_fn) = self.vtable.drop {
unsafe { drop_fn(self.instance) };
}
}
}
unsafe impl Send for PluginOutputSink {}
unsafe impl Sync for PluginOutputSink {}
#[cfg(test)]
mod tests {
#[test]
fn test_handled_tags_default() {
let tags: Vec<String> = Vec::new();
assert!(tags.is_empty());
}
}