Skip to main content

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 wasmparser::{Parser, Payload};
20use wasmtime::{Engine, Instance, Linker, Module, Store};
21use wasmtime_wasi::{WasiCtx, WasiCtxBuilder};
22
23/// Enum representing different plugin runtime types
24#[derive(Debug, Clone, PartialEq, Eq)]
25pub enum RuntimeType {
26    /// Native Rust plugin compiled to WASM
27    Rust,
28    /// TinyGo compiled plugin
29    TinyGo,
30    /// AssemblyScript compiled plugin
31    AssemblyScript,
32    /// Remote plugin accessed via HTTP/gRPC
33    Remote(RemoteRuntimeConfig),
34}
35
36/// Configuration for remote plugin runtime
37#[derive(Debug, Clone, PartialEq, Eq)]
38pub struct RemoteRuntimeConfig {
39    /// Protocol to use (http or grpc)
40    pub protocol: RemoteProtocol,
41    /// Endpoint URL
42    pub endpoint: String,
43    /// Request timeout in milliseconds
44    pub timeout_ms: u64,
45    /// Maximum number of retries
46    pub max_retries: u32,
47    /// Authentication configuration
48    pub auth: Option<RemoteAuthConfig>,
49}
50
51/// Protocol for remote plugin communication
52#[derive(Debug, Clone, PartialEq, Eq)]
53pub enum RemoteProtocol {
54    /// HTTP/REST protocol
55    Http,
56    /// gRPC protocol
57    Grpc,
58}
59
60/// Authentication configuration for remote plugin runtime
61#[derive(Debug, Clone, PartialEq, Eq)]
62pub struct RemoteAuthConfig {
63    /// Authentication type (bearer, api_key, etc.)
64    pub auth_type: String,
65    /// Authentication value
66    pub value: String,
67}
68
69/// Trait that all runtime adapters must implement
70///
71/// This allows different WASM runtimes and remote plugins to be used interchangeably
72#[async_trait]
73pub trait RuntimeAdapter: Send + Sync {
74    /// Get the runtime type
75    fn runtime_type(&self) -> RuntimeType;
76
77    /// Initialize the runtime (called once during plugin load)
78    async fn initialize(&mut self) -> Result<(), PluginError>;
79
80    /// Call authentication plugin
81    async fn call_auth(
82        &self,
83        context: &PluginContext,
84        request: &AuthRequest,
85    ) -> Result<AuthResponse, PluginError>;
86
87    /// Call template function plugin
88    async fn call_template_function(
89        &self,
90        function_name: &str,
91        args: &[serde_json::Value],
92        context: &ResolutionContext,
93    ) -> Result<serde_json::Value, PluginError>;
94
95    /// Call response generator plugin
96    async fn call_response_generator(
97        &self,
98        context: &PluginContext,
99        request: &ResponseRequest,
100    ) -> Result<ResponseData, PluginError>;
101
102    /// Call data source query plugin
103    async fn call_datasource_query(
104        &self,
105        query: &DataQuery,
106        context: &PluginContext,
107    ) -> Result<DataResult, PluginError>;
108
109    /// Health check - returns true if the plugin is healthy
110    async fn health_check(&self) -> Result<bool, PluginError>;
111
112    /// Cleanup resources (called during plugin unload)
113    async fn cleanup(&mut self) -> Result<(), PluginError>;
114
115    /// Get runtime-specific metrics
116    fn get_metrics(&self) -> HashMap<String, serde_json::Value> {
117        HashMap::new()
118    }
119}
120
121/// Detect runtime type from WASM binary
122pub fn detect_runtime_type(wasm_bytes: &[u8]) -> Result<RuntimeType, PluginError> {
123    // Parse WASM module to detect runtime type
124    // This is a simplified version - real implementation would parse WASM sections
125
126    // Check for TinyGo signatures
127    if has_tinygo_signature(wasm_bytes) {
128        return Ok(RuntimeType::TinyGo);
129    }
130
131    // Check for AssemblyScript signatures
132    if has_assemblyscript_signature(wasm_bytes) {
133        return Ok(RuntimeType::AssemblyScript);
134    }
135
136    // Default to Rust
137    Ok(RuntimeType::Rust)
138}
139
140/// Check if WASM binary has TinyGo signature
141fn has_tinygo_signature(wasm_bytes: &[u8]) -> bool {
142    let (exports, custom_sections) = extract_wasm_signatures(wasm_bytes);
143
144    // TinyGo modules typically export these runtime helpers.
145    let has_runtime_exports = exports.contains("resume") && exports.contains("getsp");
146    let has_tinygo_custom = custom_sections.iter().any(|s| s.contains("tinygo"));
147
148    has_runtime_exports
149        || has_tinygo_custom
150        || String::from_utf8_lossy(wasm_bytes).contains("tinygo")
151}
152
153/// Check if WASM binary has AssemblyScript signature
154fn has_assemblyscript_signature(wasm_bytes: &[u8]) -> bool {
155    let (exports, custom_sections) = extract_wasm_signatures(wasm_bytes);
156
157    // AssemblyScript modules typically export allocation/pinning helpers.
158    let has_alloc_exports =
159        exports.contains("__new") && (exports.contains("__pin") || exports.contains("__unpin"));
160    let has_as_custom = custom_sections
161        .iter()
162        .any(|s| s.contains("assemblyscript") || s.contains("asc"));
163
164    has_alloc_exports
165        || has_as_custom
166        || String::from_utf8_lossy(wasm_bytes).contains("assemblyscript")
167}
168
169fn extract_wasm_signatures(wasm_bytes: &[u8]) -> (std::collections::HashSet<String>, Vec<String>) {
170    use std::collections::HashSet;
171
172    let mut exports = HashSet::new();
173    let mut custom_sections = Vec::new();
174
175    for payload in Parser::new(0).parse_all(wasm_bytes) {
176        match payload {
177            Ok(Payload::ExportSection(section)) => {
178                for export in section.into_iter().flatten() {
179                    exports.insert(export.name.to_string());
180                }
181            }
182            Ok(Payload::CustomSection(section)) => {
183                custom_sections.push(section.name().to_string());
184            }
185            Ok(_) => {}
186            Err(_) => break,
187        }
188    }
189
190    (exports, custom_sections)
191}
192
193/// Factory for creating runtime adapters
194pub struct RuntimeAdapterFactory;
195
196impl RuntimeAdapterFactory {
197    /// Create a runtime adapter for the given runtime type
198    pub fn create(
199        runtime_type: RuntimeType,
200        plugin_id: PluginId,
201        wasm_bytes: Vec<u8>,
202    ) -> Result<Box<dyn RuntimeAdapter>, PluginError> {
203        match runtime_type {
204            RuntimeType::Rust => Ok(Box::new(RustAdapter::new(plugin_id, wasm_bytes)?)),
205            RuntimeType::TinyGo => Ok(Box::new(TinyGoAdapter::new(plugin_id, wasm_bytes)?)),
206            RuntimeType::AssemblyScript => {
207                Ok(Box::new(AssemblyScriptAdapter::new(plugin_id, wasm_bytes)?))
208            }
209            RuntimeType::Remote(config) => Ok(Box::new(RemoteAdapter::new(plugin_id, config)?)),
210        }
211    }
212}
213
214// ============================================================================
215// Rust Runtime Adapter (Existing Implementation)
216// ============================================================================
217
218/// WASM runtime adapter for plugins compiled from Rust
219pub struct RustAdapter {
220    plugin_id: PluginId,
221    engine: Arc<Engine>,
222    module: Module,
223    // Store and Instance need to be behind a Mutex since they're not Send/Sync
224    runtime: Mutex<Option<WasmRuntime>>,
225}
226
227struct WasmRuntime {
228    store: Store<WasiCtx>,
229    instance: Instance,
230}
231
232impl RustAdapter {
233    /// Create a new Rust runtime adapter
234    ///
235    /// # Arguments
236    /// * `plugin_id` - Unique identifier for the plugin
237    /// * `wasm_bytes` - WebAssembly binary bytes
238    ///
239    /// # Returns
240    /// `Ok(RustAdapter)` on success, `Err(PluginError)` if WASM module cannot be loaded
241    pub fn new(plugin_id: PluginId, wasm_bytes: Vec<u8>) -> Result<Self, PluginError> {
242        let engine = Arc::new(Engine::default());
243        let module = Module::from_binary(&engine, &wasm_bytes)
244            .map_err(|e| PluginError::execution(format!("Failed to load WASM module: {}", e)))?;
245
246        Ok(Self {
247            plugin_id,
248            engine,
249            module,
250            runtime: Mutex::new(None),
251        })
252    }
253
254    /// Helper to call a WASM function with JSON input/output
255    fn call_wasm_json(
256        &self,
257        function_name: &str,
258        input_data: serde_json::Value,
259    ) -> Result<serde_json::Value, PluginError> {
260        let mut runtime_guard =
261            self.runtime.lock().unwrap_or_else(|poisoned| poisoned.into_inner());
262        let runtime = runtime_guard.as_mut().ok_or_else(|| {
263            PluginError::execution("Runtime not initialized. Call initialize() first.".to_string())
264        })?;
265
266        let input_json = serde_json::to_string(&input_data)
267            .map_err(|e| PluginError::execution(format!("Failed to serialize input: {}", e)))?;
268
269        let input_bytes = input_json.as_bytes();
270        let input_len = input_bytes.len() as i32;
271
272        // Get memory and alloc function
273        let memory =
274            runtime.instance.get_memory(&mut runtime.store, "memory").ok_or_else(|| {
275                PluginError::execution("WASM module must export 'memory'".to_string())
276            })?;
277
278        let alloc_func = runtime
279            .instance
280            .get_typed_func::<i32, i32>(&mut runtime.store, "alloc")
281            .map_err(|e| {
282            PluginError::execution(format!("Failed to get alloc function: {}", e))
283        })?;
284
285        // Allocate memory for input
286        let input_ptr = alloc_func
287            .call(&mut runtime.store, input_len)
288            .map_err(|e| PluginError::execution(format!("Failed to allocate memory: {}", e)))?;
289
290        // Write input to WASM memory
291        memory
292            .write(&mut runtime.store, input_ptr as usize, input_bytes)
293            .map_err(|e| PluginError::execution(format!("Failed to write input: {}", e)))?;
294
295        // Call the plugin function
296        let plugin_func = runtime
297            .instance
298            .get_typed_func::<(i32, i32), (i32, i32)>(&mut runtime.store, function_name)
299            .map_err(|e| {
300                PluginError::execution(format!("Function '{}' not found: {}", function_name, e))
301            })?;
302
303        let (output_ptr, output_len) =
304            plugin_func.call(&mut runtime.store, (input_ptr, input_len)).map_err(|e| {
305                PluginError::execution(format!(
306                    "Failed to call function '{}': {}",
307                    function_name, e
308                ))
309            })?;
310
311        // Read output from WASM memory
312        let mut output_bytes = vec![0u8; output_len as usize];
313        memory
314            .read(&runtime.store, output_ptr as usize, &mut output_bytes)
315            .map_err(|e| PluginError::execution(format!("Failed to read output: {}", e)))?;
316
317        // Deallocate memory if dealloc function exists
318        if let Ok(dealloc_func) =
319            runtime.instance.get_typed_func::<(i32, i32), ()>(&mut runtime.store, "dealloc")
320        {
321            let _ = dealloc_func.call(&mut runtime.store, (input_ptr, input_len));
322            let _ = dealloc_func.call(&mut runtime.store, (output_ptr, output_len));
323        }
324
325        // Parse output as JSON
326        let output_str = String::from_utf8(output_bytes)
327            .map_err(|e| PluginError::execution(format!("Failed to decode output: {}", e)))?;
328
329        serde_json::from_str(&output_str)
330            .map_err(|e| PluginError::execution(format!("Failed to parse output JSON: {}", e)))
331    }
332}
333
334#[async_trait]
335impl RuntimeAdapter for RustAdapter {
336    fn runtime_type(&self) -> RuntimeType {
337        RuntimeType::Rust
338    }
339
340    async fn initialize(&mut self) -> Result<(), PluginError> {
341        // Initialize wasmtime instance with the Rust WASM module
342        tracing::info!("Initializing Rust plugin: {}", self.plugin_id);
343
344        // Create WASI context
345        let wasi_ctx = WasiCtxBuilder::new().inherit_stderr().inherit_stdout().build();
346
347        // Create store
348        let mut store = Store::new(&self.engine, wasi_ctx);
349
350        // Create linker
351        let linker = Linker::new(&self.engine);
352
353        // Instantiate the module
354        let instance = linker
355            .instantiate(&mut store, &self.module)
356            .map_err(|e| PluginError::execution(format!("Failed to instantiate module: {}", e)))?;
357
358        // Store the runtime
359        let mut runtime_guard =
360            self.runtime.lock().unwrap_or_else(|poisoned| poisoned.into_inner());
361        *runtime_guard = Some(WasmRuntime { store, instance });
362
363        tracing::info!("Successfully initialized Rust plugin: {}", self.plugin_id);
364        Ok(())
365    }
366
367    async fn call_auth(
368        &self,
369        context: &PluginContext,
370        request: &AuthRequest,
371    ) -> Result<AuthResponse, PluginError> {
372        // Call Rust WASM plugin's auth function
373        // Note: AuthRequest contains axum::http types which might not serialize well
374        // So we create a simplified version for WASM
375        let input = serde_json::json!({
376            "context": context,
377            "method": request.method.to_string(),
378            "uri": request.uri.to_string(),
379            "query_params": request.query_params,
380            "client_ip": request.client_ip,
381            "user_agent": request.user_agent,
382        });
383
384        let result = self.call_wasm_json("authenticate", input)?;
385        serde_json::from_value(result)
386            .map_err(|e| PluginError::execution(format!("Failed to parse AuthResponse: {}", e)))
387    }
388
389    async fn call_template_function(
390        &self,
391        function_name: &str,
392        args: &[serde_json::Value],
393        context: &ResolutionContext,
394    ) -> Result<serde_json::Value, PluginError> {
395        let input = serde_json::json!({
396            "function_name": function_name,
397            "args": args,
398            "context": context,
399        });
400
401        self.call_wasm_json("template_function", input)
402    }
403
404    async fn call_response_generator(
405        &self,
406        context: &PluginContext,
407        request: &ResponseRequest,
408    ) -> Result<ResponseData, PluginError> {
409        // Create simplified request for WASM (avoid non-serializable types)
410        let input = serde_json::json!({
411            "context": context,
412            "method": request.method.to_string(),
413            "uri": request.uri,
414            "path": request.path,
415            "query_params": request.query_params,
416            "path_params": request.path_params,
417            "client_ip": request.client_ip,
418            "user_agent": request.user_agent,
419        });
420
421        let result = self.call_wasm_json("generate_response", input)?;
422        serde_json::from_value(result)
423            .map_err(|e| PluginError::execution(format!("Failed to parse ResponseData: {}", e)))
424    }
425
426    async fn call_datasource_query(
427        &self,
428        query: &DataQuery,
429        context: &PluginContext,
430    ) -> Result<DataResult, PluginError> {
431        // DataQuery should be serializable, but create simplified version to be safe
432        let input = serde_json::json!({
433            "query_type": format!("{:?}", query.query_type),
434            "query": query.query,
435            "parameters": query.parameters,
436            "limit": query.limit,
437            "offset": query.offset,
438            "context": context,
439        });
440
441        let result = self.call_wasm_json("query_datasource", input)?;
442        serde_json::from_value(result)
443            .map_err(|e| PluginError::execution(format!("Failed to parse DataResult: {}", e)))
444    }
445
446    async fn health_check(&self) -> Result<bool, PluginError> {
447        Ok(true)
448    }
449
450    async fn cleanup(&mut self) -> Result<(), PluginError> {
451        Ok(())
452    }
453}
454
455// ============================================================================
456// TinyGo Runtime Adapter
457// ============================================================================
458
459/// WASM runtime adapter for plugins compiled from TinyGo
460pub struct TinyGoAdapter {
461    plugin_id: PluginId,
462    engine: Arc<Engine>,
463    module: Module,
464    runtime: Mutex<Option<WasmRuntime>>,
465}
466
467impl TinyGoAdapter {
468    /// Create a new TinyGo runtime adapter
469    ///
470    /// # Arguments
471    /// * `plugin_id` - Unique identifier for the plugin
472    /// * `wasm_bytes` - WebAssembly binary bytes from TinyGo compilation
473    ///
474    /// # Returns
475    /// `Ok(TinyGoAdapter)` on success, `Err(PluginError)` if WASM module cannot be loaded
476    pub fn new(plugin_id: PluginId, wasm_bytes: Vec<u8>) -> Result<Self, PluginError> {
477        let engine = Arc::new(Engine::default());
478        let module = Module::from_binary(&engine, &wasm_bytes).map_err(|e| {
479            PluginError::execution(format!("Failed to load TinyGo WASM module: {}", e))
480        })?;
481
482        Ok(Self {
483            plugin_id,
484            engine,
485            module,
486            runtime: Mutex::new(None),
487        })
488    }
489
490    /// Helper to call a TinyGo WASM function with JSON input/output
491    /// TinyGo uses specific memory management and calling conventions
492    fn call_wasm_json(
493        &self,
494        function_name: &str,
495        input_data: serde_json::Value,
496    ) -> Result<serde_json::Value, PluginError> {
497        let mut runtime_guard =
498            self.runtime.lock().unwrap_or_else(|poisoned| poisoned.into_inner());
499        let runtime = runtime_guard.as_mut().ok_or_else(|| {
500            PluginError::execution("Runtime not initialized. Call initialize() first.".to_string())
501        })?;
502
503        let input_json = serde_json::to_string(&input_data)
504            .map_err(|e| PluginError::execution(format!("Failed to serialize input: {}", e)))?;
505
506        let input_bytes = input_json.as_bytes();
507        let input_len = input_bytes.len() as i32;
508
509        // Get memory (TinyGo always exports memory)
510        let memory =
511            runtime.instance.get_memory(&mut runtime.store, "memory").ok_or_else(|| {
512                PluginError::execution("TinyGo WASM module must export 'memory'".to_string())
513            })?;
514
515        // TinyGo uses malloc instead of alloc
516        let malloc_func = runtime
517            .instance
518            .get_typed_func::<i32, i32>(&mut runtime.store, "malloc")
519            .map_err(|e| {
520                PluginError::execution(format!(
521                    "Failed to get malloc function (TinyGo specific): {}",
522                    e
523                ))
524            })?;
525
526        // Allocate memory for input
527        let input_ptr = malloc_func
528            .call(&mut runtime.store, input_len)
529            .map_err(|e| PluginError::execution(format!("Failed to allocate memory: {}", e)))?;
530
531        // Write input to WASM memory
532        memory
533            .write(&mut runtime.store, input_ptr as usize, input_bytes)
534            .map_err(|e| PluginError::execution(format!("Failed to write input: {}", e)))?;
535
536        // Call the plugin function
537        let plugin_func = runtime
538            .instance
539            .get_typed_func::<(i32, i32), (i32, i32)>(&mut runtime.store, function_name)
540            .map_err(|e| {
541                PluginError::execution(format!("Function '{}' not found: {}", function_name, e))
542            })?;
543
544        let (output_ptr, output_len) =
545            plugin_func.call(&mut runtime.store, (input_ptr, input_len)).map_err(|e| {
546                PluginError::execution(format!(
547                    "Failed to call function '{}': {}",
548                    function_name, e
549                ))
550            })?;
551
552        // Read output from WASM memory
553        let mut output_bytes = vec![0u8; output_len as usize];
554        memory
555            .read(&runtime.store, output_ptr as usize, &mut output_bytes)
556            .map_err(|e| PluginError::execution(format!("Failed to read output: {}", e)))?;
557
558        // TinyGo uses free instead of dealloc
559        if let Ok(free_func) =
560            runtime.instance.get_typed_func::<i32, ()>(&mut runtime.store, "free")
561        {
562            let _ = free_func.call(&mut runtime.store, input_ptr);
563            let _ = free_func.call(&mut runtime.store, output_ptr);
564        }
565
566        // Parse output as JSON
567        let output_str = String::from_utf8(output_bytes)
568            .map_err(|e| PluginError::execution(format!("Failed to decode output: {}", e)))?;
569
570        serde_json::from_str(&output_str)
571            .map_err(|e| PluginError::execution(format!("Failed to parse output JSON: {}", e)))
572    }
573}
574
575#[async_trait]
576impl RuntimeAdapter for TinyGoAdapter {
577    fn runtime_type(&self) -> RuntimeType {
578        RuntimeType::TinyGo
579    }
580
581    async fn initialize(&mut self) -> Result<(), PluginError> {
582        // Initialize wasmtime instance with TinyGo-specific configuration
583        // TinyGo requires special memory management and import handling
584        tracing::info!("Initializing TinyGo plugin: {}", self.plugin_id);
585
586        // Create WASI context (TinyGo supports WASI)
587        let wasi_ctx = WasiCtxBuilder::new().inherit_stderr().inherit_stdout().build();
588
589        // Create store
590        let mut store = Store::new(&self.engine, wasi_ctx);
591
592        // Create linker with TinyGo-specific imports
593        let linker = Linker::new(&self.engine);
594
595        // TinyGo may require additional imports like syscall/js
596        // For now, we'll use the basic linker
597
598        // Instantiate the module
599        let instance = linker.instantiate(&mut store, &self.module).map_err(|e| {
600            PluginError::execution(format!("Failed to instantiate TinyGo module: {}", e))
601        })?;
602
603        // Store the runtime
604        let mut runtime_guard =
605            self.runtime.lock().unwrap_or_else(|poisoned| poisoned.into_inner());
606        *runtime_guard = Some(WasmRuntime { store, instance });
607
608        tracing::info!("Successfully initialized TinyGo plugin: {}", self.plugin_id);
609        Ok(())
610    }
611
612    async fn call_auth(
613        &self,
614        context: &PluginContext,
615        request: &AuthRequest,
616    ) -> Result<AuthResponse, PluginError> {
617        // Call TinyGo WASM plugin's auth function
618        let input = serde_json::json!({
619            "context": context,
620            "method": request.method.to_string(),
621            "uri": request.uri.to_string(),
622            "query_params": request.query_params,
623            "client_ip": request.client_ip,
624            "user_agent": request.user_agent,
625        });
626
627        let result = self.call_wasm_json("authenticate", input)?;
628        serde_json::from_value(result)
629            .map_err(|e| PluginError::execution(format!("Failed to parse AuthResponse: {}", e)))
630    }
631
632    async fn call_template_function(
633        &self,
634        function_name: &str,
635        args: &[serde_json::Value],
636        context: &ResolutionContext,
637    ) -> Result<serde_json::Value, PluginError> {
638        let input = serde_json::json!({
639            "function_name": function_name,
640            "args": args,
641            "context": context,
642        });
643
644        self.call_wasm_json("template_function", input)
645    }
646
647    async fn call_response_generator(
648        &self,
649        context: &PluginContext,
650        request: &ResponseRequest,
651    ) -> Result<ResponseData, PluginError> {
652        // Create simplified request for WASM (avoid non-serializable types)
653        let input = serde_json::json!({
654            "context": context,
655            "method": request.method.to_string(),
656            "uri": request.uri,
657            "path": request.path,
658            "query_params": request.query_params,
659            "path_params": request.path_params,
660            "client_ip": request.client_ip,
661            "user_agent": request.user_agent,
662        });
663
664        let result = self.call_wasm_json("generate_response", input)?;
665        serde_json::from_value(result)
666            .map_err(|e| PluginError::execution(format!("Failed to parse ResponseData: {}", e)))
667    }
668
669    async fn call_datasource_query(
670        &self,
671        query: &DataQuery,
672        context: &PluginContext,
673    ) -> Result<DataResult, PluginError> {
674        // DataQuery should be serializable, but create simplified version to be safe
675        let input = serde_json::json!({
676            "query_type": format!("{:?}", query.query_type),
677            "query": query.query,
678            "parameters": query.parameters,
679            "limit": query.limit,
680            "offset": query.offset,
681            "context": context,
682        });
683
684        let result = self.call_wasm_json("query_datasource", input)?;
685        serde_json::from_value(result)
686            .map_err(|e| PluginError::execution(format!("Failed to parse DataResult: {}", e)))
687    }
688
689    async fn health_check(&self) -> Result<bool, PluginError> {
690        Ok(true)
691    }
692
693    async fn cleanup(&mut self) -> Result<(), PluginError> {
694        Ok(())
695    }
696}
697
698// ============================================================================
699// AssemblyScript Runtime Adapter
700// ============================================================================
701
702/// WASM runtime adapter for plugins compiled from AssemblyScript
703pub struct AssemblyScriptAdapter {
704    plugin_id: PluginId,
705    engine: Arc<Engine>,
706    module: Module,
707    runtime: Mutex<Option<WasmRuntime>>,
708}
709
710impl AssemblyScriptAdapter {
711    /// Create a new AssemblyScript runtime adapter
712    ///
713    /// # Arguments
714    /// * `plugin_id` - Unique identifier for the plugin
715    /// * `wasm_bytes` - WebAssembly binary bytes from AssemblyScript compilation
716    ///
717    /// # Returns
718    /// `Ok(AssemblyScriptAdapter)` on success, `Err(PluginError)` if WASM module cannot be loaded
719    pub fn new(plugin_id: PluginId, wasm_bytes: Vec<u8>) -> Result<Self, PluginError> {
720        let engine = Arc::new(Engine::default());
721        let module = Module::from_binary(&engine, &wasm_bytes).map_err(|e| {
722            PluginError::execution(format!("Failed to load AssemblyScript WASM module: {}", e))
723        })?;
724
725        Ok(Self {
726            plugin_id,
727            engine,
728            module,
729            runtime: Mutex::new(None),
730        })
731    }
732
733    /// Helper to call an AssemblyScript WASM function with JSON input/output
734    /// AssemblyScript uses __new, __pin, __unpin for memory management
735    fn call_wasm_json(
736        &self,
737        function_name: &str,
738        input_data: serde_json::Value,
739    ) -> Result<serde_json::Value, PluginError> {
740        let mut runtime_guard =
741            self.runtime.lock().unwrap_or_else(|poisoned| poisoned.into_inner());
742        let runtime = runtime_guard.as_mut().ok_or_else(|| {
743            PluginError::execution("Runtime not initialized. Call initialize() first.".to_string())
744        })?;
745
746        let input_json = serde_json::to_string(&input_data)
747            .map_err(|e| PluginError::execution(format!("Failed to serialize input: {}", e)))?;
748
749        let input_bytes = input_json.as_bytes();
750        let input_len = input_bytes.len() as i32;
751
752        // Get memory
753        let memory =
754            runtime.instance.get_memory(&mut runtime.store, "memory").ok_or_else(|| {
755                PluginError::execution(
756                    "AssemblyScript WASM module must export 'memory'".to_string(),
757                )
758            })?;
759
760        // AssemblyScript uses __new for allocation
761        // Signature: __new(size: usize, id: u32) -> usize
762        // For strings, id is typically 1
763        let new_func = runtime
764            .instance
765            .get_typed_func::<(i32, i32), i32>(&mut runtime.store, "__new")
766            .map_err(|e| {
767                PluginError::execution(format!(
768                    "Failed to get __new function (AssemblyScript specific): {}",
769                    e
770                ))
771            })?;
772
773        // Allocate memory for input (id=1 for string type)
774        let input_ptr = new_func
775            .call(&mut runtime.store, (input_len, 1))
776            .map_err(|e| PluginError::execution(format!("Failed to allocate memory: {}", e)))?;
777
778        // Pin the allocated memory to prevent GC
779        if let Ok(pin_func) =
780            runtime.instance.get_typed_func::<i32, i32>(&mut runtime.store, "__pin")
781        {
782            let _ = pin_func.call(&mut runtime.store, input_ptr);
783        }
784
785        // Write input to WASM memory
786        memory
787            .write(&mut runtime.store, input_ptr as usize, input_bytes)
788            .map_err(|e| PluginError::execution(format!("Failed to write input: {}", e)))?;
789
790        // Call the plugin function
791        let plugin_func = runtime
792            .instance
793            .get_typed_func::<(i32, i32), (i32, i32)>(&mut runtime.store, function_name)
794            .map_err(|e| {
795                PluginError::execution(format!("Function '{}' not found: {}", function_name, e))
796            })?;
797
798        let (output_ptr, output_len) =
799            plugin_func.call(&mut runtime.store, (input_ptr, input_len)).map_err(|e| {
800                PluginError::execution(format!(
801                    "Failed to call function '{}': {}",
802                    function_name, e
803                ))
804            })?;
805
806        // Read output from WASM memory
807        let mut output_bytes = vec![0u8; output_len as usize];
808        memory
809            .read(&runtime.store, output_ptr as usize, &mut output_bytes)
810            .map_err(|e| PluginError::execution(format!("Failed to read output: {}", e)))?;
811
812        // Unpin the allocated memory
813        if let Ok(unpin_func) =
814            runtime.instance.get_typed_func::<i32, ()>(&mut runtime.store, "__unpin")
815        {
816            let _ = unpin_func.call(&mut runtime.store, input_ptr);
817            let _ = unpin_func.call(&mut runtime.store, output_ptr);
818        }
819
820        // Parse output as JSON
821        let output_str = String::from_utf8(output_bytes)
822            .map_err(|e| PluginError::execution(format!("Failed to decode output: {}", e)))?;
823
824        serde_json::from_str(&output_str)
825            .map_err(|e| PluginError::execution(format!("Failed to parse output JSON: {}", e)))
826    }
827}
828
829#[async_trait]
830impl RuntimeAdapter for AssemblyScriptAdapter {
831    fn runtime_type(&self) -> RuntimeType {
832        RuntimeType::AssemblyScript
833    }
834
835    async fn initialize(&mut self) -> Result<(), PluginError> {
836        tracing::info!("Initializing AssemblyScript plugin: {}", self.plugin_id);
837
838        // Create WASI context (AssemblyScript may use WASI features)
839        let wasi_ctx = WasiCtxBuilder::new().inherit_stderr().inherit_stdout().build();
840
841        // Create store
842        let mut store = Store::new(&self.engine, wasi_ctx);
843
844        // Create linker
845        let linker = Linker::new(&self.engine);
846
847        // AssemblyScript modules typically don't require special imports
848        // They use standard WASM with memory management functions
849
850        // Instantiate the module
851        let instance = linker.instantiate(&mut store, &self.module).map_err(|e| {
852            PluginError::execution(format!("Failed to instantiate AssemblyScript module: {}", e))
853        })?;
854
855        // Store the runtime
856        let mut runtime_guard =
857            self.runtime.lock().unwrap_or_else(|poisoned| poisoned.into_inner());
858        *runtime_guard = Some(WasmRuntime { store, instance });
859
860        tracing::info!("Successfully initialized AssemblyScript plugin: {}", self.plugin_id);
861        Ok(())
862    }
863
864    async fn call_auth(
865        &self,
866        context: &PluginContext,
867        request: &AuthRequest,
868    ) -> Result<AuthResponse, PluginError> {
869        let input = serde_json::json!({
870            "context": context,
871            "method": request.method.to_string(),
872            "uri": request.uri.to_string(),
873            "query_params": request.query_params,
874            "client_ip": request.client_ip,
875            "user_agent": request.user_agent,
876        });
877
878        let result = self.call_wasm_json("authenticate", input)?;
879        serde_json::from_value(result)
880            .map_err(|e| PluginError::execution(format!("Failed to parse AuthResponse: {}", e)))
881    }
882
883    async fn call_template_function(
884        &self,
885        function_name: &str,
886        args: &[serde_json::Value],
887        context: &ResolutionContext,
888    ) -> Result<serde_json::Value, PluginError> {
889        let input = serde_json::json!({
890            "function_name": function_name,
891            "args": args,
892            "context": context,
893        });
894
895        self.call_wasm_json("template_function", input)
896    }
897
898    async fn call_response_generator(
899        &self,
900        context: &PluginContext,
901        request: &ResponseRequest,
902    ) -> Result<ResponseData, PluginError> {
903        // Create simplified request for WASM (avoid non-serializable types)
904        let input = serde_json::json!({
905            "context": context,
906            "method": request.method.to_string(),
907            "uri": request.uri,
908            "path": request.path,
909            "query_params": request.query_params,
910            "path_params": request.path_params,
911            "client_ip": request.client_ip,
912            "user_agent": request.user_agent,
913        });
914
915        let result = self.call_wasm_json("generate_response", input)?;
916        serde_json::from_value(result)
917            .map_err(|e| PluginError::execution(format!("Failed to parse ResponseData: {}", e)))
918    }
919
920    async fn call_datasource_query(
921        &self,
922        query: &DataQuery,
923        context: &PluginContext,
924    ) -> Result<DataResult, PluginError> {
925        // DataQuery should be serializable, but create simplified version to be safe
926        let input = serde_json::json!({
927            "query_type": format!("{:?}", query.query_type),
928            "query": query.query,
929            "parameters": query.parameters,
930            "limit": query.limit,
931            "offset": query.offset,
932            "context": context,
933        });
934
935        let result = self.call_wasm_json("query_datasource", input)?;
936        serde_json::from_value(result)
937            .map_err(|e| PluginError::execution(format!("Failed to parse DataResult: {}", e)))
938    }
939
940    async fn health_check(&self) -> Result<bool, PluginError> {
941        Ok(true)
942    }
943
944    async fn cleanup(&mut self) -> Result<(), PluginError> {
945        Ok(())
946    }
947}
948
949// ============================================================================
950// Remote Runtime Adapter (HTTP/gRPC)
951// ============================================================================
952
953/// Runtime adapter for plugins running on remote servers (HTTP or gRPC)
954pub struct RemoteAdapter {
955    plugin_id: PluginId,
956    config: RemoteRuntimeConfig,
957    client: reqwest::Client,
958}
959
960impl RemoteAdapter {
961    /// Create a new remote runtime adapter
962    ///
963    /// # Arguments
964    /// * `plugin_id` - Unique identifier for the plugin
965    /// * `config` - Remote runtime configuration (URL, protocol, auth, etc.)
966    ///
967    /// # Returns
968    /// `Ok(RemoteAdapter)` on success, `Err(PluginError)` if HTTP client cannot be created
969    pub fn new(plugin_id: PluginId, config: RemoteRuntimeConfig) -> Result<Self, PluginError> {
970        let client = reqwest::Client::builder()
971            .timeout(std::time::Duration::from_millis(config.timeout_ms))
972            .build()
973            .map_err(|e| PluginError::execution(format!("Failed to create HTTP client: {}", e)))?;
974
975        Ok(Self {
976            plugin_id,
977            config,
978            client,
979        })
980    }
981
982    async fn call_remote_plugin(
983        &self,
984        endpoint: &str,
985        body: serde_json::Value,
986    ) -> Result<serde_json::Value, PluginError> {
987        let url = format!("{}{}", self.config.endpoint, endpoint);
988
989        let mut request = self.client.post(&url).json(&body);
990
991        // Add authentication if configured
992        if let Some(auth) = &self.config.auth {
993            request = match auth.auth_type.as_str() {
994                "bearer" => request.bearer_auth(&auth.value),
995                "api_key" => request.header("X-API-Key", &auth.value),
996                _ => request,
997            };
998        }
999
1000        let response = request
1001            .send()
1002            .await
1003            .map_err(|e| PluginError::execution(format!("Remote plugin call failed: {}", e)))?;
1004
1005        if !response.status().is_success() {
1006            return Err(PluginError::execution(format!(
1007                "Remote plugin returned error status: {}",
1008                response.status()
1009            )));
1010        }
1011
1012        let result: serde_json::Value = response
1013            .json()
1014            .await
1015            .map_err(|e| PluginError::execution(format!("Failed to parse response: {}", e)))?;
1016
1017        Ok(result)
1018    }
1019}
1020
1021#[async_trait]
1022impl RuntimeAdapter for RemoteAdapter {
1023    fn runtime_type(&self) -> RuntimeType {
1024        RuntimeType::Remote(self.config.clone())
1025    }
1026
1027    async fn initialize(&mut self) -> Result<(), PluginError> {
1028        tracing::info!("Initializing remote plugin: {}", self.plugin_id);
1029
1030        // Perform health check during initialization
1031        self.health_check().await?;
1032
1033        Ok(())
1034    }
1035
1036    async fn call_auth(
1037        &self,
1038        context: &PluginContext,
1039        request: &AuthRequest,
1040    ) -> Result<AuthResponse, PluginError> {
1041        let body = serde_json::json!({
1042            "context": context,
1043            "method": request.method.to_string(),
1044            "uri": request.uri.to_string(),
1045            "query_params": request.query_params,
1046            "client_ip": request.client_ip,
1047            "user_agent": request.user_agent,
1048        });
1049
1050        let result = self.call_remote_plugin("/plugin/authenticate", body).await?;
1051
1052        // Parse the AuthResponse from the response
1053        serde_json::from_value(result)
1054            .map_err(|e| PluginError::execution(format!("Failed to parse AuthResponse: {}", e)))
1055    }
1056
1057    async fn call_template_function(
1058        &self,
1059        function_name: &str,
1060        args: &[serde_json::Value],
1061        context: &ResolutionContext,
1062    ) -> Result<serde_json::Value, PluginError> {
1063        let body = serde_json::json!({
1064            "function_name": function_name,
1065            "args": args,
1066            "context": context,
1067        });
1068
1069        self.call_remote_plugin("/plugin/template/execute", body).await
1070    }
1071
1072    async fn call_response_generator(
1073        &self,
1074        context: &PluginContext,
1075        request: &ResponseRequest,
1076    ) -> Result<ResponseData, PluginError> {
1077        // Create simplified request
1078        let body = serde_json::json!({
1079            "context": context,
1080            "method": request.method.to_string(),
1081            "uri": request.uri,
1082            "path": request.path,
1083            "query_params": request.query_params,
1084            "path_params": request.path_params,
1085            "client_ip": request.client_ip,
1086            "user_agent": request.user_agent,
1087        });
1088
1089        let result = self.call_remote_plugin("/plugin/response/generate", body).await?;
1090
1091        serde_json::from_value(result)
1092            .map_err(|e| PluginError::execution(format!("Failed to parse ResponseData: {}", e)))
1093    }
1094
1095    async fn call_datasource_query(
1096        &self,
1097        query: &DataQuery,
1098        context: &PluginContext,
1099    ) -> Result<DataResult, PluginError> {
1100        let body = serde_json::json!({
1101            "query_type": format!("{:?}", query.query_type),
1102            "query": query.query,
1103            "parameters": query.parameters,
1104            "limit": query.limit,
1105            "offset": query.offset,
1106            "context": context,
1107        });
1108
1109        let result = self.call_remote_plugin("/plugin/datasource/query", body).await?;
1110
1111        serde_json::from_value(result)
1112            .map_err(|e| PluginError::execution(format!("Failed to parse DataResult: {}", e)))
1113    }
1114
1115    async fn health_check(&self) -> Result<bool, PluginError> {
1116        // Try to call health endpoint
1117        let url = format!("{}/health", self.config.endpoint);
1118
1119        match self.client.get(&url).send().await {
1120            Ok(response) => Ok(response.status().is_success()),
1121            Err(_) => Ok(false),
1122        }
1123    }
1124
1125    async fn cleanup(&mut self) -> Result<(), PluginError> {
1126        tracing::info!("Cleaning up remote plugin: {}", self.plugin_id);
1127        Ok(())
1128    }
1129
1130    fn get_metrics(&self) -> HashMap<String, serde_json::Value> {
1131        let mut metrics = HashMap::new();
1132        metrics.insert("plugin_id".to_string(), serde_json::json!(self.plugin_id.as_str()));
1133        metrics.insert("endpoint".to_string(), serde_json::json!(self.config.endpoint));
1134        metrics.insert(
1135            "protocol".to_string(),
1136            serde_json::json!(format!("{:?}", self.config.protocol)),
1137        );
1138        metrics
1139    }
1140}
1141
1142#[cfg(test)]
1143mod tests {
1144    use super::*;
1145
1146    // ===== RuntimeType Tests =====
1147
1148    #[test]
1149    fn test_runtime_type_equality() {
1150        assert_eq!(RuntimeType::Rust, RuntimeType::Rust);
1151        assert_eq!(RuntimeType::TinyGo, RuntimeType::TinyGo);
1152        assert_eq!(RuntimeType::AssemblyScript, RuntimeType::AssemblyScript);
1153
1154        assert_ne!(RuntimeType::Rust, RuntimeType::TinyGo);
1155    }
1156
1157    #[test]
1158    fn test_runtime_type_clone() {
1159        let rt = RuntimeType::Rust;
1160        let cloned = rt.clone();
1161        assert_eq!(rt, cloned);
1162    }
1163
1164    #[test]
1165    fn test_runtime_type_detection() {
1166        // Test with empty bytes (should default to Rust)
1167        let empty_bytes = vec![];
1168        let runtime = detect_runtime_type(&empty_bytes).unwrap();
1169        assert_eq!(runtime, RuntimeType::Rust);
1170    }
1171
1172    #[test]
1173    fn test_runtime_type_detection_tinygo() {
1174        // Test with bytes containing "tinygo"
1175        let tinygo_bytes = b"wasm module with tinygo runtime".to_vec();
1176        let runtime = detect_runtime_type(&tinygo_bytes).unwrap();
1177        assert_eq!(runtime, RuntimeType::TinyGo);
1178    }
1179
1180    #[test]
1181    fn test_runtime_type_detection_assemblyscript() {
1182        // Test with bytes containing "assemblyscript"
1183        let as_bytes = b"wasm module with assemblyscript runtime".to_vec();
1184        let runtime = detect_runtime_type(&as_bytes).unwrap();
1185        assert_eq!(runtime, RuntimeType::AssemblyScript);
1186    }
1187
1188    #[test]
1189    fn test_has_tinygo_signature() {
1190        assert!(has_tinygo_signature(b"this contains tinygo"));
1191        assert!(!has_tinygo_signature(b"this does not contain it"));
1192    }
1193
1194    #[test]
1195    fn test_has_assemblyscript_signature() {
1196        assert!(has_assemblyscript_signature(b"this contains assemblyscript"));
1197        assert!(!has_assemblyscript_signature(b"this does not contain it"));
1198    }
1199
1200    // ===== RemoteRuntimeConfig Tests =====
1201
1202    #[test]
1203    fn test_remote_runtime_config() {
1204        let config = RemoteRuntimeConfig {
1205            protocol: RemoteProtocol::Http,
1206            endpoint: "http://localhost:8080".to_string(),
1207            timeout_ms: 5000,
1208            max_retries: 3,
1209            auth: Some(RemoteAuthConfig {
1210                auth_type: "bearer".to_string(),
1211                value: "secret-token".to_string(),
1212            }),
1213        };
1214
1215        assert_eq!(config.endpoint, "http://localhost:8080");
1216        assert_eq!(config.timeout_ms, 5000);
1217        assert_eq!(config.max_retries, 3);
1218        assert!(config.auth.is_some());
1219    }
1220
1221    #[test]
1222    fn test_remote_runtime_config_without_auth() {
1223        let config = RemoteRuntimeConfig {
1224            protocol: RemoteProtocol::Grpc,
1225            endpoint: "grpc://localhost:9090".to_string(),
1226            timeout_ms: 10000,
1227            max_retries: 5,
1228            auth: None,
1229        };
1230
1231        assert_eq!(config.protocol, RemoteProtocol::Grpc);
1232        assert!(config.auth.is_none());
1233    }
1234
1235    #[test]
1236    fn test_remote_runtime_config_clone() {
1237        let config = RemoteRuntimeConfig {
1238            protocol: RemoteProtocol::Http,
1239            endpoint: "http://localhost:8080".to_string(),
1240            timeout_ms: 5000,
1241            max_retries: 3,
1242            auth: None,
1243        };
1244
1245        let cloned = config.clone();
1246        assert_eq!(config.endpoint, cloned.endpoint);
1247        assert_eq!(config.timeout_ms, cloned.timeout_ms);
1248    }
1249
1250    #[test]
1251    fn test_remote_runtime_config_equality() {
1252        let config1 = RemoteRuntimeConfig {
1253            protocol: RemoteProtocol::Http,
1254            endpoint: "http://localhost:8080".to_string(),
1255            timeout_ms: 5000,
1256            max_retries: 3,
1257            auth: None,
1258        };
1259
1260        let config2 = RemoteRuntimeConfig {
1261            protocol: RemoteProtocol::Http,
1262            endpoint: "http://localhost:8080".to_string(),
1263            timeout_ms: 5000,
1264            max_retries: 3,
1265            auth: None,
1266        };
1267
1268        assert_eq!(config1, config2);
1269    }
1270
1271    // ===== RemoteProtocol Tests =====
1272
1273    #[test]
1274    fn test_remote_protocol_equality() {
1275        assert_eq!(RemoteProtocol::Http, RemoteProtocol::Http);
1276        assert_eq!(RemoteProtocol::Grpc, RemoteProtocol::Grpc);
1277        assert_ne!(RemoteProtocol::Http, RemoteProtocol::Grpc);
1278    }
1279
1280    #[test]
1281    fn test_remote_protocol_clone() {
1282        let proto = RemoteProtocol::Http;
1283        let cloned = proto.clone();
1284        assert_eq!(proto, cloned);
1285    }
1286
1287    // ===== RemoteAuthConfig Tests =====
1288
1289    #[test]
1290    fn test_remote_auth_config() {
1291        let auth = RemoteAuthConfig {
1292            auth_type: "bearer".to_string(),
1293            value: "secret-token".to_string(),
1294        };
1295
1296        assert_eq!(auth.auth_type, "bearer");
1297        assert_eq!(auth.value, "secret-token");
1298    }
1299
1300    #[test]
1301    fn test_remote_auth_config_api_key() {
1302        let auth = RemoteAuthConfig {
1303            auth_type: "api_key".to_string(),
1304            value: "my-api-key".to_string(),
1305        };
1306
1307        assert_eq!(auth.auth_type, "api_key");
1308    }
1309
1310    #[test]
1311    fn test_remote_auth_config_clone() {
1312        let auth = RemoteAuthConfig {
1313            auth_type: "bearer".to_string(),
1314            value: "token".to_string(),
1315        };
1316
1317        let cloned = auth.clone();
1318        assert_eq!(auth.auth_type, cloned.auth_type);
1319        assert_eq!(auth.value, cloned.value);
1320    }
1321
1322    #[test]
1323    fn test_remote_auth_config_equality() {
1324        let auth1 = RemoteAuthConfig {
1325            auth_type: "bearer".to_string(),
1326            value: "token".to_string(),
1327        };
1328
1329        let auth2 = RemoteAuthConfig {
1330            auth_type: "bearer".to_string(),
1331            value: "token".to_string(),
1332        };
1333
1334        assert_eq!(auth1, auth2);
1335    }
1336
1337    // ===== RuntimeAdapterFactory Tests =====
1338
1339    #[test]
1340    fn test_factory_create_rust_adapter() {
1341        let plugin_id = PluginId::new("test-plugin");
1342        // Create minimal valid WASM module (magic bytes + version)
1343        let wasm_bytes = vec![0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00];
1344
1345        let result = RuntimeAdapterFactory::create(RuntimeType::Rust, plugin_id, wasm_bytes);
1346
1347        assert!(result.is_ok());
1348        let adapter = result.unwrap();
1349        assert_eq!(adapter.runtime_type(), RuntimeType::Rust);
1350    }
1351
1352    #[test]
1353    fn test_factory_create_tinygo_adapter() {
1354        let plugin_id = PluginId::new("test-plugin");
1355        let wasm_bytes = vec![0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00];
1356
1357        let result = RuntimeAdapterFactory::create(RuntimeType::TinyGo, plugin_id, wasm_bytes);
1358
1359        assert!(result.is_ok());
1360        let adapter = result.unwrap();
1361        assert_eq!(adapter.runtime_type(), RuntimeType::TinyGo);
1362    }
1363
1364    #[test]
1365    fn test_factory_create_assemblyscript_adapter() {
1366        let plugin_id = PluginId::new("test-plugin");
1367        let wasm_bytes = vec![0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00];
1368
1369        let result =
1370            RuntimeAdapterFactory::create(RuntimeType::AssemblyScript, plugin_id, wasm_bytes);
1371
1372        assert!(result.is_ok());
1373        let adapter = result.unwrap();
1374        assert_eq!(adapter.runtime_type(), RuntimeType::AssemblyScript);
1375    }
1376
1377    #[test]
1378    fn test_factory_create_remote_adapter() {
1379        let plugin_id = PluginId::new("test-plugin");
1380        let config = RemoteRuntimeConfig {
1381            protocol: RemoteProtocol::Http,
1382            endpoint: "http://localhost:8080".to_string(),
1383            timeout_ms: 5000,
1384            max_retries: 3,
1385            auth: None,
1386        };
1387
1388        let result =
1389            RuntimeAdapterFactory::create(RuntimeType::Remote(config.clone()), plugin_id, vec![]);
1390
1391        assert!(result.is_ok());
1392        let adapter = result.unwrap();
1393        assert_eq!(adapter.runtime_type(), RuntimeType::Remote(config));
1394    }
1395
1396    #[test]
1397    fn test_factory_create_with_invalid_wasm() {
1398        let plugin_id = PluginId::new("test-plugin");
1399        // Invalid WASM bytes
1400        let wasm_bytes = vec![0x00, 0x00, 0x00, 0x00];
1401
1402        let result = RuntimeAdapterFactory::create(RuntimeType::Rust, plugin_id, wasm_bytes);
1403
1404        assert!(result.is_err());
1405    }
1406
1407    // ===== RemoteAdapter Tests =====
1408
1409    #[test]
1410    fn test_remote_adapter_creation() {
1411        let plugin_id = PluginId::new("test-plugin");
1412        let config = RemoteRuntimeConfig {
1413            protocol: RemoteProtocol::Http,
1414            endpoint: "http://localhost:8080".to_string(),
1415            timeout_ms: 5000,
1416            max_retries: 3,
1417            auth: None,
1418        };
1419
1420        let result = RemoteAdapter::new(plugin_id, config.clone());
1421        assert!(result.is_ok());
1422
1423        let adapter = result.unwrap();
1424        assert_eq!(adapter.runtime_type(), RuntimeType::Remote(config));
1425    }
1426
1427    #[test]
1428    fn test_remote_adapter_get_metrics() {
1429        let plugin_id = PluginId::new("test-plugin");
1430        let config = RemoteRuntimeConfig {
1431            protocol: RemoteProtocol::Http,
1432            endpoint: "http://localhost:8080".to_string(),
1433            timeout_ms: 5000,
1434            max_retries: 3,
1435            auth: None,
1436        };
1437
1438        let adapter = RemoteAdapter::new(plugin_id.clone(), config).unwrap();
1439        let metrics = adapter.get_metrics();
1440
1441        assert!(metrics.contains_key("plugin_id"));
1442        assert!(metrics.contains_key("endpoint"));
1443        assert!(metrics.contains_key("protocol"));
1444        assert_eq!(metrics.get("plugin_id").unwrap(), &serde_json::json!(plugin_id.as_str()));
1445    }
1446
1447    // ===== Edge Cases and Error Handling =====
1448
1449    #[test]
1450    fn test_runtime_type_with_mixed_signatures() {
1451        // Test with bytes containing both signatures (tinygo should win as it's checked first)
1452        let mixed_bytes = b"tinygo and assemblyscript".to_vec();
1453        let runtime = detect_runtime_type(&mixed_bytes).unwrap();
1454        assert_eq!(runtime, RuntimeType::TinyGo);
1455    }
1456
1457    #[test]
1458    fn test_empty_wasm_bytes() {
1459        let plugin_id = PluginId::new("test");
1460        let result = RustAdapter::new(plugin_id, vec![]);
1461        assert!(result.is_err());
1462    }
1463
1464    #[test]
1465    fn test_runtime_type_debug() {
1466        let rt = RuntimeType::Rust;
1467        let debug_str = format!("{:?}", rt);
1468        assert!(debug_str.contains("Rust"));
1469    }
1470
1471    #[test]
1472    fn test_remote_protocol_debug() {
1473        let proto = RemoteProtocol::Http;
1474        let debug_str = format!("{:?}", proto);
1475        assert!(debug_str.contains("Http"));
1476    }
1477}