wit_component/
lib.rs

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