wit_component_update/
lib.rs

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