use wasmparser::{Parser, Payload};
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct ComponentShape {
pub imports: Vec<String>,
pub type_imports: Vec<String>,
pub exports: Vec<String>,
pub function_imports: Vec<String>,
}
fn function_imports(bytes: &[u8], imports: &[String]) -> Vec<String> {
use wasmparser::component_types::ComponentEntityType;
let mut validator = wasmparser::Validator::new_with_features(wasmparser::WasmFeatures::all());
let Ok(types) = validator.validate_all(bytes) else {
return imports.to_vec();
};
imports
.iter()
.filter(
|name| match types.component_item_for_import(name).map(|item| &item.ty) {
Some(ComponentEntityType::Instance(id)) => types[*id]
.exports
.values()
.any(|item| matches!(item.ty, ComponentEntityType::Func(_))),
_ => true,
},
)
.cloned()
.collect()
}
pub fn inspect(bytes: &[u8]) -> Result<ComponentShape, String> {
let mut shape = ComponentShape::default();
let mut depth = 0usize;
for payload in Parser::new(0).parse_all(bytes) {
let payload = payload.map_err(|error| format!("not a valid wasm binary: {error}"))?;
match payload {
Payload::Version { encoding, .. } if depth == 0 => {
if encoding != wasmparser::Encoding::Component {
return Err(
"a core wasm module, not a component: build for wasm32-wasip2".into(),
);
}
}
Payload::ModuleSection { .. } | Payload::ComponentSection { .. } => depth += 1,
Payload::End(_) => depth = depth.saturating_sub(1),
Payload::ComponentImportSection(reader) if depth == 0 => {
for import in reader {
let import = import.map_err(|error| error.to_string())?;
let name = import.name.name.to_string();
if matches!(import.ty, wasmparser::ComponentTypeRef::Type(_)) {
shape.type_imports.push(name);
} else {
shape.imports.push(name);
}
}
}
Payload::ComponentExportSection(reader) if depth == 0 => {
for export in reader {
let export = export.map_err(|error| error.to_string())?;
shape.exports.push(export.name.name.to_string());
}
}
_ => {}
}
}
shape.function_imports = function_imports(bytes, &shape.imports);
Ok(shape)
}