wasmer_wit_component/
cli.rs

1//! The WebAssembly component tool command line interface.
2
3#![deny(missing_docs)]
4
5use crate::{
6    decode_interface_component, ComponentEncoder, InterfaceEncoder, InterfacePrinter,
7    StringEncoding,
8};
9use anyhow::{bail, Context, Result};
10use clap::Parser;
11use std::path::{Path, PathBuf};
12use wasmer_wit_parser::Interface;
13
14fn parse_named_interface(s: &str) -> Result<Interface> {
15    let (name, path) = s
16        .split_once('=')
17        .ok_or_else(|| anyhow::anyhow!("expected a value with format `NAME=INTERFACE`"))?;
18
19    parse_interface(Some(name.to_string()), Path::new(path))
20}
21
22fn parse_unnamed_interface(s: &str) -> Result<Interface> {
23    parse_interface(None, Path::new(s))
24}
25
26fn parse_interface(name: Option<String>, path: &Path) -> Result<Interface> {
27    if !path.is_file() {
28        bail!("interface file `{}` does not exist", path.display(),);
29    }
30
31    let mut interface = Interface::parse_file(&path)
32        .with_context(|| format!("failed to parse interface file `{}`", path.display()))?;
33
34    interface.name = name.unwrap_or_else(|| "".to_string());
35
36    Ok(interface)
37}
38
39/// WebAssembly component encoder.
40///
41/// Encodes a WebAssembly component from a core WebAssembly module.
42#[derive(Debug, Parser)]
43#[clap(name = "component-encoder", version = env!("CARGO_PKG_VERSION"))]
44pub struct WitComponentApp {
45    /// The path to an interface definition file the component imports.
46    #[clap(long = "import", value_name = "NAME=INTERFACE", parse(try_from_str = parse_named_interface))]
47    pub imports: Vec<Interface>,
48
49    /// The path to an interface definition file the component exports.
50    #[clap(long = "export", value_name = "NAME=INTERFACE", parse(try_from_str = parse_named_interface))]
51    pub exports: Vec<Interface>,
52
53    /// The path of the output WebAssembly component.
54    #[clap(long, short = 'o', value_name = "OUTPUT")]
55    pub output: Option<PathBuf>,
56
57    /// The default interface the component exports.
58    #[clap(long, short = 'i', value_name = "INTERFACE", parse(try_from_str = parse_unnamed_interface))]
59    pub interface: Option<Interface>,
60
61    /// Skip validation of the output component.
62    #[clap(long)]
63    pub skip_validation: bool,
64
65    /// The expected string encoding format for the component.
66    /// Supported values are: `utf8` (default), `utf16`, and `compact-utf16`.
67    #[clap(long, value_name = "ENCODING")]
68    pub encoding: Option<StringEncoding>,
69
70    /// Path to the WebAssembly module to encode.
71    #[clap(index = 1, value_name = "MODULE")]
72    pub module: PathBuf,
73}
74
75impl WitComponentApp {
76    /// Executes the application.
77    pub fn execute(self) -> Result<()> {
78        if !self.module.is_file() {
79            bail!(
80                "module `{}` does not exist as a file",
81                self.module.display()
82            );
83        }
84
85        let output = self.output.unwrap_or_else(|| {
86            let mut stem: PathBuf = self.module.file_stem().unwrap().into();
87            stem.set_extension("wasm");
88            stem
89        });
90
91        let module = wat::parse_file(&self.module)
92            .with_context(|| format!("failed to parse module `{}`", self.module.display()))?;
93
94        let mut encoder = ComponentEncoder::default()
95            .module(&module)
96            .imports(&self.imports)
97            .exports(&self.exports)
98            .validate(!self.skip_validation);
99
100        if let Some(interface) = &self.interface {
101            encoder = encoder.interface(interface);
102        }
103
104        if let Some(encoding) = &self.encoding {
105            encoder = encoder.encoding(*encoding);
106        }
107
108        let bytes = encoder.encode().with_context(|| {
109            format!(
110                "failed to encode a component from module `{}`",
111                self.module.display()
112            )
113        })?;
114
115        std::fs::write(&output, bytes)
116            .with_context(|| format!("failed to write output file `{}`", output.display()))?;
117
118        println!("encoded component `{}`", output.display());
119
120        Ok(())
121    }
122}
123
124/// WebAssembly interface encoder.
125///
126/// Encodes a WebAssembly interface as a WebAssembly component.
127#[derive(Debug, Parser)]
128#[clap(name = "wit2wasm", version = env!("CARGO_PKG_VERSION"))]
129pub struct WitToWasmApp {
130    /// The path of the output WebAssembly component.
131    #[clap(long, short = 'o', value_name = "OUTPUT")]
132    pub output: Option<PathBuf>,
133
134    /// The path to the WebAssembly interface file to encode.
135    #[clap(index = 1, value_name = "INTERFACE")]
136    pub interface: PathBuf,
137}
138
139impl WitToWasmApp {
140    /// Executes the application.
141    pub fn execute(self) -> Result<()> {
142        let output = self.output.unwrap_or_else(|| {
143            let mut stem: PathBuf = self.interface.file_stem().unwrap().into();
144            stem.set_extension("wasm");
145            stem
146        });
147
148        let interface = parse_interface(None, &self.interface)?;
149
150        let encoder = InterfaceEncoder::new(&interface).validate(true);
151
152        let bytes = encoder.encode().with_context(|| {
153            format!(
154                "failed to encode a component from interface `{}`",
155                self.interface.display()
156            )
157        })?;
158
159        std::fs::write(&output, bytes)
160            .with_context(|| format!("failed to write output file `{}`", output.display()))?;
161
162        println!("encoded interface as component `{}`", output.display());
163
164        Ok(())
165    }
166}
167
168/// WebAssembly interface decoder.
169///
170/// Decodes a WebAssembly interface from a WebAssembly component.
171#[derive(Debug, Parser)]
172#[clap(name = "wit2wasm", version = env!("CARGO_PKG_VERSION"))]
173pub struct WasmToWitApp {
174    /// The path of the output WebAssembly interface file.
175    #[clap(long, short = 'o', value_name = "OUTPUT")]
176    pub output: Option<PathBuf>,
177
178    /// The path to the WebAssembly component to decode.
179    #[clap(index = 1, value_name = "COMPONENT")]
180    pub component: PathBuf,
181}
182
183impl WasmToWitApp {
184    /// Executes the application.
185    pub fn execute(self) -> Result<()> {
186        let output = self.output.unwrap_or_else(|| {
187            let mut stem: PathBuf = self.component.file_stem().unwrap().into();
188            stem.set_extension("wit");
189            stem
190        });
191
192        if !self.component.is_file() {
193            bail!(
194                "component `{}` does not exist as a file",
195                self.component.display()
196            );
197        }
198
199        let bytes = wat::parse_file(&self.component)
200            .with_context(|| format!("failed to parse component `{}`", self.component.display()))?;
201
202        let interface = decode_interface_component(&bytes).with_context(|| {
203            format!("failed to decode component `{}`", self.component.display())
204        })?;
205
206        let mut printer = InterfacePrinter::default();
207
208        std::fs::write(&output, printer.print(&interface)?)
209            .with_context(|| format!("failed to write output file `{}`", output.display()))?;
210
211        println!("decoded interface to `{}`", output.display());
212
213        Ok(())
214    }
215}