Rust Components
Creating a basic component
Note: Some of these steps are taken from https://github.com/bytecodealliance/wit-bindgen
This example will create a simple Slipway component in rust which reverses a string.
Initial Setup
If you don't already have it, add the wasm32-wasip2
target:
rustup target add wasm32-wasip2
Create a folder for your component:
mkdir slipway_example
cd slipway_example
Create a new rust library:
cargo init --lib .
Add wit-bindgen
as a dependency by executing:
cargo add wit-bindgen
For our example we'll also add serde
and serde_json
:
cargo add serde --features serde_derive
cargo add serde_json
In order to compile a WASI dynamic library, the following must be added to the Cargo.toml
file:
[lib]
crate-type = ["cdylib"]
Copy the Slipway .wit
file to the wit
folder:
mkdir wit
slipway wit > wit/slipway.wit
Generate bindings from the .wit
file and implement the component in src/lib.rs
:
wit_bindgen::generate!({
world: "slipway",
});
struct Component;
export!(Component);
impl Guest for Component {
fn run(input: String) -> Result<String, ComponentError> {
slipway_host::log_info("Hello world!");
Ok(input)
}
}
Implementing the Example
Next we'll add some logic which will deserialize the input, convert the string contained in the input to uppercase, and serialize it as the component output. The entire file will look like this:
use std::fmt::Display;
use serde::{Deserialize, Serialize};
wit_bindgen::generate!({
world: "slipway",
});
struct Component;
export!(Component);
impl Guest for Component {
fn run(input_string: String) -> Result<String, ComponentError> {
// Deserialize the input.
let input: Input = serde_json::from_str(&input_string)
.map_err(|e| ComponentError::new("Failed to deserialize input.", e))?;
// Example of logging through the host.
slipway_host::log_info(&format!("Formatting: {}", input.text));
// Create the output.
let output = Output {
text_uppercase: input.text.to_uppercase(),
};
// Serialize the output.
let output_string = serde_json::to_string(&output)
.map_err(|e| ComponentError::new("Failed to serialize output.", e))?;
// Return the output.
Ok(output_string)
}
}
#[derive(Deserialize)]
struct Input {
text: String,
}
#[derive(Serialize)]
struct Output {
text_uppercase: String,
}
// Helper function to make creating a new ComponentError easier.
impl ComponentError {
fn new(message: &str, error: impl Display) -> Self {
ComponentError {
message: message.to_string(),
inner: vec![error.to_string()],
}
}
}
Build the component targeting wasm32-wasip2
:
cargo build --target wasm32-wasip2 --release
Bundling as a Slipway Component
Create a Slipway Component JSON configuration file slipway_component.json
:
{
"publisher": "acme",
"name": "example",
"description": "An example slipway component.",
"version": "0.0.1",
"input": {
"properties": {
"text": { "type": "string" }
}
},
"output": {
"properties": {
"text_reversed": { "type": "string" }
}
}
}
For the purposes of the example, we've set the publisher to acme
and the component name to example
.
This file uses JsonTypeDef to state that the component should take as an input an object with a text field containing a string, and output an object containing a text_reversed
field which contains a string. This matches the Input
and Output
structs in the rust code.
Slipway will use these definitions validate the inputs and outputs to ensure the component only receives the data it expects, and only outputs the expected data. It is also possible to use JSON Schema.
Assemble the component in a folder:
mkdir -p components/component
cp target/wasm32-wasip2/release/slipway_example.wasm components/component/run.wasm
cp slipway_component.json components/component/slipway_component.json
Package the component folder into a file:
slipway package components/component
You should see an output similar to:
INFO Written component tar file to: components/acme.example.0.0.1.tar
This .tar
file is the final component.
Automating the Build/Assemble/Package steps
To make it easier to iterate, let's create a shell script which handles the building, assembling and packaging.
Create a file called build.sh
:
touch build.sh
chmod +x build.sh
Add the shell commands we already ran above to build.sh
, with an additional line to clean the components folder each time.:
cargo build --target wasm32-wasip2 --release
rm -rf components
mkdir -p components/component
cp target/wasm32-wasip2/release/slipway_example.wasm components/component/run.wasm
cp slipway_component.json components/component/slipway_component.json
slipway package components/component
Run build.sh
to ensure it all works:
./build.sh
Running the component
We can create a simple Slipway Rig that provides the component with an input.
Create a file called rig.json
which contains the following JSON:
{
"publisher": "acme",
"name": "example_rig",
"version": "0.0.1",
"description": "Calls the example component",
"rigging": {
"example": {
"component": "file:components/acme.example.0.0.1.tar",
"input": {
"text": "Hello World!"
}
}
}
}
Run the rig:
slipway run rig.json --allow-local-components
You should see some output, which will end with:
Component "example" output:
{
"text_uppercase": "HELLO WORLD!"
}
In the above rig we referenced the component using a local file URL file:components/acme.example.0.0.1.tar
. While this is convenient, in the long run it can be better to reference the components as we would if they were deployed to a Slipway Registry.
Here I've changed the component reference to a registry reference:
{
"publisher": "acme",
"name": "example_rig",
"version": "0.0.1",
"description": "Calls the example component",
"rigging": {
"example": {
"component": "acme.example.0.0.1",
"input": {
"text": "Hello World!"
}
}
}
}
To run this we have to change the slipway
command slightly to:
- Tell Slipway to use a local registry, and the format of that registry.
- Allow registry components, rather than local components.
slipway run rig.json --registry file:components/{publisher}.{name}.{version}.tar --allow-registry-components
Slipway will try any registries supplied on the command line in order, and ultimately will fall back to the default Slipway registry. This allows us to test local components as if they were deployed, with minimal changes to our rigs once the components have actually been deployed.
If you add --log-level debug
to the list of arguments you will see some extra debug output showing where the components are being loaded from.
Local registries are particularly useful while developing multiple components, for example you could specify the registry URL as:
--registry file:../slipway_{name}/components/{publisher}.{name}.{version}.tar
The above will cause Slipway to find components in their respective project folders, searching from the parent folder.