1#[macro_export]
12macro_rules! log_error {
13 ($ctx:expr, $($arg:tt)*) => {
14 $ctx.log_error(&format!($($arg)*))
15 };
16}
17
18#[macro_export]
20macro_rules! log_warn {
21 ($ctx:expr, $($arg:tt)*) => {
22 $ctx.log_warn(&format!($($arg)*))
23 };
24}
25
26#[macro_export]
28macro_rules! log_info {
29 ($ctx:expr, $($arg:tt)*) => {
30 $ctx.log_info(&format!($($arg)*))
31 };
32}
33
34#[macro_export]
36macro_rules! log_debug {
37 ($ctx:expr, $($arg:tt)*) => {
38 $ctx.log_debug(&format!($($arg)*))
39 };
40}
41
42#[macro_export]
44macro_rules! log_trace {
45 ($ctx:expr, $($arg:tt)*) => {
46 $ctx.log_trace(&format!($($arg)*))
47 };
48}
49
50#[macro_export]
66macro_rules! export_wasm {
67 ($plugin_type:ty) => {
68 static PLUGIN: std::sync::LazyLock<$plugin_type> =
70 std::sync::LazyLock::new(|| <$plugin_type>::new());
71
72 #[unsafe(no_mangle)]
74 pub extern "C" fn aiway_alloc(size: i32) -> i32 {
75 let layout = std::alloc::Layout::from_size_align(size as usize, 1).unwrap();
76 unsafe {
77 let ptr = std::alloc::alloc(layout);
78 ptr as i32
79 }
80 }
81
82 #[unsafe(no_mangle)]
84 pub extern "C" fn aiway_dealloc(ptr: i32, size: i32) {
85 let layout = std::alloc::Layout::from_size_align(size as usize, 1).unwrap();
86 unsafe {
87 std::alloc::dealloc(ptr as *mut u8, layout);
88 }
89 }
90
91 #[unsafe(no_mangle)]
95 pub extern "C" fn plugin_info() -> i64 {
96 let info = aiway_plugin::wasm_types::WasmPluginInfo {
97 name: PLUGIN.name().to_string(),
98 version: PLUGIN.info().version.to_string(),
99 description: PLUGIN.info().description.clone(),
100 default_config: aiway_plugin::serde_json::to_string(&PLUGIN.info().default_config)
101 .unwrap_or_default(),
102 readme: PLUGIN.info().readme.clone(),
103 };
104
105 let bytes = bincode::serialize(&info).unwrap();
106 let len = bytes.len();
107 let ptr = bytes.as_ptr() as i32;
108 std::mem::forget(bytes);
109
110 ((ptr as i64) << 32) | (len as i64)
111 }
112
113 #[unsafe(no_mangle)]
124 pub extern "C" fn aiway_call(hook_id: i32, input_ptr: i32, input_len: i32) -> i64 {
125 let input_slice =
127 unsafe { std::slice::from_raw_parts(input_ptr as *const u8, input_len as usize) };
128
129 let input: aiway_plugin::wasm_types::WasmInput = match bincode::deserialize(input_slice)
130 {
131 Ok(v) => v,
132 Err(e) => return encode_error(&format!("deserialize input failed: {}", e)),
133 };
134
135 let result: Result<aiway_plugin::wasm_types::WasmOutput, String> = match hook_id {
137 aiway_plugin::wasm_types::HOOK_ON_REQUEST => handle_on_request(&PLUGIN, &input),
138 aiway_plugin::wasm_types::HOOK_ON_REQUEST_BODY => {
139 handle_on_request_body(&PLUGIN, &input)
140 }
141 aiway_plugin::wasm_types::HOOK_ON_RESPONSE => handle_on_response(&PLUGIN, &input),
142 aiway_plugin::wasm_types::HOOK_ON_RESPONSE_BODY => {
143 handle_on_response_body(&PLUGIN, &input)
144 }
145 aiway_plugin::wasm_types::HOOK_ON_LOGGING => handle_on_logging(&PLUGIN, &input),
146 _ => Err(format!("unknown hook_id: {}", hook_id)),
147 };
148
149 match result {
150 Ok(output) => encode_output(&output),
151 Err(err_bytes) => encode_reject_error(err_bytes.as_bytes()),
152 }
153 }
154
155 fn encode_output(output: &aiway_plugin::wasm_types::WasmOutput) -> i64 {
157 match bincode::serialize(output) {
158 Ok(bytes) => {
159 let len = bytes.len();
160 let ptr = bytes.as_ptr() as i32;
161 std::mem::forget(bytes);
162 ((ptr as i64) << 32) | (len as i64)
163 }
164 Err(e) => encode_error(&format!("serialize output failed: {}", e)),
165 }
166 }
167
168 fn encode_error(msg: &str) -> i64 {
170 let bytes = msg.as_bytes();
171 let dst = unsafe { std::slice::from_raw_parts_mut(1 as *mut u8, bytes.len()) };
173 dst.copy_from_slice(bytes);
174 (0i64 << 32) | (bytes.len() as i64)
175 }
176
177 fn encode_reject_error(err_bytes: &[u8]) -> i64 {
183 let dst = unsafe { std::slice::from_raw_parts_mut(1 as *mut u8, err_bytes.len()) };
184 dst.copy_from_slice(err_bytes);
185 (0i64 << 32) | (err_bytes.len() as i64)
186 }
187
188 fn encode_plugin_error(e: aiway_plugin::PluginError) -> String {
190 match e {
191 aiway_plugin::PluginError::Reject(status, message) => {
192 let msg_bytes = message.as_bytes();
193 let mut buf = Vec::with_capacity(4 + msg_bytes.len());
194 buf.extend_from_slice(&(status as u32).to_be_bytes());
195 buf.extend_from_slice(msg_bytes);
196 unsafe { String::from_utf8_unchecked(buf) }
198 }
199 other => format!("{}", other),
200 }
201 }
202
203 fn parse_config(config_str: &str) -> Result<aiway_plugin::serde_json::Value, String> {
205 aiway_plugin::serde_json::from_str(config_str)
206 .map_err(|e| format!("parse config failed: {}", e))
207 }
208
209 fn handle_on_request(
211 plugin: &$plugin_type,
212 input: &aiway_plugin::wasm_types::WasmInput,
213 ) -> Result<aiway_plugin::wasm_types::WasmOutput, String> {
214 let config = parse_config(&input.config)?;
215 let mut dummy_head = build_dummy_request_parts(input.head.as_ref().unwrap());
216 let mut ctx = aiway_plugin::WasmHttpContext;
217
218 aiway_plugin::block_on(async {
219 plugin.on_request(&config, &mut dummy_head, &mut ctx).await
220 })
221 .map_err(encode_plugin_error)?;
222
223 let output_head = aiway_plugin::wasm_types::WasmHead::from_request_parts(&dummy_head);
224
225 Ok(aiway_plugin::wasm_types::WasmOutput {
226 head: Some(output_head),
227 body: None,
228 })
229 }
230
231 fn handle_on_request_body(
233 plugin: &$plugin_type,
234 input: &aiway_plugin::wasm_types::WasmInput,
235 ) -> Result<aiway_plugin::wasm_types::WasmOutput, String> {
236 let config = parse_config(&input.config)?;
237 let mut body = input
238 .body
239 .as_ref()
240 .map(|b| aiway_plugin::Bytes::from(b.clone()));
241 let mut ctx = aiway_plugin::WasmHttpContext;
242
243 aiway_plugin::block_on(async {
244 plugin.on_request_body(&config, &mut body, &mut ctx).await
245 })
246 .map_err(encode_plugin_error)?;
247
248 Ok(aiway_plugin::wasm_types::WasmOutput {
249 head: None,
250 body: body.map(|b| b.to_vec()),
251 })
252 }
253
254 fn handle_on_response(
256 plugin: &$plugin_type,
257 input: &aiway_plugin::wasm_types::WasmInput,
258 ) -> Result<aiway_plugin::wasm_types::WasmOutput, String> {
259 let config = parse_config(&input.config)?;
260 let mut dummy_head = build_dummy_response_parts(input.head.as_ref().unwrap());
261 let mut ctx = aiway_plugin::WasmHttpContext;
262
263 aiway_plugin::block_on(async {
264 plugin.on_response(&config, &mut dummy_head, &mut ctx).await
265 })
266 .map_err(encode_plugin_error)?;
267
268 let output_head = aiway_plugin::wasm_types::WasmHead::from_response_parts(&dummy_head);
269
270 Ok(aiway_plugin::wasm_types::WasmOutput {
271 head: Some(output_head),
272 body: None,
273 })
274 }
275
276 fn handle_on_response_body(
278 plugin: &$plugin_type,
279 input: &aiway_plugin::wasm_types::WasmInput,
280 ) -> Result<aiway_plugin::wasm_types::WasmOutput, String> {
281 let config = parse_config(&input.config)?;
282 let mut body = input
283 .body
284 .as_ref()
285 .map(|b| aiway_plugin::Bytes::from(b.clone()));
286 let mut ctx = aiway_plugin::WasmHttpContext;
287
288 aiway_plugin::block_on(async {
289 plugin.on_response_body(&config, &mut body, &mut ctx).await
290 })
291 .map_err(encode_plugin_error)?;
292
293 Ok(aiway_plugin::wasm_types::WasmOutput {
294 head: None,
295 body: body.map(|b| b.to_vec()),
296 })
297 }
298
299 fn handle_on_logging(
301 plugin: &$plugin_type,
302 input: &aiway_plugin::wasm_types::WasmInput,
303 ) -> Result<aiway_plugin::wasm_types::WasmOutput, String> {
304 let config = parse_config(&input.config)?;
305 let mut ctx = aiway_plugin::WasmHttpContext;
306
307 aiway_plugin::block_on(async {
308 plugin.on_logging(&config, &mut ctx).await;
309 });
310
311 Ok(aiway_plugin::wasm_types::WasmOutput {
312 head: None,
313 body: None,
314 })
315 }
316
317 fn build_dummy_request_parts(
319 head: &aiway_plugin::wasm_types::WasmHead,
320 ) -> aiway_plugin::http::request::Parts {
321 let method: aiway_plugin::http::Method = head
322 .method
323 .as_deref()
324 .unwrap_or("GET")
325 .parse()
326 .unwrap_or(aiway_plugin::http::Method::GET);
327
328 let uri: aiway_plugin::http::Uri = head
329 .uri
330 .as_deref()
331 .unwrap_or("/")
332 .parse()
333 .unwrap_or_default();
334
335 let mut headers = aiway_plugin::http::HeaderMap::new();
336 for (k, v) in &head.headers {
337 if let (Ok(name), Ok(value)) = (
338 aiway_plugin::http::HeaderName::from_bytes(k.as_bytes()),
339 aiway_plugin::http::HeaderValue::from_str(v),
340 ) {
341 headers.insert(name, value);
342 }
343 }
344
345 let (mut parts, _) = aiway_plugin::http::Request::builder()
346 .method(method)
347 .uri(uri)
348 .body(())
349 .unwrap()
350 .into_parts();
351 parts.headers = headers;
352 parts
353 }
354
355 fn build_dummy_response_parts(
357 head: &aiway_plugin::wasm_types::WasmHead,
358 ) -> aiway_plugin::http::response::Parts {
359 let status = aiway_plugin::http::StatusCode::from_u16(head.status.unwrap_or(200))
360 .unwrap_or(aiway_plugin::http::StatusCode::OK);
361
362 let mut headers = aiway_plugin::http::HeaderMap::new();
363 for (k, v) in &head.headers {
364 if let (Ok(name), Ok(value)) = (
365 aiway_plugin::http::HeaderName::from_bytes(k.as_bytes()),
366 aiway_plugin::http::HeaderValue::from_str(v),
367 ) {
368 headers.insert(name, value);
369 }
370 }
371
372 let (mut parts, _) = aiway_plugin::http::Response::builder()
373 .status(status)
374 .body(())
375 .unwrap()
376 .into_parts();
377 parts.headers = headers;
378 parts
379 }
380 };
381}