use serde::{Deserialize, Serialize};
use std::collections::HashMap;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PluginContext {
pub method: String,
pub path: String,
pub headers: HashMap<String, String>,
pub query: HashMap<String, String>,
#[serde(default)]
pub body: serde_json::Value,
#[serde(default)]
pub user_id: Option<String>,
#[serde(default)]
pub is_admin: bool,
}
#[repr(i32)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum LogLevel {
Error = 0,
Warn = 1,
Info = 2,
Debug = 3,
Trace = 4,
}
#[allow(dead_code)]
pub struct HostFunctions;
impl HostFunctions {
pub const LOG: &'static str = "log";
pub const STATE_GET: &'static str = "state_get";
pub const STATE_SET: &'static str = "state_set";
pub const STATE_REMOVE: &'static str = "state_remove";
}
pub mod helpers {
use super::*;
pub unsafe fn read_bytes(ptr: *const u8, len: usize) -> Vec<u8> {
let mut buffer = vec![0u8; len];
unsafe {
std::ptr::copy_nonoverlapping(ptr, buffer.as_mut_ptr(), len);
}
buffer
}
pub unsafe fn read_length_prefixed(ptr: *const u8) -> Vec<u8> {
if ptr.is_null() {
return Vec::new();
}
unsafe {
let len = *(ptr as *const u32);
let data_ptr = ptr.add(4);
read_bytes(data_ptr, len as usize)
}
}
pub unsafe fn write_length_prefixed(data: &[u8], allocate_fn: extern "C" fn(i32) -> *mut u8) -> *mut u8 {
let len = data.len() as u32;
let total_size = 4 + data.len();
let ptr = allocate_fn(total_size as i32);
unsafe {
*(ptr as *mut u32) = len;
let data_ptr = ptr.add(4);
std::ptr::copy_nonoverlapping(data.as_ptr(), data_ptr, data.len());
}
ptr
}
pub fn deserialize_context(ptr: *const u8, len: usize) -> Result<PluginContext, serde_json::Error> {
let bytes = unsafe { read_bytes(ptr, len) };
serde_json::from_slice(&bytes)
}
pub unsafe fn serialize_response<T: Serialize>(
value: &T,
allocate_fn: extern "C" fn(i32) -> *mut u8,
) -> Result<*mut u8, serde_json::Error> {
let json = serde_json::to_vec(value)?;
unsafe {
Ok(write_length_prefixed(&json, allocate_fn))
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_log_level_values() {
assert_eq!(LogLevel::Error as i32, 0);
assert_eq!(LogLevel::Warn as i32, 1);
assert_eq!(LogLevel::Info as i32, 2);
assert_eq!(LogLevel::Debug as i32, 3);
assert_eq!(LogLevel::Trace as i32, 4);
}
#[test]
fn test_plugin_context_serialization() {
let context = PluginContext {
method: "GET".to_string(),
path: "/test".to_string(),
headers: HashMap::new(),
query: HashMap::new(),
body: serde_json::json!({}),
user_id: Some("user123".to_string()),
is_admin: false,
};
let json = serde_json::to_string(&context).unwrap();
let deserialized: PluginContext = serde_json::from_str(&json).unwrap();
assert_eq!(context.method, deserialized.method);
assert_eq!(context.path, deserialized.path);
assert_eq!(context.user_id, deserialized.user_id);
}
}