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::p2::{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 = self.runtime.lock().unwrap();
226 let runtime = runtime_guard.as_mut().ok_or_else(|| {
227 PluginError::execution("Runtime not initialized. Call initialize() first.".to_string())
228 })?;
229
230 let input_json = serde_json::to_string(&input_data)
231 .map_err(|e| PluginError::execution(format!("Failed to serialize input: {}", e)))?;
232
233 let input_bytes = input_json.as_bytes();
234 let input_len = input_bytes.len() as i32;
235
236 let memory =
238 runtime.instance.get_memory(&mut runtime.store, "memory").ok_or_else(|| {
239 PluginError::execution("WASM module must export 'memory'".to_string())
240 })?;
241
242 let alloc_func = runtime
243 .instance
244 .get_typed_func::<i32, i32>(&mut runtime.store, "alloc")
245 .map_err(|e| {
246 PluginError::execution(format!("Failed to get alloc function: {}", e))
247 })?;
248
249 let input_ptr = alloc_func
251 .call(&mut runtime.store, input_len)
252 .map_err(|e| PluginError::execution(format!("Failed to allocate memory: {}", e)))?;
253
254 memory
256 .write(&mut runtime.store, input_ptr as usize, input_bytes)
257 .map_err(|e| PluginError::execution(format!("Failed to write input: {}", e)))?;
258
259 let plugin_func = runtime
261 .instance
262 .get_typed_func::<(i32, i32), (i32, i32)>(&mut runtime.store, function_name)
263 .map_err(|e| {
264 PluginError::execution(format!("Function '{}' not found: {}", function_name, e))
265 })?;
266
267 let (output_ptr, output_len) =
268 plugin_func.call(&mut runtime.store, (input_ptr, input_len)).map_err(|e| {
269 PluginError::execution(format!(
270 "Failed to call function '{}': {}",
271 function_name, e
272 ))
273 })?;
274
275 let mut output_bytes = vec![0u8; output_len as usize];
277 memory
278 .read(&runtime.store, output_ptr as usize, &mut output_bytes)
279 .map_err(|e| PluginError::execution(format!("Failed to read output: {}", e)))?;
280
281 if let Ok(dealloc_func) =
283 runtime.instance.get_typed_func::<(i32, i32), ()>(&mut runtime.store, "dealloc")
284 {
285 let _ = dealloc_func.call(&mut runtime.store, (input_ptr, input_len));
286 let _ = dealloc_func.call(&mut runtime.store, (output_ptr, output_len));
287 }
288
289 let output_str = String::from_utf8(output_bytes)
291 .map_err(|e| PluginError::execution(format!("Failed to decode output: {}", e)))?;
292
293 serde_json::from_str(&output_str)
294 .map_err(|e| PluginError::execution(format!("Failed to parse output JSON: {}", e)))
295 }
296}
297
298#[async_trait]
299impl RuntimeAdapter for RustAdapter {
300 fn runtime_type(&self) -> RuntimeType {
301 RuntimeType::Rust
302 }
303
304 async fn initialize(&mut self) -> Result<(), PluginError> {
305 tracing::info!("Initializing Rust plugin: {}", self.plugin_id);
307
308 let wasi_ctx = WasiCtxBuilder::new().inherit_stderr().inherit_stdout().build();
310
311 let mut store = Store::new(&self.engine, wasi_ctx);
313
314 let linker = Linker::new(&self.engine);
316
317 let instance = linker
319 .instantiate(&mut store, &self.module)
320 .map_err(|e| PluginError::execution(format!("Failed to instantiate module: {}", e)))?;
321
322 let mut runtime_guard = self.runtime.lock().unwrap();
324 *runtime_guard = Some(WasmRuntime { store, instance });
325
326 tracing::info!("Successfully initialized Rust plugin: {}", self.plugin_id);
327 Ok(())
328 }
329
330 async fn call_auth(
331 &self,
332 context: &PluginContext,
333 request: &AuthRequest,
334 ) -> Result<AuthResponse, PluginError> {
335 let input = serde_json::json!({
339 "context": context,
340 "method": request.method.to_string(),
341 "uri": request.uri.to_string(),
342 "query_params": request.query_params,
343 "client_ip": request.client_ip,
344 "user_agent": request.user_agent,
345 });
346
347 let result = self.call_wasm_json("authenticate", input)?;
348 serde_json::from_value(result)
349 .map_err(|e| PluginError::execution(format!("Failed to parse AuthResponse: {}", e)))
350 }
351
352 async fn call_template_function(
353 &self,
354 function_name: &str,
355 args: &[serde_json::Value],
356 context: &ResolutionContext,
357 ) -> Result<serde_json::Value, PluginError> {
358 let input = serde_json::json!({
359 "function_name": function_name,
360 "args": args,
361 "context": context,
362 });
363
364 self.call_wasm_json("template_function", input)
365 }
366
367 async fn call_response_generator(
368 &self,
369 context: &PluginContext,
370 request: &ResponseRequest,
371 ) -> Result<ResponseData, PluginError> {
372 let input = serde_json::json!({
374 "context": context,
375 "method": request.method.to_string(),
376 "uri": request.uri,
377 "path": request.path,
378 "query_params": request.query_params,
379 "path_params": request.path_params,
380 "client_ip": request.client_ip,
381 "user_agent": request.user_agent,
382 });
383
384 let result = self.call_wasm_json("generate_response", input)?;
385 serde_json::from_value(result)
386 .map_err(|e| PluginError::execution(format!("Failed to parse ResponseData: {}", e)))
387 }
388
389 async fn call_datasource_query(
390 &self,
391 query: &DataQuery,
392 context: &PluginContext,
393 ) -> Result<DataResult, PluginError> {
394 let input = serde_json::json!({
396 "query_type": format!("{:?}", query.query_type),
397 "query": query.query,
398 "parameters": query.parameters,
399 "limit": query.limit,
400 "offset": query.offset,
401 "context": context,
402 });
403
404 let result = self.call_wasm_json("query_datasource", input)?;
405 serde_json::from_value(result)
406 .map_err(|e| PluginError::execution(format!("Failed to parse DataResult: {}", e)))
407 }
408
409 async fn health_check(&self) -> Result<bool, PluginError> {
410 Ok(true)
411 }
412
413 async fn cleanup(&mut self) -> Result<(), PluginError> {
414 Ok(())
415 }
416}
417
418pub struct TinyGoAdapter {
424 plugin_id: PluginId,
425 engine: Arc<Engine>,
426 module: Module,
427 runtime: Mutex<Option<WasmRuntime>>,
428}
429
430impl TinyGoAdapter {
431 pub fn new(plugin_id: PluginId, wasm_bytes: Vec<u8>) -> Result<Self, PluginError> {
440 let engine = Arc::new(Engine::default());
441 let module = Module::from_binary(&engine, &wasm_bytes).map_err(|e| {
442 PluginError::execution(format!("Failed to load TinyGo WASM module: {}", e))
443 })?;
444
445 Ok(Self {
446 plugin_id,
447 engine,
448 module,
449 runtime: Mutex::new(None),
450 })
451 }
452
453 fn call_wasm_json(
456 &self,
457 function_name: &str,
458 input_data: serde_json::Value,
459 ) -> Result<serde_json::Value, PluginError> {
460 let mut runtime_guard = self.runtime.lock().unwrap();
461 let runtime = runtime_guard.as_mut().ok_or_else(|| {
462 PluginError::execution("Runtime not initialized. Call initialize() first.".to_string())
463 })?;
464
465 let input_json = serde_json::to_string(&input_data)
466 .map_err(|e| PluginError::execution(format!("Failed to serialize input: {}", e)))?;
467
468 let input_bytes = input_json.as_bytes();
469 let input_len = input_bytes.len() as i32;
470
471 let memory =
473 runtime.instance.get_memory(&mut runtime.store, "memory").ok_or_else(|| {
474 PluginError::execution("TinyGo WASM module must export 'memory'".to_string())
475 })?;
476
477 let malloc_func = runtime
479 .instance
480 .get_typed_func::<i32, i32>(&mut runtime.store, "malloc")
481 .map_err(|e| {
482 PluginError::execution(format!(
483 "Failed to get malloc function (TinyGo specific): {}",
484 e
485 ))
486 })?;
487
488 let input_ptr = malloc_func
490 .call(&mut runtime.store, input_len)
491 .map_err(|e| PluginError::execution(format!("Failed to allocate memory: {}", e)))?;
492
493 memory
495 .write(&mut runtime.store, input_ptr as usize, input_bytes)
496 .map_err(|e| PluginError::execution(format!("Failed to write input: {}", e)))?;
497
498 let plugin_func = runtime
500 .instance
501 .get_typed_func::<(i32, i32), (i32, i32)>(&mut runtime.store, function_name)
502 .map_err(|e| {
503 PluginError::execution(format!("Function '{}' not found: {}", function_name, e))
504 })?;
505
506 let (output_ptr, output_len) =
507 plugin_func.call(&mut runtime.store, (input_ptr, input_len)).map_err(|e| {
508 PluginError::execution(format!(
509 "Failed to call function '{}': {}",
510 function_name, e
511 ))
512 })?;
513
514 let mut output_bytes = vec![0u8; output_len as usize];
516 memory
517 .read(&runtime.store, output_ptr as usize, &mut output_bytes)
518 .map_err(|e| PluginError::execution(format!("Failed to read output: {}", e)))?;
519
520 if let Ok(free_func) =
522 runtime.instance.get_typed_func::<i32, ()>(&mut runtime.store, "free")
523 {
524 let _ = free_func.call(&mut runtime.store, input_ptr);
525 let _ = free_func.call(&mut runtime.store, output_ptr);
526 }
527
528 let output_str = String::from_utf8(output_bytes)
530 .map_err(|e| PluginError::execution(format!("Failed to decode output: {}", e)))?;
531
532 serde_json::from_str(&output_str)
533 .map_err(|e| PluginError::execution(format!("Failed to parse output JSON: {}", e)))
534 }
535}
536
537#[async_trait]
538impl RuntimeAdapter for TinyGoAdapter {
539 fn runtime_type(&self) -> RuntimeType {
540 RuntimeType::TinyGo
541 }
542
543 async fn initialize(&mut self) -> Result<(), PluginError> {
544 tracing::info!("Initializing TinyGo plugin: {}", self.plugin_id);
547
548 let wasi_ctx = WasiCtxBuilder::new().inherit_stderr().inherit_stdout().build();
550
551 let mut store = Store::new(&self.engine, wasi_ctx);
553
554 let linker = Linker::new(&self.engine);
556
557 let instance = linker.instantiate(&mut store, &self.module).map_err(|e| {
562 PluginError::execution(format!("Failed to instantiate TinyGo module: {}", e))
563 })?;
564
565 let mut runtime_guard = self.runtime.lock().unwrap();
567 *runtime_guard = Some(WasmRuntime { store, instance });
568
569 tracing::info!("Successfully initialized TinyGo plugin: {}", self.plugin_id);
570 Ok(())
571 }
572
573 async fn call_auth(
574 &self,
575 context: &PluginContext,
576 request: &AuthRequest,
577 ) -> Result<AuthResponse, PluginError> {
578 let input = serde_json::json!({
580 "context": context,
581 "method": request.method.to_string(),
582 "uri": request.uri.to_string(),
583 "query_params": request.query_params,
584 "client_ip": request.client_ip,
585 "user_agent": request.user_agent,
586 });
587
588 let result = self.call_wasm_json("authenticate", input)?;
589 serde_json::from_value(result)
590 .map_err(|e| PluginError::execution(format!("Failed to parse AuthResponse: {}", e)))
591 }
592
593 async fn call_template_function(
594 &self,
595 function_name: &str,
596 args: &[serde_json::Value],
597 context: &ResolutionContext,
598 ) -> Result<serde_json::Value, PluginError> {
599 let input = serde_json::json!({
600 "function_name": function_name,
601 "args": args,
602 "context": context,
603 });
604
605 self.call_wasm_json("template_function", input)
606 }
607
608 async fn call_response_generator(
609 &self,
610 context: &PluginContext,
611 request: &ResponseRequest,
612 ) -> Result<ResponseData, PluginError> {
613 let input = serde_json::json!({
615 "context": context,
616 "method": request.method.to_string(),
617 "uri": request.uri,
618 "path": request.path,
619 "query_params": request.query_params,
620 "path_params": request.path_params,
621 "client_ip": request.client_ip,
622 "user_agent": request.user_agent,
623 });
624
625 let result = self.call_wasm_json("generate_response", input)?;
626 serde_json::from_value(result)
627 .map_err(|e| PluginError::execution(format!("Failed to parse ResponseData: {}", e)))
628 }
629
630 async fn call_datasource_query(
631 &self,
632 query: &DataQuery,
633 context: &PluginContext,
634 ) -> Result<DataResult, PluginError> {
635 let input = serde_json::json!({
637 "query_type": format!("{:?}", query.query_type),
638 "query": query.query,
639 "parameters": query.parameters,
640 "limit": query.limit,
641 "offset": query.offset,
642 "context": context,
643 });
644
645 let result = self.call_wasm_json("query_datasource", input)?;
646 serde_json::from_value(result)
647 .map_err(|e| PluginError::execution(format!("Failed to parse DataResult: {}", e)))
648 }
649
650 async fn health_check(&self) -> Result<bool, PluginError> {
651 Ok(true)
652 }
653
654 async fn cleanup(&mut self) -> Result<(), PluginError> {
655 Ok(())
656 }
657}
658
659pub struct AssemblyScriptAdapter {
665 plugin_id: PluginId,
666 engine: Arc<Engine>,
667 module: Module,
668 runtime: Mutex<Option<WasmRuntime>>,
669}
670
671impl AssemblyScriptAdapter {
672 pub fn new(plugin_id: PluginId, wasm_bytes: Vec<u8>) -> Result<Self, PluginError> {
681 let engine = Arc::new(Engine::default());
682 let module = Module::from_binary(&engine, &wasm_bytes).map_err(|e| {
683 PluginError::execution(format!("Failed to load AssemblyScript WASM module: {}", e))
684 })?;
685
686 Ok(Self {
687 plugin_id,
688 engine,
689 module,
690 runtime: Mutex::new(None),
691 })
692 }
693
694 fn call_wasm_json(
697 &self,
698 function_name: &str,
699 input_data: serde_json::Value,
700 ) -> Result<serde_json::Value, PluginError> {
701 let mut runtime_guard = self.runtime.lock().unwrap();
702 let runtime = runtime_guard.as_mut().ok_or_else(|| {
703 PluginError::execution("Runtime not initialized. Call initialize() first.".to_string())
704 })?;
705
706 let input_json = serde_json::to_string(&input_data)
707 .map_err(|e| PluginError::execution(format!("Failed to serialize input: {}", e)))?;
708
709 let input_bytes = input_json.as_bytes();
710 let input_len = input_bytes.len() as i32;
711
712 let memory =
714 runtime.instance.get_memory(&mut runtime.store, "memory").ok_or_else(|| {
715 PluginError::execution(
716 "AssemblyScript WASM module must export 'memory'".to_string(),
717 )
718 })?;
719
720 let new_func = runtime
724 .instance
725 .get_typed_func::<(i32, i32), i32>(&mut runtime.store, "__new")
726 .map_err(|e| {
727 PluginError::execution(format!(
728 "Failed to get __new function (AssemblyScript specific): {}",
729 e
730 ))
731 })?;
732
733 let input_ptr = new_func
735 .call(&mut runtime.store, (input_len, 1))
736 .map_err(|e| PluginError::execution(format!("Failed to allocate memory: {}", e)))?;
737
738 if let Ok(pin_func) =
740 runtime.instance.get_typed_func::<i32, i32>(&mut runtime.store, "__pin")
741 {
742 let _ = pin_func.call(&mut runtime.store, input_ptr);
743 }
744
745 memory
747 .write(&mut runtime.store, input_ptr as usize, input_bytes)
748 .map_err(|e| PluginError::execution(format!("Failed to write input: {}", e)))?;
749
750 let plugin_func = runtime
752 .instance
753 .get_typed_func::<(i32, i32), (i32, i32)>(&mut runtime.store, function_name)
754 .map_err(|e| {
755 PluginError::execution(format!("Function '{}' not found: {}", function_name, e))
756 })?;
757
758 let (output_ptr, output_len) =
759 plugin_func.call(&mut runtime.store, (input_ptr, input_len)).map_err(|e| {
760 PluginError::execution(format!(
761 "Failed to call function '{}': {}",
762 function_name, e
763 ))
764 })?;
765
766 let mut output_bytes = vec![0u8; output_len as usize];
768 memory
769 .read(&runtime.store, output_ptr as usize, &mut output_bytes)
770 .map_err(|e| PluginError::execution(format!("Failed to read output: {}", e)))?;
771
772 if let Ok(unpin_func) =
774 runtime.instance.get_typed_func::<i32, ()>(&mut runtime.store, "__unpin")
775 {
776 let _ = unpin_func.call(&mut runtime.store, input_ptr);
777 let _ = unpin_func.call(&mut runtime.store, output_ptr);
778 }
779
780 let output_str = String::from_utf8(output_bytes)
782 .map_err(|e| PluginError::execution(format!("Failed to decode output: {}", e)))?;
783
784 serde_json::from_str(&output_str)
785 .map_err(|e| PluginError::execution(format!("Failed to parse output JSON: {}", e)))
786 }
787}
788
789#[async_trait]
790impl RuntimeAdapter for AssemblyScriptAdapter {
791 fn runtime_type(&self) -> RuntimeType {
792 RuntimeType::AssemblyScript
793 }
794
795 async fn initialize(&mut self) -> Result<(), PluginError> {
796 tracing::info!("Initializing AssemblyScript plugin: {}", self.plugin_id);
797
798 let wasi_ctx = WasiCtxBuilder::new().inherit_stderr().inherit_stdout().build();
800
801 let mut store = Store::new(&self.engine, wasi_ctx);
803
804 let linker = Linker::new(&self.engine);
806
807 let instance = linker.instantiate(&mut store, &self.module).map_err(|e| {
812 PluginError::execution(format!("Failed to instantiate AssemblyScript module: {}", e))
813 })?;
814
815 let mut runtime_guard = self.runtime.lock().unwrap();
817 *runtime_guard = Some(WasmRuntime { store, instance });
818
819 tracing::info!("Successfully initialized AssemblyScript plugin: {}", self.plugin_id);
820 Ok(())
821 }
822
823 async fn call_auth(
824 &self,
825 context: &PluginContext,
826 request: &AuthRequest,
827 ) -> Result<AuthResponse, PluginError> {
828 let input = serde_json::json!({
829 "context": context,
830 "method": request.method.to_string(),
831 "uri": request.uri.to_string(),
832 "query_params": request.query_params,
833 "client_ip": request.client_ip,
834 "user_agent": request.user_agent,
835 });
836
837 let result = self.call_wasm_json("authenticate", input)?;
838 serde_json::from_value(result)
839 .map_err(|e| PluginError::execution(format!("Failed to parse AuthResponse: {}", e)))
840 }
841
842 async fn call_template_function(
843 &self,
844 function_name: &str,
845 args: &[serde_json::Value],
846 context: &ResolutionContext,
847 ) -> Result<serde_json::Value, PluginError> {
848 let input = serde_json::json!({
849 "function_name": function_name,
850 "args": args,
851 "context": context,
852 });
853
854 self.call_wasm_json("template_function", input)
855 }
856
857 async fn call_response_generator(
858 &self,
859 context: &PluginContext,
860 request: &ResponseRequest,
861 ) -> Result<ResponseData, PluginError> {
862 let input = serde_json::json!({
864 "context": context,
865 "method": request.method.to_string(),
866 "uri": request.uri,
867 "path": request.path,
868 "query_params": request.query_params,
869 "path_params": request.path_params,
870 "client_ip": request.client_ip,
871 "user_agent": request.user_agent,
872 });
873
874 let result = self.call_wasm_json("generate_response", input)?;
875 serde_json::from_value(result)
876 .map_err(|e| PluginError::execution(format!("Failed to parse ResponseData: {}", e)))
877 }
878
879 async fn call_datasource_query(
880 &self,
881 query: &DataQuery,
882 context: &PluginContext,
883 ) -> Result<DataResult, PluginError> {
884 let input = serde_json::json!({
886 "query_type": format!("{:?}", query.query_type),
887 "query": query.query,
888 "parameters": query.parameters,
889 "limit": query.limit,
890 "offset": query.offset,
891 "context": context,
892 });
893
894 let result = self.call_wasm_json("query_datasource", input)?;
895 serde_json::from_value(result)
896 .map_err(|e| PluginError::execution(format!("Failed to parse DataResult: {}", e)))
897 }
898
899 async fn health_check(&self) -> Result<bool, PluginError> {
900 Ok(true)
901 }
902
903 async fn cleanup(&mut self) -> Result<(), PluginError> {
904 Ok(())
905 }
906}
907
908pub struct RemoteAdapter {
914 plugin_id: PluginId,
915 config: RemoteRuntimeConfig,
916 client: reqwest::Client,
917}
918
919impl RemoteAdapter {
920 pub fn new(plugin_id: PluginId, config: RemoteRuntimeConfig) -> Result<Self, PluginError> {
929 let client = reqwest::Client::builder()
930 .timeout(std::time::Duration::from_millis(config.timeout_ms))
931 .build()
932 .map_err(|e| PluginError::execution(format!("Failed to create HTTP client: {}", e)))?;
933
934 Ok(Self {
935 plugin_id,
936 config,
937 client,
938 })
939 }
940
941 async fn call_remote_plugin(
942 &self,
943 endpoint: &str,
944 body: serde_json::Value,
945 ) -> Result<serde_json::Value, PluginError> {
946 let url = format!("{}{}", self.config.endpoint, endpoint);
947
948 let mut request = self.client.post(&url).json(&body);
949
950 if let Some(auth) = &self.config.auth {
952 request = match auth.auth_type.as_str() {
953 "bearer" => request.bearer_auth(&auth.value),
954 "api_key" => request.header("X-API-Key", &auth.value),
955 _ => request,
956 };
957 }
958
959 let response = request
960 .send()
961 .await
962 .map_err(|e| PluginError::execution(format!("Remote plugin call failed: {}", e)))?;
963
964 if !response.status().is_success() {
965 return Err(PluginError::execution(format!(
966 "Remote plugin returned error status: {}",
967 response.status()
968 )));
969 }
970
971 let result: serde_json::Value = response
972 .json()
973 .await
974 .map_err(|e| PluginError::execution(format!("Failed to parse response: {}", e)))?;
975
976 Ok(result)
977 }
978}
979
980#[async_trait]
981impl RuntimeAdapter for RemoteAdapter {
982 fn runtime_type(&self) -> RuntimeType {
983 RuntimeType::Remote(self.config.clone())
984 }
985
986 async fn initialize(&mut self) -> Result<(), PluginError> {
987 tracing::info!("Initializing remote plugin: {}", self.plugin_id);
988
989 self.health_check().await?;
991
992 Ok(())
993 }
994
995 async fn call_auth(
996 &self,
997 context: &PluginContext,
998 request: &AuthRequest,
999 ) -> Result<AuthResponse, PluginError> {
1000 let body = serde_json::json!({
1001 "context": context,
1002 "method": request.method.to_string(),
1003 "uri": request.uri.to_string(),
1004 "query_params": request.query_params,
1005 "client_ip": request.client_ip,
1006 "user_agent": request.user_agent,
1007 });
1008
1009 let result = self.call_remote_plugin("/plugin/authenticate", body).await?;
1010
1011 serde_json::from_value(result)
1013 .map_err(|e| PluginError::execution(format!("Failed to parse AuthResponse: {}", e)))
1014 }
1015
1016 async fn call_template_function(
1017 &self,
1018 function_name: &str,
1019 args: &[serde_json::Value],
1020 context: &ResolutionContext,
1021 ) -> Result<serde_json::Value, PluginError> {
1022 let body = serde_json::json!({
1023 "function_name": function_name,
1024 "args": args,
1025 "context": context,
1026 });
1027
1028 self.call_remote_plugin("/plugin/template/execute", body).await
1029 }
1030
1031 async fn call_response_generator(
1032 &self,
1033 context: &PluginContext,
1034 request: &ResponseRequest,
1035 ) -> Result<ResponseData, PluginError> {
1036 let body = serde_json::json!({
1038 "context": context,
1039 "method": request.method.to_string(),
1040 "uri": request.uri,
1041 "path": request.path,
1042 "query_params": request.query_params,
1043 "path_params": request.path_params,
1044 "client_ip": request.client_ip,
1045 "user_agent": request.user_agent,
1046 });
1047
1048 let result = self.call_remote_plugin("/plugin/response/generate", body).await?;
1049
1050 serde_json::from_value(result)
1051 .map_err(|e| PluginError::execution(format!("Failed to parse ResponseData: {}", e)))
1052 }
1053
1054 async fn call_datasource_query(
1055 &self,
1056 query: &DataQuery,
1057 context: &PluginContext,
1058 ) -> Result<DataResult, PluginError> {
1059 let body = serde_json::json!({
1060 "query_type": format!("{:?}", query.query_type),
1061 "query": query.query,
1062 "parameters": query.parameters,
1063 "limit": query.limit,
1064 "offset": query.offset,
1065 "context": context,
1066 });
1067
1068 let result = self.call_remote_plugin("/plugin/datasource/query", body).await?;
1069
1070 serde_json::from_value(result)
1071 .map_err(|e| PluginError::execution(format!("Failed to parse DataResult: {}", e)))
1072 }
1073
1074 async fn health_check(&self) -> Result<bool, PluginError> {
1075 let url = format!("{}/health", self.config.endpoint);
1077
1078 match self.client.get(&url).send().await {
1079 Ok(response) => Ok(response.status().is_success()),
1080 Err(_) => Ok(false),
1081 }
1082 }
1083
1084 async fn cleanup(&mut self) -> Result<(), PluginError> {
1085 tracing::info!("Cleaning up remote plugin: {}", self.plugin_id);
1086 Ok(())
1087 }
1088
1089 fn get_metrics(&self) -> HashMap<String, serde_json::Value> {
1090 let mut metrics = HashMap::new();
1091 metrics.insert("plugin_id".to_string(), serde_json::json!(self.plugin_id.as_str()));
1092 metrics.insert("endpoint".to_string(), serde_json::json!(self.config.endpoint));
1093 metrics.insert(
1094 "protocol".to_string(),
1095 serde_json::json!(format!("{:?}", self.config.protocol)),
1096 );
1097 metrics
1098 }
1099}
1100
1101#[cfg(test)]
1102mod tests {
1103 use super::*;
1104
1105 #[test]
1106 fn test_runtime_type_detection() {
1107 let empty_bytes = vec![];
1109 let runtime = detect_runtime_type(&empty_bytes).unwrap();
1110 assert_eq!(runtime, RuntimeType::Rust);
1111 }
1112
1113 #[test]
1114 fn test_remote_runtime_config() {
1115 let config = RemoteRuntimeConfig {
1116 protocol: RemoteProtocol::Http,
1117 endpoint: "http://localhost:8080".to_string(),
1118 timeout_ms: 5000,
1119 max_retries: 3,
1120 auth: Some(RemoteAuthConfig {
1121 auth_type: "bearer".to_string(),
1122 value: "secret-token".to_string(),
1123 }),
1124 };
1125
1126 assert_eq!(config.endpoint, "http://localhost:8080");
1127 assert_eq!(config.timeout_ms, 5000);
1128 }
1129}