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 wasmtime::{Engine, Instance, Linker, Module, Store};
20use wasmtime_wasi::{WasiCtx, WasiCtxBuilder};
21
22#[derive(Debug, Clone, PartialEq, Eq)]
24pub enum RuntimeType {
25 Rust,
27 TinyGo,
29 AssemblyScript,
31 Remote(RemoteRuntimeConfig),
33}
34
35#[derive(Debug, Clone, PartialEq, Eq)]
37pub struct RemoteRuntimeConfig {
38 pub protocol: RemoteProtocol,
40 pub endpoint: String,
42 pub timeout_ms: u64,
44 pub max_retries: u32,
46 pub auth: Option<RemoteAuthConfig>,
48}
49
50#[derive(Debug, Clone, PartialEq, Eq)]
52pub enum RemoteProtocol {
53 Http,
55 Grpc,
57}
58
59#[derive(Debug, Clone, PartialEq, Eq)]
61pub struct RemoteAuthConfig {
62 pub auth_type: String,
64 pub value: String,
66}
67
68#[async_trait]
72pub trait RuntimeAdapter: Send + Sync {
73 fn runtime_type(&self) -> RuntimeType;
75
76 async fn initialize(&mut self) -> Result<(), PluginError>;
78
79 async fn call_auth(
81 &self,
82 context: &PluginContext,
83 request: &AuthRequest,
84 ) -> Result<AuthResponse, PluginError>;
85
86 async fn call_template_function(
88 &self,
89 function_name: &str,
90 args: &[serde_json::Value],
91 context: &ResolutionContext,
92 ) -> Result<serde_json::Value, PluginError>;
93
94 async fn call_response_generator(
96 &self,
97 context: &PluginContext,
98 request: &ResponseRequest,
99 ) -> Result<ResponseData, PluginError>;
100
101 async fn call_datasource_query(
103 &self,
104 query: &DataQuery,
105 context: &PluginContext,
106 ) -> Result<DataResult, PluginError>;
107
108 async fn health_check(&self) -> Result<bool, PluginError>;
110
111 async fn cleanup(&mut self) -> Result<(), PluginError>;
113
114 fn get_metrics(&self) -> HashMap<String, serde_json::Value> {
116 HashMap::new()
117 }
118}
119
120pub fn detect_runtime_type(wasm_bytes: &[u8]) -> Result<RuntimeType, PluginError> {
122 if has_tinygo_signature(wasm_bytes) {
127 return Ok(RuntimeType::TinyGo);
128 }
129
130 if has_assemblyscript_signature(wasm_bytes) {
132 return Ok(RuntimeType::AssemblyScript);
133 }
134
135 Ok(RuntimeType::Rust)
137}
138
139fn has_tinygo_signature(wasm_bytes: &[u8]) -> bool {
141 String::from_utf8_lossy(wasm_bytes).contains("tinygo")
147}
148
149fn has_assemblyscript_signature(wasm_bytes: &[u8]) -> bool {
151 String::from_utf8_lossy(wasm_bytes).contains("assemblyscript")
156}
157
158pub struct RuntimeAdapterFactory;
160
161impl RuntimeAdapterFactory {
162 pub fn create(
164 runtime_type: RuntimeType,
165 plugin_id: PluginId,
166 wasm_bytes: Vec<u8>,
167 ) -> Result<Box<dyn RuntimeAdapter>, PluginError> {
168 match runtime_type {
169 RuntimeType::Rust => Ok(Box::new(RustAdapter::new(plugin_id, wasm_bytes)?)),
170 RuntimeType::TinyGo => Ok(Box::new(TinyGoAdapter::new(plugin_id, wasm_bytes)?)),
171 RuntimeType::AssemblyScript => {
172 Ok(Box::new(AssemblyScriptAdapter::new(plugin_id, wasm_bytes)?))
173 }
174 RuntimeType::Remote(config) => Ok(Box::new(RemoteAdapter::new(plugin_id, config)?)),
175 }
176 }
177}
178
179pub struct RustAdapter {
185 plugin_id: PluginId,
186 engine: Arc<Engine>,
187 module: Module,
188 runtime: Mutex<Option<WasmRuntime>>,
190}
191
192struct WasmRuntime {
193 store: Store<WasiCtx>,
194 instance: Instance,
195}
196
197impl RustAdapter {
198 pub fn new(plugin_id: PluginId, wasm_bytes: Vec<u8>) -> Result<Self, PluginError> {
207 let engine = Arc::new(Engine::default());
208 let module = Module::from_binary(&engine, &wasm_bytes)
209 .map_err(|e| PluginError::execution(format!("Failed to load WASM module: {}", e)))?;
210
211 Ok(Self {
212 plugin_id,
213 engine,
214 module,
215 runtime: Mutex::new(None),
216 })
217 }
218
219 fn call_wasm_json(
221 &self,
222 function_name: &str,
223 input_data: serde_json::Value,
224 ) -> Result<serde_json::Value, PluginError> {
225 let mut runtime_guard =
226 self.runtime.lock().unwrap_or_else(|poisoned| poisoned.into_inner());
227 let runtime = runtime_guard.as_mut().ok_or_else(|| {
228 PluginError::execution("Runtime not initialized. Call initialize() first.".to_string())
229 })?;
230
231 let input_json = serde_json::to_string(&input_data)
232 .map_err(|e| PluginError::execution(format!("Failed to serialize input: {}", e)))?;
233
234 let input_bytes = input_json.as_bytes();
235 let input_len = input_bytes.len() as i32;
236
237 let memory =
239 runtime.instance.get_memory(&mut runtime.store, "memory").ok_or_else(|| {
240 PluginError::execution("WASM module must export 'memory'".to_string())
241 })?;
242
243 let alloc_func = runtime
244 .instance
245 .get_typed_func::<i32, i32>(&mut runtime.store, "alloc")
246 .map_err(|e| {
247 PluginError::execution(format!("Failed to get alloc function: {}", e))
248 })?;
249
250 let input_ptr = alloc_func
252 .call(&mut runtime.store, input_len)
253 .map_err(|e| PluginError::execution(format!("Failed to allocate memory: {}", e)))?;
254
255 memory
257 .write(&mut runtime.store, input_ptr as usize, input_bytes)
258 .map_err(|e| PluginError::execution(format!("Failed to write input: {}", e)))?;
259
260 let plugin_func = runtime
262 .instance
263 .get_typed_func::<(i32, i32), (i32, i32)>(&mut runtime.store, function_name)
264 .map_err(|e| {
265 PluginError::execution(format!("Function '{}' not found: {}", function_name, e))
266 })?;
267
268 let (output_ptr, output_len) =
269 plugin_func.call(&mut runtime.store, (input_ptr, input_len)).map_err(|e| {
270 PluginError::execution(format!(
271 "Failed to call function '{}': {}",
272 function_name, e
273 ))
274 })?;
275
276 let mut output_bytes = vec![0u8; output_len as usize];
278 memory
279 .read(&runtime.store, output_ptr as usize, &mut output_bytes)
280 .map_err(|e| PluginError::execution(format!("Failed to read output: {}", e)))?;
281
282 if let Ok(dealloc_func) =
284 runtime.instance.get_typed_func::<(i32, i32), ()>(&mut runtime.store, "dealloc")
285 {
286 let _ = dealloc_func.call(&mut runtime.store, (input_ptr, input_len));
287 let _ = dealloc_func.call(&mut runtime.store, (output_ptr, output_len));
288 }
289
290 let output_str = String::from_utf8(output_bytes)
292 .map_err(|e| PluginError::execution(format!("Failed to decode output: {}", e)))?;
293
294 serde_json::from_str(&output_str)
295 .map_err(|e| PluginError::execution(format!("Failed to parse output JSON: {}", e)))
296 }
297}
298
299#[async_trait]
300impl RuntimeAdapter for RustAdapter {
301 fn runtime_type(&self) -> RuntimeType {
302 RuntimeType::Rust
303 }
304
305 async fn initialize(&mut self) -> Result<(), PluginError> {
306 tracing::info!("Initializing Rust plugin: {}", self.plugin_id);
308
309 let wasi_ctx = WasiCtxBuilder::new().inherit_stderr().inherit_stdout().build();
311
312 let mut store = Store::new(&self.engine, wasi_ctx);
314
315 let linker = Linker::new(&self.engine);
317
318 let instance = linker
320 .instantiate(&mut store, &self.module)
321 .map_err(|e| PluginError::execution(format!("Failed to instantiate module: {}", e)))?;
322
323 let mut runtime_guard =
325 self.runtime.lock().unwrap_or_else(|poisoned| poisoned.into_inner());
326 *runtime_guard = Some(WasmRuntime { store, instance });
327
328 tracing::info!("Successfully initialized Rust plugin: {}", self.plugin_id);
329 Ok(())
330 }
331
332 async fn call_auth(
333 &self,
334 context: &PluginContext,
335 request: &AuthRequest,
336 ) -> Result<AuthResponse, PluginError> {
337 let input = serde_json::json!({
341 "context": context,
342 "method": request.method.to_string(),
343 "uri": request.uri.to_string(),
344 "query_params": request.query_params,
345 "client_ip": request.client_ip,
346 "user_agent": request.user_agent,
347 });
348
349 let result = self.call_wasm_json("authenticate", input)?;
350 serde_json::from_value(result)
351 .map_err(|e| PluginError::execution(format!("Failed to parse AuthResponse: {}", e)))
352 }
353
354 async fn call_template_function(
355 &self,
356 function_name: &str,
357 args: &[serde_json::Value],
358 context: &ResolutionContext,
359 ) -> Result<serde_json::Value, PluginError> {
360 let input = serde_json::json!({
361 "function_name": function_name,
362 "args": args,
363 "context": context,
364 });
365
366 self.call_wasm_json("template_function", input)
367 }
368
369 async fn call_response_generator(
370 &self,
371 context: &PluginContext,
372 request: &ResponseRequest,
373 ) -> Result<ResponseData, PluginError> {
374 let input = serde_json::json!({
376 "context": context,
377 "method": request.method.to_string(),
378 "uri": request.uri,
379 "path": request.path,
380 "query_params": request.query_params,
381 "path_params": request.path_params,
382 "client_ip": request.client_ip,
383 "user_agent": request.user_agent,
384 });
385
386 let result = self.call_wasm_json("generate_response", input)?;
387 serde_json::from_value(result)
388 .map_err(|e| PluginError::execution(format!("Failed to parse ResponseData: {}", e)))
389 }
390
391 async fn call_datasource_query(
392 &self,
393 query: &DataQuery,
394 context: &PluginContext,
395 ) -> Result<DataResult, PluginError> {
396 let input = serde_json::json!({
398 "query_type": format!("{:?}", query.query_type),
399 "query": query.query,
400 "parameters": query.parameters,
401 "limit": query.limit,
402 "offset": query.offset,
403 "context": context,
404 });
405
406 let result = self.call_wasm_json("query_datasource", input)?;
407 serde_json::from_value(result)
408 .map_err(|e| PluginError::execution(format!("Failed to parse DataResult: {}", e)))
409 }
410
411 async fn health_check(&self) -> Result<bool, PluginError> {
412 Ok(true)
413 }
414
415 async fn cleanup(&mut self) -> Result<(), PluginError> {
416 Ok(())
417 }
418}
419
420pub struct TinyGoAdapter {
426 plugin_id: PluginId,
427 engine: Arc<Engine>,
428 module: Module,
429 runtime: Mutex<Option<WasmRuntime>>,
430}
431
432impl TinyGoAdapter {
433 pub fn new(plugin_id: PluginId, wasm_bytes: Vec<u8>) -> Result<Self, PluginError> {
442 let engine = Arc::new(Engine::default());
443 let module = Module::from_binary(&engine, &wasm_bytes).map_err(|e| {
444 PluginError::execution(format!("Failed to load TinyGo WASM module: {}", e))
445 })?;
446
447 Ok(Self {
448 plugin_id,
449 engine,
450 module,
451 runtime: Mutex::new(None),
452 })
453 }
454
455 fn call_wasm_json(
458 &self,
459 function_name: &str,
460 input_data: serde_json::Value,
461 ) -> Result<serde_json::Value, PluginError> {
462 let mut runtime_guard =
463 self.runtime.lock().unwrap_or_else(|poisoned| poisoned.into_inner());
464 let runtime = runtime_guard.as_mut().ok_or_else(|| {
465 PluginError::execution("Runtime not initialized. Call initialize() first.".to_string())
466 })?;
467
468 let input_json = serde_json::to_string(&input_data)
469 .map_err(|e| PluginError::execution(format!("Failed to serialize input: {}", e)))?;
470
471 let input_bytes = input_json.as_bytes();
472 let input_len = input_bytes.len() as i32;
473
474 let memory =
476 runtime.instance.get_memory(&mut runtime.store, "memory").ok_or_else(|| {
477 PluginError::execution("TinyGo WASM module must export 'memory'".to_string())
478 })?;
479
480 let malloc_func = runtime
482 .instance
483 .get_typed_func::<i32, i32>(&mut runtime.store, "malloc")
484 .map_err(|e| {
485 PluginError::execution(format!(
486 "Failed to get malloc function (TinyGo specific): {}",
487 e
488 ))
489 })?;
490
491 let input_ptr = malloc_func
493 .call(&mut runtime.store, input_len)
494 .map_err(|e| PluginError::execution(format!("Failed to allocate memory: {}", e)))?;
495
496 memory
498 .write(&mut runtime.store, input_ptr as usize, input_bytes)
499 .map_err(|e| PluginError::execution(format!("Failed to write input: {}", e)))?;
500
501 let plugin_func = runtime
503 .instance
504 .get_typed_func::<(i32, i32), (i32, i32)>(&mut runtime.store, function_name)
505 .map_err(|e| {
506 PluginError::execution(format!("Function '{}' not found: {}", function_name, e))
507 })?;
508
509 let (output_ptr, output_len) =
510 plugin_func.call(&mut runtime.store, (input_ptr, input_len)).map_err(|e| {
511 PluginError::execution(format!(
512 "Failed to call function '{}': {}",
513 function_name, e
514 ))
515 })?;
516
517 let mut output_bytes = vec![0u8; output_len as usize];
519 memory
520 .read(&runtime.store, output_ptr as usize, &mut output_bytes)
521 .map_err(|e| PluginError::execution(format!("Failed to read output: {}", e)))?;
522
523 if let Ok(free_func) =
525 runtime.instance.get_typed_func::<i32, ()>(&mut runtime.store, "free")
526 {
527 let _ = free_func.call(&mut runtime.store, input_ptr);
528 let _ = free_func.call(&mut runtime.store, output_ptr);
529 }
530
531 let output_str = String::from_utf8(output_bytes)
533 .map_err(|e| PluginError::execution(format!("Failed to decode output: {}", e)))?;
534
535 serde_json::from_str(&output_str)
536 .map_err(|e| PluginError::execution(format!("Failed to parse output JSON: {}", e)))
537 }
538}
539
540#[async_trait]
541impl RuntimeAdapter for TinyGoAdapter {
542 fn runtime_type(&self) -> RuntimeType {
543 RuntimeType::TinyGo
544 }
545
546 async fn initialize(&mut self) -> Result<(), PluginError> {
547 tracing::info!("Initializing TinyGo plugin: {}", self.plugin_id);
550
551 let wasi_ctx = WasiCtxBuilder::new().inherit_stderr().inherit_stdout().build();
553
554 let mut store = Store::new(&self.engine, wasi_ctx);
556
557 let linker = Linker::new(&self.engine);
559
560 let instance = linker.instantiate(&mut store, &self.module).map_err(|e| {
565 PluginError::execution(format!("Failed to instantiate TinyGo module: {}", e))
566 })?;
567
568 let mut runtime_guard =
570 self.runtime.lock().unwrap_or_else(|poisoned| poisoned.into_inner());
571 *runtime_guard = Some(WasmRuntime { store, instance });
572
573 tracing::info!("Successfully initialized TinyGo plugin: {}", self.plugin_id);
574 Ok(())
575 }
576
577 async fn call_auth(
578 &self,
579 context: &PluginContext,
580 request: &AuthRequest,
581 ) -> Result<AuthResponse, PluginError> {
582 let input = serde_json::json!({
584 "context": context,
585 "method": request.method.to_string(),
586 "uri": request.uri.to_string(),
587 "query_params": request.query_params,
588 "client_ip": request.client_ip,
589 "user_agent": request.user_agent,
590 });
591
592 let result = self.call_wasm_json("authenticate", input)?;
593 serde_json::from_value(result)
594 .map_err(|e| PluginError::execution(format!("Failed to parse AuthResponse: {}", e)))
595 }
596
597 async fn call_template_function(
598 &self,
599 function_name: &str,
600 args: &[serde_json::Value],
601 context: &ResolutionContext,
602 ) -> Result<serde_json::Value, PluginError> {
603 let input = serde_json::json!({
604 "function_name": function_name,
605 "args": args,
606 "context": context,
607 });
608
609 self.call_wasm_json("template_function", input)
610 }
611
612 async fn call_response_generator(
613 &self,
614 context: &PluginContext,
615 request: &ResponseRequest,
616 ) -> Result<ResponseData, PluginError> {
617 let input = serde_json::json!({
619 "context": context,
620 "method": request.method.to_string(),
621 "uri": request.uri,
622 "path": request.path,
623 "query_params": request.query_params,
624 "path_params": request.path_params,
625 "client_ip": request.client_ip,
626 "user_agent": request.user_agent,
627 });
628
629 let result = self.call_wasm_json("generate_response", input)?;
630 serde_json::from_value(result)
631 .map_err(|e| PluginError::execution(format!("Failed to parse ResponseData: {}", e)))
632 }
633
634 async fn call_datasource_query(
635 &self,
636 query: &DataQuery,
637 context: &PluginContext,
638 ) -> Result<DataResult, PluginError> {
639 let input = serde_json::json!({
641 "query_type": format!("{:?}", query.query_type),
642 "query": query.query,
643 "parameters": query.parameters,
644 "limit": query.limit,
645 "offset": query.offset,
646 "context": context,
647 });
648
649 let result = self.call_wasm_json("query_datasource", input)?;
650 serde_json::from_value(result)
651 .map_err(|e| PluginError::execution(format!("Failed to parse DataResult: {}", e)))
652 }
653
654 async fn health_check(&self) -> Result<bool, PluginError> {
655 Ok(true)
656 }
657
658 async fn cleanup(&mut self) -> Result<(), PluginError> {
659 Ok(())
660 }
661}
662
663pub struct AssemblyScriptAdapter {
669 plugin_id: PluginId,
670 engine: Arc<Engine>,
671 module: Module,
672 runtime: Mutex<Option<WasmRuntime>>,
673}
674
675impl AssemblyScriptAdapter {
676 pub fn new(plugin_id: PluginId, wasm_bytes: Vec<u8>) -> Result<Self, PluginError> {
685 let engine = Arc::new(Engine::default());
686 let module = Module::from_binary(&engine, &wasm_bytes).map_err(|e| {
687 PluginError::execution(format!("Failed to load AssemblyScript WASM module: {}", e))
688 })?;
689
690 Ok(Self {
691 plugin_id,
692 engine,
693 module,
694 runtime: Mutex::new(None),
695 })
696 }
697
698 fn call_wasm_json(
701 &self,
702 function_name: &str,
703 input_data: serde_json::Value,
704 ) -> Result<serde_json::Value, PluginError> {
705 let mut runtime_guard =
706 self.runtime.lock().unwrap_or_else(|poisoned| poisoned.into_inner());
707 let runtime = runtime_guard.as_mut().ok_or_else(|| {
708 PluginError::execution("Runtime not initialized. Call initialize() first.".to_string())
709 })?;
710
711 let input_json = serde_json::to_string(&input_data)
712 .map_err(|e| PluginError::execution(format!("Failed to serialize input: {}", e)))?;
713
714 let input_bytes = input_json.as_bytes();
715 let input_len = input_bytes.len() as i32;
716
717 let memory =
719 runtime.instance.get_memory(&mut runtime.store, "memory").ok_or_else(|| {
720 PluginError::execution(
721 "AssemblyScript WASM module must export 'memory'".to_string(),
722 )
723 })?;
724
725 let new_func = runtime
729 .instance
730 .get_typed_func::<(i32, i32), i32>(&mut runtime.store, "__new")
731 .map_err(|e| {
732 PluginError::execution(format!(
733 "Failed to get __new function (AssemblyScript specific): {}",
734 e
735 ))
736 })?;
737
738 let input_ptr = new_func
740 .call(&mut runtime.store, (input_len, 1))
741 .map_err(|e| PluginError::execution(format!("Failed to allocate memory: {}", e)))?;
742
743 if let Ok(pin_func) =
745 runtime.instance.get_typed_func::<i32, i32>(&mut runtime.store, "__pin")
746 {
747 let _ = pin_func.call(&mut runtime.store, input_ptr);
748 }
749
750 memory
752 .write(&mut runtime.store, input_ptr as usize, input_bytes)
753 .map_err(|e| PluginError::execution(format!("Failed to write input: {}", e)))?;
754
755 let plugin_func = runtime
757 .instance
758 .get_typed_func::<(i32, i32), (i32, i32)>(&mut runtime.store, function_name)
759 .map_err(|e| {
760 PluginError::execution(format!("Function '{}' not found: {}", function_name, e))
761 })?;
762
763 let (output_ptr, output_len) =
764 plugin_func.call(&mut runtime.store, (input_ptr, input_len)).map_err(|e| {
765 PluginError::execution(format!(
766 "Failed to call function '{}': {}",
767 function_name, e
768 ))
769 })?;
770
771 let mut output_bytes = vec![0u8; output_len as usize];
773 memory
774 .read(&runtime.store, output_ptr as usize, &mut output_bytes)
775 .map_err(|e| PluginError::execution(format!("Failed to read output: {}", e)))?;
776
777 if let Ok(unpin_func) =
779 runtime.instance.get_typed_func::<i32, ()>(&mut runtime.store, "__unpin")
780 {
781 let _ = unpin_func.call(&mut runtime.store, input_ptr);
782 let _ = unpin_func.call(&mut runtime.store, output_ptr);
783 }
784
785 let output_str = String::from_utf8(output_bytes)
787 .map_err(|e| PluginError::execution(format!("Failed to decode output: {}", e)))?;
788
789 serde_json::from_str(&output_str)
790 .map_err(|e| PluginError::execution(format!("Failed to parse output JSON: {}", e)))
791 }
792}
793
794#[async_trait]
795impl RuntimeAdapter for AssemblyScriptAdapter {
796 fn runtime_type(&self) -> RuntimeType {
797 RuntimeType::AssemblyScript
798 }
799
800 async fn initialize(&mut self) -> Result<(), PluginError> {
801 tracing::info!("Initializing AssemblyScript plugin: {}", self.plugin_id);
802
803 let wasi_ctx = WasiCtxBuilder::new().inherit_stderr().inherit_stdout().build();
805
806 let mut store = Store::new(&self.engine, wasi_ctx);
808
809 let linker = Linker::new(&self.engine);
811
812 let instance = linker.instantiate(&mut store, &self.module).map_err(|e| {
817 PluginError::execution(format!("Failed to instantiate AssemblyScript module: {}", e))
818 })?;
819
820 let mut runtime_guard =
822 self.runtime.lock().unwrap_or_else(|poisoned| poisoned.into_inner());
823 *runtime_guard = Some(WasmRuntime { store, instance });
824
825 tracing::info!("Successfully initialized AssemblyScript plugin: {}", self.plugin_id);
826 Ok(())
827 }
828
829 async fn call_auth(
830 &self,
831 context: &PluginContext,
832 request: &AuthRequest,
833 ) -> Result<AuthResponse, PluginError> {
834 let input = serde_json::json!({
835 "context": context,
836 "method": request.method.to_string(),
837 "uri": request.uri.to_string(),
838 "query_params": request.query_params,
839 "client_ip": request.client_ip,
840 "user_agent": request.user_agent,
841 });
842
843 let result = self.call_wasm_json("authenticate", input)?;
844 serde_json::from_value(result)
845 .map_err(|e| PluginError::execution(format!("Failed to parse AuthResponse: {}", e)))
846 }
847
848 async fn call_template_function(
849 &self,
850 function_name: &str,
851 args: &[serde_json::Value],
852 context: &ResolutionContext,
853 ) -> Result<serde_json::Value, PluginError> {
854 let input = serde_json::json!({
855 "function_name": function_name,
856 "args": args,
857 "context": context,
858 });
859
860 self.call_wasm_json("template_function", input)
861 }
862
863 async fn call_response_generator(
864 &self,
865 context: &PluginContext,
866 request: &ResponseRequest,
867 ) -> Result<ResponseData, PluginError> {
868 let input = serde_json::json!({
870 "context": context,
871 "method": request.method.to_string(),
872 "uri": request.uri,
873 "path": request.path,
874 "query_params": request.query_params,
875 "path_params": request.path_params,
876 "client_ip": request.client_ip,
877 "user_agent": request.user_agent,
878 });
879
880 let result = self.call_wasm_json("generate_response", input)?;
881 serde_json::from_value(result)
882 .map_err(|e| PluginError::execution(format!("Failed to parse ResponseData: {}", e)))
883 }
884
885 async fn call_datasource_query(
886 &self,
887 query: &DataQuery,
888 context: &PluginContext,
889 ) -> Result<DataResult, PluginError> {
890 let input = serde_json::json!({
892 "query_type": format!("{:?}", query.query_type),
893 "query": query.query,
894 "parameters": query.parameters,
895 "limit": query.limit,
896 "offset": query.offset,
897 "context": context,
898 });
899
900 let result = self.call_wasm_json("query_datasource", input)?;
901 serde_json::from_value(result)
902 .map_err(|e| PluginError::execution(format!("Failed to parse DataResult: {}", e)))
903 }
904
905 async fn health_check(&self) -> Result<bool, PluginError> {
906 Ok(true)
907 }
908
909 async fn cleanup(&mut self) -> Result<(), PluginError> {
910 Ok(())
911 }
912}
913
914pub struct RemoteAdapter {
920 plugin_id: PluginId,
921 config: RemoteRuntimeConfig,
922 client: reqwest::Client,
923}
924
925impl RemoteAdapter {
926 pub fn new(plugin_id: PluginId, config: RemoteRuntimeConfig) -> Result<Self, PluginError> {
935 let client = reqwest::Client::builder()
936 .timeout(std::time::Duration::from_millis(config.timeout_ms))
937 .build()
938 .map_err(|e| PluginError::execution(format!("Failed to create HTTP client: {}", e)))?;
939
940 Ok(Self {
941 plugin_id,
942 config,
943 client,
944 })
945 }
946
947 async fn call_remote_plugin(
948 &self,
949 endpoint: &str,
950 body: serde_json::Value,
951 ) -> Result<serde_json::Value, PluginError> {
952 let url = format!("{}{}", self.config.endpoint, endpoint);
953
954 let mut request = self.client.post(&url).json(&body);
955
956 if let Some(auth) = &self.config.auth {
958 request = match auth.auth_type.as_str() {
959 "bearer" => request.bearer_auth(&auth.value),
960 "api_key" => request.header("X-API-Key", &auth.value),
961 _ => request,
962 };
963 }
964
965 let response = request
966 .send()
967 .await
968 .map_err(|e| PluginError::execution(format!("Remote plugin call failed: {}", e)))?;
969
970 if !response.status().is_success() {
971 return Err(PluginError::execution(format!(
972 "Remote plugin returned error status: {}",
973 response.status()
974 )));
975 }
976
977 let result: serde_json::Value = response
978 .json()
979 .await
980 .map_err(|e| PluginError::execution(format!("Failed to parse response: {}", e)))?;
981
982 Ok(result)
983 }
984}
985
986#[async_trait]
987impl RuntimeAdapter for RemoteAdapter {
988 fn runtime_type(&self) -> RuntimeType {
989 RuntimeType::Remote(self.config.clone())
990 }
991
992 async fn initialize(&mut self) -> Result<(), PluginError> {
993 tracing::info!("Initializing remote plugin: {}", self.plugin_id);
994
995 self.health_check().await?;
997
998 Ok(())
999 }
1000
1001 async fn call_auth(
1002 &self,
1003 context: &PluginContext,
1004 request: &AuthRequest,
1005 ) -> Result<AuthResponse, PluginError> {
1006 let body = serde_json::json!({
1007 "context": context,
1008 "method": request.method.to_string(),
1009 "uri": request.uri.to_string(),
1010 "query_params": request.query_params,
1011 "client_ip": request.client_ip,
1012 "user_agent": request.user_agent,
1013 });
1014
1015 let result = self.call_remote_plugin("/plugin/authenticate", body).await?;
1016
1017 serde_json::from_value(result)
1019 .map_err(|e| PluginError::execution(format!("Failed to parse AuthResponse: {}", e)))
1020 }
1021
1022 async fn call_template_function(
1023 &self,
1024 function_name: &str,
1025 args: &[serde_json::Value],
1026 context: &ResolutionContext,
1027 ) -> Result<serde_json::Value, PluginError> {
1028 let body = serde_json::json!({
1029 "function_name": function_name,
1030 "args": args,
1031 "context": context,
1032 });
1033
1034 self.call_remote_plugin("/plugin/template/execute", body).await
1035 }
1036
1037 async fn call_response_generator(
1038 &self,
1039 context: &PluginContext,
1040 request: &ResponseRequest,
1041 ) -> Result<ResponseData, PluginError> {
1042 let body = serde_json::json!({
1044 "context": context,
1045 "method": request.method.to_string(),
1046 "uri": request.uri,
1047 "path": request.path,
1048 "query_params": request.query_params,
1049 "path_params": request.path_params,
1050 "client_ip": request.client_ip,
1051 "user_agent": request.user_agent,
1052 });
1053
1054 let result = self.call_remote_plugin("/plugin/response/generate", body).await?;
1055
1056 serde_json::from_value(result)
1057 .map_err(|e| PluginError::execution(format!("Failed to parse ResponseData: {}", e)))
1058 }
1059
1060 async fn call_datasource_query(
1061 &self,
1062 query: &DataQuery,
1063 context: &PluginContext,
1064 ) -> Result<DataResult, PluginError> {
1065 let body = serde_json::json!({
1066 "query_type": format!("{:?}", query.query_type),
1067 "query": query.query,
1068 "parameters": query.parameters,
1069 "limit": query.limit,
1070 "offset": query.offset,
1071 "context": context,
1072 });
1073
1074 let result = self.call_remote_plugin("/plugin/datasource/query", body).await?;
1075
1076 serde_json::from_value(result)
1077 .map_err(|e| PluginError::execution(format!("Failed to parse DataResult: {}", e)))
1078 }
1079
1080 async fn health_check(&self) -> Result<bool, PluginError> {
1081 let url = format!("{}/health", self.config.endpoint);
1083
1084 match self.client.get(&url).send().await {
1085 Ok(response) => Ok(response.status().is_success()),
1086 Err(_) => Ok(false),
1087 }
1088 }
1089
1090 async fn cleanup(&mut self) -> Result<(), PluginError> {
1091 tracing::info!("Cleaning up remote plugin: {}", self.plugin_id);
1092 Ok(())
1093 }
1094
1095 fn get_metrics(&self) -> HashMap<String, serde_json::Value> {
1096 let mut metrics = HashMap::new();
1097 metrics.insert("plugin_id".to_string(), serde_json::json!(self.plugin_id.as_str()));
1098 metrics.insert("endpoint".to_string(), serde_json::json!(self.config.endpoint));
1099 metrics.insert(
1100 "protocol".to_string(),
1101 serde_json::json!(format!("{:?}", self.config.protocol)),
1102 );
1103 metrics
1104 }
1105}
1106
1107#[cfg(test)]
1108mod tests {
1109 use super::*;
1110
1111 #[test]
1114 fn test_runtime_type_equality() {
1115 assert_eq!(RuntimeType::Rust, RuntimeType::Rust);
1116 assert_eq!(RuntimeType::TinyGo, RuntimeType::TinyGo);
1117 assert_eq!(RuntimeType::AssemblyScript, RuntimeType::AssemblyScript);
1118
1119 assert_ne!(RuntimeType::Rust, RuntimeType::TinyGo);
1120 }
1121
1122 #[test]
1123 fn test_runtime_type_clone() {
1124 let rt = RuntimeType::Rust;
1125 let cloned = rt.clone();
1126 assert_eq!(rt, cloned);
1127 }
1128
1129 #[test]
1130 fn test_runtime_type_detection() {
1131 let empty_bytes = vec![];
1133 let runtime = detect_runtime_type(&empty_bytes).unwrap();
1134 assert_eq!(runtime, RuntimeType::Rust);
1135 }
1136
1137 #[test]
1138 fn test_runtime_type_detection_tinygo() {
1139 let tinygo_bytes = b"wasm module with tinygo runtime".to_vec();
1141 let runtime = detect_runtime_type(&tinygo_bytes).unwrap();
1142 assert_eq!(runtime, RuntimeType::TinyGo);
1143 }
1144
1145 #[test]
1146 fn test_runtime_type_detection_assemblyscript() {
1147 let as_bytes = b"wasm module with assemblyscript runtime".to_vec();
1149 let runtime = detect_runtime_type(&as_bytes).unwrap();
1150 assert_eq!(runtime, RuntimeType::AssemblyScript);
1151 }
1152
1153 #[test]
1154 fn test_has_tinygo_signature() {
1155 assert!(has_tinygo_signature(b"this contains tinygo"));
1156 assert!(!has_tinygo_signature(b"this does not contain it"));
1157 }
1158
1159 #[test]
1160 fn test_has_assemblyscript_signature() {
1161 assert!(has_assemblyscript_signature(b"this contains assemblyscript"));
1162 assert!(!has_assemblyscript_signature(b"this does not contain it"));
1163 }
1164
1165 #[test]
1168 fn test_remote_runtime_config() {
1169 let config = RemoteRuntimeConfig {
1170 protocol: RemoteProtocol::Http,
1171 endpoint: "http://localhost:8080".to_string(),
1172 timeout_ms: 5000,
1173 max_retries: 3,
1174 auth: Some(RemoteAuthConfig {
1175 auth_type: "bearer".to_string(),
1176 value: "secret-token".to_string(),
1177 }),
1178 };
1179
1180 assert_eq!(config.endpoint, "http://localhost:8080");
1181 assert_eq!(config.timeout_ms, 5000);
1182 assert_eq!(config.max_retries, 3);
1183 assert!(config.auth.is_some());
1184 }
1185
1186 #[test]
1187 fn test_remote_runtime_config_without_auth() {
1188 let config = RemoteRuntimeConfig {
1189 protocol: RemoteProtocol::Grpc,
1190 endpoint: "grpc://localhost:9090".to_string(),
1191 timeout_ms: 10000,
1192 max_retries: 5,
1193 auth: None,
1194 };
1195
1196 assert_eq!(config.protocol, RemoteProtocol::Grpc);
1197 assert!(config.auth.is_none());
1198 }
1199
1200 #[test]
1201 fn test_remote_runtime_config_clone() {
1202 let config = RemoteRuntimeConfig {
1203 protocol: RemoteProtocol::Http,
1204 endpoint: "http://localhost:8080".to_string(),
1205 timeout_ms: 5000,
1206 max_retries: 3,
1207 auth: None,
1208 };
1209
1210 let cloned = config.clone();
1211 assert_eq!(config.endpoint, cloned.endpoint);
1212 assert_eq!(config.timeout_ms, cloned.timeout_ms);
1213 }
1214
1215 #[test]
1216 fn test_remote_runtime_config_equality() {
1217 let config1 = RemoteRuntimeConfig {
1218 protocol: RemoteProtocol::Http,
1219 endpoint: "http://localhost:8080".to_string(),
1220 timeout_ms: 5000,
1221 max_retries: 3,
1222 auth: None,
1223 };
1224
1225 let config2 = RemoteRuntimeConfig {
1226 protocol: RemoteProtocol::Http,
1227 endpoint: "http://localhost:8080".to_string(),
1228 timeout_ms: 5000,
1229 max_retries: 3,
1230 auth: None,
1231 };
1232
1233 assert_eq!(config1, config2);
1234 }
1235
1236 #[test]
1239 fn test_remote_protocol_equality() {
1240 assert_eq!(RemoteProtocol::Http, RemoteProtocol::Http);
1241 assert_eq!(RemoteProtocol::Grpc, RemoteProtocol::Grpc);
1242 assert_ne!(RemoteProtocol::Http, RemoteProtocol::Grpc);
1243 }
1244
1245 #[test]
1246 fn test_remote_protocol_clone() {
1247 let proto = RemoteProtocol::Http;
1248 let cloned = proto.clone();
1249 assert_eq!(proto, cloned);
1250 }
1251
1252 #[test]
1255 fn test_remote_auth_config() {
1256 let auth = RemoteAuthConfig {
1257 auth_type: "bearer".to_string(),
1258 value: "secret-token".to_string(),
1259 };
1260
1261 assert_eq!(auth.auth_type, "bearer");
1262 assert_eq!(auth.value, "secret-token");
1263 }
1264
1265 #[test]
1266 fn test_remote_auth_config_api_key() {
1267 let auth = RemoteAuthConfig {
1268 auth_type: "api_key".to_string(),
1269 value: "my-api-key".to_string(),
1270 };
1271
1272 assert_eq!(auth.auth_type, "api_key");
1273 }
1274
1275 #[test]
1276 fn test_remote_auth_config_clone() {
1277 let auth = RemoteAuthConfig {
1278 auth_type: "bearer".to_string(),
1279 value: "token".to_string(),
1280 };
1281
1282 let cloned = auth.clone();
1283 assert_eq!(auth.auth_type, cloned.auth_type);
1284 assert_eq!(auth.value, cloned.value);
1285 }
1286
1287 #[test]
1288 fn test_remote_auth_config_equality() {
1289 let auth1 = RemoteAuthConfig {
1290 auth_type: "bearer".to_string(),
1291 value: "token".to_string(),
1292 };
1293
1294 let auth2 = RemoteAuthConfig {
1295 auth_type: "bearer".to_string(),
1296 value: "token".to_string(),
1297 };
1298
1299 assert_eq!(auth1, auth2);
1300 }
1301
1302 #[test]
1305 fn test_factory_create_rust_adapter() {
1306 let plugin_id = PluginId::new("test-plugin");
1307 let wasm_bytes = vec![0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00];
1309
1310 let result = RuntimeAdapterFactory::create(RuntimeType::Rust, plugin_id, wasm_bytes);
1311
1312 assert!(result.is_ok());
1313 let adapter = result.unwrap();
1314 assert_eq!(adapter.runtime_type(), RuntimeType::Rust);
1315 }
1316
1317 #[test]
1318 fn test_factory_create_tinygo_adapter() {
1319 let plugin_id = PluginId::new("test-plugin");
1320 let wasm_bytes = vec![0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00];
1321
1322 let result = RuntimeAdapterFactory::create(RuntimeType::TinyGo, plugin_id, wasm_bytes);
1323
1324 assert!(result.is_ok());
1325 let adapter = result.unwrap();
1326 assert_eq!(adapter.runtime_type(), RuntimeType::TinyGo);
1327 }
1328
1329 #[test]
1330 fn test_factory_create_assemblyscript_adapter() {
1331 let plugin_id = PluginId::new("test-plugin");
1332 let wasm_bytes = vec![0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00];
1333
1334 let result =
1335 RuntimeAdapterFactory::create(RuntimeType::AssemblyScript, plugin_id, wasm_bytes);
1336
1337 assert!(result.is_ok());
1338 let adapter = result.unwrap();
1339 assert_eq!(adapter.runtime_type(), RuntimeType::AssemblyScript);
1340 }
1341
1342 #[test]
1343 fn test_factory_create_remote_adapter() {
1344 let plugin_id = PluginId::new("test-plugin");
1345 let config = RemoteRuntimeConfig {
1346 protocol: RemoteProtocol::Http,
1347 endpoint: "http://localhost:8080".to_string(),
1348 timeout_ms: 5000,
1349 max_retries: 3,
1350 auth: None,
1351 };
1352
1353 let result =
1354 RuntimeAdapterFactory::create(RuntimeType::Remote(config.clone()), plugin_id, vec![]);
1355
1356 assert!(result.is_ok());
1357 let adapter = result.unwrap();
1358 assert_eq!(adapter.runtime_type(), RuntimeType::Remote(config));
1359 }
1360
1361 #[test]
1362 fn test_factory_create_with_invalid_wasm() {
1363 let plugin_id = PluginId::new("test-plugin");
1364 let wasm_bytes = vec![0x00, 0x00, 0x00, 0x00];
1366
1367 let result = RuntimeAdapterFactory::create(RuntimeType::Rust, plugin_id, wasm_bytes);
1368
1369 assert!(result.is_err());
1370 }
1371
1372 #[test]
1375 fn test_remote_adapter_creation() {
1376 let plugin_id = PluginId::new("test-plugin");
1377 let config = RemoteRuntimeConfig {
1378 protocol: RemoteProtocol::Http,
1379 endpoint: "http://localhost:8080".to_string(),
1380 timeout_ms: 5000,
1381 max_retries: 3,
1382 auth: None,
1383 };
1384
1385 let result = RemoteAdapter::new(plugin_id, config.clone());
1386 assert!(result.is_ok());
1387
1388 let adapter = result.unwrap();
1389 assert_eq!(adapter.runtime_type(), RuntimeType::Remote(config));
1390 }
1391
1392 #[test]
1393 fn test_remote_adapter_get_metrics() {
1394 let plugin_id = PluginId::new("test-plugin");
1395 let config = RemoteRuntimeConfig {
1396 protocol: RemoteProtocol::Http,
1397 endpoint: "http://localhost:8080".to_string(),
1398 timeout_ms: 5000,
1399 max_retries: 3,
1400 auth: None,
1401 };
1402
1403 let adapter = RemoteAdapter::new(plugin_id.clone(), config).unwrap();
1404 let metrics = adapter.get_metrics();
1405
1406 assert!(metrics.contains_key("plugin_id"));
1407 assert!(metrics.contains_key("endpoint"));
1408 assert!(metrics.contains_key("protocol"));
1409 assert_eq!(metrics.get("plugin_id").unwrap(), &serde_json::json!(plugin_id.as_str()));
1410 }
1411
1412 #[test]
1415 fn test_runtime_type_with_mixed_signatures() {
1416 let mixed_bytes = b"tinygo and assemblyscript".to_vec();
1418 let runtime = detect_runtime_type(&mixed_bytes).unwrap();
1419 assert_eq!(runtime, RuntimeType::TinyGo);
1420 }
1421
1422 #[test]
1423 fn test_empty_wasm_bytes() {
1424 let plugin_id = PluginId::new("test");
1425 let result = RustAdapter::new(plugin_id, vec![]);
1426 assert!(result.is_err());
1427 }
1428
1429 #[test]
1430 fn test_runtime_type_debug() {
1431 let rt = RuntimeType::Rust;
1432 let debug_str = format!("{:?}", rt);
1433 assert!(debug_str.contains("Rust"));
1434 }
1435
1436 #[test]
1437 fn test_remote_protocol_debug() {
1438 let proto = RemoteProtocol::Http;
1439 let debug_str = format!("{:?}", proto);
1440 assert!(debug_str.contains("Http"));
1441 }
1442}