shape_runtime/plugins/
output_sink.rs1use std::ffi::c_void;
6use std::ptr;
7
8use serde_json::Value;
9use shape_abi_v1::OutputSinkVTable;
10
11use shape_ast::error::{Result, ShapeError};
12
13pub struct PluginOutputSink {
17 name: String,
19 vtable: &'static OutputSinkVTable,
21 instance: *mut c_void,
23 handled_tags: Vec<String>,
25}
26
27impl PluginOutputSink {
28 pub fn new(name: String, vtable: &'static OutputSinkVTable, config: &Value) -> Result<Self> {
35 let config_bytes = rmp_serde::to_vec(config).map_err(|e| ShapeError::RuntimeError {
37 message: format!("Failed to serialize plugin config: {}", e),
38 location: None,
39 })?;
40
41 let init_fn = vtable.init.ok_or_else(|| ShapeError::RuntimeError {
43 message: format!("Plugin '{}' has no init function", name),
44 location: None,
45 })?;
46
47 let instance = unsafe { init_fn(config_bytes.as_ptr(), config_bytes.len()) };
48 if instance.is_null() {
49 return Err(ShapeError::RuntimeError {
50 message: format!("Plugin '{}' init returned null", name),
51 location: None,
52 });
53 }
54
55 let handled_tags = Self::get_handled_tags_from_vtable(vtable, instance)?;
57
58 Ok(Self {
59 name,
60 vtable,
61 instance,
62 handled_tags,
63 })
64 }
65
66 pub fn name(&self) -> &str {
68 &self.name
69 }
70
71 pub fn handled_tags(&self) -> &[String] {
73 &self.handled_tags
74 }
75
76 pub fn send(&self, alert: &Value) -> Result<()> {
81 let send_fn = self.vtable.send.ok_or_else(|| ShapeError::RuntimeError {
82 message: format!("Plugin '{}' has no send function", self.name),
83 location: None,
84 })?;
85
86 let alert_bytes = rmp_serde::to_vec(alert).map_err(|e| ShapeError::RuntimeError {
87 message: format!("Failed to serialize alert: {}", e),
88 location: None,
89 })?;
90
91 let result = unsafe { send_fn(self.instance, alert_bytes.as_ptr(), alert_bytes.len()) };
92
93 if result != 0 {
94 return Err(ShapeError::RuntimeError {
95 message: format!("Plugin '{}' send failed with code {}", self.name, result),
96 location: None,
97 });
98 }
99
100 Ok(())
101 }
102
103 pub fn flush(&self) -> Result<()> {
105 let flush_fn = match self.vtable.flush {
106 Some(f) => f,
107 None => return Ok(()), };
109
110 let result = unsafe { flush_fn(self.instance) };
111
112 if result != 0 {
113 return Err(ShapeError::RuntimeError {
114 message: format!("Plugin '{}' flush failed with code {}", self.name, result),
115 location: None,
116 });
117 }
118
119 Ok(())
120 }
121
122 fn get_handled_tags_from_vtable(
127 vtable: &OutputSinkVTable,
128 instance: *mut c_void,
129 ) -> Result<Vec<String>> {
130 let get_tags_fn = match vtable.get_handled_tags {
131 Some(f) => f,
132 None => return Ok(Vec::new()), };
134
135 let mut out_ptr: *mut u8 = ptr::null_mut();
136 let mut out_len: usize = 0;
137
138 unsafe { get_tags_fn(instance, &mut out_ptr, &mut out_len) };
139
140 if out_ptr.is_null() || out_len == 0 {
141 return Ok(Vec::new());
142 }
143
144 let data_slice = unsafe { std::slice::from_raw_parts(out_ptr, out_len) };
145 let tags: Vec<String> = rmp_serde::from_slice(data_slice).unwrap_or_else(|_| Vec::new());
146
147 if let Some(free_fn) = vtable.free_buffer {
149 unsafe { free_fn(out_ptr, out_len) };
150 }
151
152 Ok(tags)
153 }
154}
155
156impl Drop for PluginOutputSink {
157 fn drop(&mut self) {
158 let _ = self.flush();
160
161 if let Some(drop_fn) = self.vtable.drop {
162 unsafe { drop_fn(self.instance) };
163 }
164 }
165}
166
167unsafe impl Send for PluginOutputSink {}
170unsafe impl Sync for PluginOutputSink {}
171
172#[cfg(test)]
173mod tests {
174 #[test]
175 fn test_handled_tags_default() {
176 let tags: Vec<String> = Vec::new();
178 assert!(tags.is_empty());
179 }
180}