mockforge_plugin_loader/
runtime_adapter.rs

1//! Runtime adapter for supporting multiple plugin runtimes
2//!
3//! This module provides an abstraction layer that allows MockForge to load and execute
4//! plugins written in different languages and compiled with different WASM runtimes.
5//!
6//! Supported runtimes:
7//! - Rust (native, via wasmtime)
8//! - TinyGo (Go compiled to WASM)
9//! - AssemblyScript (TypeScript-like, compiled to WASM)
10//! - Remote (HTTP/gRPC-based plugins in any language)
11
12use 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/// Enum representing different plugin runtime types
23#[derive(Debug, Clone, PartialEq, Eq)]
24pub enum RuntimeType {
25    /// Native Rust plugin compiled to WASM
26    Rust,
27    /// TinyGo compiled plugin
28    TinyGo,
29    /// AssemblyScript compiled plugin
30    AssemblyScript,
31    /// Remote plugin accessed via HTTP/gRPC
32    Remote(RemoteRuntimeConfig),
33}
34
35/// Configuration for remote plugin runtime
36#[derive(Debug, Clone, PartialEq, Eq)]
37pub struct RemoteRuntimeConfig {
38    /// Protocol to use (http or grpc)
39    pub protocol: RemoteProtocol,
40    /// Endpoint URL
41    pub endpoint: String,
42    /// Request timeout in milliseconds
43    pub timeout_ms: u64,
44    /// Maximum number of retries
45    pub max_retries: u32,
46    /// Authentication configuration
47    pub auth: Option<RemoteAuthConfig>,
48}
49
50/// Protocol for remote plugin communication
51#[derive(Debug, Clone, PartialEq, Eq)]
52pub enum RemoteProtocol {
53    /// HTTP/REST protocol
54    Http,
55    /// gRPC protocol
56    Grpc,
57}
58
59/// Authentication configuration for remote plugin runtime
60#[derive(Debug, Clone, PartialEq, Eq)]
61pub struct RemoteAuthConfig {
62    /// Authentication type (bearer, api_key, etc.)
63    pub auth_type: String,
64    /// Authentication value
65    pub value: String,
66}
67
68/// Trait that all runtime adapters must implement
69///
70/// This allows different WASM runtimes and remote plugins to be used interchangeably
71#[async_trait]
72pub trait RuntimeAdapter: Send + Sync {
73    /// Get the runtime type
74    fn runtime_type(&self) -> RuntimeType;
75
76    /// Initialize the runtime (called once during plugin load)
77    async fn initialize(&mut self) -> Result<(), PluginError>;
78
79    /// Call authentication plugin
80    async fn call_auth(
81        &self,
82        context: &PluginContext,
83        request: &AuthRequest,
84    ) -> Result<AuthResponse, PluginError>;
85
86    /// Call template function plugin
87    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    /// Call response generator plugin
95    async fn call_response_generator(
96        &self,
97        context: &PluginContext,
98        request: &ResponseRequest,
99    ) -> Result<ResponseData, PluginError>;
100
101    /// Call data source query plugin
102    async fn call_datasource_query(
103        &self,
104        query: &DataQuery,
105        context: &PluginContext,
106    ) -> Result<DataResult, PluginError>;
107
108    /// Health check - returns true if the plugin is healthy
109    async fn health_check(&self) -> Result<bool, PluginError>;
110
111    /// Cleanup resources (called during plugin unload)
112    async fn cleanup(&mut self) -> Result<(), PluginError>;
113
114    /// Get runtime-specific metrics
115    fn get_metrics(&self) -> HashMap<String, serde_json::Value> {
116        HashMap::new()
117    }
118}
119
120/// Detect runtime type from WASM binary
121pub fn detect_runtime_type(wasm_bytes: &[u8]) -> Result<RuntimeType, PluginError> {
122    // Parse WASM module to detect runtime type
123    // This is a simplified version - real implementation would parse WASM sections
124
125    // Check for TinyGo signatures
126    if has_tinygo_signature(wasm_bytes) {
127        return Ok(RuntimeType::TinyGo);
128    }
129
130    // Check for AssemblyScript signatures
131    if has_assemblyscript_signature(wasm_bytes) {
132        return Ok(RuntimeType::AssemblyScript);
133    }
134
135    // Default to Rust
136    Ok(RuntimeType::Rust)
137}
138
139/// Check if WASM binary has TinyGo signature
140fn has_tinygo_signature(wasm_bytes: &[u8]) -> bool {
141    // Look for TinyGo-specific exports or custom sections
142    // TinyGo typically exports functions like "resume" and "getsp"
143    // This is a placeholder - real implementation would use wasmparser
144
145    // Simple heuristic: check for "tinygo" in custom sections
146    String::from_utf8_lossy(wasm_bytes).contains("tinygo")
147}
148
149/// Check if WASM binary has AssemblyScript signature
150fn has_assemblyscript_signature(wasm_bytes: &[u8]) -> bool {
151    // Look for AssemblyScript-specific exports or custom sections
152    // AS typically has "__new", "__pin", "__unpin" exports
153
154    // Simple heuristic: check for "assemblyscript" in custom sections
155    String::from_utf8_lossy(wasm_bytes).contains("assemblyscript")
156}
157
158/// Factory for creating runtime adapters
159pub struct RuntimeAdapterFactory;
160
161impl RuntimeAdapterFactory {
162    /// Create a runtime adapter for the given runtime type
163    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
179// ============================================================================
180// Rust Runtime Adapter (Existing Implementation)
181// ============================================================================
182
183/// WASM runtime adapter for plugins compiled from Rust
184pub struct RustAdapter {
185    plugin_id: PluginId,
186    engine: Arc<Engine>,
187    module: Module,
188    // Store and Instance need to be behind a Mutex since they're not Send/Sync
189    runtime: Mutex<Option<WasmRuntime>>,
190}
191
192struct WasmRuntime {
193    store: Store<WasiCtx>,
194    instance: Instance,
195}
196
197impl RustAdapter {
198    /// Create a new Rust runtime adapter
199    ///
200    /// # Arguments
201    /// * `plugin_id` - Unique identifier for the plugin
202    /// * `wasm_bytes` - WebAssembly binary bytes
203    ///
204    /// # Returns
205    /// `Ok(RustAdapter)` on success, `Err(PluginError)` if WASM module cannot be loaded
206    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    /// Helper to call a WASM function with JSON input/output
220    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        // Get memory and alloc function
237        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        // Allocate memory for input
250        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        // Write input to WASM memory
255        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        // Call the plugin function
260        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        // Read output from WASM memory
276        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        // Deallocate memory if dealloc function exists
282        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        // Parse output as JSON
290        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        // Initialize wasmtime instance with the Rust WASM module
306        tracing::info!("Initializing Rust plugin: {}", self.plugin_id);
307
308        // Create WASI context
309        let wasi_ctx = WasiCtxBuilder::new().inherit_stderr().inherit_stdout().build();
310
311        // Create store
312        let mut store = Store::new(&self.engine, wasi_ctx);
313
314        // Create linker
315        let linker = Linker::new(&self.engine);
316
317        // Instantiate the module
318        let instance = linker
319            .instantiate(&mut store, &self.module)
320            .map_err(|e| PluginError::execution(format!("Failed to instantiate module: {}", e)))?;
321
322        // Store the runtime
323        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        // Call Rust WASM plugin's auth function
336        // Note: AuthRequest contains axum::http types which might not serialize well
337        // So we create a simplified version for WASM
338        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        // Create simplified request for WASM (avoid non-serializable types)
373        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        // DataQuery should be serializable, but create simplified version to be safe
395        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
418// ============================================================================
419// TinyGo Runtime Adapter
420// ============================================================================
421
422/// WASM runtime adapter for plugins compiled from TinyGo
423pub struct TinyGoAdapter {
424    plugin_id: PluginId,
425    engine: Arc<Engine>,
426    module: Module,
427    runtime: Mutex<Option<WasmRuntime>>,
428}
429
430impl TinyGoAdapter {
431    /// Create a new TinyGo runtime adapter
432    ///
433    /// # Arguments
434    /// * `plugin_id` - Unique identifier for the plugin
435    /// * `wasm_bytes` - WebAssembly binary bytes from TinyGo compilation
436    ///
437    /// # Returns
438    /// `Ok(TinyGoAdapter)` on success, `Err(PluginError)` if WASM module cannot be loaded
439    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    /// Helper to call a TinyGo WASM function with JSON input/output
454    /// TinyGo uses specific memory management and calling conventions
455    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        // Get memory (TinyGo always exports memory)
472        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        // TinyGo uses malloc instead of alloc
478        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        // Allocate memory for input
489        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        // Write input to WASM memory
494        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        // Call the plugin function
499        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        // Read output from WASM memory
515        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        // TinyGo uses free instead of dealloc
521        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        // Parse output as JSON
529        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        // Initialize wasmtime instance with TinyGo-specific configuration
545        // TinyGo requires special memory management and import handling
546        tracing::info!("Initializing TinyGo plugin: {}", self.plugin_id);
547
548        // Create WASI context (TinyGo supports WASI)
549        let wasi_ctx = WasiCtxBuilder::new().inherit_stderr().inherit_stdout().build();
550
551        // Create store
552        let mut store = Store::new(&self.engine, wasi_ctx);
553
554        // Create linker with TinyGo-specific imports
555        let linker = Linker::new(&self.engine);
556
557        // TinyGo may require additional imports like syscall/js
558        // For now, we'll use the basic linker
559
560        // Instantiate the module
561        let instance = linker.instantiate(&mut store, &self.module).map_err(|e| {
562            PluginError::execution(format!("Failed to instantiate TinyGo module: {}", e))
563        })?;
564
565        // Store the runtime
566        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        // Call TinyGo WASM plugin's auth function
579        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        // Create simplified request for WASM (avoid non-serializable types)
614        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        // DataQuery should be serializable, but create simplified version to be safe
636        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
659// ============================================================================
660// AssemblyScript Runtime Adapter
661// ============================================================================
662
663/// WASM runtime adapter for plugins compiled from AssemblyScript
664pub struct AssemblyScriptAdapter {
665    plugin_id: PluginId,
666    engine: Arc<Engine>,
667    module: Module,
668    runtime: Mutex<Option<WasmRuntime>>,
669}
670
671impl AssemblyScriptAdapter {
672    /// Create a new AssemblyScript runtime adapter
673    ///
674    /// # Arguments
675    /// * `plugin_id` - Unique identifier for the plugin
676    /// * `wasm_bytes` - WebAssembly binary bytes from AssemblyScript compilation
677    ///
678    /// # Returns
679    /// `Ok(AssemblyScriptAdapter)` on success, `Err(PluginError)` if WASM module cannot be loaded
680    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    /// Helper to call an AssemblyScript WASM function with JSON input/output
695    /// AssemblyScript uses __new, __pin, __unpin for memory management
696    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        // Get memory
713        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        // AssemblyScript uses __new for allocation
721        // Signature: __new(size: usize, id: u32) -> usize
722        // For strings, id is typically 1
723        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        // Allocate memory for input (id=1 for string type)
734        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        // Pin the allocated memory to prevent GC
739        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        // Write input to WASM memory
746        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        // Call the plugin function
751        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        // Read output from WASM memory
767        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        // Unpin the allocated memory
773        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        // Parse output as JSON
781        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        // Create WASI context (AssemblyScript may use WASI features)
799        let wasi_ctx = WasiCtxBuilder::new().inherit_stderr().inherit_stdout().build();
800
801        // Create store
802        let mut store = Store::new(&self.engine, wasi_ctx);
803
804        // Create linker
805        let linker = Linker::new(&self.engine);
806
807        // AssemblyScript modules typically don't require special imports
808        // They use standard WASM with memory management functions
809
810        // Instantiate the module
811        let instance = linker.instantiate(&mut store, &self.module).map_err(|e| {
812            PluginError::execution(format!("Failed to instantiate AssemblyScript module: {}", e))
813        })?;
814
815        // Store the runtime
816        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        // Create simplified request for WASM (avoid non-serializable types)
863        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        // DataQuery should be serializable, but create simplified version to be safe
885        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
908// ============================================================================
909// Remote Runtime Adapter (HTTP/gRPC)
910// ============================================================================
911
912/// Runtime adapter for plugins running on remote servers (HTTP or gRPC)
913pub struct RemoteAdapter {
914    plugin_id: PluginId,
915    config: RemoteRuntimeConfig,
916    client: reqwest::Client,
917}
918
919impl RemoteAdapter {
920    /// Create a new remote runtime adapter
921    ///
922    /// # Arguments
923    /// * `plugin_id` - Unique identifier for the plugin
924    /// * `config` - Remote runtime configuration (URL, protocol, auth, etc.)
925    ///
926    /// # Returns
927    /// `Ok(RemoteAdapter)` on success, `Err(PluginError)` if HTTP client cannot be created
928    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        // Add authentication if configured
951        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        // Perform health check during initialization
990        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        // Parse the AuthResponse from the response
1012        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        // Create simplified request
1037        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        // Try to call health endpoint
1076        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        // Test with empty bytes (should default to Rust)
1108        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}