Skip to main content

standard_plugin_cli/
component.rs

1//! What a built component imports and exports.
2
3use wasmparser::{Parser, Payload};
4
5#[derive(Clone, Debug, Default, PartialEq, Eq)]
6pub struct ComponentShape {
7    /// Top-level instance and function import names
8    /// (`standard:plugin/values@2.0.0`, `wasi:...`).
9    pub imports: Vec<String>,
10    /// Top-level type imports (`event`, `json`): bound to a type the
11    /// component defines, they need nothing from the host.
12    pub type_imports: Vec<String>,
13    /// Top-level export names (`activate`, ...).
14    pub exports: Vec<String>,
15    /// The instance imports the component takes at least one function
16    /// from; an interface imported only for its types (`account` for the
17    /// daemon world's `pane`) is not among them.
18    pub function_imports: Vec<String>,
19}
20
21/// The instance imports that carry a function, by validating the component
22/// and reading each import's instance type. A component the validator
23/// refuses counts every instance import.
24fn function_imports(bytes: &[u8], imports: &[String]) -> Vec<String> {
25    use wasmparser::component_types::ComponentEntityType;
26    let mut validator = wasmparser::Validator::new_with_features(wasmparser::WasmFeatures::all());
27    let Ok(types) = validator.validate_all(bytes) else {
28        return imports.to_vec();
29    };
30    imports
31        .iter()
32        .filter(
33            |name| match types.component_item_for_import(name).map(|item| &item.ty) {
34                Some(ComponentEntityType::Instance(id)) => types[*id]
35                    .exports
36                    .values()
37                    .any(|item| matches!(item.ty, ComponentEntityType::Func(_))),
38                _ => true,
39            },
40        )
41        .cloned()
42        .collect()
43}
44
45/// Reads a component's top-level imports and exports. An error names why
46/// the bytes are not a component.
47pub fn inspect(bytes: &[u8]) -> Result<ComponentShape, String> {
48    let mut shape = ComponentShape::default();
49    let mut depth = 0usize;
50    for payload in Parser::new(0).parse_all(bytes) {
51        let payload = payload.map_err(|error| format!("not a valid wasm binary: {error}"))?;
52        match payload {
53            Payload::Version { encoding, .. } if depth == 0 => {
54                if encoding != wasmparser::Encoding::Component {
55                    return Err(
56                        "a core wasm module, not a component: build for wasm32-wasip2".into(),
57                    );
58                }
59            }
60            Payload::ModuleSection { .. } | Payload::ComponentSection { .. } => depth += 1,
61            Payload::End(_) => depth = depth.saturating_sub(1),
62            Payload::ComponentImportSection(reader) if depth == 0 => {
63                for import in reader {
64                    let import = import.map_err(|error| error.to_string())?;
65                    let name = import.name.name.to_string();
66                    if matches!(import.ty, wasmparser::ComponentTypeRef::Type(_)) {
67                        shape.type_imports.push(name);
68                    } else {
69                        shape.imports.push(name);
70                    }
71                }
72            }
73            Payload::ComponentExportSection(reader) if depth == 0 => {
74                for export in reader {
75                    let export = export.map_err(|error| error.to_string())?;
76                    shape.exports.push(export.name.name.to_string());
77                }
78            }
79            _ => {}
80        }
81    }
82    shape.function_imports = function_imports(bytes, &shape.imports);
83    Ok(shape)
84}