1use async_trait::async_trait;
13use mockforge_plugin_core::{
14 AuthRequest, AuthResponse, DataQuery, DataResult, PluginContext, PluginError, PluginId,
15 ResolutionContext, ResponseData, ResponseRequest,
16};
17use std::collections::HashMap;
18use std::sync::{Arc, Mutex};
19use wasmparser::{Parser, Payload};
20use wasmtime::{Engine, Instance, Linker, Module, Store};
21use wasmtime_wasi::{WasiCtx, WasiCtxBuilder};
22
23#[derive(Debug, Clone, PartialEq, Eq)]
25pub enum RuntimeType {
26 Rust,
28 TinyGo,
30 AssemblyScript,
32 Remote(RemoteRuntimeConfig),
34}
35
36#[derive(Debug, Clone, PartialEq, Eq)]
38pub struct RemoteRuntimeConfig {
39 pub protocol: RemoteProtocol,
41 pub endpoint: String,
43 pub timeout_ms: u64,
45 pub max_retries: u32,
47 pub auth: Option<RemoteAuthConfig>,
49}
50
51#[derive(Debug, Clone, PartialEq, Eq)]
53pub enum RemoteProtocol {
54 Http,
56 Grpc,
58}
59
60#[derive(Debug, Clone, PartialEq, Eq)]
62pub struct RemoteAuthConfig {
63 pub auth_type: String,
65 pub value: String,
67}
68
69#[async_trait]
73pub trait RuntimeAdapter: Send + Sync {
74 fn runtime_type(&self) -> RuntimeType;
76
77 async fn initialize(&mut self) -> Result<(), PluginError>;
79
80 async fn call_auth(
82 &self,
83 context: &PluginContext,
84 request: &AuthRequest,
85 ) -> Result<AuthResponse, PluginError>;
86
87 async fn call_template_function(
89 &self,
90 function_name: &str,
91 args: &[serde_json::Value],
92 context: &ResolutionContext,
93 ) -> Result<serde_json::Value, PluginError>;
94
95 async fn call_response_generator(
97 &self,
98 context: &PluginContext,
99 request: &ResponseRequest,
100 ) -> Result<ResponseData, PluginError>;
101
102 async fn call_datasource_query(
104 &self,
105 query: &DataQuery,
106 context: &PluginContext,
107 ) -> Result<DataResult, PluginError>;
108
109 async fn health_check(&self) -> Result<bool, PluginError>;
111
112 async fn cleanup(&mut self) -> Result<(), PluginError>;
114
115 fn get_metrics(&self) -> HashMap<String, serde_json::Value> {
117 HashMap::new()
118 }
119}
120
121pub fn detect_runtime_type(wasm_bytes: &[u8]) -> Result<RuntimeType, PluginError> {
123 if has_tinygo_signature(wasm_bytes) {
128 return Ok(RuntimeType::TinyGo);
129 }
130
131 if has_assemblyscript_signature(wasm_bytes) {
133 return Ok(RuntimeType::AssemblyScript);
134 }
135
136 Ok(RuntimeType::Rust)
138}
139
140fn has_tinygo_signature(wasm_bytes: &[u8]) -> bool {
142 let (exports, custom_sections) = extract_wasm_signatures(wasm_bytes);
143
144 let has_runtime_exports = exports.contains("resume") && exports.contains("getsp");
146 let has_tinygo_custom = custom_sections.iter().any(|s| s.contains("tinygo"));
147
148 has_runtime_exports
149 || has_tinygo_custom
150 || String::from_utf8_lossy(wasm_bytes).contains("tinygo")
151}
152
153fn has_assemblyscript_signature(wasm_bytes: &[u8]) -> bool {
155 let (exports, custom_sections) = extract_wasm_signatures(wasm_bytes);
156
157 let has_alloc_exports =
159 exports.contains("__new") && (exports.contains("__pin") || exports.contains("__unpin"));
160 let has_as_custom = custom_sections
161 .iter()
162 .any(|s| s.contains("assemblyscript") || s.contains("asc"));
163
164 has_alloc_exports
165 || has_as_custom
166 || String::from_utf8_lossy(wasm_bytes).contains("assemblyscript")
167}
168
169fn extract_wasm_signatures(wasm_bytes: &[u8]) -> (std::collections::HashSet<String>, Vec<String>) {
170 use std::collections::HashSet;
171
172 let mut exports = HashSet::new();
173 let mut custom_sections = Vec::new();
174
175 for payload in Parser::new(0).parse_all(wasm_bytes) {
176 match payload {
177 Ok(Payload::ExportSection(section)) => {
178 for export in section.into_iter().flatten() {
179 exports.insert(export.name.to_string());
180 }
181 }
182 Ok(Payload::CustomSection(section)) => {
183 custom_sections.push(section.name().to_string());
184 }
185 Ok(_) => {}
186 Err(_) => break,
187 }
188 }
189
190 (exports, custom_sections)
191}
192
193pub struct RuntimeAdapterFactory;
195
196impl RuntimeAdapterFactory {
197 pub fn create(
199 runtime_type: RuntimeType,
200 plugin_id: PluginId,
201 wasm_bytes: Vec<u8>,
202 ) -> Result<Box<dyn RuntimeAdapter>, PluginError> {
203 match runtime_type {
204 RuntimeType::Rust => Ok(Box::new(RustAdapter::new(plugin_id, wasm_bytes)?)),
205 RuntimeType::TinyGo => Ok(Box::new(TinyGoAdapter::new(plugin_id, wasm_bytes)?)),
206 RuntimeType::AssemblyScript => {
207 Ok(Box::new(AssemblyScriptAdapter::new(plugin_id, wasm_bytes)?))
208 }
209 RuntimeType::Remote(config) => Ok(Box::new(RemoteAdapter::new(plugin_id, config)?)),
210 }
211 }
212}
213
214pub struct RustAdapter {
220 plugin_id: PluginId,
221 engine: Arc<Engine>,
222 module: Module,
223 runtime: Mutex<Option<WasmRuntime>>,
225}
226
227struct WasmRuntime {
228 store: Store<WasiCtx>,
229 instance: Instance,
230}
231
232impl RustAdapter {
233 pub fn new(plugin_id: PluginId, wasm_bytes: Vec<u8>) -> Result<Self, PluginError> {
242 let engine = Arc::new(Engine::default());
243 let module = Module::from_binary(&engine, &wasm_bytes)
244 .map_err(|e| PluginError::execution(format!("Failed to load WASM module: {}", e)))?;
245
246 Ok(Self {
247 plugin_id,
248 engine,
249 module,
250 runtime: Mutex::new(None),
251 })
252 }
253
254 fn call_wasm_json(
256 &self,
257 function_name: &str,
258 input_data: serde_json::Value,
259 ) -> Result<serde_json::Value, PluginError> {
260 let mut runtime_guard =
261 self.runtime.lock().unwrap_or_else(|poisoned| poisoned.into_inner());
262 let runtime = runtime_guard.as_mut().ok_or_else(|| {
263 PluginError::execution("Runtime not initialized. Call initialize() first.".to_string())
264 })?;
265
266 let input_json = serde_json::to_string(&input_data)
267 .map_err(|e| PluginError::execution(format!("Failed to serialize input: {}", e)))?;
268
269 let input_bytes = input_json.as_bytes();
270 let input_len = input_bytes.len() as i32;
271
272 let memory =
274 runtime.instance.get_memory(&mut runtime.store, "memory").ok_or_else(|| {
275 PluginError::execution("WASM module must export 'memory'".to_string())
276 })?;
277
278 let alloc_func = runtime
279 .instance
280 .get_typed_func::<i32, i32>(&mut runtime.store, "alloc")
281 .map_err(|e| {
282 PluginError::execution(format!("Failed to get alloc function: {}", e))
283 })?;
284
285 let input_ptr = alloc_func
287 .call(&mut runtime.store, input_len)
288 .map_err(|e| PluginError::execution(format!("Failed to allocate memory: {}", e)))?;
289
290 memory
292 .write(&mut runtime.store, input_ptr as usize, input_bytes)
293 .map_err(|e| PluginError::execution(format!("Failed to write input: {}", e)))?;
294
295 let plugin_func = runtime
297 .instance
298 .get_typed_func::<(i32, i32), (i32, i32)>(&mut runtime.store, function_name)
299 .map_err(|e| {
300 PluginError::execution(format!("Function '{}' not found: {}", function_name, e))
301 })?;
302
303 let (output_ptr, output_len) =
304 plugin_func.call(&mut runtime.store, (input_ptr, input_len)).map_err(|e| {
305 PluginError::execution(format!(
306 "Failed to call function '{}': {}",
307 function_name, e
308 ))
309 })?;
310
311 let mut output_bytes = vec![0u8; output_len as usize];
313 memory
314 .read(&runtime.store, output_ptr as usize, &mut output_bytes)
315 .map_err(|e| PluginError::execution(format!("Failed to read output: {}", e)))?;
316
317 if let Ok(dealloc_func) =
319 runtime.instance.get_typed_func::<(i32, i32), ()>(&mut runtime.store, "dealloc")
320 {
321 let _ = dealloc_func.call(&mut runtime.store, (input_ptr, input_len));
322 let _ = dealloc_func.call(&mut runtime.store, (output_ptr, output_len));
323 }
324
325 let output_str = String::from_utf8(output_bytes)
327 .map_err(|e| PluginError::execution(format!("Failed to decode output: {}", e)))?;
328
329 serde_json::from_str(&output_str)
330 .map_err(|e| PluginError::execution(format!("Failed to parse output JSON: {}", e)))
331 }
332}
333
334#[async_trait]
335impl RuntimeAdapter for RustAdapter {
336 fn runtime_type(&self) -> RuntimeType {
337 RuntimeType::Rust
338 }
339
340 async fn initialize(&mut self) -> Result<(), PluginError> {
341 tracing::info!("Initializing Rust plugin: {}", self.plugin_id);
343
344 let wasi_ctx = WasiCtxBuilder::new().inherit_stderr().inherit_stdout().build();
346
347 let mut store = Store::new(&self.engine, wasi_ctx);
349
350 let linker = Linker::new(&self.engine);
352
353 let instance = linker
355 .instantiate(&mut store, &self.module)
356 .map_err(|e| PluginError::execution(format!("Failed to instantiate module: {}", e)))?;
357
358 let mut runtime_guard =
360 self.runtime.lock().unwrap_or_else(|poisoned| poisoned.into_inner());
361 *runtime_guard = Some(WasmRuntime { store, instance });
362
363 tracing::info!("Successfully initialized Rust plugin: {}", self.plugin_id);
364 Ok(())
365 }
366
367 async fn call_auth(
368 &self,
369 context: &PluginContext,
370 request: &AuthRequest,
371 ) -> Result<AuthResponse, PluginError> {
372 let input = serde_json::json!({
376 "context": context,
377 "method": request.method.to_string(),
378 "uri": request.uri.to_string(),
379 "query_params": request.query_params,
380 "client_ip": request.client_ip,
381 "user_agent": request.user_agent,
382 });
383
384 let result = self.call_wasm_json("authenticate", input)?;
385 serde_json::from_value(result)
386 .map_err(|e| PluginError::execution(format!("Failed to parse AuthResponse: {}", e)))
387 }
388
389 async fn call_template_function(
390 &self,
391 function_name: &str,
392 args: &[serde_json::Value],
393 context: &ResolutionContext,
394 ) -> Result<serde_json::Value, PluginError> {
395 let input = serde_json::json!({
396 "function_name": function_name,
397 "args": args,
398 "context": context,
399 });
400
401 self.call_wasm_json("template_function", input)
402 }
403
404 async fn call_response_generator(
405 &self,
406 context: &PluginContext,
407 request: &ResponseRequest,
408 ) -> Result<ResponseData, PluginError> {
409 let input = serde_json::json!({
411 "context": context,
412 "method": request.method.to_string(),
413 "uri": request.uri,
414 "path": request.path,
415 "query_params": request.query_params,
416 "path_params": request.path_params,
417 "client_ip": request.client_ip,
418 "user_agent": request.user_agent,
419 });
420
421 let result = self.call_wasm_json("generate_response", input)?;
422 serde_json::from_value(result)
423 .map_err(|e| PluginError::execution(format!("Failed to parse ResponseData: {}", e)))
424 }
425
426 async fn call_datasource_query(
427 &self,
428 query: &DataQuery,
429 context: &PluginContext,
430 ) -> Result<DataResult, PluginError> {
431 let input = serde_json::json!({
433 "query_type": format!("{:?}", query.query_type),
434 "query": query.query,
435 "parameters": query.parameters,
436 "limit": query.limit,
437 "offset": query.offset,
438 "context": context,
439 });
440
441 let result = self.call_wasm_json("query_datasource", input)?;
442 serde_json::from_value(result)
443 .map_err(|e| PluginError::execution(format!("Failed to parse DataResult: {}", e)))
444 }
445
446 async fn health_check(&self) -> Result<bool, PluginError> {
447 Ok(true)
448 }
449
450 async fn cleanup(&mut self) -> Result<(), PluginError> {
451 Ok(())
452 }
453}
454
455pub struct TinyGoAdapter {
461 plugin_id: PluginId,
462 engine: Arc<Engine>,
463 module: Module,
464 runtime: Mutex<Option<WasmRuntime>>,
465}
466
467impl TinyGoAdapter {
468 pub fn new(plugin_id: PluginId, wasm_bytes: Vec<u8>) -> Result<Self, PluginError> {
477 let engine = Arc::new(Engine::default());
478 let module = Module::from_binary(&engine, &wasm_bytes).map_err(|e| {
479 PluginError::execution(format!("Failed to load TinyGo WASM module: {}", e))
480 })?;
481
482 Ok(Self {
483 plugin_id,
484 engine,
485 module,
486 runtime: Mutex::new(None),
487 })
488 }
489
490 fn call_wasm_json(
493 &self,
494 function_name: &str,
495 input_data: serde_json::Value,
496 ) -> Result<serde_json::Value, PluginError> {
497 let mut runtime_guard =
498 self.runtime.lock().unwrap_or_else(|poisoned| poisoned.into_inner());
499 let runtime = runtime_guard.as_mut().ok_or_else(|| {
500 PluginError::execution("Runtime not initialized. Call initialize() first.".to_string())
501 })?;
502
503 let input_json = serde_json::to_string(&input_data)
504 .map_err(|e| PluginError::execution(format!("Failed to serialize input: {}", e)))?;
505
506 let input_bytes = input_json.as_bytes();
507 let input_len = input_bytes.len() as i32;
508
509 let memory =
511 runtime.instance.get_memory(&mut runtime.store, "memory").ok_or_else(|| {
512 PluginError::execution("TinyGo WASM module must export 'memory'".to_string())
513 })?;
514
515 let malloc_func = runtime
517 .instance
518 .get_typed_func::<i32, i32>(&mut runtime.store, "malloc")
519 .map_err(|e| {
520 PluginError::execution(format!(
521 "Failed to get malloc function (TinyGo specific): {}",
522 e
523 ))
524 })?;
525
526 let input_ptr = malloc_func
528 .call(&mut runtime.store, input_len)
529 .map_err(|e| PluginError::execution(format!("Failed to allocate memory: {}", e)))?;
530
531 memory
533 .write(&mut runtime.store, input_ptr as usize, input_bytes)
534 .map_err(|e| PluginError::execution(format!("Failed to write input: {}", e)))?;
535
536 let plugin_func = runtime
538 .instance
539 .get_typed_func::<(i32, i32), (i32, i32)>(&mut runtime.store, function_name)
540 .map_err(|e| {
541 PluginError::execution(format!("Function '{}' not found: {}", function_name, e))
542 })?;
543
544 let (output_ptr, output_len) =
545 plugin_func.call(&mut runtime.store, (input_ptr, input_len)).map_err(|e| {
546 PluginError::execution(format!(
547 "Failed to call function '{}': {}",
548 function_name, e
549 ))
550 })?;
551
552 let mut output_bytes = vec![0u8; output_len as usize];
554 memory
555 .read(&runtime.store, output_ptr as usize, &mut output_bytes)
556 .map_err(|e| PluginError::execution(format!("Failed to read output: {}", e)))?;
557
558 if let Ok(free_func) =
560 runtime.instance.get_typed_func::<i32, ()>(&mut runtime.store, "free")
561 {
562 let _ = free_func.call(&mut runtime.store, input_ptr);
563 let _ = free_func.call(&mut runtime.store, output_ptr);
564 }
565
566 let output_str = String::from_utf8(output_bytes)
568 .map_err(|e| PluginError::execution(format!("Failed to decode output: {}", e)))?;
569
570 serde_json::from_str(&output_str)
571 .map_err(|e| PluginError::execution(format!("Failed to parse output JSON: {}", e)))
572 }
573}
574
575#[async_trait]
576impl RuntimeAdapter for TinyGoAdapter {
577 fn runtime_type(&self) -> RuntimeType {
578 RuntimeType::TinyGo
579 }
580
581 async fn initialize(&mut self) -> Result<(), PluginError> {
582 tracing::info!("Initializing TinyGo plugin: {}", self.plugin_id);
585
586 let wasi_ctx = WasiCtxBuilder::new().inherit_stderr().inherit_stdout().build();
588
589 let mut store = Store::new(&self.engine, wasi_ctx);
591
592 let linker = Linker::new(&self.engine);
594
595 let instance = linker.instantiate(&mut store, &self.module).map_err(|e| {
600 PluginError::execution(format!("Failed to instantiate TinyGo module: {}", e))
601 })?;
602
603 let mut runtime_guard =
605 self.runtime.lock().unwrap_or_else(|poisoned| poisoned.into_inner());
606 *runtime_guard = Some(WasmRuntime { store, instance });
607
608 tracing::info!("Successfully initialized TinyGo plugin: {}", self.plugin_id);
609 Ok(())
610 }
611
612 async fn call_auth(
613 &self,
614 context: &PluginContext,
615 request: &AuthRequest,
616 ) -> Result<AuthResponse, PluginError> {
617 let input = serde_json::json!({
619 "context": context,
620 "method": request.method.to_string(),
621 "uri": request.uri.to_string(),
622 "query_params": request.query_params,
623 "client_ip": request.client_ip,
624 "user_agent": request.user_agent,
625 });
626
627 let result = self.call_wasm_json("authenticate", input)?;
628 serde_json::from_value(result)
629 .map_err(|e| PluginError::execution(format!("Failed to parse AuthResponse: {}", e)))
630 }
631
632 async fn call_template_function(
633 &self,
634 function_name: &str,
635 args: &[serde_json::Value],
636 context: &ResolutionContext,
637 ) -> Result<serde_json::Value, PluginError> {
638 let input = serde_json::json!({
639 "function_name": function_name,
640 "args": args,
641 "context": context,
642 });
643
644 self.call_wasm_json("template_function", input)
645 }
646
647 async fn call_response_generator(
648 &self,
649 context: &PluginContext,
650 request: &ResponseRequest,
651 ) -> Result<ResponseData, PluginError> {
652 let input = serde_json::json!({
654 "context": context,
655 "method": request.method.to_string(),
656 "uri": request.uri,
657 "path": request.path,
658 "query_params": request.query_params,
659 "path_params": request.path_params,
660 "client_ip": request.client_ip,
661 "user_agent": request.user_agent,
662 });
663
664 let result = self.call_wasm_json("generate_response", input)?;
665 serde_json::from_value(result)
666 .map_err(|e| PluginError::execution(format!("Failed to parse ResponseData: {}", e)))
667 }
668
669 async fn call_datasource_query(
670 &self,
671 query: &DataQuery,
672 context: &PluginContext,
673 ) -> Result<DataResult, PluginError> {
674 let input = serde_json::json!({
676 "query_type": format!("{:?}", query.query_type),
677 "query": query.query,
678 "parameters": query.parameters,
679 "limit": query.limit,
680 "offset": query.offset,
681 "context": context,
682 });
683
684 let result = self.call_wasm_json("query_datasource", input)?;
685 serde_json::from_value(result)
686 .map_err(|e| PluginError::execution(format!("Failed to parse DataResult: {}", e)))
687 }
688
689 async fn health_check(&self) -> Result<bool, PluginError> {
690 Ok(true)
691 }
692
693 async fn cleanup(&mut self) -> Result<(), PluginError> {
694 Ok(())
695 }
696}
697
698pub struct AssemblyScriptAdapter {
704 plugin_id: PluginId,
705 engine: Arc<Engine>,
706 module: Module,
707 runtime: Mutex<Option<WasmRuntime>>,
708}
709
710impl AssemblyScriptAdapter {
711 pub fn new(plugin_id: PluginId, wasm_bytes: Vec<u8>) -> Result<Self, PluginError> {
720 let engine = Arc::new(Engine::default());
721 let module = Module::from_binary(&engine, &wasm_bytes).map_err(|e| {
722 PluginError::execution(format!("Failed to load AssemblyScript WASM module: {}", e))
723 })?;
724
725 Ok(Self {
726 plugin_id,
727 engine,
728 module,
729 runtime: Mutex::new(None),
730 })
731 }
732
733 fn call_wasm_json(
736 &self,
737 function_name: &str,
738 input_data: serde_json::Value,
739 ) -> Result<serde_json::Value, PluginError> {
740 let mut runtime_guard =
741 self.runtime.lock().unwrap_or_else(|poisoned| poisoned.into_inner());
742 let runtime = runtime_guard.as_mut().ok_or_else(|| {
743 PluginError::execution("Runtime not initialized. Call initialize() first.".to_string())
744 })?;
745
746 let input_json = serde_json::to_string(&input_data)
747 .map_err(|e| PluginError::execution(format!("Failed to serialize input: {}", e)))?;
748
749 let input_bytes = input_json.as_bytes();
750 let input_len = input_bytes.len() as i32;
751
752 let memory =
754 runtime.instance.get_memory(&mut runtime.store, "memory").ok_or_else(|| {
755 PluginError::execution(
756 "AssemblyScript WASM module must export 'memory'".to_string(),
757 )
758 })?;
759
760 let new_func = runtime
764 .instance
765 .get_typed_func::<(i32, i32), i32>(&mut runtime.store, "__new")
766 .map_err(|e| {
767 PluginError::execution(format!(
768 "Failed to get __new function (AssemblyScript specific): {}",
769 e
770 ))
771 })?;
772
773 let input_ptr = new_func
775 .call(&mut runtime.store, (input_len, 1))
776 .map_err(|e| PluginError::execution(format!("Failed to allocate memory: {}", e)))?;
777
778 if let Ok(pin_func) =
780 runtime.instance.get_typed_func::<i32, i32>(&mut runtime.store, "__pin")
781 {
782 let _ = pin_func.call(&mut runtime.store, input_ptr);
783 }
784
785 memory
787 .write(&mut runtime.store, input_ptr as usize, input_bytes)
788 .map_err(|e| PluginError::execution(format!("Failed to write input: {}", e)))?;
789
790 let plugin_func = runtime
792 .instance
793 .get_typed_func::<(i32, i32), (i32, i32)>(&mut runtime.store, function_name)
794 .map_err(|e| {
795 PluginError::execution(format!("Function '{}' not found: {}", function_name, e))
796 })?;
797
798 let (output_ptr, output_len) =
799 plugin_func.call(&mut runtime.store, (input_ptr, input_len)).map_err(|e| {
800 PluginError::execution(format!(
801 "Failed to call function '{}': {}",
802 function_name, e
803 ))
804 })?;
805
806 let mut output_bytes = vec![0u8; output_len as usize];
808 memory
809 .read(&runtime.store, output_ptr as usize, &mut output_bytes)
810 .map_err(|e| PluginError::execution(format!("Failed to read output: {}", e)))?;
811
812 if let Ok(unpin_func) =
814 runtime.instance.get_typed_func::<i32, ()>(&mut runtime.store, "__unpin")
815 {
816 let _ = unpin_func.call(&mut runtime.store, input_ptr);
817 let _ = unpin_func.call(&mut runtime.store, output_ptr);
818 }
819
820 let output_str = String::from_utf8(output_bytes)
822 .map_err(|e| PluginError::execution(format!("Failed to decode output: {}", e)))?;
823
824 serde_json::from_str(&output_str)
825 .map_err(|e| PluginError::execution(format!("Failed to parse output JSON: {}", e)))
826 }
827}
828
829#[async_trait]
830impl RuntimeAdapter for AssemblyScriptAdapter {
831 fn runtime_type(&self) -> RuntimeType {
832 RuntimeType::AssemblyScript
833 }
834
835 async fn initialize(&mut self) -> Result<(), PluginError> {
836 tracing::info!("Initializing AssemblyScript plugin: {}", self.plugin_id);
837
838 let wasi_ctx = WasiCtxBuilder::new().inherit_stderr().inherit_stdout().build();
840
841 let mut store = Store::new(&self.engine, wasi_ctx);
843
844 let linker = Linker::new(&self.engine);
846
847 let instance = linker.instantiate(&mut store, &self.module).map_err(|e| {
852 PluginError::execution(format!("Failed to instantiate AssemblyScript module: {}", e))
853 })?;
854
855 let mut runtime_guard =
857 self.runtime.lock().unwrap_or_else(|poisoned| poisoned.into_inner());
858 *runtime_guard = Some(WasmRuntime { store, instance });
859
860 tracing::info!("Successfully initialized AssemblyScript plugin: {}", self.plugin_id);
861 Ok(())
862 }
863
864 async fn call_auth(
865 &self,
866 context: &PluginContext,
867 request: &AuthRequest,
868 ) -> Result<AuthResponse, PluginError> {
869 let input = serde_json::json!({
870 "context": context,
871 "method": request.method.to_string(),
872 "uri": request.uri.to_string(),
873 "query_params": request.query_params,
874 "client_ip": request.client_ip,
875 "user_agent": request.user_agent,
876 });
877
878 let result = self.call_wasm_json("authenticate", input)?;
879 serde_json::from_value(result)
880 .map_err(|e| PluginError::execution(format!("Failed to parse AuthResponse: {}", e)))
881 }
882
883 async fn call_template_function(
884 &self,
885 function_name: &str,
886 args: &[serde_json::Value],
887 context: &ResolutionContext,
888 ) -> Result<serde_json::Value, PluginError> {
889 let input = serde_json::json!({
890 "function_name": function_name,
891 "args": args,
892 "context": context,
893 });
894
895 self.call_wasm_json("template_function", input)
896 }
897
898 async fn call_response_generator(
899 &self,
900 context: &PluginContext,
901 request: &ResponseRequest,
902 ) -> Result<ResponseData, PluginError> {
903 let input = serde_json::json!({
905 "context": context,
906 "method": request.method.to_string(),
907 "uri": request.uri,
908 "path": request.path,
909 "query_params": request.query_params,
910 "path_params": request.path_params,
911 "client_ip": request.client_ip,
912 "user_agent": request.user_agent,
913 });
914
915 let result = self.call_wasm_json("generate_response", input)?;
916 serde_json::from_value(result)
917 .map_err(|e| PluginError::execution(format!("Failed to parse ResponseData: {}", e)))
918 }
919
920 async fn call_datasource_query(
921 &self,
922 query: &DataQuery,
923 context: &PluginContext,
924 ) -> Result<DataResult, PluginError> {
925 let input = serde_json::json!({
927 "query_type": format!("{:?}", query.query_type),
928 "query": query.query,
929 "parameters": query.parameters,
930 "limit": query.limit,
931 "offset": query.offset,
932 "context": context,
933 });
934
935 let result = self.call_wasm_json("query_datasource", input)?;
936 serde_json::from_value(result)
937 .map_err(|e| PluginError::execution(format!("Failed to parse DataResult: {}", e)))
938 }
939
940 async fn health_check(&self) -> Result<bool, PluginError> {
941 Ok(true)
942 }
943
944 async fn cleanup(&mut self) -> Result<(), PluginError> {
945 Ok(())
946 }
947}
948
949pub struct RemoteAdapter {
955 plugin_id: PluginId,
956 config: RemoteRuntimeConfig,
957 client: reqwest::Client,
958}
959
960impl RemoteAdapter {
961 pub fn new(plugin_id: PluginId, config: RemoteRuntimeConfig) -> Result<Self, PluginError> {
970 let client = reqwest::Client::builder()
971 .timeout(std::time::Duration::from_millis(config.timeout_ms))
972 .build()
973 .map_err(|e| PluginError::execution(format!("Failed to create HTTP client: {}", e)))?;
974
975 Ok(Self {
976 plugin_id,
977 config,
978 client,
979 })
980 }
981
982 async fn call_remote_plugin(
983 &self,
984 endpoint: &str,
985 body: serde_json::Value,
986 ) -> Result<serde_json::Value, PluginError> {
987 let url = format!("{}{}", self.config.endpoint, endpoint);
988
989 let mut request = self.client.post(&url).json(&body);
990
991 if let Some(auth) = &self.config.auth {
993 request = match auth.auth_type.as_str() {
994 "bearer" => request.bearer_auth(&auth.value),
995 "api_key" => request.header("X-API-Key", &auth.value),
996 _ => request,
997 };
998 }
999
1000 let response = request
1001 .send()
1002 .await
1003 .map_err(|e| PluginError::execution(format!("Remote plugin call failed: {}", e)))?;
1004
1005 if !response.status().is_success() {
1006 return Err(PluginError::execution(format!(
1007 "Remote plugin returned error status: {}",
1008 response.status()
1009 )));
1010 }
1011
1012 let result: serde_json::Value = response
1013 .json()
1014 .await
1015 .map_err(|e| PluginError::execution(format!("Failed to parse response: {}", e)))?;
1016
1017 Ok(result)
1018 }
1019}
1020
1021#[async_trait]
1022impl RuntimeAdapter for RemoteAdapter {
1023 fn runtime_type(&self) -> RuntimeType {
1024 RuntimeType::Remote(self.config.clone())
1025 }
1026
1027 async fn initialize(&mut self) -> Result<(), PluginError> {
1028 tracing::info!("Initializing remote plugin: {}", self.plugin_id);
1029
1030 self.health_check().await?;
1032
1033 Ok(())
1034 }
1035
1036 async fn call_auth(
1037 &self,
1038 context: &PluginContext,
1039 request: &AuthRequest,
1040 ) -> Result<AuthResponse, PluginError> {
1041 let body = serde_json::json!({
1042 "context": context,
1043 "method": request.method.to_string(),
1044 "uri": request.uri.to_string(),
1045 "query_params": request.query_params,
1046 "client_ip": request.client_ip,
1047 "user_agent": request.user_agent,
1048 });
1049
1050 let result = self.call_remote_plugin("/plugin/authenticate", body).await?;
1051
1052 serde_json::from_value(result)
1054 .map_err(|e| PluginError::execution(format!("Failed to parse AuthResponse: {}", e)))
1055 }
1056
1057 async fn call_template_function(
1058 &self,
1059 function_name: &str,
1060 args: &[serde_json::Value],
1061 context: &ResolutionContext,
1062 ) -> Result<serde_json::Value, PluginError> {
1063 let body = serde_json::json!({
1064 "function_name": function_name,
1065 "args": args,
1066 "context": context,
1067 });
1068
1069 self.call_remote_plugin("/plugin/template/execute", body).await
1070 }
1071
1072 async fn call_response_generator(
1073 &self,
1074 context: &PluginContext,
1075 request: &ResponseRequest,
1076 ) -> Result<ResponseData, PluginError> {
1077 let body = serde_json::json!({
1079 "context": context,
1080 "method": request.method.to_string(),
1081 "uri": request.uri,
1082 "path": request.path,
1083 "query_params": request.query_params,
1084 "path_params": request.path_params,
1085 "client_ip": request.client_ip,
1086 "user_agent": request.user_agent,
1087 });
1088
1089 let result = self.call_remote_plugin("/plugin/response/generate", body).await?;
1090
1091 serde_json::from_value(result)
1092 .map_err(|e| PluginError::execution(format!("Failed to parse ResponseData: {}", e)))
1093 }
1094
1095 async fn call_datasource_query(
1096 &self,
1097 query: &DataQuery,
1098 context: &PluginContext,
1099 ) -> Result<DataResult, PluginError> {
1100 let body = serde_json::json!({
1101 "query_type": format!("{:?}", query.query_type),
1102 "query": query.query,
1103 "parameters": query.parameters,
1104 "limit": query.limit,
1105 "offset": query.offset,
1106 "context": context,
1107 });
1108
1109 let result = self.call_remote_plugin("/plugin/datasource/query", body).await?;
1110
1111 serde_json::from_value(result)
1112 .map_err(|e| PluginError::execution(format!("Failed to parse DataResult: {}", e)))
1113 }
1114
1115 async fn health_check(&self) -> Result<bool, PluginError> {
1116 let url = format!("{}/health", self.config.endpoint);
1118
1119 match self.client.get(&url).send().await {
1120 Ok(response) => Ok(response.status().is_success()),
1121 Err(_) => Ok(false),
1122 }
1123 }
1124
1125 async fn cleanup(&mut self) -> Result<(), PluginError> {
1126 tracing::info!("Cleaning up remote plugin: {}", self.plugin_id);
1127 Ok(())
1128 }
1129
1130 fn get_metrics(&self) -> HashMap<String, serde_json::Value> {
1131 let mut metrics = HashMap::new();
1132 metrics.insert("plugin_id".to_string(), serde_json::json!(self.plugin_id.as_str()));
1133 metrics.insert("endpoint".to_string(), serde_json::json!(self.config.endpoint));
1134 metrics.insert(
1135 "protocol".to_string(),
1136 serde_json::json!(format!("{:?}", self.config.protocol)),
1137 );
1138 metrics
1139 }
1140}
1141
1142#[cfg(test)]
1143mod tests {
1144 use super::*;
1145
1146 #[test]
1149 fn test_runtime_type_equality() {
1150 assert_eq!(RuntimeType::Rust, RuntimeType::Rust);
1151 assert_eq!(RuntimeType::TinyGo, RuntimeType::TinyGo);
1152 assert_eq!(RuntimeType::AssemblyScript, RuntimeType::AssemblyScript);
1153
1154 assert_ne!(RuntimeType::Rust, RuntimeType::TinyGo);
1155 }
1156
1157 #[test]
1158 fn test_runtime_type_clone() {
1159 let rt = RuntimeType::Rust;
1160 let cloned = rt.clone();
1161 assert_eq!(rt, cloned);
1162 }
1163
1164 #[test]
1165 fn test_runtime_type_detection() {
1166 let empty_bytes = vec![];
1168 let runtime = detect_runtime_type(&empty_bytes).unwrap();
1169 assert_eq!(runtime, RuntimeType::Rust);
1170 }
1171
1172 #[test]
1173 fn test_runtime_type_detection_tinygo() {
1174 let tinygo_bytes = b"wasm module with tinygo runtime".to_vec();
1176 let runtime = detect_runtime_type(&tinygo_bytes).unwrap();
1177 assert_eq!(runtime, RuntimeType::TinyGo);
1178 }
1179
1180 #[test]
1181 fn test_runtime_type_detection_assemblyscript() {
1182 let as_bytes = b"wasm module with assemblyscript runtime".to_vec();
1184 let runtime = detect_runtime_type(&as_bytes).unwrap();
1185 assert_eq!(runtime, RuntimeType::AssemblyScript);
1186 }
1187
1188 #[test]
1189 fn test_has_tinygo_signature() {
1190 assert!(has_tinygo_signature(b"this contains tinygo"));
1191 assert!(!has_tinygo_signature(b"this does not contain it"));
1192 }
1193
1194 #[test]
1195 fn test_has_assemblyscript_signature() {
1196 assert!(has_assemblyscript_signature(b"this contains assemblyscript"));
1197 assert!(!has_assemblyscript_signature(b"this does not contain it"));
1198 }
1199
1200 #[test]
1203 fn test_remote_runtime_config() {
1204 let config = RemoteRuntimeConfig {
1205 protocol: RemoteProtocol::Http,
1206 endpoint: "http://localhost:8080".to_string(),
1207 timeout_ms: 5000,
1208 max_retries: 3,
1209 auth: Some(RemoteAuthConfig {
1210 auth_type: "bearer".to_string(),
1211 value: "secret-token".to_string(),
1212 }),
1213 };
1214
1215 assert_eq!(config.endpoint, "http://localhost:8080");
1216 assert_eq!(config.timeout_ms, 5000);
1217 assert_eq!(config.max_retries, 3);
1218 assert!(config.auth.is_some());
1219 }
1220
1221 #[test]
1222 fn test_remote_runtime_config_without_auth() {
1223 let config = RemoteRuntimeConfig {
1224 protocol: RemoteProtocol::Grpc,
1225 endpoint: "grpc://localhost:9090".to_string(),
1226 timeout_ms: 10000,
1227 max_retries: 5,
1228 auth: None,
1229 };
1230
1231 assert_eq!(config.protocol, RemoteProtocol::Grpc);
1232 assert!(config.auth.is_none());
1233 }
1234
1235 #[test]
1236 fn test_remote_runtime_config_clone() {
1237 let config = RemoteRuntimeConfig {
1238 protocol: RemoteProtocol::Http,
1239 endpoint: "http://localhost:8080".to_string(),
1240 timeout_ms: 5000,
1241 max_retries: 3,
1242 auth: None,
1243 };
1244
1245 let cloned = config.clone();
1246 assert_eq!(config.endpoint, cloned.endpoint);
1247 assert_eq!(config.timeout_ms, cloned.timeout_ms);
1248 }
1249
1250 #[test]
1251 fn test_remote_runtime_config_equality() {
1252 let config1 = RemoteRuntimeConfig {
1253 protocol: RemoteProtocol::Http,
1254 endpoint: "http://localhost:8080".to_string(),
1255 timeout_ms: 5000,
1256 max_retries: 3,
1257 auth: None,
1258 };
1259
1260 let config2 = RemoteRuntimeConfig {
1261 protocol: RemoteProtocol::Http,
1262 endpoint: "http://localhost:8080".to_string(),
1263 timeout_ms: 5000,
1264 max_retries: 3,
1265 auth: None,
1266 };
1267
1268 assert_eq!(config1, config2);
1269 }
1270
1271 #[test]
1274 fn test_remote_protocol_equality() {
1275 assert_eq!(RemoteProtocol::Http, RemoteProtocol::Http);
1276 assert_eq!(RemoteProtocol::Grpc, RemoteProtocol::Grpc);
1277 assert_ne!(RemoteProtocol::Http, RemoteProtocol::Grpc);
1278 }
1279
1280 #[test]
1281 fn test_remote_protocol_clone() {
1282 let proto = RemoteProtocol::Http;
1283 let cloned = proto.clone();
1284 assert_eq!(proto, cloned);
1285 }
1286
1287 #[test]
1290 fn test_remote_auth_config() {
1291 let auth = RemoteAuthConfig {
1292 auth_type: "bearer".to_string(),
1293 value: "secret-token".to_string(),
1294 };
1295
1296 assert_eq!(auth.auth_type, "bearer");
1297 assert_eq!(auth.value, "secret-token");
1298 }
1299
1300 #[test]
1301 fn test_remote_auth_config_api_key() {
1302 let auth = RemoteAuthConfig {
1303 auth_type: "api_key".to_string(),
1304 value: "my-api-key".to_string(),
1305 };
1306
1307 assert_eq!(auth.auth_type, "api_key");
1308 }
1309
1310 #[test]
1311 fn test_remote_auth_config_clone() {
1312 let auth = RemoteAuthConfig {
1313 auth_type: "bearer".to_string(),
1314 value: "token".to_string(),
1315 };
1316
1317 let cloned = auth.clone();
1318 assert_eq!(auth.auth_type, cloned.auth_type);
1319 assert_eq!(auth.value, cloned.value);
1320 }
1321
1322 #[test]
1323 fn test_remote_auth_config_equality() {
1324 let auth1 = RemoteAuthConfig {
1325 auth_type: "bearer".to_string(),
1326 value: "token".to_string(),
1327 };
1328
1329 let auth2 = RemoteAuthConfig {
1330 auth_type: "bearer".to_string(),
1331 value: "token".to_string(),
1332 };
1333
1334 assert_eq!(auth1, auth2);
1335 }
1336
1337 #[test]
1340 fn test_factory_create_rust_adapter() {
1341 let plugin_id = PluginId::new("test-plugin");
1342 let wasm_bytes = vec![0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00];
1344
1345 let result = RuntimeAdapterFactory::create(RuntimeType::Rust, plugin_id, wasm_bytes);
1346
1347 assert!(result.is_ok());
1348 let adapter = result.unwrap();
1349 assert_eq!(adapter.runtime_type(), RuntimeType::Rust);
1350 }
1351
1352 #[test]
1353 fn test_factory_create_tinygo_adapter() {
1354 let plugin_id = PluginId::new("test-plugin");
1355 let wasm_bytes = vec![0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00];
1356
1357 let result = RuntimeAdapterFactory::create(RuntimeType::TinyGo, plugin_id, wasm_bytes);
1358
1359 assert!(result.is_ok());
1360 let adapter = result.unwrap();
1361 assert_eq!(adapter.runtime_type(), RuntimeType::TinyGo);
1362 }
1363
1364 #[test]
1365 fn test_factory_create_assemblyscript_adapter() {
1366 let plugin_id = PluginId::new("test-plugin");
1367 let wasm_bytes = vec![0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00];
1368
1369 let result =
1370 RuntimeAdapterFactory::create(RuntimeType::AssemblyScript, plugin_id, wasm_bytes);
1371
1372 assert!(result.is_ok());
1373 let adapter = result.unwrap();
1374 assert_eq!(adapter.runtime_type(), RuntimeType::AssemblyScript);
1375 }
1376
1377 #[test]
1378 fn test_factory_create_remote_adapter() {
1379 let plugin_id = PluginId::new("test-plugin");
1380 let config = RemoteRuntimeConfig {
1381 protocol: RemoteProtocol::Http,
1382 endpoint: "http://localhost:8080".to_string(),
1383 timeout_ms: 5000,
1384 max_retries: 3,
1385 auth: None,
1386 };
1387
1388 let result =
1389 RuntimeAdapterFactory::create(RuntimeType::Remote(config.clone()), plugin_id, vec![]);
1390
1391 assert!(result.is_ok());
1392 let adapter = result.unwrap();
1393 assert_eq!(adapter.runtime_type(), RuntimeType::Remote(config));
1394 }
1395
1396 #[test]
1397 fn test_factory_create_with_invalid_wasm() {
1398 let plugin_id = PluginId::new("test-plugin");
1399 let wasm_bytes = vec![0x00, 0x00, 0x00, 0x00];
1401
1402 let result = RuntimeAdapterFactory::create(RuntimeType::Rust, plugin_id, wasm_bytes);
1403
1404 assert!(result.is_err());
1405 }
1406
1407 #[test]
1410 fn test_remote_adapter_creation() {
1411 let plugin_id = PluginId::new("test-plugin");
1412 let config = RemoteRuntimeConfig {
1413 protocol: RemoteProtocol::Http,
1414 endpoint: "http://localhost:8080".to_string(),
1415 timeout_ms: 5000,
1416 max_retries: 3,
1417 auth: None,
1418 };
1419
1420 let result = RemoteAdapter::new(plugin_id, config.clone());
1421 assert!(result.is_ok());
1422
1423 let adapter = result.unwrap();
1424 assert_eq!(adapter.runtime_type(), RuntimeType::Remote(config));
1425 }
1426
1427 #[test]
1428 fn test_remote_adapter_get_metrics() {
1429 let plugin_id = PluginId::new("test-plugin");
1430 let config = RemoteRuntimeConfig {
1431 protocol: RemoteProtocol::Http,
1432 endpoint: "http://localhost:8080".to_string(),
1433 timeout_ms: 5000,
1434 max_retries: 3,
1435 auth: None,
1436 };
1437
1438 let adapter = RemoteAdapter::new(plugin_id.clone(), config).unwrap();
1439 let metrics = adapter.get_metrics();
1440
1441 assert!(metrics.contains_key("plugin_id"));
1442 assert!(metrics.contains_key("endpoint"));
1443 assert!(metrics.contains_key("protocol"));
1444 assert_eq!(metrics.get("plugin_id").unwrap(), &serde_json::json!(plugin_id.as_str()));
1445 }
1446
1447 #[test]
1450 fn test_runtime_type_with_mixed_signatures() {
1451 let mixed_bytes = b"tinygo and assemblyscript".to_vec();
1453 let runtime = detect_runtime_type(&mixed_bytes).unwrap();
1454 assert_eq!(runtime, RuntimeType::TinyGo);
1455 }
1456
1457 #[test]
1458 fn test_empty_wasm_bytes() {
1459 let plugin_id = PluginId::new("test");
1460 let result = RustAdapter::new(plugin_id, vec![]);
1461 assert!(result.is_err());
1462 }
1463
1464 #[test]
1465 fn test_runtime_type_debug() {
1466 let rt = RuntimeType::Rust;
1467 let debug_str = format!("{:?}", rt);
1468 assert!(debug_str.contains("Rust"));
1469 }
1470
1471 #[test]
1472 fn test_remote_protocol_debug() {
1473 let proto = RemoteProtocol::Http;
1474 let debug_str = format!("{:?}", proto);
1475 assert!(debug_str.contains("Http"));
1476 }
1477}