Skip to main content

wit_component/
lib.rs

1//! The WebAssembly component tooling.
2
3#![deny(missing_docs)]
4#![cfg_attr(docsrs, feature(doc_cfg))]
5
6use std::str::FromStr;
7use std::{borrow::Cow, fmt::Display};
8
9use anyhow::{Result, bail};
10use wasm_encoder::{CanonicalOption, Encode, Section};
11use wit_parser::{Resolve, WorldId};
12
13mod encoding;
14mod gc;
15mod linking;
16mod printing;
17mod targets;
18mod validation;
19
20pub use encoding::{ComponentEncoder, LibraryInfo, encode};
21pub use linking::Linker;
22pub use printing::*;
23pub use targets::*;
24pub use validation::AdapterModuleDidNotExport;
25pub use wit_parser::decoding::{DecodedWasm, decode, decode_reader};
26
27pub mod metadata;
28
29#[cfg(feature = "dummy-module")]
30pub use dummy::dummy_module;
31#[cfg(feature = "dummy-module")]
32mod dummy;
33
34#[cfg(feature = "semver-check")]
35mod semver_check;
36#[cfg(feature = "semver-check")]
37pub use semver_check::*;
38
39/// Supported string encoding formats.
40#[derive(Default, Debug, Copy, Clone, PartialEq, Eq, Hash)]
41pub enum StringEncoding {
42    /// Strings are encoded with UTF-8.
43    #[default]
44    UTF8,
45    /// Strings are encoded with UTF-16.
46    UTF16,
47    /// Strings are encoded with compact UTF-16 (i.e. Latin1+UTF-16).
48    CompactUTF16,
49}
50
51impl Display for StringEncoding {
52    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
53        match self {
54            StringEncoding::UTF8 => write!(f, "utf8"),
55            StringEncoding::UTF16 => write!(f, "utf16"),
56            StringEncoding::CompactUTF16 => write!(f, "compact-utf16"),
57        }
58    }
59}
60
61impl FromStr for StringEncoding {
62    type Err = anyhow::Error;
63
64    fn from_str(s: &str) -> Result<Self> {
65        match s {
66            "utf8" => Ok(StringEncoding::UTF8),
67            "utf16" => Ok(StringEncoding::UTF16),
68            "compact-utf16" => Ok(StringEncoding::CompactUTF16),
69            _ => bail!("unknown string encoding `{s}`"),
70        }
71    }
72}
73
74impl From<StringEncoding> for wasm_encoder::CanonicalOption {
75    fn from(e: StringEncoding) -> wasm_encoder::CanonicalOption {
76        match e {
77            StringEncoding::UTF8 => CanonicalOption::UTF8,
78            StringEncoding::UTF16 => CanonicalOption::UTF16,
79            StringEncoding::CompactUTF16 => CanonicalOption::CompactUTF16,
80        }
81    }
82}
83
84/// A producer section to be added to all modules and components synthesized by
85/// this crate
86pub(crate) fn base_producers() -> wasm_metadata::Producers {
87    let mut producer = wasm_metadata::Producers::empty();
88    producer.add("processed-by", "wit-component", env!("CARGO_PKG_VERSION"));
89    producer
90}
91
92/// Embed component metadata in a buffer of bytes that contains a Wasm module
93pub fn embed_component_metadata(
94    bytes: &mut Vec<u8>,
95    wit_resolver: &Resolve,
96    world: WorldId,
97    encoding: StringEncoding,
98    canonical_names: bool,
99) -> Result<()> {
100    let encoded = metadata::encode(&wit_resolver, world, encoding, None, canonical_names)?;
101
102    let section = wasm_encoder::CustomSection {
103        name: "component-type".into(),
104        data: Cow::Borrowed(&encoded),
105    };
106    bytes.push(section.id());
107    section.encode(bytes);
108
109    Ok(())
110}
111
112#[cfg(test)]
113mod tests {
114    use anyhow::Result;
115    use wasmparser::Payload;
116    use wit_parser::Resolve;
117
118    use super::{StringEncoding, embed_component_metadata};
119
120    const MODULE_WAT: &str = r#"
121(module
122  (type (;0;) (func))
123  (func (;0;) (type 0)
124    nop
125  )
126)
127"#;
128
129    const COMPONENT_WIT: &str = r#"
130package test:foo;
131world test-world {}
132"#;
133
134    #[test]
135    fn component_metadata_embedding_works() -> Result<()> {
136        let mut bytes = wat::parse_str(MODULE_WAT)?;
137
138        // Get original len & custom section count
139        let original_len = bytes.len();
140        let payloads = wasmparser::Parser::new(0).parse_all(&bytes);
141        let original_custom_section_count = payloads.fold(0, |acc, payload| {
142            if let Ok(Payload::CustomSection { .. }) = payload {
143                acc + 1
144            } else {
145                acc
146            }
147        });
148
149        // Parse pre-canned WIT to build resolver
150        let mut resolver = Resolve::default();
151        let pkg = resolver.push_str("in-code.wit", COMPONENT_WIT)?;
152        let world = resolver.select_world(&[pkg], Some("test-world"))?;
153
154        // Embed component metadata
155        embed_component_metadata(&mut bytes, &resolver, world, StringEncoding::UTF8, true)?;
156
157        // Re-retrieve custom section count, and search for the component-type custom section along the way
158        let mut found_component_section = false;
159        let new_custom_section_count =
160            wasmparser::Parser::new(0)
161                .parse_all(&bytes)
162                .fold(0, |acc, payload| {
163                    if let Ok(Payload::CustomSection(reader)) = payload {
164                        if reader.name() == "component-type" {
165                            found_component_section = true;
166                        }
167                        acc + 1
168                    } else {
169                        acc
170                    }
171                });
172
173        assert!(original_len < bytes.len());
174        assert_eq!(original_custom_section_count + 1, new_custom_section_count);
175        assert!(found_component_section);
176
177        Ok(())
178    }
179}