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 };
103
104 let bytes = bincode::serialize(&info).unwrap();
105 let len = bytes.len();
106 let ptr = bytes.as_ptr() as i32;
107 std::mem::forget(bytes);
108
109 ((ptr as i64) << 32) | (len as i64)
110 }
111
112 #[unsafe(no_mangle)]
123 pub extern "C" fn aiway_call(hook_id: i32, input_ptr: i32, input_len: i32) -> i64 {
124 let input_slice =
126 unsafe { std::slice::from_raw_parts(input_ptr as *const u8, input_len as usize) };
127
128 let input: aiway_plugin::wasm_types::WasmInput = match bincode::deserialize(input_slice)
129 {
130 Ok(v) => v,
131 Err(e) => return encode_error(&format!("deserialize input failed: {}", e)),
132 };
133
134 let result = match hook_id {
136 aiway_plugin::wasm_types::HOOK_ON_REQUEST => handle_on_request(&PLUGIN, &input),
137 aiway_plugin::wasm_types::HOOK_ON_REQUEST_BODY => {
138 handle_on_request_body(&PLUGIN, &input)
139 }
140 aiway_plugin::wasm_types::HOOK_ON_RESPONSE => handle_on_response(&PLUGIN, &input),
141 aiway_plugin::wasm_types::HOOK_ON_RESPONSE_BODY => {
142 handle_on_response_body(&PLUGIN, &input)
143 }
144 aiway_plugin::wasm_types::HOOK_ON_LOGGING => handle_on_logging(&PLUGIN, &input),
145 _ => Err(format!("unknown hook_id: {}", hook_id)),
146 };
147
148 match result {
149 Ok(output) => encode_output(&output),
150 Err(err_msg) => encode_error(&err_msg),
151 }
152 }
153
154 fn encode_output(output: &aiway_plugin::wasm_types::WasmOutput) -> i64 {
156 match bincode::serialize(output) {
157 Ok(bytes) => {
158 let len = bytes.len();
159 let ptr = bytes.as_ptr() as i32;
160 std::mem::forget(bytes);
161 ((ptr as i64) << 32) | (len as i64)
162 }
163 Err(e) => encode_error(&format!("serialize output failed: {}", e)),
164 }
165 }
166
167 fn encode_error(msg: &str) -> i64 {
169 let bytes = msg.as_bytes();
170 let dst = unsafe { std::slice::from_raw_parts_mut(1 as *mut u8, bytes.len()) };
172 dst.copy_from_slice(bytes);
173 (0i64 << 32) | (bytes.len() as i64)
174 }
175
176 fn parse_config(config_str: &str) -> Result<aiway_plugin::serde_json::Value, String> {
178 aiway_plugin::serde_json::from_str(config_str)
179 .map_err(|e| format!("parse config failed: {}", e))
180 }
181
182 fn handle_on_request(
184 plugin: &$plugin_type,
185 input: &aiway_plugin::wasm_types::WasmInput,
186 ) -> Result<aiway_plugin::wasm_types::WasmOutput, String> {
187 let config = parse_config(&input.config)?;
188 let mut dummy_head = build_dummy_request_parts(input.head.as_ref().unwrap());
189 let mut ctx = aiway_plugin::WasmHttpContext;
190
191 aiway_plugin::block_on(async {
192 plugin.on_request(&config, &mut dummy_head, &mut ctx).await
193 })
194 .map_err(|e| format!("{}", e))?;
195
196 let output_head = aiway_plugin::wasm_types::WasmHead::from_request_parts(&dummy_head);
197
198 Ok(aiway_plugin::wasm_types::WasmOutput {
199 head: Some(output_head),
200 body: None,
201 })
202 }
203
204 fn handle_on_request_body(
206 plugin: &$plugin_type,
207 input: &aiway_plugin::wasm_types::WasmInput,
208 ) -> Result<aiway_plugin::wasm_types::WasmOutput, String> {
209 let config = parse_config(&input.config)?;
210 let mut body = input
211 .body
212 .as_ref()
213 .map(|b| aiway_plugin::Bytes::from(b.clone()));
214 let mut ctx = aiway_plugin::WasmHttpContext;
215
216 aiway_plugin::block_on(async {
217 plugin.on_request_body(&config, &mut body, &mut ctx).await
218 })
219 .map_err(|e| format!("{}", e))?;
220
221 Ok(aiway_plugin::wasm_types::WasmOutput {
222 head: None,
223 body: body.map(|b| b.to_vec()),
224 })
225 }
226
227 fn handle_on_response(
229 plugin: &$plugin_type,
230 input: &aiway_plugin::wasm_types::WasmInput,
231 ) -> Result<aiway_plugin::wasm_types::WasmOutput, String> {
232 let config = parse_config(&input.config)?;
233 let mut dummy_head = build_dummy_response_parts(input.head.as_ref().unwrap());
234 let mut ctx = aiway_plugin::WasmHttpContext;
235
236 aiway_plugin::block_on(async {
237 plugin.on_response(&config, &mut dummy_head, &mut ctx).await
238 })
239 .map_err(|e| format!("{}", e))?;
240
241 let output_head = aiway_plugin::wasm_types::WasmHead::from_response_parts(&dummy_head);
242
243 Ok(aiway_plugin::wasm_types::WasmOutput {
244 head: Some(output_head),
245 body: None,
246 })
247 }
248
249 fn handle_on_response_body(
252 plugin: &$plugin_type,
253 input: &aiway_plugin::wasm_types::WasmInput,
254 ) -> Result<aiway_plugin::wasm_types::WasmOutput, String> {
255 let config = parse_config(&input.config)?;
256 let mut body = input
257 .body
258 .as_ref()
259 .map(|b| aiway_plugin::Bytes::from(b.clone()));
260 let mut ctx = aiway_plugin::WasmHttpContext;
261
262 plugin
263 .on_response_body(&config, &mut body, &mut ctx)
264 .map_err(|e| format!("{}", e))?;
265
266 Ok(aiway_plugin::wasm_types::WasmOutput {
267 head: None,
268 body: body.map(|b| b.to_vec()),
269 })
270 }
271
272 fn handle_on_logging(
274 plugin: &$plugin_type,
275 input: &aiway_plugin::wasm_types::WasmInput,
276 ) -> Result<aiway_plugin::wasm_types::WasmOutput, String> {
277 let config = parse_config(&input.config)?;
278 let mut ctx = aiway_plugin::WasmHttpContext;
279
280 aiway_plugin::block_on(async {
281 plugin.on_logging(&config, &mut ctx).await;
282 });
283
284 Ok(aiway_plugin::wasm_types::WasmOutput {
285 head: None,
286 body: None,
287 })
288 }
289
290 fn build_dummy_request_parts(
292 head: &aiway_plugin::wasm_types::WasmHead,
293 ) -> aiway_plugin::http::request::Parts {
294 let method: aiway_plugin::http::Method = head
295 .method
296 .as_deref()
297 .unwrap_or("GET")
298 .parse()
299 .unwrap_or(aiway_plugin::http::Method::GET);
300
301 let uri: aiway_plugin::http::Uri = head
302 .uri
303 .as_deref()
304 .unwrap_or("/")
305 .parse()
306 .unwrap_or_default();
307
308 let mut headers = aiway_plugin::http::HeaderMap::new();
309 for (k, v) in &head.headers {
310 if let (Ok(name), Ok(value)) = (
311 aiway_plugin::http::HeaderName::from_bytes(k.as_bytes()),
312 aiway_plugin::http::HeaderValue::from_str(v),
313 ) {
314 headers.insert(name, value);
315 }
316 }
317
318 let (mut parts, _) = aiway_plugin::http::Request::builder()
319 .method(method)
320 .uri(uri)
321 .body(())
322 .unwrap()
323 .into_parts();
324 parts.headers = headers;
325 parts
326 }
327
328 fn build_dummy_response_parts(
330 head: &aiway_plugin::wasm_types::WasmHead,
331 ) -> aiway_plugin::http::response::Parts {
332 let status = aiway_plugin::http::StatusCode::from_u16(head.status.unwrap_or(200))
333 .unwrap_or(aiway_plugin::http::StatusCode::OK);
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::Response::builder()
346 .status(status)
347 .body(())
348 .unwrap()
349 .into_parts();
350 parts.headers = headers;
351 parts
352 }
353 };
354}