#[macro_export]
macro_rules! log_error {
($ctx:expr, $($arg:tt)*) => {
$ctx.log_error(&format!($($arg)*))
};
}
#[macro_export]
macro_rules! log_warn {
($ctx:expr, $($arg:tt)*) => {
$ctx.log_warn(&format!($($arg)*))
};
}
#[macro_export]
macro_rules! log_info {
($ctx:expr, $($arg:tt)*) => {
$ctx.log_info(&format!($($arg)*))
};
}
#[macro_export]
macro_rules! log_debug {
($ctx:expr, $($arg:tt)*) => {
$ctx.log_debug(&format!($($arg)*))
};
}
#[macro_export]
macro_rules! log_trace {
($ctx:expr, $($arg:tt)*) => {
$ctx.log_trace(&format!($($arg)*))
};
}
#[macro_export]
macro_rules! export_wasm {
($plugin_type:ty) => {
static PLUGIN: std::sync::LazyLock<$plugin_type> =
std::sync::LazyLock::new(|| <$plugin_type>::new());
#[unsafe(no_mangle)]
pub extern "C" fn aiway_alloc(size: i32) -> i32 {
let layout = std::alloc::Layout::from_size_align(size as usize, 1).unwrap();
unsafe {
let ptr = std::alloc::alloc(layout);
ptr as i32
}
}
#[unsafe(no_mangle)]
pub extern "C" fn aiway_dealloc(ptr: i32, size: i32) {
let layout = std::alloc::Layout::from_size_align(size as usize, 1).unwrap();
unsafe {
std::alloc::dealloc(ptr as *mut u8, layout);
}
}
#[unsafe(no_mangle)]
pub extern "C" fn plugin_info() -> i64 {
let info = aiway_plugin::wasm_types::WasmPluginInfo {
name: PLUGIN.name().to_string(),
version: PLUGIN.info().version.to_string(),
description: PLUGIN.info().description.clone(),
default_config: aiway_plugin::serde_json::to_string(&PLUGIN.info().default_config)
.unwrap_or_default(),
};
let bytes = bincode::serialize(&info).unwrap();
let len = bytes.len();
let ptr = bytes.as_ptr() as i32;
std::mem::forget(bytes);
((ptr as i64) << 32) | (len as i64)
}
#[unsafe(no_mangle)]
pub extern "C" fn aiway_call(hook_id: i32, input_ptr: i32, input_len: i32) -> i64 {
let input_slice =
unsafe { std::slice::from_raw_parts(input_ptr as *const u8, input_len as usize) };
let input: aiway_plugin::wasm_types::WasmInput = match bincode::deserialize(input_slice)
{
Ok(v) => v,
Err(e) => return encode_error(&format!("deserialize input failed: {}", e)),
};
let result = match hook_id {
aiway_plugin::wasm_types::HOOK_ON_REQUEST => handle_on_request(&PLUGIN, &input),
aiway_plugin::wasm_types::HOOK_ON_REQUEST_BODY => {
handle_on_request_body(&PLUGIN, &input)
}
aiway_plugin::wasm_types::HOOK_ON_RESPONSE => handle_on_response(&PLUGIN, &input),
aiway_plugin::wasm_types::HOOK_ON_RESPONSE_BODY => {
handle_on_response_body(&PLUGIN, &input)
}
aiway_plugin::wasm_types::HOOK_ON_LOGGING => handle_on_logging(&PLUGIN, &input),
_ => Err(format!("unknown hook_id: {}", hook_id)),
};
match result {
Ok(output) => encode_output(&output),
Err(err_msg) => encode_error(&err_msg),
}
}
fn encode_output(output: &aiway_plugin::wasm_types::WasmOutput) -> i64 {
match bincode::serialize(output) {
Ok(bytes) => {
let len = bytes.len();
let ptr = bytes.as_ptr() as i32;
std::mem::forget(bytes);
((ptr as i64) << 32) | (len as i64)
}
Err(e) => encode_error(&format!("serialize output failed: {}", e)),
}
}
fn encode_error(msg: &str) -> i64 {
let bytes = msg.as_bytes();
let dst = unsafe { std::slice::from_raw_parts_mut(1 as *mut u8, bytes.len()) };
dst.copy_from_slice(bytes);
(0i64 << 32) | (bytes.len() as i64)
}
fn parse_config(config_str: &str) -> Result<aiway_plugin::serde_json::Value, String> {
aiway_plugin::serde_json::from_str(config_str)
.map_err(|e| format!("parse config failed: {}", e))
}
fn handle_on_request(
plugin: &$plugin_type,
input: &aiway_plugin::wasm_types::WasmInput,
) -> Result<aiway_plugin::wasm_types::WasmOutput, String> {
let config = parse_config(&input.config)?;
let mut dummy_head = build_dummy_request_parts(input.head.as_ref().unwrap());
let mut ctx = aiway_plugin::WasmHttpContext;
aiway_plugin::block_on(async {
plugin.on_request(&config, &mut dummy_head, &mut ctx).await
})
.map_err(|e| format!("{}", e))?;
let output_head = aiway_plugin::wasm_types::WasmHead::from_request_parts(&dummy_head);
Ok(aiway_plugin::wasm_types::WasmOutput {
head: Some(output_head),
body: None,
})
}
fn handle_on_request_body(
plugin: &$plugin_type,
input: &aiway_plugin::wasm_types::WasmInput,
) -> Result<aiway_plugin::wasm_types::WasmOutput, String> {
let config = parse_config(&input.config)?;
let mut body = input
.body
.as_ref()
.map(|b| aiway_plugin::Bytes::from(b.clone()));
let mut ctx = aiway_plugin::WasmHttpContext;
aiway_plugin::block_on(async {
plugin.on_request_body(&config, &mut body, &mut ctx).await
})
.map_err(|e| format!("{}", e))?;
Ok(aiway_plugin::wasm_types::WasmOutput {
head: None,
body: body.map(|b| b.to_vec()),
})
}
fn handle_on_response(
plugin: &$plugin_type,
input: &aiway_plugin::wasm_types::WasmInput,
) -> Result<aiway_plugin::wasm_types::WasmOutput, String> {
let config = parse_config(&input.config)?;
let mut dummy_head = build_dummy_response_parts(input.head.as_ref().unwrap());
let mut ctx = aiway_plugin::WasmHttpContext;
aiway_plugin::block_on(async {
plugin.on_response(&config, &mut dummy_head, &mut ctx).await
})
.map_err(|e| format!("{}", e))?;
let output_head = aiway_plugin::wasm_types::WasmHead::from_response_parts(&dummy_head);
Ok(aiway_plugin::wasm_types::WasmOutput {
head: Some(output_head),
body: None,
})
}
fn handle_on_response_body(
plugin: &$plugin_type,
input: &aiway_plugin::wasm_types::WasmInput,
) -> Result<aiway_plugin::wasm_types::WasmOutput, String> {
let config = parse_config(&input.config)?;
let mut body = input
.body
.as_ref()
.map(|b| aiway_plugin::Bytes::from(b.clone()));
let mut ctx = aiway_plugin::WasmHttpContext;
plugin
.on_response_body(&config, &mut body, &mut ctx)
.map_err(|e| format!("{}", e))?;
Ok(aiway_plugin::wasm_types::WasmOutput {
head: None,
body: body.map(|b| b.to_vec()),
})
}
fn handle_on_logging(
plugin: &$plugin_type,
input: &aiway_plugin::wasm_types::WasmInput,
) -> Result<aiway_plugin::wasm_types::WasmOutput, String> {
let config = parse_config(&input.config)?;
let mut ctx = aiway_plugin::WasmHttpContext;
aiway_plugin::block_on(async {
plugin.on_logging(&config, &mut ctx).await;
});
Ok(aiway_plugin::wasm_types::WasmOutput {
head: None,
body: None,
})
}
fn build_dummy_request_parts(
head: &aiway_plugin::wasm_types::WasmHead,
) -> aiway_plugin::http::request::Parts {
let method: aiway_plugin::http::Method = head
.method
.as_deref()
.unwrap_or("GET")
.parse()
.unwrap_or(aiway_plugin::http::Method::GET);
let uri: aiway_plugin::http::Uri = head
.uri
.as_deref()
.unwrap_or("/")
.parse()
.unwrap_or_default();
let mut headers = aiway_plugin::http::HeaderMap::new();
for (k, v) in &head.headers {
if let (Ok(name), Ok(value)) = (
aiway_plugin::http::HeaderName::from_bytes(k.as_bytes()),
aiway_plugin::http::HeaderValue::from_str(v),
) {
headers.insert(name, value);
}
}
let (mut parts, _) = aiway_plugin::http::Request::builder()
.method(method)
.uri(uri)
.body(())
.unwrap()
.into_parts();
parts.headers = headers;
parts
}
fn build_dummy_response_parts(
head: &aiway_plugin::wasm_types::WasmHead,
) -> aiway_plugin::http::response::Parts {
let status = aiway_plugin::http::StatusCode::from_u16(head.status.unwrap_or(200))
.unwrap_or(aiway_plugin::http::StatusCode::OK);
let mut headers = aiway_plugin::http::HeaderMap::new();
for (k, v) in &head.headers {
if let (Ok(name), Ok(value)) = (
aiway_plugin::http::HeaderName::from_bytes(k.as_bytes()),
aiway_plugin::http::HeaderValue::from_str(v),
) {
headers.insert(name, value);
}
}
let (mut parts, _) = aiway_plugin::http::Response::builder()
.status(status)
.body(())
.unwrap()
.into_parts();
parts.headers = headers;
parts
}
};
}