Skip to content

Commit 8374daa

Browse files
feat(jco): add import/export metadata getter fn to wasm-tools
1 parent b27976e commit 8374daa

File tree

4 files changed

+152
-13
lines changed

4 files changed

+152
-13
lines changed

crates/wasm-tools-component/src/lib.rs

Lines changed: 115 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ use std::path::PathBuf;
55
use wasm_encoder::{Encode, Section};
66
use wasm_metadata::Producers;
77
use wit_component::{ComponentEncoder, DecodedWasm, WitPrinter};
8-
use wit_parser::Resolve;
8+
use wit_parser::{Resolve, WorldItem};
99

1010
mod bindings {
1111
use super::WasmToolsJs;
@@ -16,8 +16,8 @@ mod bindings {
1616
}
1717

1818
use bindings::exports::local::wasm_tools::tools::{
19-
EmbedOpts, EnabledFeatureSet, Guest, ModuleMetaType, ModuleMetadata, ProducersFields,
20-
StringEncoding,
19+
EmbedOpts, EnabledFeatureSet, Guest, InterfaceMetadata, ModuleMetaType, ModuleMetadata,
20+
ProducersFields, SemverVersion, StringEncoding, WitMetadata,
2121
};
2222

2323
struct WasmToolsJs;
@@ -59,8 +59,6 @@ impl Guest for WasmToolsJs {
5959
let decoded = wit_component::decode(&binary)
6060
.map_err(|e| format!("Failed to decode wit component\n{e:?}"))?;
6161

62-
// let world = decode_world("component", &binary);
63-
6462
let doc = match &decoded {
6563
DecodedWasm::WitPackage(_, _) => panic!("Unexpected wit package"),
6664
DecodedWasm::Component(resolve, world) => resolve.worlds[*world].package.unwrap(),
@@ -74,6 +72,118 @@ impl Guest for WasmToolsJs {
7472
Ok(printer.output.to_string())
7573
}
7674

75+
fn component_wit_metadata_for_world(
76+
binary: Vec<u8>,
77+
maybe_world_name: Option<String>,
78+
) -> Result<WitMetadata, String> {
79+
let decoded = wit_component::decode(&binary)
80+
.map_err(|e| format!("Failed to decode wit component\n{e:?}"))?;
81+
82+
let (resolve, world_id) = match &decoded {
83+
DecodedWasm::WitPackage(_, _) => panic!("Unexpected wit package"),
84+
DecodedWasm::Component(resolve, world) => (resolve, world),
85+
};
86+
87+
let world = match maybe_world_name {
88+
Some(world_name) => {
89+
resolve
90+
.worlds
91+
.iter()
92+
.find(|w| w.1.name == world_name)
93+
.ok_or_else(|| String::from("failed to find wold with given name"))?
94+
.1
95+
}
96+
None => resolve
97+
.worlds
98+
.get(*world_id)
99+
.ok_or_else(|| String::from("failed to find package id"))?,
100+
};
101+
102+
let mut imports = Vec::new();
103+
for (import_key, import_item) in world.imports.iter() {
104+
let WorldItem::Interface { id, .. } = import_item else {
105+
continue;
106+
};
107+
let iface = resolve
108+
.interfaces
109+
.get(*id)
110+
.ok_or_else(|| format!("failed to find interface with id [{id:?}]"))?;
111+
let iface_name = iface
112+
.name
113+
.clone()
114+
.ok_or_else(|| format!("missing iface name for world key [{import_key:?}]"))?;
115+
let pkg_id = iface
116+
.package
117+
.ok_or_else(|| format!("no package for interface [{:?}]", iface.name))?;
118+
let pkg = resolve
119+
.packages
120+
.get(pkg_id)
121+
.ok_or_else(|| format!("no package with ID [{pkg_id:?}]"))?;
122+
123+
imports.push(InterfaceMetadata {
124+
namespace: pkg.name.namespace.clone(),
125+
package: pkg.name.name.clone(),
126+
interface: iface_name.clone(),
127+
version: pkg.name.version.as_ref().map(|v| SemverVersion {
128+
major: v.major,
129+
minor: v.minor,
130+
patch: v.patch,
131+
pre: match v.pre.as_str() {
132+
"" => None,
133+
v => Some(v.into()),
134+
},
135+
build: match v.build.as_str() {
136+
"" => None,
137+
v => Some(v.into()),
138+
},
139+
}),
140+
});
141+
}
142+
143+
let mut exports = Vec::new();
144+
for (export_key, export_item) in world.exports.iter() {
145+
let WorldItem::Interface { id, .. } = export_item else {
146+
continue;
147+
};
148+
let iface = resolve
149+
.interfaces
150+
.get(*id)
151+
.ok_or_else(|| format!("failed to find interface with id [{id:?}]"))?;
152+
let iface_name = iface
153+
.name
154+
.clone()
155+
.ok_or_else(|| format!("missing iface name for world key [{export_key:?}]"))?;
156+
let pkg_id = iface
157+
.package
158+
.ok_or_else(|| format!("no package for interface [{:?}]", iface.name))?;
159+
let pkg = resolve
160+
.packages
161+
.get(pkg_id)
162+
.ok_or_else(|| format!("no package with ID [{pkg_id:?}]"))?;
163+
164+
exports.push(InterfaceMetadata {
165+
namespace: pkg.name.namespace.clone(),
166+
package: pkg.name.name.clone(),
167+
interface: iface_name.clone(),
168+
version: pkg.name.version.as_ref().map(|v| SemverVersion {
169+
major: v.major,
170+
minor: v.minor,
171+
patch: v.patch,
172+
pre: match v.pre.as_str() {
173+
"" => None,
174+
v => Some(v.into()),
175+
},
176+
build: match v.build.as_str() {
177+
"" => None,
178+
v => Some(v.into()),
179+
},
180+
}),
181+
});
182+
}
183+
184+
Ok(WitMetadata { imports, exports })
185+
}
186+
77187
fn component_embed(embed_opts: EmbedOpts) -> Result<Vec<u8>, String> {
78188
let binary = &embed_opts.binary;
79189

crates/wasm-tools-component/wit/wasm-tools.wit

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,35 @@ interface tools {
1919
/// Extract a *.wit interface from a component, optionally providing a document name to extract
2020
component-wit: func(binary: list<u8>) -> result<string, string>;
2121

22+
/// Simple representation of semantic versions
23+
// see: https://docs.rs/semver/1.0.28/semver/struct.Version.html
24+
record semver-version {
25+
major: u64,
26+
minor: u64,
27+
patch: u64,
28+
pre: option<string>,
29+
build: option<string>,
30+
}
31+
32+
/// Metadata that can be returned related to an interface
33+
record interface-metadata {
34+
namespace: string,
35+
%package: string,
36+
%interface: string,
37+
version: option<semver-version>,
38+
}
39+
40+
/// Information about a given WIT interface
41+
record wit-metadata {
42+
imports: list<interface-metadata>,
43+
exports: list<interface-metadata>,
44+
}
45+
46+
/// Extract metadata from a WIT interface for a component for a given world
47+
///
48+
/// If no world is specified, the root world is used
49+
component-wit-metadata-for-world: func(wit-bytes: list<u8>, world-name: option<string>) -> result<wit-metadata, string>;
50+
2251
type producers-fields = list<tuple<string, list<tuple<string, string>>>>;
2352

2453
/// Enumerate enabled features

packages/jco/types/api.d.ts

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,39 +2,39 @@
22
* @param {Parameters<import('../obj/wasm-tools.js').print>[0]} binary
33
* @return {Promise<ReturnType<import('../obj/wasm-tools.js').print>>}
44
*/
5-
export function print(binary: Parameters<any>[0]): Promise<ReturnType<any>>;
5+
export function print(binary: Parameters<import("../obj/wasm-tools.js").print>[0]): Promise<ReturnType<import("../obj/wasm-tools.js").print>>;
66
/**
77
* @param {Parameters<import('../obj/wasm-tools.js').parse>[0]} wat
88
* @return {Promise<ReturnType<import('../obj/wasm-tools.js').parse>>}
99
*/
10-
export function parse(wat: Parameters<any>[0]): Promise<ReturnType<any>>;
10+
export function parse(wat: Parameters<import("../obj/wasm-tools.js").parse>[0]): Promise<ReturnType<import("../obj/wasm-tools.js").parse>>;
1111
/**
1212
* @param {Parameters<import('../obj/wasm-tools.js').componentWit>[0]} binary
1313
* @return {Promise<ReturnType<import('../obj/wasm-tools.js').componentWit>>}
1414
*/
15-
export function componentWit(binary: Parameters<any>[0]): Promise<ReturnType<any>>;
15+
export function componentWit(binary: Parameters<import("../obj/wasm-tools.js").componentWit>[0]): Promise<ReturnType<import("../obj/wasm-tools.js").componentWit>>;
1616
/**
1717
* @param {Parameters<import('../obj/wasm-tools.js').componentNew>[0]} binary
1818
* @param {Parameters<import('../obj/wasm-tools.js').componentNew>[1]} adapters
1919
* @return {Promise<ReturnType<import('../obj/wasm-tools.js').componentNew>>}
2020
*/
21-
export function componentNew(binary: Parameters<any>[0], adapters: Parameters<any>[1]): Promise<ReturnType<any>>;
21+
export function componentNew(binary: Parameters<import("../obj/wasm-tools.js").componentNew>[0], adapters: Parameters<import("../obj/wasm-tools.js").componentNew>[1]): Promise<ReturnType<import("../obj/wasm-tools.js").componentNew>>;
2222
/**
2323
* @param {Parameters<import('../obj/wasm-tools.js').componentEmbed>[0]} embedOpts
2424
* @return {Promise<ReturnType<import('../obj/wasm-tools.js').componentEmbed>>}
2525
*/
26-
export function componentEmbed(embedOpts: Parameters<any>[0]): Promise<ReturnType<any>>;
26+
export function componentEmbed(embedOpts: Parameters<import("../obj/wasm-tools.js").componentEmbed>[0]): Promise<ReturnType<import("../obj/wasm-tools.js").componentEmbed>>;
2727
/**
2828
* @param {Parameters<import('../obj/wasm-tools.js').metadataAdd>[0]} binary
2929
* @param {Parameters<import('../obj/wasm-tools.js').metadataAdd>[1]} metadata
3030
* @return {Promise<ReturnType<import('../obj/wasm-tools.js').metadataAdd>>}
3131
*/
32-
export function metadataAdd(binary: Parameters<any>[0], metadata: Parameters<any>[1]): Promise<ReturnType<any>>;
32+
export function metadataAdd(binary: Parameters<import("../obj/wasm-tools.js").metadataAdd>[0], metadata: Parameters<import("../obj/wasm-tools.js").metadataAdd>[1]): Promise<ReturnType<import("../obj/wasm-tools.js").metadataAdd>>;
3333
/**
3434
* @param {Parameters<import('../obj/wasm-tools.js').metadataShow>[0]} binary
3535
* @return {Promise<ReturnType<import('../obj/wasm-tools.js').metadataShow>>}
3636
*/
37-
export function metadataShow(binary: Parameters<any>[0]): Promise<ReturnType<any>>;
37+
export function metadataShow(binary: Parameters<import("../obj/wasm-tools.js").metadataShow>[0]): Promise<ReturnType<import("../obj/wasm-tools.js").metadataShow>>;
3838
export function preview1AdapterCommandPath(): URL;
3939
export function preview1AdapterReactorPath(): URL;
4040
export { optimizeComponent as opt } from "./cmd/opt.js";

packages/jco/types/api.d.ts.map

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)