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)]
51pub enum RemoteProtocol {
52 Http,
53 Grpc,
54}
55
56#[derive(Debug, Clone, PartialEq, Eq)]
57pub struct RemoteAuthConfig {
58 pub auth_type: String,
60 pub value: String,
62}
63
64#[async_trait]
68pub trait RuntimeAdapter: Send + Sync {
69 fn runtime_type(&self) -> RuntimeType;
71
72 async fn initialize(&mut self) -> Result<(), PluginError>;
74
75 async fn call_auth(
77 &self,
78 context: &PluginContext,
79 request: &AuthRequest,
80 ) -> Result<AuthResponse, PluginError>;
81
82 async fn call_template_function(
84 &self,
85 function_name: &str,
86 args: &[serde_json::Value],
87 context: &ResolutionContext,
88 ) -> Result<serde_json::Value, PluginError>;
89
90 async fn call_response_generator(
92 &self,
93 context: &PluginContext,
94 request: &ResponseRequest,
95 ) -> Result<ResponseData, PluginError>;
96
97 async fn call_datasource_query(
99 &self,
100 query: &DataQuery,
101 context: &PluginContext,
102 ) -> Result<DataResult, PluginError>;
103
104 async fn health_check(&self) -> Result<bool, PluginError>;
106
107 async fn cleanup(&mut self) -> Result<(), PluginError>;
109
110 fn get_metrics(&self) -> HashMap<String, serde_json::Value> {
112 HashMap::new()
113 }
114}
115
116pub fn detect_runtime_type(wasm_bytes: &[u8]) -> Result<RuntimeType, PluginError> {
118 if has_tinygo_signature(wasm_bytes) {
123 return Ok(RuntimeType::TinyGo);
124 }
125
126 if has_assemblyscript_signature(wasm_bytes) {
128 return Ok(RuntimeType::AssemblyScript);
129 }
130
131 Ok(RuntimeType::Rust)
133}
134
135fn has_tinygo_signature(wasm_bytes: &[u8]) -> bool {
137 String::from_utf8_lossy(wasm_bytes).contains("tinygo")
143}
144
145fn has_assemblyscript_signature(wasm_bytes: &[u8]) -> bool {
147 String::from_utf8_lossy(wasm_bytes).contains("assemblyscript")
152}
153
154pub struct RuntimeAdapterFactory;
156
157impl RuntimeAdapterFactory {
158 pub fn create(
160 runtime_type: RuntimeType,
161 plugin_id: PluginId,
162 wasm_bytes: Vec<u8>,
163 ) -> Result<Box<dyn RuntimeAdapter>, PluginError> {
164 match runtime_type {
165 RuntimeType::Rust => Ok(Box::new(RustAdapter::new(plugin_id, wasm_bytes)?)),
166 RuntimeType::TinyGo => Ok(Box::new(TinyGoAdapter::new(plugin_id, wasm_bytes)?)),
167 RuntimeType::AssemblyScript => {
168 Ok(Box::new(AssemblyScriptAdapter::new(plugin_id, wasm_bytes)?))
169 }
170 RuntimeType::Remote(config) => Ok(Box::new(RemoteAdapter::new(plugin_id, config)?)),
171 }
172 }
173}
174
175pub struct RustAdapter {
180 plugin_id: PluginId,
181 engine: Arc<Engine>,
182 module: Module,
183 runtime: Mutex<Option<WasmRuntime>>,
185}
186
187struct WasmRuntime {
188 store: Store<WasiCtx>,
189 instance: Instance,
190}
191
192impl RustAdapter {
193 pub fn new(plugin_id: PluginId, wasm_bytes: Vec<u8>) -> Result<Self, PluginError> {
194 let engine = Arc::new(Engine::default());
195 let module = Module::from_binary(&engine, &wasm_bytes)
196 .map_err(|e| PluginError::execution(format!("Failed to load WASM module: {}", e)))?;
197
198 Ok(Self {
199 plugin_id,
200 engine,
201 module,
202 runtime: Mutex::new(None),
203 })
204 }
205
206 fn call_wasm_json(
208 &self,
209 function_name: &str,
210 input_data: serde_json::Value,
211 ) -> Result<serde_json::Value, PluginError> {
212 let mut runtime_guard = self.runtime.lock().unwrap();
213 let runtime = runtime_guard.as_mut().ok_or_else(|| {
214 PluginError::execution("Runtime not initialized. Call initialize() first.".to_string())
215 })?;
216
217 let input_json = serde_json::to_string(&input_data)
218 .map_err(|e| PluginError::execution(format!("Failed to serialize input: {}", e)))?;
219
220 let input_bytes = input_json.as_bytes();
221 let input_len = input_bytes.len() as i32;
222
223 let memory =
225 runtime.instance.get_memory(&mut runtime.store, "memory").ok_or_else(|| {
226 PluginError::execution("WASM module must export 'memory'".to_string())
227 })?;
228
229 let alloc_func = runtime
230 .instance
231 .get_typed_func::<i32, i32>(&mut runtime.store, "alloc")
232 .map_err(|e| {
233 PluginError::execution(format!("Failed to get alloc function: {}", e))
234 })?;
235
236 let input_ptr = alloc_func
238 .call(&mut runtime.store, input_len)
239 .map_err(|e| PluginError::execution(format!("Failed to allocate memory: {}", e)))?;
240
241 memory
243 .write(&mut runtime.store, input_ptr as usize, input_bytes)
244 .map_err(|e| PluginError::execution(format!("Failed to write input: {}", e)))?;
245
246 let plugin_func = runtime
248 .instance
249 .get_typed_func::<(i32, i32), (i32, i32)>(&mut runtime.store, function_name)
250 .map_err(|e| {
251 PluginError::execution(format!("Function '{}' not found: {}", function_name, e))
252 })?;
253
254 let (output_ptr, output_len) =
255 plugin_func.call(&mut runtime.store, (input_ptr, input_len)).map_err(|e| {
256 PluginError::execution(format!(
257 "Failed to call function '{}': {}",
258 function_name, e
259 ))
260 })?;
261
262 let mut output_bytes = vec![0u8; output_len as usize];
264 memory
265 .read(&runtime.store, output_ptr as usize, &mut output_bytes)
266 .map_err(|e| PluginError::execution(format!("Failed to read output: {}", e)))?;
267
268 if let Ok(dealloc_func) =
270 runtime.instance.get_typed_func::<(i32, i32), ()>(&mut runtime.store, "dealloc")
271 {
272 let _ = dealloc_func.call(&mut runtime.store, (input_ptr, input_len));
273 let _ = dealloc_func.call(&mut runtime.store, (output_ptr, output_len));
274 }
275
276 let output_str = String::from_utf8(output_bytes)
278 .map_err(|e| PluginError::execution(format!("Failed to decode output: {}", e)))?;
279
280 serde_json::from_str(&output_str)
281 .map_err(|e| PluginError::execution(format!("Failed to parse output JSON: {}", e)))
282 }
283}
284
285#[async_trait]
286impl RuntimeAdapter for RustAdapter {
287 fn runtime_type(&self) -> RuntimeType {
288 RuntimeType::Rust
289 }
290
291 async fn initialize(&mut self) -> Result<(), PluginError> {
292 tracing::info!("Initializing Rust plugin: {}", self.plugin_id);
294
295 let wasi_ctx = WasiCtxBuilder::new().inherit_stderr().inherit_stdout().build();
297
298 let mut store = Store::new(&self.engine, wasi_ctx);
300
301 let linker = Linker::new(&self.engine);
303
304 let instance = linker
306 .instantiate(&mut store, &self.module)
307 .map_err(|e| PluginError::execution(format!("Failed to instantiate module: {}", e)))?;
308
309 let mut runtime_guard = self.runtime.lock().unwrap();
311 *runtime_guard = Some(WasmRuntime { store, instance });
312
313 tracing::info!("Successfully initialized Rust plugin: {}", self.plugin_id);
314 Ok(())
315 }
316
317 async fn call_auth(
318 &self,
319 context: &PluginContext,
320 request: &AuthRequest,
321 ) -> Result<AuthResponse, PluginError> {
322 let input = serde_json::json!({
326 "context": context,
327 "method": request.method.to_string(),
328 "uri": request.uri.to_string(),
329 "query_params": request.query_params,
330 "client_ip": request.client_ip,
331 "user_agent": request.user_agent,
332 });
333
334 let result = self.call_wasm_json("authenticate", input)?;
335 serde_json::from_value(result)
336 .map_err(|e| PluginError::execution(format!("Failed to parse AuthResponse: {}", e)))
337 }
338
339 async fn call_template_function(
340 &self,
341 function_name: &str,
342 args: &[serde_json::Value],
343 context: &ResolutionContext,
344 ) -> Result<serde_json::Value, PluginError> {
345 let input = serde_json::json!({
346 "function_name": function_name,
347 "args": args,
348 "context": context,
349 });
350
351 self.call_wasm_json("template_function", input)
352 }
353
354 async fn call_response_generator(
355 &self,
356 context: &PluginContext,
357 request: &ResponseRequest,
358 ) -> Result<ResponseData, PluginError> {
359 let input = serde_json::json!({
361 "context": context,
362 "method": request.method.to_string(),
363 "uri": request.uri,
364 "path": request.path,
365 "query_params": request.query_params,
366 "path_params": request.path_params,
367 "client_ip": request.client_ip,
368 "user_agent": request.user_agent,
369 });
370
371 let result = self.call_wasm_json("generate_response", input)?;
372 serde_json::from_value(result)
373 .map_err(|e| PluginError::execution(format!("Failed to parse ResponseData: {}", e)))
374 }
375
376 async fn call_datasource_query(
377 &self,
378 query: &DataQuery,
379 context: &PluginContext,
380 ) -> Result<DataResult, PluginError> {
381 let input = serde_json::json!({
383 "query_type": format!("{:?}", query.query_type),
384 "query": query.query,
385 "parameters": query.parameters,
386 "limit": query.limit,
387 "offset": query.offset,
388 "context": context,
389 });
390
391 let result = self.call_wasm_json("query_datasource", input)?;
392 serde_json::from_value(result)
393 .map_err(|e| PluginError::execution(format!("Failed to parse DataResult: {}", e)))
394 }
395
396 async fn health_check(&self) -> Result<bool, PluginError> {
397 Ok(true)
398 }
399
400 async fn cleanup(&mut self) -> Result<(), PluginError> {
401 Ok(())
402 }
403}
404
405pub struct TinyGoAdapter {
410 plugin_id: PluginId,
411 engine: Arc<Engine>,
412 module: Module,
413 runtime: Mutex<Option<WasmRuntime>>,
414}
415
416impl TinyGoAdapter {
417 pub fn new(plugin_id: PluginId, wasm_bytes: Vec<u8>) -> Result<Self, PluginError> {
418 let engine = Arc::new(Engine::default());
419 let module = Module::from_binary(&engine, &wasm_bytes).map_err(|e| {
420 PluginError::execution(format!("Failed to load TinyGo WASM module: {}", e))
421 })?;
422
423 Ok(Self {
424 plugin_id,
425 engine,
426 module,
427 runtime: Mutex::new(None),
428 })
429 }
430
431 fn call_wasm_json(
434 &self,
435 function_name: &str,
436 input_data: serde_json::Value,
437 ) -> Result<serde_json::Value, PluginError> {
438 let mut runtime_guard = self.runtime.lock().unwrap();
439 let runtime = runtime_guard.as_mut().ok_or_else(|| {
440 PluginError::execution("Runtime not initialized. Call initialize() first.".to_string())
441 })?;
442
443 let input_json = serde_json::to_string(&input_data)
444 .map_err(|e| PluginError::execution(format!("Failed to serialize input: {}", e)))?;
445
446 let input_bytes = input_json.as_bytes();
447 let input_len = input_bytes.len() as i32;
448
449 let memory =
451 runtime.instance.get_memory(&mut runtime.store, "memory").ok_or_else(|| {
452 PluginError::execution("TinyGo WASM module must export 'memory'".to_string())
453 })?;
454
455 let malloc_func = runtime
457 .instance
458 .get_typed_func::<i32, i32>(&mut runtime.store, "malloc")
459 .map_err(|e| {
460 PluginError::execution(format!(
461 "Failed to get malloc function (TinyGo specific): {}",
462 e
463 ))
464 })?;
465
466 let input_ptr = malloc_func
468 .call(&mut runtime.store, input_len)
469 .map_err(|e| PluginError::execution(format!("Failed to allocate memory: {}", e)))?;
470
471 memory
473 .write(&mut runtime.store, input_ptr as usize, input_bytes)
474 .map_err(|e| PluginError::execution(format!("Failed to write input: {}", e)))?;
475
476 let plugin_func = runtime
478 .instance
479 .get_typed_func::<(i32, i32), (i32, i32)>(&mut runtime.store, function_name)
480 .map_err(|e| {
481 PluginError::execution(format!("Function '{}' not found: {}", function_name, e))
482 })?;
483
484 let (output_ptr, output_len) =
485 plugin_func.call(&mut runtime.store, (input_ptr, input_len)).map_err(|e| {
486 PluginError::execution(format!(
487 "Failed to call function '{}': {}",
488 function_name, e
489 ))
490 })?;
491
492 let mut output_bytes = vec![0u8; output_len as usize];
494 memory
495 .read(&runtime.store, output_ptr as usize, &mut output_bytes)
496 .map_err(|e| PluginError::execution(format!("Failed to read output: {}", e)))?;
497
498 if let Ok(free_func) =
500 runtime.instance.get_typed_func::<i32, ()>(&mut runtime.store, "free")
501 {
502 let _ = free_func.call(&mut runtime.store, input_ptr);
503 let _ = free_func.call(&mut runtime.store, output_ptr);
504 }
505
506 let output_str = String::from_utf8(output_bytes)
508 .map_err(|e| PluginError::execution(format!("Failed to decode output: {}", e)))?;
509
510 serde_json::from_str(&output_str)
511 .map_err(|e| PluginError::execution(format!("Failed to parse output JSON: {}", e)))
512 }
513}
514
515#[async_trait]
516impl RuntimeAdapter for TinyGoAdapter {
517 fn runtime_type(&self) -> RuntimeType {
518 RuntimeType::TinyGo
519 }
520
521 async fn initialize(&mut self) -> Result<(), PluginError> {
522 tracing::info!("Initializing TinyGo plugin: {}", self.plugin_id);
525
526 let wasi_ctx = WasiCtxBuilder::new().inherit_stderr().inherit_stdout().build();
528
529 let mut store = Store::new(&self.engine, wasi_ctx);
531
532 let linker = Linker::new(&self.engine);
534
535 let instance = linker.instantiate(&mut store, &self.module).map_err(|e| {
540 PluginError::execution(format!("Failed to instantiate TinyGo module: {}", e))
541 })?;
542
543 let mut runtime_guard = self.runtime.lock().unwrap();
545 *runtime_guard = Some(WasmRuntime { store, instance });
546
547 tracing::info!("Successfully initialized TinyGo plugin: {}", self.plugin_id);
548 Ok(())
549 }
550
551 async fn call_auth(
552 &self,
553 context: &PluginContext,
554 request: &AuthRequest,
555 ) -> Result<AuthResponse, PluginError> {
556 let input = serde_json::json!({
558 "context": context,
559 "method": request.method.to_string(),
560 "uri": request.uri.to_string(),
561 "query_params": request.query_params,
562 "client_ip": request.client_ip,
563 "user_agent": request.user_agent,
564 });
565
566 let result = self.call_wasm_json("authenticate", input)?;
567 serde_json::from_value(result)
568 .map_err(|e| PluginError::execution(format!("Failed to parse AuthResponse: {}", e)))
569 }
570
571 async fn call_template_function(
572 &self,
573 function_name: &str,
574 args: &[serde_json::Value],
575 context: &ResolutionContext,
576 ) -> Result<serde_json::Value, PluginError> {
577 let input = serde_json::json!({
578 "function_name": function_name,
579 "args": args,
580 "context": context,
581 });
582
583 self.call_wasm_json("template_function", input)
584 }
585
586 async fn call_response_generator(
587 &self,
588 context: &PluginContext,
589 request: &ResponseRequest,
590 ) -> Result<ResponseData, PluginError> {
591 let input = serde_json::json!({
593 "context": context,
594 "method": request.method.to_string(),
595 "uri": request.uri,
596 "path": request.path,
597 "query_params": request.query_params,
598 "path_params": request.path_params,
599 "client_ip": request.client_ip,
600 "user_agent": request.user_agent,
601 });
602
603 let result = self.call_wasm_json("generate_response", input)?;
604 serde_json::from_value(result)
605 .map_err(|e| PluginError::execution(format!("Failed to parse ResponseData: {}", e)))
606 }
607
608 async fn call_datasource_query(
609 &self,
610 query: &DataQuery,
611 context: &PluginContext,
612 ) -> Result<DataResult, PluginError> {
613 let input = serde_json::json!({
615 "query_type": format!("{:?}", query.query_type),
616 "query": query.query,
617 "parameters": query.parameters,
618 "limit": query.limit,
619 "offset": query.offset,
620 "context": context,
621 });
622
623 let result = self.call_wasm_json("query_datasource", input)?;
624 serde_json::from_value(result)
625 .map_err(|e| PluginError::execution(format!("Failed to parse DataResult: {}", e)))
626 }
627
628 async fn health_check(&self) -> Result<bool, PluginError> {
629 Ok(true)
630 }
631
632 async fn cleanup(&mut self) -> Result<(), PluginError> {
633 Ok(())
634 }
635}
636
637pub struct AssemblyScriptAdapter {
642 plugin_id: PluginId,
643 engine: Arc<Engine>,
644 module: Module,
645 runtime: Mutex<Option<WasmRuntime>>,
646}
647
648impl AssemblyScriptAdapter {
649 pub fn new(plugin_id: PluginId, wasm_bytes: Vec<u8>) -> Result<Self, PluginError> {
650 let engine = Arc::new(Engine::default());
651 let module = Module::from_binary(&engine, &wasm_bytes).map_err(|e| {
652 PluginError::execution(format!("Failed to load AssemblyScript WASM module: {}", e))
653 })?;
654
655 Ok(Self {
656 plugin_id,
657 engine,
658 module,
659 runtime: Mutex::new(None),
660 })
661 }
662
663 fn call_wasm_json(
666 &self,
667 function_name: &str,
668 input_data: serde_json::Value,
669 ) -> Result<serde_json::Value, PluginError> {
670 let mut runtime_guard = self.runtime.lock().unwrap();
671 let runtime = runtime_guard.as_mut().ok_or_else(|| {
672 PluginError::execution("Runtime not initialized. Call initialize() first.".to_string())
673 })?;
674
675 let input_json = serde_json::to_string(&input_data)
676 .map_err(|e| PluginError::execution(format!("Failed to serialize input: {}", e)))?;
677
678 let input_bytes = input_json.as_bytes();
679 let input_len = input_bytes.len() as i32;
680
681 let memory =
683 runtime.instance.get_memory(&mut runtime.store, "memory").ok_or_else(|| {
684 PluginError::execution(
685 "AssemblyScript WASM module must export 'memory'".to_string(),
686 )
687 })?;
688
689 let new_func = runtime
693 .instance
694 .get_typed_func::<(i32, i32), i32>(&mut runtime.store, "__new")
695 .map_err(|e| {
696 PluginError::execution(format!(
697 "Failed to get __new function (AssemblyScript specific): {}",
698 e
699 ))
700 })?;
701
702 let input_ptr = new_func
704 .call(&mut runtime.store, (input_len, 1))
705 .map_err(|e| PluginError::execution(format!("Failed to allocate memory: {}", e)))?;
706
707 if let Ok(pin_func) =
709 runtime.instance.get_typed_func::<i32, i32>(&mut runtime.store, "__pin")
710 {
711 let _ = pin_func.call(&mut runtime.store, input_ptr);
712 }
713
714 memory
716 .write(&mut runtime.store, input_ptr as usize, input_bytes)
717 .map_err(|e| PluginError::execution(format!("Failed to write input: {}", e)))?;
718
719 let plugin_func = runtime
721 .instance
722 .get_typed_func::<(i32, i32), (i32, i32)>(&mut runtime.store, function_name)
723 .map_err(|e| {
724 PluginError::execution(format!("Function '{}' not found: {}", function_name, e))
725 })?;
726
727 let (output_ptr, output_len) =
728 plugin_func.call(&mut runtime.store, (input_ptr, input_len)).map_err(|e| {
729 PluginError::execution(format!(
730 "Failed to call function '{}': {}",
731 function_name, e
732 ))
733 })?;
734
735 let mut output_bytes = vec![0u8; output_len as usize];
737 memory
738 .read(&runtime.store, output_ptr as usize, &mut output_bytes)
739 .map_err(|e| PluginError::execution(format!("Failed to read output: {}", e)))?;
740
741 if let Ok(unpin_func) =
743 runtime.instance.get_typed_func::<i32, ()>(&mut runtime.store, "__unpin")
744 {
745 let _ = unpin_func.call(&mut runtime.store, input_ptr);
746 let _ = unpin_func.call(&mut runtime.store, output_ptr);
747 }
748
749 let output_str = String::from_utf8(output_bytes)
751 .map_err(|e| PluginError::execution(format!("Failed to decode output: {}", e)))?;
752
753 serde_json::from_str(&output_str)
754 .map_err(|e| PluginError::execution(format!("Failed to parse output JSON: {}", e)))
755 }
756}
757
758#[async_trait]
759impl RuntimeAdapter for AssemblyScriptAdapter {
760 fn runtime_type(&self) -> RuntimeType {
761 RuntimeType::AssemblyScript
762 }
763
764 async fn initialize(&mut self) -> Result<(), PluginError> {
765 tracing::info!("Initializing AssemblyScript plugin: {}", self.plugin_id);
766
767 let wasi_ctx = WasiCtxBuilder::new().inherit_stderr().inherit_stdout().build();
769
770 let mut store = Store::new(&self.engine, wasi_ctx);
772
773 let linker = Linker::new(&self.engine);
775
776 let instance = linker.instantiate(&mut store, &self.module).map_err(|e| {
781 PluginError::execution(format!("Failed to instantiate AssemblyScript module: {}", e))
782 })?;
783
784 let mut runtime_guard = self.runtime.lock().unwrap();
786 *runtime_guard = Some(WasmRuntime { store, instance });
787
788 tracing::info!("Successfully initialized AssemblyScript plugin: {}", self.plugin_id);
789 Ok(())
790 }
791
792 async fn call_auth(
793 &self,
794 context: &PluginContext,
795 request: &AuthRequest,
796 ) -> Result<AuthResponse, PluginError> {
797 let input = serde_json::json!({
798 "context": context,
799 "method": request.method.to_string(),
800 "uri": request.uri.to_string(),
801 "query_params": request.query_params,
802 "client_ip": request.client_ip,
803 "user_agent": request.user_agent,
804 });
805
806 let result = self.call_wasm_json("authenticate", input)?;
807 serde_json::from_value(result)
808 .map_err(|e| PluginError::execution(format!("Failed to parse AuthResponse: {}", e)))
809 }
810
811 async fn call_template_function(
812 &self,
813 function_name: &str,
814 args: &[serde_json::Value],
815 context: &ResolutionContext,
816 ) -> Result<serde_json::Value, PluginError> {
817 let input = serde_json::json!({
818 "function_name": function_name,
819 "args": args,
820 "context": context,
821 });
822
823 self.call_wasm_json("template_function", input)
824 }
825
826 async fn call_response_generator(
827 &self,
828 context: &PluginContext,
829 request: &ResponseRequest,
830 ) -> Result<ResponseData, PluginError> {
831 let input = serde_json::json!({
833 "context": context,
834 "method": request.method.to_string(),
835 "uri": request.uri,
836 "path": request.path,
837 "query_params": request.query_params,
838 "path_params": request.path_params,
839 "client_ip": request.client_ip,
840 "user_agent": request.user_agent,
841 });
842
843 let result = self.call_wasm_json("generate_response", input)?;
844 serde_json::from_value(result)
845 .map_err(|e| PluginError::execution(format!("Failed to parse ResponseData: {}", e)))
846 }
847
848 async fn call_datasource_query(
849 &self,
850 query: &DataQuery,
851 context: &PluginContext,
852 ) -> Result<DataResult, PluginError> {
853 let input = serde_json::json!({
855 "query_type": format!("{:?}", query.query_type),
856 "query": query.query,
857 "parameters": query.parameters,
858 "limit": query.limit,
859 "offset": query.offset,
860 "context": context,
861 });
862
863 let result = self.call_wasm_json("query_datasource", input)?;
864 serde_json::from_value(result)
865 .map_err(|e| PluginError::execution(format!("Failed to parse DataResult: {}", e)))
866 }
867
868 async fn health_check(&self) -> Result<bool, PluginError> {
869 Ok(true)
870 }
871
872 async fn cleanup(&mut self) -> Result<(), PluginError> {
873 Ok(())
874 }
875}
876
877pub struct RemoteAdapter {
882 plugin_id: PluginId,
883 config: RemoteRuntimeConfig,
884 client: reqwest::Client,
885}
886
887impl RemoteAdapter {
888 pub fn new(plugin_id: PluginId, config: RemoteRuntimeConfig) -> Result<Self, PluginError> {
889 let client = reqwest::Client::builder()
890 .timeout(std::time::Duration::from_millis(config.timeout_ms))
891 .build()
892 .map_err(|e| PluginError::execution(format!("Failed to create HTTP client: {}", e)))?;
893
894 Ok(Self {
895 plugin_id,
896 config,
897 client,
898 })
899 }
900
901 async fn call_remote_plugin(
902 &self,
903 endpoint: &str,
904 body: serde_json::Value,
905 ) -> Result<serde_json::Value, PluginError> {
906 let url = format!("{}{}", self.config.endpoint, endpoint);
907
908 let mut request = self.client.post(&url).json(&body);
909
910 if let Some(auth) = &self.config.auth {
912 request = match auth.auth_type.as_str() {
913 "bearer" => request.bearer_auth(&auth.value),
914 "api_key" => request.header("X-API-Key", &auth.value),
915 _ => request,
916 };
917 }
918
919 let response = request
920 .send()
921 .await
922 .map_err(|e| PluginError::execution(format!("Remote plugin call failed: {}", e)))?;
923
924 if !response.status().is_success() {
925 return Err(PluginError::execution(format!(
926 "Remote plugin returned error status: {}",
927 response.status()
928 )));
929 }
930
931 let result: serde_json::Value = response
932 .json()
933 .await
934 .map_err(|e| PluginError::execution(format!("Failed to parse response: {}", e)))?;
935
936 Ok(result)
937 }
938}
939
940#[async_trait]
941impl RuntimeAdapter for RemoteAdapter {
942 fn runtime_type(&self) -> RuntimeType {
943 RuntimeType::Remote(self.config.clone())
944 }
945
946 async fn initialize(&mut self) -> Result<(), PluginError> {
947 tracing::info!("Initializing remote plugin: {}", self.plugin_id);
948
949 self.health_check().await?;
951
952 Ok(())
953 }
954
955 async fn call_auth(
956 &self,
957 context: &PluginContext,
958 request: &AuthRequest,
959 ) -> Result<AuthResponse, PluginError> {
960 let body = serde_json::json!({
961 "context": context,
962 "method": request.method.to_string(),
963 "uri": request.uri.to_string(),
964 "query_params": request.query_params,
965 "client_ip": request.client_ip,
966 "user_agent": request.user_agent,
967 });
968
969 let result = self.call_remote_plugin("/plugin/authenticate", body).await?;
970
971 serde_json::from_value(result)
973 .map_err(|e| PluginError::execution(format!("Failed to parse AuthResponse: {}", e)))
974 }
975
976 async fn call_template_function(
977 &self,
978 function_name: &str,
979 args: &[serde_json::Value],
980 context: &ResolutionContext,
981 ) -> Result<serde_json::Value, PluginError> {
982 let body = serde_json::json!({
983 "function_name": function_name,
984 "args": args,
985 "context": context,
986 });
987
988 self.call_remote_plugin("/plugin/template/execute", body).await
989 }
990
991 async fn call_response_generator(
992 &self,
993 context: &PluginContext,
994 request: &ResponseRequest,
995 ) -> Result<ResponseData, PluginError> {
996 let body = serde_json::json!({
998 "context": context,
999 "method": request.method.to_string(),
1000 "uri": request.uri,
1001 "path": request.path,
1002 "query_params": request.query_params,
1003 "path_params": request.path_params,
1004 "client_ip": request.client_ip,
1005 "user_agent": request.user_agent,
1006 });
1007
1008 let result = self.call_remote_plugin("/plugin/response/generate", body).await?;
1009
1010 serde_json::from_value(result)
1011 .map_err(|e| PluginError::execution(format!("Failed to parse ResponseData: {}", e)))
1012 }
1013
1014 async fn call_datasource_query(
1015 &self,
1016 query: &DataQuery,
1017 context: &PluginContext,
1018 ) -> Result<DataResult, PluginError> {
1019 let body = serde_json::json!({
1020 "query_type": format!("{:?}", query.query_type),
1021 "query": query.query,
1022 "parameters": query.parameters,
1023 "limit": query.limit,
1024 "offset": query.offset,
1025 "context": context,
1026 });
1027
1028 let result = self.call_remote_plugin("/plugin/datasource/query", body).await?;
1029
1030 serde_json::from_value(result)
1031 .map_err(|e| PluginError::execution(format!("Failed to parse DataResult: {}", e)))
1032 }
1033
1034 async fn health_check(&self) -> Result<bool, PluginError> {
1035 let url = format!("{}/health", self.config.endpoint);
1037
1038 match self.client.get(&url).send().await {
1039 Ok(response) => Ok(response.status().is_success()),
1040 Err(_) => Ok(false),
1041 }
1042 }
1043
1044 async fn cleanup(&mut self) -> Result<(), PluginError> {
1045 tracing::info!("Cleaning up remote plugin: {}", self.plugin_id);
1046 Ok(())
1047 }
1048
1049 fn get_metrics(&self) -> HashMap<String, serde_json::Value> {
1050 let mut metrics = HashMap::new();
1051 metrics.insert("plugin_id".to_string(), serde_json::json!(self.plugin_id.as_str()));
1052 metrics.insert("endpoint".to_string(), serde_json::json!(self.config.endpoint));
1053 metrics.insert(
1054 "protocol".to_string(),
1055 serde_json::json!(format!("{:?}", self.config.protocol)),
1056 );
1057 metrics
1058 }
1059}
1060
1061#[cfg(test)]
1062mod tests {
1063 use super::*;
1064
1065 #[test]
1066 fn test_runtime_type_detection() {
1067 let empty_bytes = vec![];
1069 let runtime = detect_runtime_type(&empty_bytes).unwrap();
1070 assert_eq!(runtime, RuntimeType::Rust);
1071 }
1072
1073 #[test]
1074 fn test_remote_runtime_config() {
1075 let config = RemoteRuntimeConfig {
1076 protocol: RemoteProtocol::Http,
1077 endpoint: "http://localhost:8080".to_string(),
1078 timeout_ms: 5000,
1079 max_retries: 3,
1080 auth: Some(RemoteAuthConfig {
1081 auth_type: "bearer".to_string(),
1082 value: "secret-token".to_string(),
1083 }),
1084 };
1085
1086 assert_eq!(config.endpoint, "http://localhost:8080");
1087 assert_eq!(config.timeout_ms, 5000);
1088 }
1089}