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#[derive(Debug, Clone, PartialEq, Eq)]
51pub enum RemoteProtocol {
52    Http,
53    Grpc,
54}
55
56#[derive(Debug, Clone, PartialEq, Eq)]
57pub struct RemoteAuthConfig {
58    /// Authentication type (bearer, api_key, etc.)
59    pub auth_type: String,
60    /// Authentication value
61    pub value: String,
62}
63
64/// Trait that all runtime adapters must implement
65///
66/// This allows different WASM runtimes and remote plugins to be used interchangeably
67#[async_trait]
68pub trait RuntimeAdapter: Send + Sync {
69    /// Get the runtime type
70    fn runtime_type(&self) -> RuntimeType;
71
72    /// Initialize the runtime (called once during plugin load)
73    async fn initialize(&mut self) -> Result<(), PluginError>;
74
75    /// Call authentication plugin
76    async fn call_auth(
77        &self,
78        context: &PluginContext,
79        request: &AuthRequest,
80    ) -> Result<AuthResponse, PluginError>;
81
82    /// Call template function plugin
83    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    /// Call response generator plugin
91    async fn call_response_generator(
92        &self,
93        context: &PluginContext,
94        request: &ResponseRequest,
95    ) -> Result<ResponseData, PluginError>;
96
97    /// Call data source query plugin
98    async fn call_datasource_query(
99        &self,
100        query: &DataQuery,
101        context: &PluginContext,
102    ) -> Result<DataResult, PluginError>;
103
104    /// Health check - returns true if the plugin is healthy
105    async fn health_check(&self) -> Result<bool, PluginError>;
106
107    /// Cleanup resources (called during plugin unload)
108    async fn cleanup(&mut self) -> Result<(), PluginError>;
109
110    /// Get runtime-specific metrics
111    fn get_metrics(&self) -> HashMap<String, serde_json::Value> {
112        HashMap::new()
113    }
114}
115
116/// Detect runtime type from WASM binary
117pub fn detect_runtime_type(wasm_bytes: &[u8]) -> Result<RuntimeType, PluginError> {
118    // Parse WASM module to detect runtime type
119    // This is a simplified version - real implementation would parse WASM sections
120
121    // Check for TinyGo signatures
122    if has_tinygo_signature(wasm_bytes) {
123        return Ok(RuntimeType::TinyGo);
124    }
125
126    // Check for AssemblyScript signatures
127    if has_assemblyscript_signature(wasm_bytes) {
128        return Ok(RuntimeType::AssemblyScript);
129    }
130
131    // Default to Rust
132    Ok(RuntimeType::Rust)
133}
134
135/// Check if WASM binary has TinyGo signature
136fn has_tinygo_signature(wasm_bytes: &[u8]) -> bool {
137    // Look for TinyGo-specific exports or custom sections
138    // TinyGo typically exports functions like "resume" and "getsp"
139    // This is a placeholder - real implementation would use wasmparser
140
141    // Simple heuristic: check for "tinygo" in custom sections
142    String::from_utf8_lossy(wasm_bytes).contains("tinygo")
143}
144
145/// Check if WASM binary has AssemblyScript signature
146fn has_assemblyscript_signature(wasm_bytes: &[u8]) -> bool {
147    // Look for AssemblyScript-specific exports or custom sections
148    // AS typically has "__new", "__pin", "__unpin" exports
149
150    // Simple heuristic: check for "assemblyscript" in custom sections
151    String::from_utf8_lossy(wasm_bytes).contains("assemblyscript")
152}
153
154/// Factory for creating runtime adapters
155pub struct RuntimeAdapterFactory;
156
157impl RuntimeAdapterFactory {
158    /// Create a runtime adapter for the given runtime type
159    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
175// ============================================================================
176// Rust Runtime Adapter (Existing Implementation)
177// ============================================================================
178
179pub struct RustAdapter {
180    plugin_id: PluginId,
181    engine: Arc<Engine>,
182    module: Module,
183    // Store and Instance need to be behind a Mutex since they're not Send/Sync
184    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    /// Helper to call a WASM function with JSON input/output
207    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        // Get memory and alloc function
224        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        // Allocate memory for input
237        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        // Write input to WASM memory
242        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        // Call the plugin function
247        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        // Read output from WASM memory
263        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        // Deallocate memory if dealloc function exists
269        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        // Parse output as JSON
277        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        // Initialize wasmtime instance with the Rust WASM module
293        tracing::info!("Initializing Rust plugin: {}", self.plugin_id);
294
295        // Create WASI context
296        let wasi_ctx = WasiCtxBuilder::new().inherit_stderr().inherit_stdout().build();
297
298        // Create store
299        let mut store = Store::new(&self.engine, wasi_ctx);
300
301        // Create linker
302        let linker = Linker::new(&self.engine);
303
304        // Instantiate the module
305        let instance = linker
306            .instantiate(&mut store, &self.module)
307            .map_err(|e| PluginError::execution(format!("Failed to instantiate module: {}", e)))?;
308
309        // Store the runtime
310        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        // Call Rust WASM plugin's auth function
323        // Note: AuthRequest contains axum::http types which might not serialize well
324        // So we create a simplified version for WASM
325        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        // Create simplified request for WASM (avoid non-serializable types)
360        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        // DataQuery should be serializable, but create simplified version to be safe
382        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
405// ============================================================================
406// TinyGo Runtime Adapter
407// ============================================================================
408
409pub 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    /// Helper to call a TinyGo WASM function with JSON input/output
432    /// TinyGo uses specific memory management and calling conventions
433    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        // Get memory (TinyGo always exports memory)
450        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        // TinyGo uses malloc instead of alloc
456        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        // Allocate memory for input
467        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        // Write input to WASM memory
472        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        // Call the plugin function
477        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        // Read output from WASM memory
493        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        // TinyGo uses free instead of dealloc
499        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        // Parse output as JSON
507        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        // Initialize wasmtime instance with TinyGo-specific configuration
523        // TinyGo requires special memory management and import handling
524        tracing::info!("Initializing TinyGo plugin: {}", self.plugin_id);
525
526        // Create WASI context (TinyGo supports WASI)
527        let wasi_ctx = WasiCtxBuilder::new().inherit_stderr().inherit_stdout().build();
528
529        // Create store
530        let mut store = Store::new(&self.engine, wasi_ctx);
531
532        // Create linker with TinyGo-specific imports
533        let linker = Linker::new(&self.engine);
534
535        // TinyGo may require additional imports like syscall/js
536        // For now, we'll use the basic linker
537
538        // Instantiate the module
539        let instance = linker.instantiate(&mut store, &self.module).map_err(|e| {
540            PluginError::execution(format!("Failed to instantiate TinyGo module: {}", e))
541        })?;
542
543        // Store the runtime
544        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        // Call TinyGo WASM plugin's auth function
557        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        // Create simplified request for WASM (avoid non-serializable types)
592        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        // DataQuery should be serializable, but create simplified version to be safe
614        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
637// ============================================================================
638// AssemblyScript Runtime Adapter
639// ============================================================================
640
641pub 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    /// Helper to call an AssemblyScript WASM function with JSON input/output
664    /// AssemblyScript uses __new, __pin, __unpin for memory management
665    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        // Get memory
682        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        // AssemblyScript uses __new for allocation
690        // Signature: __new(size: usize, id: u32) -> usize
691        // For strings, id is typically 1
692        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        // Allocate memory for input (id=1 for string type)
703        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        // Pin the allocated memory to prevent GC
708        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        // Write input to WASM memory
715        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        // Call the plugin function
720        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        // Read output from WASM memory
736        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        // Unpin the allocated memory
742        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        // Parse output as JSON
750        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        // Create WASI context (AssemblyScript may use WASI features)
768        let wasi_ctx = WasiCtxBuilder::new().inherit_stderr().inherit_stdout().build();
769
770        // Create store
771        let mut store = Store::new(&self.engine, wasi_ctx);
772
773        // Create linker
774        let linker = Linker::new(&self.engine);
775
776        // AssemblyScript modules typically don't require special imports
777        // They use standard WASM with memory management functions
778
779        // Instantiate the module
780        let instance = linker.instantiate(&mut store, &self.module).map_err(|e| {
781            PluginError::execution(format!("Failed to instantiate AssemblyScript module: {}", e))
782        })?;
783
784        // Store the runtime
785        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        // Create simplified request for WASM (avoid non-serializable types)
832        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        // DataQuery should be serializable, but create simplified version to be safe
854        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
877// ============================================================================
878// Remote Runtime Adapter (HTTP/gRPC)
879// ============================================================================
880
881pub 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        // Add authentication if configured
911        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        // Perform health check during initialization
950        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        // Parse the AuthResponse from the response
972        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        // Create simplified request
997        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        // Try to call health endpoint
1036        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        // Test with empty bytes (should default to Rust)
1068        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}