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#[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 linker.allow_shadowing(true);
190
191 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 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 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 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 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 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 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 = ¶ms[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 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 Self::reconstruct_wit_return(&results, &function)
481 }
482 }
483 }
484
485 fn reconstruct_wit_return(results: &[Val], function: &Function) -> Result<serde_json::Value> {
487 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 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 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 (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 (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 (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 (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 (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 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 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 (serde_json::Value::Null, wasmtime::component::Type::Option(_)) => Ok(Val::Option(None)),
683
684 (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 _ => 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 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 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 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 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 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}