Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
101 changes: 34 additions & 67 deletions components/extract-wit/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,81 +5,76 @@ use std::collections::BTreeMap;
use crate::{
componentized::component::types::{Component, Error},
exports::componentized::component::wit::{
Docs, Enum, EnumCase, Flag, Flags, Function, FunctionKind, Guest, GuestWit, Handle,
IncludeName, Interface, InterfaceId, List, Map, Package, PackageId, PackageName, Param,
Record, RecordField, Result as Result_, Stability, Stable, Tuple, Type, TypeDef,
TypeDefKind, TypeId, TypeOwner, Unstable, Variant, VariantCase, Version, VersionIdentifier,
Wit, World, WorldId, WorldInclude, WorldItem, WorldItemInterface, WorldKey,
Docs, Enum, EnumCase, Flag, Flags, Function, FunctionKind, Guest, Handle, IncludeName,
Interface, InterfaceId, List, Map, Package, PackageId, PackageName, Param, Record,
RecordField, Result as Result_, Stability, Stable, Tuple, Type, TypeDef, TypeDefKind,
TypeId, TypeOwner, Unstable, Variant, VariantCase, Version, VersionIdentifier, Wit, World,
WorldId, WorldInclude, WorldItem, WorldItemInterface, WorldKey,
},
};

pub(crate) struct ExtractWit;

impl Guest for ExtractWit {
type Wit = ExtractedWit;

#[allow(async_fn_in_trait)]
async fn extract(component: &Component) -> Result<(Wit, Package), Error> {
let wasm = component.into_wasm();
let decoded = wit_component::decode(&wasm)?;

let wit = ExtractedWit::new(decoded.resolve());
let package = wit
.package(ExtractedWit::package_id(decoded.package()))
.expect("decoded package must exist");
async fn extract(component: Component) -> Result<Wit, Error> {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This function actually returns WIT for any valid bytes, including a package that has 0..N worlds, right? Maybe the extract func should be explicitly more general than "component". It looks like the Wit type itself already is.

let decoded = wit_component::decode(&component)?;

Ok((Wit::new(wit), package))
Wit::new(decoded.resolve(), decoded.package())
}
}

pub(crate) struct ExtractedWit {
worlds: BTreeMap<u32, World>,
interfaces: BTreeMap<u32, Interface>,
types: BTreeMap<u32, TypeDef>,
packages: BTreeMap<u32, Package>,
}

impl ExtractedWit {
fn new(resolve: &wit_parser::Resolve) -> Self {
Self {
impl Wit {
fn new(
resolve: &wit_parser::Resolve,
package_id: wit_parser::PackageId,
) -> Result<Self, Error> {
let wit = Self {
worlds: resolve.worlds.clone().into_iter().fold(
BTreeMap::new(),
|mut worlds, (id, world)| {
worlds.insert(Self::world_id(id).world_id, Self::world(world));
worlds.insert(Self::world_id(id), Self::world(world));
worlds
},
),
interfaces: resolve.interfaces.clone().into_iter().fold(
BTreeMap::new(),
|mut interfaces, (id, interface)| {
interfaces.insert(
Self::interface_id(id).interface_id,
Self::interface(interface),
);
interfaces.insert(Self::interface_id(id), Self::interface(interface));
interfaces
},
),
types: resolve.types.clone().into_iter().fold(
BTreeMap::new(),
|mut types, (id, type_def)| {
types.insert(Self::type_id(id).type_id, Self::type_def(type_def));
types.insert(Self::type_id(id), Self::type_def(type_def));
types
},
),
packages: resolve.packages.clone().into_iter().fold(
BTreeMap::new(),
|mut packages, (id, package)| {
packages.insert(Self::package_id(id).package_id, Self::package(package));
packages.insert(Self::package_id(id), Self::package(package));
packages
},
),

default_package: Some(Self::package_id(package_id)),
};

if wit
.packages
.get(&wit.default_package.clone().unwrap())
.is_none()
{
Err(Error::Other(Some("decoded package must exist".to_string())))?;
}

Ok(wit)
}

fn world_id(id: wit_parser::WorldId) -> WorldId {
WorldId {
world_id: u32::try_from(id.index()).expect("id too large"),
}
WorldId::from(format!("world:{}", id.index()))
}

fn world(world: wit_parser::World) -> World {
Expand Down Expand Up @@ -147,9 +142,7 @@ impl ExtractedWit {
}

fn interface_id(id: wit_parser::InterfaceId) -> InterfaceId {
InterfaceId {
interface_id: u32::try_from(id.index()).expect("id too large"),
}
InterfaceId::from(format!("interface:{}", id.index()))
}

fn interface(interface: wit_parser::Interface) -> Interface {
Expand Down Expand Up @@ -205,9 +198,7 @@ impl ExtractedWit {
}

fn type_id(id: wit_parser::TypeId) -> TypeId {
TypeId {
type_id: u32::try_from(id.index()).expect("id too large"),
}
TypeId::from(format!("type:{}", id.index()))
}

fn type_(type_: wit_parser::Type) -> Type {
Expand Down Expand Up @@ -369,9 +360,7 @@ impl ExtractedWit {
}

fn package_id(id: wit_parser::PackageId) -> PackageId {
PackageId {
package_id: u32::try_from(id.index()).expect("id too large"),
}
PackageId::from(format!("package:{}", id.index()))
}

fn package(package: wit_parser::Package) -> Package {
Expand Down Expand Up @@ -444,28 +433,6 @@ impl ExtractedWit {
}
}

impl GuestWit for ExtractedWit {
#[allow(async_fn_in_trait)]
fn world(&self, id: WorldId) -> Option<World> {
self.worlds.get(&id.world_id).cloned()
}

#[allow(async_fn_in_trait)]
fn interface(&self, id: InterfaceId) -> Option<Interface> {
self.interfaces.get(&id.interface_id).cloned()
}

#[allow(async_fn_in_trait)]
fn type_(&self, id: TypeId) -> Option<TypeDef> {
self.types.get(&id.type_id).cloned()
}

#[allow(async_fn_in_trait)]
fn package(&self, id: PackageId) -> Option<Package> {
self.packages.get(&id.package_id).cloned()
}
}

impl From<anyhow::Error> for Error {
fn from(value: anyhow::Error) -> Self {
Self::Other(Some(value.to_string()))
Expand Down
38 changes: 6 additions & 32 deletions components/wac-loader/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,57 +3,31 @@
use wac_graph::{types::Package, CompositionGraph, EncodeOptions};

use crate::exports::componentized::component::{
types::{Component, ComponentBorrow, Error, Guest as TypesGuest, GuestComponent},
types::{Component, Error},
wac_loader::Guest,
};

pub(crate) struct WacLoader;

impl TypesGuest for WacLoader {
type Component = WacComponent;
}

impl Guest for WacLoader {
#[allow(async_fn_in_trait)]
async fn plug(
socket: ComponentBorrow<'_>,
plugs: Vec<ComponentBorrow<'_>>,
) -> Result<Component, Error> {
async fn plug(socket: Component, plugs: Vec<Component>) -> Result<Component, Error> {
let mut graph = CompositionGraph::new();

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this component also import a wasm-validator, or are the error messages from wac_graph just as useful?

@scothis scothis Sep 4, 2026

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure if wac validates anything more that the interfaces being combined, plug even less so than graph. We can add explicit validation if it proves useful in the future.

let socket: &WacComponent = socket.get();
let socket = Package::from_bytes("socket", None, socket.into_wasm(), graph.types_mut())?;
let socket = Package::from_bytes("socket", None, socket, graph.types_mut())?;
let socket = graph.register_package(socket)?;

let mut graph_plugs = Vec::new();
for plug in plugs {
let plug: &WacComponent = plug.get();
let plug = Package::from_bytes("plug", None, plug.into_wasm(), graph.types_mut())?;
let plug = Package::from_bytes("plug", None, plug, graph.types_mut())?;
let plug = graph.register_package(plug)?;
graph_plugs.push(plug);
}

wac_graph::plug(&mut graph, graph_plugs, socket)?;
let composed_wasm = graph.encode(EncodeOptions::default())?;

Ok(Component::new(WacComponent::new(composed_wasm)))
}
}
let component = graph.encode(EncodeOptions::default())?;

pub(crate) struct WacComponent {
wasm: Vec<u8>,
}

impl WacComponent {
fn new(wasm: Vec<u8>) -> Self {
Self { wasm }
}
}

impl GuestComponent for WacComponent {
#[allow(async_fn_in_trait)]
fn into_wasm(&self) -> Vec<u8> {
self.wasm.clone()
Ok(component)
}
}

Expand Down
28 changes: 2 additions & 26 deletions components/wasm-loader/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,41 +1,17 @@
#![no_main]

use crate::{
exports::componentized::component::types::{
Component, Error, Guest as TypesGuest, GuestComponent,
},
exports::componentized::component::types::{Component, Error},
exports::componentized::component::wasm_loader::Guest,
};
use wit_bindgen::rt::async_support::StreamReader;

pub(crate) struct WasmLoader;

impl TypesGuest for WasmLoader {
type Component = WasmComponent;
}

impl Guest for WasmLoader {
#[allow(async_fn_in_trait)]
async fn load(wasm: StreamReader<u8>) -> Result<Component, Error> {
let wasm = wasm.collect().await;
Ok(Component::new(WasmComponent::new(wasm)))
}
}

pub(crate) struct WasmComponent {
wasm: Vec<u8>,
}

impl WasmComponent {
fn new(wasm: Vec<u8>) -> Self {
Self { wasm }
}
}

impl GuestComponent for WasmComponent {
#[allow(async_fn_in_trait)]
fn into_wasm(&self) -> Vec<u8> {
self.wasm.clone()
Ok(wasm.collect().await)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Now that this is effectively a single-line implementation, is having it in a component justifiable? Why wouldn't a consuming component just call collect() itself instead of call this component's function?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yea, can clean that up when restructuring the loaders

}
}

Expand Down
35 changes: 13 additions & 22 deletions components/wit/deps/componentized-component-0.0.0-0/package.wit
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,7 @@ interface types {
other(option<string>),
}

resource component {
into-wasm: func() -> list<u8>;
}
type component = list<u8>;
}

interface wasm-loader {
Expand Down Expand Up @@ -43,15 +41,13 @@ interface wasm-directory-loader {
interface wac-loader {
use types.{component, error};

plug: async func(socket: borrow<component>, plugs: list<borrow<component>>) -> result<component, error>;
plug: async func(socket: component, plugs: list<component>) -> result<component, error>;
}

interface wit {
use types.{component, error};

record type-id {
type-id: u32,
}
type type-id = string;

variant %type {
%bool,
Expand Down Expand Up @@ -209,13 +205,9 @@ interface wit {
external-id: option<string>,
}

record interface-id {
interface-id: u32,
}
type interface-id = string;

record world-id {
world-id: u32,
}
type world-id = string;

variant type-owner {
%world(world-id),
Expand Down Expand Up @@ -261,9 +253,7 @@ interface wit {
%interface(interface-id),
}

record package-id {
package-id: u32,
}
type package-id = string;

record %interface {
name: option<string>,
Expand Down Expand Up @@ -297,14 +287,15 @@ interface wit {
worlds: list<tuple<string, world-id>>,
}

resource wit {
%interface: func(id: interface-id) -> option<%interface>;
%package: func(id: package-id) -> option<%package>;
%type: func(id: type-id) -> option<type-def>;
%world: func(id: world-id) -> option<%world>;
record wit {
interfaces: map<string, %interface>,
packages: map<string, %package>,
types: map<string, type-def>,
worlds: map<string, %world>,
default-package: option<package-id>,
}

extract: async func(component: borrow<component>) -> result<tuple<wit, %package>, error>;
extract: async func(component: component) -> result<wit, error>;
}

world imports {
Expand Down
1 change: 0 additions & 1 deletion components/wit/worlds.wit
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ world extract-wit {
}

world wac-loader {
import componentized:component/types@0.0.0-0;
export componentized:component/types@0.0.0-0;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The explicit types export shouldn't be necessary.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it is because of the error

export componentized:component/wac-loader@0.0.0-0;
}
Expand Down
2 changes: 1 addition & 1 deletion wit/loader.wit
Original file line number Diff line number Diff line change
Expand Up @@ -31,5 +31,5 @@ interface wasm-directory-loader {
interface wac-loader {
use types.{component, error};

plug: async func(socket: borrow<component>, plugs: list<borrow<component>>) -> result<component, error>;
plug: async func(socket: component, plugs: list<component>) -> result<component, error>;
}
4 changes: 1 addition & 3 deletions wit/types.wit
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,6 @@ interface types {
other(option<string>),
}

resource component {
into-wasm: func() -> list<u8>;
}
type component = list<u8>;

}
Loading