Skip to main content

act_runtime/
info.rs

1//! Reading `act:component` out of a wasm binary, and the error type every
2//! call surface returns.
3
4use anyhow::Result;
5
6use crate::act;
7
8pub use act_types::ComponentInfo;
9/// Read component info from the `act:component` custom section (CBOR-encoded)
10/// and standard WASM metadata sections (`version`, `description`) as fallback.
11pub fn read_component_info(component_bytes: &[u8]) -> Result<ComponentInfo> {
12    let mut info = ComponentInfo::default();
13
14    for payload in wasmparser::Parser::new(0).parse_all(component_bytes) {
15        if let Ok(wasmparser::Payload::CustomSection(section)) = payload {
16            match section.name() {
17                act_types::constants::SECTION_ACT_COMPONENT => {
18                    info = ciborium::from_reader(section.data())
19                        .map_err(|e| anyhow::anyhow!("failed to decode act:component CBOR: {e}"))?;
20                }
21                "version" if info.std.version.is_empty() => {
22                    info.std.version = String::from_utf8_lossy(section.data()).into_owned();
23                }
24                "description" if info.std.description.is_empty() => {
25                    info.std.description = String::from_utf8_lossy(section.data()).into_owned();
26                }
27                _ => {}
28            }
29        }
30    }
31
32    if info.std.name.is_empty() {
33        info.std.name = "unknown".to_string();
34    }
35
36    Ok(info)
37}
38
39// ── Conversion helpers ──
40impl From<&act::core::types::LocalizedString> for act_types::types::LocalizedString {
41    fn from(ls: &act::core::types::LocalizedString) -> Self {
42        match ls {
43            act::core::types::LocalizedString::Plain(s) => Self::Plain(s.clone()),
44            act::core::types::LocalizedString::Localized(pairs) => Self::from(pairs.clone()),
45        }
46    }
47}
48
49// ── Actor types ──
50/// Errors from component calls.
51///
52/// The split is what a host has to act on: [`Self::Tool`] is the component
53/// answering — it ran, and it said no — while [`Self::Internal`] is the host
54/// failing to run it at all. Reporting one as the other is how a sandbox
55/// failure comes to look like a tool's opinion.
56#[derive(Debug)]
57pub enum ComponentError {
58    /// Structured tool error from the component (has kind, message, metadata).
59    Tool(act::core::types::Error),
60    /// Infrastructure error (wasmtime, actor channel, etc.).
61    Internal(anyhow::Error),
62}
63
64impl std::fmt::Display for ComponentError {
65    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
66        match self {
67            // Same shape the CLI has always printed: the guest's kind, then
68            // whichever localization of its message is available.
69            Self::Tool(e) => {
70                let message = act_types::types::LocalizedString::from(&e.message);
71                write!(f, "{}: {}", e.kind, message.any_text())
72            }
73            Self::Internal(e) => write!(f, "{e}"),
74        }
75    }
76}
77
78impl std::error::Error for ComponentError {
79    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
80        match self {
81            Self::Tool(_) => None,
82            Self::Internal(e) => Some(e.as_ref()),
83        }
84    }
85}