49 releases (10 breaking)

0.12.1 Apr 10, 2024
0.11.8 Apr 8, 2024
0.11.7 Mar 18, 2024
0.7.0 Dec 7, 2023
0.2.0 Jul 21, 2023

#54 in WebAssembly

Download history 225/week @ 2024-01-05 252/week @ 2024-01-12 371/week @ 2024-01-19 365/week @ 2024-01-26 605/week @ 2024-02-02 302/week @ 2024-02-09 224/week @ 2024-02-16 588/week @ 2024-02-23 1052/week @ 2024-03-01 335/week @ 2024-03-08 742/week @ 2024-03-15 358/week @ 2024-03-22 253/week @ 2024-03-29 670/week @ 2024-04-05 361/week @ 2024-04-12 436/week @ 2024-04-19

1,725 downloads per month
Used in 7 crates (3 directly)

MIT license

105KB
2.5K SLoC

warpgate

Crates.io Crates.io

Warpgate is a library for downloading, resolving, and managing Extism powered WASM plugins.

The warp in warpgate stands for Web Assembly Runtime Plugins. Pretty stellar huh.

Loading plugins

Before a WASM file can be used, it must be loaded. Depending on the locator strategy used, loading a plugin can mean referencing a local file, downloading a file from a secure URL, or even making API requests to specific registries.

To begin, instantiate a PluginLoader and provide a directory path in which to cache .wasm files, and a temporary directory in which to download and unpack files.

use warpgate::PluginLoader;

let root = get_cache_root();
let loader = PluginLoader::new(root.join("plugins"), root.join("temp"));

Plugins can then be loaded with the load_plugin method, which requires a unique ID (becomes the file name), and a PluginLocator enum variant (in which to locate the .wasm file). This method returns an absolute path to the cached .wasm file on the host machine.

use warpgate::PluginLocator;

let wasm_file = loader.load_plugin(PluginLocator::SourceUrl {
	url: "https://registry.com/path/to/file.wasm".into(),
});

Locator strategies

A locator strategy defines instructions on where to locate the necessary .wasm file, and is represented as variants on the PluginLocator enum.

This enum also supports deserializing from strings (as shown below in comments), which can be useful for configuration files.

The following strategies are currently supported:

Local files

File is available on the local host machine. When deserialized, the path field is resolved as-is to file, and must be converted to an absolute path beforehand.

// source:path/to/file.wasm
PluginLocator::SourceFile {
	file: "path/to/file.wasm".into(),
	path: PathBuf::from("/absolute/path/to/file.wasm"),
}

Secure URLs

Download a file from a secure https URL.

// source:https://registry.com/path/to/file.wasm
PluginLocator::SourceUrl {
	url: "https://registry.com/path/to/file.wasm".into(),
}

GitHub releases

Download an asset from a GitHub release. This approach communicates with the GitHub API, and requires a .wasm file to be attached as an asset.

Defining a GITHUB_TOKEN environment variable is recommended to avoid rate limiting.

// github:org/repo
// github:org/repo@v1.2.3
PluginLocator::GitHub(GitHubLocator{
	file_prefix: "file_prefix".into(),
	repo_slug: "org/repo".into(),
	tag: Some("v1.2.3".into()), // Latest if `None`
})

The file_prefix cannot be configured with the string format, and defaults to the repository name in snake_case, suffixed with _plugin.

Extism plugin containers

Another mechanism of this library is providing the PluginContainer struct; a wrapper around Extism's Plugin and Manifest types. The container provides convenience methods for calling functions with serde compatible input and output types, and caching the result for subsequent calls. This is extremely useful in avoiding unnecessary overhead when communicating between the WASM guest and host.

To make use of the container, instantiate an instance with a Manifest, and optional host functions.

use warpgate::{Id, PluginContainer, PluginManifest, Wasm};

// Load the plugin and create a manifest
let wasm_file = loader.load_plugin(locator);
let manifest = PluginManifest::new([Wasm::file(wasm_file)]);

// Create a container
let container = PluginContainer::new(Id::new("id")?, manifest, [host, funcs])?;
// Or
let container = PluginContainer::new_without_functions(Id::new("id")?, manifest)?;

From here, you can call functions on the plugin with the call_func (no input) and call_func_with methods. To call and cache functions, use the alternative cache_func and cache_func_with methods.

Furthermore, these methods require a serde struct for outputs, and optionally for inputs. Non-serde based functions can be handled with the call method.

let output: AddOutput = container.cache_func_with("add", AddInput {
	left: 10,
	right: 20,
})?;

dbg!(output.sum);

Dependencies

~30–50MB
~831K SLoC