Skip to main content

greentic_component/
wasm.rs

1use anyhow::{Result, anyhow};
2use wasmparser::Parser;
3use wit_component::{DecodedWasm, metadata};
4use wit_parser::{Resolve, WorldId};
5
6/// Indicates how a world was decoded from a wasm binary.
7#[derive(Debug, Clone, Copy, PartialEq, Eq)]
8pub enum WorldSource {
9    /// Metadata was decoded from a core wasm module.
10    Metadata,
11    /// WIT was reconstructed from a fully-fledged component.
12    Component,
13}
14
15/// Resolved world information extracted from a wasm binary.
16pub struct DecodedWorld {
17    pub resolve: Resolve,
18    pub world: WorldId,
19    pub source: WorldSource,
20}
21
22/// Decode a wasm module or component into its WIT world description.
23pub fn decode_world(bytes: &[u8]) -> Result<DecodedWorld> {
24    if Parser::is_component(bytes) {
25        match wit_component::decode(bytes) {
26            Ok(DecodedWasm::Component(resolve, world)) => Ok(DecodedWorld {
27                resolve,
28                world,
29                source: WorldSource::Component,
30            }),
31            Ok(DecodedWasm::WitPackage(_, _)) | Err(_) => match metadata::decode(bytes) {
32                Ok((_maybe_module, bindgen)) => Ok(DecodedWorld {
33                    resolve: bindgen.resolve,
34                    world: bindgen.world,
35                    source: WorldSource::Metadata,
36                }),
37                Err(module_err) => Err(anyhow!(
38                    "failed to decode component and module metadata ({module_err})"
39                )),
40            },
41        }
42    } else {
43        match metadata::decode(bytes) {
44            Ok((_maybe_module, bindgen)) => Ok(DecodedWorld {
45                resolve: bindgen.resolve,
46                world: bindgen.world,
47                source: WorldSource::Metadata,
48            }),
49            Err(module_err) => match wit_component::decode(bytes) {
50                Ok(DecodedWasm::Component(resolve, world)) => Ok(DecodedWorld {
51                    resolve,
52                    world,
53                    source: WorldSource::Component,
54                }),
55                Ok(DecodedWasm::WitPackage(_, _)) => Err(module_err),
56                Err(component_err) => Err(anyhow!(
57                    "failed to decode module metadata ({module_err}) and component ({component_err})"
58                )),
59            },
60        }
61    }
62}
63
64#[cfg(test)]
65mod tests {
66    use super::*;
67    use std::path::Path;
68
69    #[test]
70    fn decode_world_rejects_invalid_binary_with_combined_error() {
71        match decode_world(b"not a wasm") {
72            Ok(_) => panic!("invalid input should fail"),
73            Err(err) => {
74                let message = err.to_string();
75                assert!(
76                    message.contains("failed to decode")
77                        || message.contains("unexpected end-of-file")
78                        || message.contains("invalid")
79                );
80            }
81        }
82    }
83
84    #[test]
85    fn decode_world_accepts_contract_fixture_component() {
86        let path = Path::new(env!("CARGO_MANIFEST_DIR"))
87            .join("tests")
88            .join("contract")
89            .join("fixtures")
90            .join("component_v0_6_0")
91            .join("component.wasm");
92        let bytes = std::fs::read(path).expect("read contract fixture");
93
94        let decoded = decode_world(&bytes).expect("decode fixture component");
95
96        assert_eq!(decoded.source, WorldSource::Component);
97    }
98}