Skip to main content

composable_runtime/runtime/
host.rs

1use anyhow::Result;
2use std::collections::HashMap;
3use std::collections::hash_map::Entry;
4use wasmtime::{
5    Cache, Config, Engine, Store,
6    component::{Component as WasmComponent, Linker, Type, Val},
7};
8use wasmtime_wasi::cli::{WasiCli, WasiCliView};
9use wasmtime_wasi::clocks::{WasiClocks, WasiClocksView};
10use wasmtime_wasi::random::{WasiRandom, WasiRandomView};
11use wasmtime_wasi::sockets::{WasiSockets, WasiSocketsView};
12use wasmtime_wasi::{ResourceTable, WasiCtxBuilder, WasiCtxView, WasiView};
13use wasmtime_wasi_http::bindings::http::types::ErrorCode;
14use wasmtime_wasi_http::body::HyperOutgoingBody;
15use wasmtime_wasi_http::types::{
16    HostFutureIncomingResponse, OutgoingRequestConfig, default_send_request,
17};
18use wasmtime_wasi_http::{HttpResult, WasiHttpCtx, WasiHttpView};
19use wasmtime_wasi_io::IoView;
20
21use crate::composition::registry::{CapabilityRegistry, ComponentRegistry};
22use crate::types::{Component, ComponentInvoker, ComponentState, Function};
23
24// Component host: wasmtime engine + registries, provides instantiation + invocation.
25#[derive(Clone)]
26pub(crate) struct ComponentHost {
27    invoker: Invoker,
28    pub(crate) component_registry: ComponentRegistry,
29    pub(crate) capability_registry: CapabilityRegistry,
30}
31
32impl ComponentHost {
33    pub(crate) fn new(
34        component_registry: ComponentRegistry,
35        capability_registry: CapabilityRegistry,
36    ) -> Result<Self> {
37        let invoker = Invoker::new()?;
38        Ok(Self {
39            invoker,
40            component_registry,
41            capability_registry,
42        })
43    }
44
45    pub(crate) async fn invoke(
46        &self,
47        component_name: &str,
48        function_name: &str,
49        args: Vec<serde_json::Value>,
50        env_vars: &[(&str, &str)],
51    ) -> Result<serde_json::Value> {
52        let spec = self
53            .component_registry
54            .get_component(component_name)
55            .ok_or_else(|| anyhow::anyhow!("Component '{component_name}' not found"))?;
56
57        let function = spec.functions.get(function_name).ok_or_else(|| {
58            anyhow::anyhow!("Function '{function_name}' not found in component '{component_name}'")
59        })?;
60
61        self.invoker
62            .invoke(
63                &spec.bytes,
64                &spec.capabilities,
65                &self.capability_registry,
66                function.clone(),
67                args,
68                env_vars,
69            )
70            .await
71    }
72
73    pub(crate) async fn instantiate(
74        &self,
75        component_name: &str,
76        env_vars: &[(&str, &str)],
77    ) -> Result<(Store<ComponentState>, wasmtime::component::Instance)> {
78        let spec = self
79            .component_registry
80            .get_component(component_name)
81            .ok_or_else(|| anyhow::anyhow!("Component '{component_name}' not found"))?;
82
83        self.invoker
84            .instantiate_from_bytes(
85                &spec.bytes,
86                &spec.capabilities,
87                &self.capability_registry,
88                env_vars,
89            )
90            .await
91    }
92}
93
94impl ComponentInvoker for ComponentHost {
95    fn get_component(&self, name: &str) -> Option<Component> {
96        self.component_registry
97            .get_component(name)
98            .map(|spec| Component {
99                name: spec.name.clone(),
100                functions: spec.functions.clone(),
101            })
102    }
103
104    fn invoke<'a>(
105        &'a self,
106        component_name: &'a str,
107        function_name: &'a str,
108        args: Vec<serde_json::Value>,
109    ) -> std::pin::Pin<
110        Box<dyn std::future::Future<Output = anyhow::Result<serde_json::Value>> + Send + 'a>,
111    > {
112        Box::pin(self.invoke(component_name, function_name, args, &[]))
113    }
114}
115
116impl IoView for ComponentState {
117    fn table(&mut self) -> &mut ResourceTable {
118        &mut self.resource_table
119    }
120}
121
122impl WasiView for ComponentState {
123    fn ctx(&mut self) -> WasiCtxView<'_> {
124        WasiCtxView {
125            ctx: &mut self.wasi_ctx,
126            table: &mut self.resource_table,
127        }
128    }
129}
130
131impl WasiHttpView for ComponentState {
132    fn ctx(&mut self) -> &mut WasiHttpCtx {
133        self.wasi_http_ctx
134            .as_mut()
135            .expect("Component requires 'http' capability, so HTTP context should be available")
136    }
137
138    fn table(&mut self) -> &mut ResourceTable {
139        &mut self.resource_table
140    }
141
142    fn send_request(
143        &mut self,
144        request: hyper::Request<HyperOutgoingBody>,
145        config: OutgoingRequestConfig,
146    ) -> HttpResult<HostFutureIncomingResponse> {
147        let is_grpc = request
148            .headers()
149            .get("content-type")
150            .and_then(|v| v.to_str().ok())
151            .is_some_and(|ct| ct.starts_with("application/grpc"));
152
153        if is_grpc {
154            if request.uri().scheme_str() == Some("https") {
155                tracing::error!("gRPC over TLS (https) is not yet supported");
156                return Err(ErrorCode::HttpProtocolError.into());
157            }
158            Ok(super::grpc::send_grpc_request(request, config))
159        } else {
160            Ok(default_send_request(request, config))
161        }
162    }
163}
164
165#[derive(Clone)]
166struct Invoker {
167    engine: Engine,
168}
169
170impl Invoker {
171    pub fn new() -> Result<Self> {
172        let mut config = Config::new();
173        config.cache(Some(Cache::from_file(None)?));
174        config.parallel_compilation(true);
175        config.wasm_component_model_async(true);
176        config.memory_init_cow(true);
177        let engine = Engine::new(&config)?;
178        Ok(Self { engine })
179    }
180
181    fn create_linker(
182        &self,
183        capabilities: &[String],
184        capability_registry: &CapabilityRegistry,
185    ) -> Result<Linker<ComponentState>> {
186        let mut linker = Linker::new(&self.engine);
187
188        // Multiple capabilities may provide the same interface
189        linker.allow_shadowing(true);
190
191        // Add WASI interfaces based on explicitly requested capabilities
192        for capability_name in capabilities {
193            if let Some(capability) = capability_registry.get_capability(capability_name) {
194                if let Some(wasi_capability) = capability.kind.strip_prefix("wasi:") {
195                    use wasmtime_wasi::p2::bindings::{cli, clocks, random, sockets};
196
197                    match wasi_capability {
198                        "p2" => {
199                            wasmtime_wasi::p2::add_to_linker_async(&mut linker)?;
200                        }
201                        "cli" => {
202                            cli::stdin::add_to_linker::<ComponentState, WasiCli>(
203                                &mut linker,
204                                ComponentState::cli,
205                            )?;
206                            cli::stdout::add_to_linker::<ComponentState, WasiCli>(
207                                &mut linker,
208                                ComponentState::cli,
209                            )?;
210                            cli::stderr::add_to_linker::<ComponentState, WasiCli>(
211                                &mut linker,
212                                ComponentState::cli,
213                            )?;
214                            cli::environment::add_to_linker::<ComponentState, WasiCli>(
215                                &mut linker,
216                                ComponentState::cli,
217                            )?;
218                        }
219                        "clocks" => {
220                            clocks::wall_clock::add_to_linker::<ComponentState, WasiClocks>(
221                                &mut linker,
222                                ComponentState::clocks,
223                            )?;
224                            clocks::monotonic_clock::add_to_linker::<ComponentState, WasiClocks>(
225                                &mut linker,
226                                ComponentState::clocks,
227                            )?;
228                        }
229                        "http" => {
230                            wasmtime_wasi_http::add_only_http_to_linker_async(&mut linker)?;
231                            // io is a transitive dep
232                            wasmtime_wasi_io::add_to_linker_async(&mut linker)?;
233                        }
234                        "io" => {
235                            wasmtime_wasi_io::add_to_linker_async(&mut linker)?;
236                        }
237                        "random" => {
238                            random::random::add_to_linker::<ComponentState, WasiRandom>(
239                                &mut linker,
240                                |state| <ComponentState as WasiRandomView>::random(state),
241                            )?;
242                            random::insecure::add_to_linker::<ComponentState, WasiRandom>(
243                                &mut linker,
244                                |state| <ComponentState as WasiRandomView>::random(state),
245                            )?;
246                            random::insecure_seed::add_to_linker::<ComponentState, WasiRandom>(
247                                &mut linker,
248                                |state| <ComponentState as WasiRandomView>::random(state),
249                            )?;
250                        }
251                        "sockets" => {
252                            sockets::tcp::add_to_linker::<ComponentState, WasiSockets>(
253                                &mut linker,
254                                ComponentState::sockets,
255                            )?;
256                            sockets::udp::add_to_linker::<ComponentState, WasiSockets>(
257                                &mut linker,
258                                ComponentState::sockets,
259                            )?;
260                            sockets::network::add_to_linker::<ComponentState, WasiSockets>(
261                                &mut linker,
262                                &Default::default(),
263                                ComponentState::sockets,
264                            )?;
265                            sockets::instance_network::add_to_linker::<ComponentState, WasiSockets>(
266                                &mut linker,
267                                ComponentState::sockets,
268                            )?;
269                            sockets::ip_name_lookup::add_to_linker::<ComponentState, WasiSockets>(
270                                &mut linker,
271                                ComponentState::sockets,
272                            )?;
273                            sockets::tcp_create_socket::add_to_linker::<ComponentState, WasiSockets>(
274                                &mut linker,
275                                ComponentState::sockets,
276                            )?;
277                            sockets::udp_create_socket::add_to_linker::<ComponentState, WasiSockets>(
278                                &mut linker,
279                                ComponentState::sockets,
280                            )?;
281                        }
282                        _ => {
283                            anyhow::bail!("Unknown capability type: '{}'", capability.kind);
284                        }
285                    }
286                } else {
287                    // Custom capability
288                    if let Some(cap) = &capability.instance {
289                        cap.link(&mut linker)?;
290                    } else {
291                        return Err(anyhow::anyhow!(
292                            "Capability '{}' requested but no capability instance registered",
293                            capability_name
294                        ));
295                    }
296                }
297            }
298        }
299        Ok(linker)
300    }
301
302    async fn instantiate_from_bytes(
303        &self,
304        bytes: &[u8],
305        capabilities: &[String],
306        capability_registry: &CapabilityRegistry,
307        env_vars: &[(&str, &str)],
308    ) -> Result<(Store<ComponentState>, wasmtime::component::Instance)> {
309        let component_bytes = bytes.to_vec();
310        let linker = self.create_linker(capabilities, capability_registry)?;
311
312        // Build WASI context based on capabilities
313        let mut wasi_builder = WasiCtxBuilder::new();
314
315        if !env_vars.is_empty() {
316            wasi_builder.envs(env_vars);
317        }
318
319        for capability_name in capabilities {
320            if let Some(capability) = capability_registry.get_capability(capability_name) {
321                let props = &capability.properties;
322                match capability.kind.as_str() {
323                    "wasi:p2" => {
324                        wasi_builder.inherit_stdio();
325                        wasi_builder.inherit_network();
326                        wasi_builder.allow_ip_name_lookup(true);
327                    }
328                    "wasi:cli" => {
329                        if props.get("inherit-stdio").and_then(|v| v.as_bool()) == Some(true) {
330                            wasi_builder.inherit_stdio();
331                        } else {
332                            if props.get("inherit-stdin").and_then(|v| v.as_bool()) == Some(true) {
333                                wasi_builder.inherit_stdin();
334                            }
335                            if props.get("inherit-stdout").and_then(|v| v.as_bool()) == Some(true) {
336                                wasi_builder.inherit_stdout();
337                            }
338                            if props.get("inherit-stderr").and_then(|v| v.as_bool()) == Some(true) {
339                                wasi_builder.inherit_stderr();
340                            }
341                        }
342                    }
343                    "wasi:sockets" => {
344                        if props.get("inherit-network").and_then(|v| v.as_bool()) == Some(true) {
345                            wasi_builder.inherit_network();
346                        }
347                        if props.get("allow-ip-name-lookup").and_then(|v| v.as_bool()) == Some(true)
348                        {
349                            wasi_builder.allow_ip_name_lookup(true);
350                        }
351                    }
352                    _ => {}
353                }
354            }
355        }
356
357        // Check if HTTP context needed
358        let needs_http = capabilities.iter().any(|capability_name| {
359            capability_registry
360                .get_capability(capability_name)
361                .and_then(|cap| cap.kind.strip_prefix("wasi:"))
362                == Some("http")
363        });
364
365        // Collect capability states before creating ComponentState
366        let mut extensions = HashMap::new();
367        for capability_name in capabilities {
368            if let Some(capability) = capability_registry.get_capability(capability_name)
369                && !capability.kind.starts_with("wasi:")
370                && let Some(cap) = &capability.instance
371                && let Some((type_id, boxed_state)) = cap.create_state_boxed()?
372            {
373                match extensions.entry(type_id) {
374                    Entry::Vacant(e) => {
375                        e.insert(boxed_state);
376                    }
377                    Entry::Occupied(_) => {
378                        anyhow::bail!("Duplicate state type for capability '{capability_name}'");
379                    }
380                }
381            }
382        }
383
384        let state = ComponentState {
385            wasi_ctx: wasi_builder.build(),
386            wasi_http_ctx: if needs_http {
387                Some(WasiHttpCtx::new())
388            } else {
389                None
390            },
391            resource_table: ResourceTable::new(),
392            extensions,
393        };
394
395        let mut store = Store::new(&self.engine, state);
396        let component = WasmComponent::from_binary(&self.engine, &component_bytes)?;
397        let instance = linker.instantiate_async(&mut store, &component).await?;
398
399        Ok((store, instance))
400    }
401
402    pub async fn invoke(
403        &self,
404        bytes: &[u8],
405        capabilities: &[String],
406        capability_registry: &CapabilityRegistry,
407        function: Function,
408        args: Vec<serde_json::Value>,
409        env_vars: &[(&str, &str)],
410    ) -> Result<serde_json::Value> {
411        let function_name = function.function_name();
412
413        let (mut store, instance) = self
414            .instantiate_from_bytes(bytes, capabilities, capability_registry, env_vars)
415            .await?;
416
417        // Look up the function - either within an interface or as a direct export
418        let func_export = if let Some(interface) = function.interface() {
419            let interface_str = interface.as_str();
420            let interface_export = instance
421                .get_export(&mut store, None, interface_str)
422                .ok_or_else(|| anyhow::anyhow!("Interface '{interface_str}' not found"))?;
423            let parent_export_idx = Some(&interface_export.1);
424            instance
425                .get_export(&mut store, parent_export_idx, function_name)
426                .ok_or_else(|| {
427                    anyhow::anyhow!(
428                        "Function '{function_name}' not found in interface '{interface_str}'"
429                    )
430                })?
431        } else {
432            instance
433                .get_export(&mut store, None, function_name)
434                .ok_or_else(|| {
435                    anyhow::anyhow!("Function '{function_name}' not found in component exports")
436                })?
437        };
438        let func = instance
439            .get_func(&mut store, func_export.1)
440            .ok_or_else(|| anyhow::anyhow!("Function handle invalid for '{function_name}'"))?;
441
442        let mut arg_vals: Vec<Val> = vec![];
443        let func_ty = func.ty(&store);
444        let params: Vec<_> = func_ty.params().collect();
445        if args.len() != params.len() {
446            return Err(anyhow::anyhow!(
447                "Wrong number of args: expected {}, got {}",
448                params.len(),
449                args.len()
450            ));
451        }
452        for (index, json_arg) in args.iter().enumerate() {
453            let param_type = &params[index].1;
454            let val = json_to_val(json_arg, param_type)
455                .map_err(|e| anyhow::anyhow!("Error converting parameter {index}: {e}"))?;
456            arg_vals.push(val);
457        }
458
459        let num_results = func_ty.results().len();
460        let mut results = vec![Val::Bool(false); num_results];
461
462        func.call_async(&mut store, &arg_vals, &mut results).await?;
463
464        // Handle results according to WIT function signature
465        match results.len() {
466            0 => Ok(serde_json::Value::Null),
467            1 => {
468                let value = &results[0];
469                match value {
470                    Val::Result(Err(Some(error_val))) => {
471                        let error_json = val_to_json(error_val);
472                        Err(anyhow::anyhow!("Component returned error: {error_json}"))
473                    }
474                    Val::Result(Err(None)) => Err(anyhow::anyhow!("Component returned error")),
475                    _ => Ok(val_to_json(value)),
476                }
477            }
478            _ => {
479                // Multiple wasmtime results - reconstruct WIT tuple/record structure
480                Self::reconstruct_wit_return(&results, &function)
481            }
482        }
483    }
484
485    // This handles the case where wasmtime decomposes tuples/records into separate Val objects
486    fn reconstruct_wit_return(results: &[Val], function: &Function) -> Result<serde_json::Value> {
487        // Check if this is a record that needs field mapping to reconstruct as an object
488        if let Some(return_schema) = function.result()
489            && let Some(schema_obj) = return_schema.as_object()
490            && schema_obj.get("type").and_then(|t| t.as_str()) == Some("object")
491            && schema_obj.contains_key("properties")
492        {
493            return Self::reconstruct_record(results, schema_obj);
494        }
495
496        // All other cases (tuples, unknown schemas, malformed schemas) -> array
497        let json_results: Vec<serde_json::Value> = results.iter().map(val_to_json).collect();
498        Ok(serde_json::Value::Array(json_results))
499    }
500
501    // Reconstruct a WIT record from multiple wasmtime results
502    fn reconstruct_record(
503        results: &[Val],
504        schema_obj: &serde_json::Map<String, serde_json::Value>,
505    ) -> Result<serde_json::Value> {
506        let properties = schema_obj
507            .get("properties")
508            .and_then(|p| p.as_object())
509            .ok_or_else(|| anyhow::anyhow!("Record schema missing properties"))?;
510
511        let mut record = serde_json::Map::new();
512        let field_names: Vec<&String> = properties.keys().collect();
513
514        if results.len() != field_names.len() {
515            return Err(anyhow::anyhow!(
516                "Mismatch between wasmtime results ({}) and record fields ({})",
517                results.len(),
518                field_names.len()
519            ));
520        }
521
522        for (i, field_name) in field_names.iter().enumerate() {
523            record.insert(field_name.to_string(), val_to_json(&results[i]));
524        }
525
526        Ok(serde_json::Value::Object(record))
527    }
528}
529
530fn json_to_val(json_value: &serde_json::Value, val_type: &Type) -> Result<Val> {
531    match (json_value, val_type) {
532        // Direct JSON type mappings
533        (serde_json::Value::Bool(b), wasmtime::component::Type::Bool) => Ok(Val::Bool(*b)),
534        (serde_json::Value::String(s), wasmtime::component::Type::String) => {
535            Ok(Val::String(s.clone()))
536        }
537        (serde_json::Value::String(s), wasmtime::component::Type::Char) => {
538            let chars: Vec<char> = s.chars().collect();
539            if chars.len() == 1 {
540                Ok(Val::Char(chars[0]))
541            } else {
542                Err(anyhow::anyhow!("Expected single character, got: {s}"))
543            }
544        }
545
546        // Number types - JSON number maps to all WIT numeric types
547        (serde_json::Value::Number(n), wasmtime::component::Type::U8) => {
548            let val = n
549                .as_u64()
550                .ok_or_else(|| anyhow::anyhow!("Invalid number for u8: {n}"))?
551                as u8;
552            Ok(Val::U8(val))
553        }
554        (serde_json::Value::Number(n), wasmtime::component::Type::U16) => {
555            let val = n
556                .as_u64()
557                .ok_or_else(|| anyhow::anyhow!("Invalid number for u16: {n}"))?
558                as u16;
559            Ok(Val::U16(val))
560        }
561        (serde_json::Value::Number(n), wasmtime::component::Type::U32) => {
562            let val = n
563                .as_u64()
564                .ok_or_else(|| anyhow::anyhow!("Invalid number for u32: {n}"))?
565                as u32;
566            Ok(Val::U32(val))
567        }
568        (serde_json::Value::Number(n), wasmtime::component::Type::U64) => {
569            let val = n
570                .as_u64()
571                .ok_or_else(|| anyhow::anyhow!("Invalid number for u64: {n}"))?;
572            Ok(Val::U64(val))
573        }
574        (serde_json::Value::Number(n), wasmtime::component::Type::S8) => {
575            let val = n
576                .as_i64()
577                .ok_or_else(|| anyhow::anyhow!("Invalid number for s8: {n}"))?
578                as i8;
579            Ok(Val::S8(val))
580        }
581        (serde_json::Value::Number(n), wasmtime::component::Type::S16) => {
582            let val = n
583                .as_i64()
584                .ok_or_else(|| anyhow::anyhow!("Invalid number for s16: {n}"))?
585                as i16;
586            Ok(Val::S16(val))
587        }
588        (serde_json::Value::Number(n), wasmtime::component::Type::S32) => {
589            let val = n
590                .as_i64()
591                .ok_or_else(|| anyhow::anyhow!("Invalid number for s32: {n}"))?
592                as i32;
593            Ok(Val::S32(val))
594        }
595        (serde_json::Value::Number(n), wasmtime::component::Type::S64) => {
596            let val = n
597                .as_i64()
598                .ok_or_else(|| anyhow::anyhow!("Invalid number for s64: {n}"))?;
599            Ok(Val::S64(val))
600        }
601        (serde_json::Value::Number(n), wasmtime::component::Type::Float32) => {
602            let val = n
603                .as_f64()
604                .ok_or_else(|| anyhow::anyhow!("Invalid number for f32: {n}"))?
605                as f32;
606            Ok(Val::Float32(val))
607        }
608        (serde_json::Value::Number(n), wasmtime::component::Type::Float64) => {
609            let val = n
610                .as_f64()
611                .ok_or_else(|| anyhow::anyhow!("Invalid number for f64: {n}"))?;
612            Ok(Val::Float64(val))
613        }
614
615        // Arrays map to lists
616        (serde_json::Value::Array(arr), wasmtime::component::Type::List(list_type)) => {
617            let element_type = list_type.ty();
618            let mut items = Vec::new();
619            for (index, item) in arr.iter().enumerate() {
620                items.push(json_to_val(item, &element_type).map_err(|e| {
621                    anyhow::anyhow!("Error converting list item at index {index}: {e}")
622                })?);
623            }
624            Ok(Val::List(items))
625        }
626
627        // Arrays map to tuples
628        (serde_json::Value::Array(arr), wasmtime::component::Type::Tuple(tuple_type)) => {
629            let tuple_types: Vec<_> = tuple_type.types().collect();
630            if arr.len() != tuple_types.len() {
631                return Err(anyhow::anyhow!(
632                    "Tuple length mismatch: expected {}, got {}",
633                    tuple_types.len(),
634                    arr.len()
635                ));
636            }
637            let mut items = Vec::new();
638            for (index, (item, item_type)) in arr.iter().zip(tuple_types.iter()).enumerate() {
639                items.push(json_to_val(item, item_type).map_err(|e| {
640                    anyhow::anyhow!("Error converting tuple item at index {index}: {e}")
641                })?);
642            }
643            Ok(Val::Tuple(items))
644        }
645
646        // Objects map to records
647        (serde_json::Value::Object(obj), wasmtime::component::Type::Record(record_type)) => {
648            let mut fields = Vec::new();
649            for field in record_type.fields() {
650                let field_name = field.name.to_string();
651                let field_type = &field.ty;
652
653                if let Some(json_value) = obj.get(&field_name) {
654                    let field_val = json_to_val(json_value, field_type)?;
655                    fields.push((field_name, field_val));
656                } else {
657                    // Check if field is optional
658                    match field_type {
659                        wasmtime::component::Type::Option(_) => {
660                            fields.push((field_name, Val::Option(None)));
661                        }
662                        _ => {
663                            return Err(anyhow::anyhow!(
664                                "Missing required field '{field_name}' in record"
665                            ));
666                        }
667                    }
668                }
669            }
670
671            // Check for extra fields that aren't in the WIT record
672            for (key, _) in obj {
673                if !record_type.fields().any(|field| field.name == key) {
674                    return Err(anyhow::anyhow!("Unexpected field '{key}' in record"));
675                }
676            }
677
678            Ok(Val::Record(fields))
679        }
680
681        // Handle null for options
682        (serde_json::Value::Null, wasmtime::component::Type::Option(_)) => Ok(Val::Option(None)),
683
684        // Handle non-null values for options
685        (json_val, wasmtime::component::Type::Option(option_type)) => {
686            let inner_type = option_type.ty();
687            let inner_val = json_to_val(json_val, &inner_type)?;
688            Ok(Val::Option(Some(Box::new(inner_val))))
689        }
690
691        // Type mismatches
692        _ => Err(anyhow::anyhow!(
693            "Type mismatch: cannot convert JSON {json_value:?} to WIT type {val_type:?}"
694        )),
695    }
696}
697
698fn val_to_json(val: &Val) -> serde_json::Value {
699    match val {
700        // Direct mappings
701        Val::Bool(b) => serde_json::Value::Bool(*b),
702        Val::String(s) => serde_json::Value::String(s.clone()),
703        Val::Char(c) => serde_json::Value::String(c.to_string()),
704
705        // All numbers become JSON numbers
706        Val::U8(n) => serde_json::Value::Number((*n as u64).into()),
707        Val::U16(n) => serde_json::Value::Number((*n as u64).into()),
708        Val::U32(n) => serde_json::Value::Number((*n as u64).into()),
709        Val::U64(n) => serde_json::Value::Number((*n).into()),
710        Val::S8(n) => serde_json::Value::Number((*n as i64).into()),
711        Val::S16(n) => serde_json::Value::Number((*n as i64).into()),
712        Val::S32(n) => serde_json::Value::Number((*n as i64).into()),
713        Val::S64(n) => serde_json::Value::Number((*n).into()),
714        Val::Float32(n) => serde_json::Number::from_f64(*n as f64)
715            .map(serde_json::Value::Number)
716            .unwrap_or(serde_json::Value::Null),
717        Val::Float64(n) => serde_json::Number::from_f64(*n)
718            .map(serde_json::Value::Number)
719            .unwrap_or(serde_json::Value::Null),
720
721        // Collections
722        Val::List(items) => {
723            let json_items: Vec<serde_json::Value> = items.iter().map(val_to_json).collect();
724            serde_json::Value::Array(json_items)
725        }
726
727        Val::Record(fields) => {
728            let mut obj = serde_json::Map::new();
729            for (name, val) in fields {
730                obj.insert(name.clone(), val_to_json(val));
731            }
732            serde_json::Value::Object(obj)
733        }
734
735        // Options
736        Val::Option(opt) => match opt {
737            Some(val) => val_to_json(val),
738            None => serde_json::Value::Null,
739        },
740
741        Val::Tuple(vals) => {
742            let json_items: Vec<serde_json::Value> = vals.iter().map(val_to_json).collect();
743            serde_json::Value::Array(json_items)
744        }
745
746        Val::Variant(name, val) => {
747            let mut obj = serde_json::Map::new();
748            obj.insert("type".to_string(), serde_json::Value::String(name.clone()));
749            if let Some(v) = val {
750                match val_to_json(v) {
751                    serde_json::Value::Object(payload_obj) => {
752                        for (k, v) in payload_obj {
753                            obj.insert(k, v);
754                        }
755                    }
756                    other => {
757                        // If payload is not an object (primitive, array, etc.),
758                        // fall back to "value" key to maintain valid JSON
759                        obj.insert("value".to_string(), other);
760                    }
761                }
762            }
763            serde_json::Value::Object(obj)
764        }
765
766        Val::Enum(variant) => serde_json::Value::String(variant.clone()),
767
768        Val::Flags(items) => {
769            let json_items: Vec<serde_json::Value> = items
770                .iter()
771                .map(|s| serde_json::Value::String(s.clone()))
772                .collect();
773            serde_json::Value::Array(json_items)
774        }
775
776        Val::Result(result) => {
777            let mut obj = serde_json::Map::new();
778            match result {
779                Ok(Some(v)) => {
780                    obj.insert("ok".to_string(), val_to_json(v));
781                }
782                Ok(None) => {
783                    obj.insert("ok".to_string(), serde_json::Value::Null);
784                }
785                Err(Some(v)) => {
786                    obj.insert("error".to_string(), val_to_json(v));
787                }
788                Err(None) => {
789                    obj.insert("error".to_string(), serde_json::Value::Null);
790                }
791            }
792            serde_json::Value::Object(obj)
793        }
794
795        Val::Resource(resource_any) => {
796            unreachable!(
797                "Resource types should be caught by validation: {:?}",
798                resource_any
799            )
800        }
801
802        Val::Future(future_any) => {
803            unreachable!(
804                "Future types should be caught by validation: {:?}",
805                future_any
806            )
807        }
808
809        Val::Stream(stream_any) => {
810            unreachable!(
811                "Stream types should be caught by validation: {:?}",
812                stream_any
813            )
814        }
815
816        Val::ErrorContext(error_context_any) => {
817            unreachable!(
818                "ErrorContext types should be caught by validation: {:?}",
819                error_context_any
820            )
821        }
822    }
823}