rustledger-plugin-types
WASM plugin interface types for rustledger.
This crate provides the canonical type definitions that plugins must use to communicate with the rustledger host. Using this crate ensures your plugin's types are always compatible with the host.
There are two distinct WASM plugin subsystems, and this crate hosts the shared types for both:
- Directive plugins transform the directive stream after parsing
(tagging, dedup, categorization). Required export:
process. Host loader:rustledger-plugin. See the "Directive Plugin Quick Start" section below. - WASM importers turn bank-statement files into directives (CSV, OFX,
custom formats). Required exports:
metadata,identify,extract,extract_enriched. Host loader:rustledger-importer::WasmImporter. See the "WASM Importer Quick Start" section below, and use thewasm_importer_main!macro (behind theguestfeature) to skip writing the export boilerplate yourself.
Installation
Add to your plugin's Cargo.toml:
[]
= "my-plugin"
= "2021"
[]
= ["cdylib"]
[]
= "0.15"
= "1"
Version compatibility: Use the same minor version as your target rustledger host (e.g., 0.15.x types for rustledger 0.15.x).
Directive Plugin Quick Start
The wasm_plugin_main! macro (behind the guest feature) generates
the required alloc + process exports from a single user
fn(PluginInput) -> PluginOutput. This is the recommended path —
collapses what used to be ~50 lines of #[unsafe(no_mangle)] extern "C" boilerplate down to a 5-line invocation. Add the feature to
your Cargo.toml:
[]
= { = "0.15", = ["guest"] }
Then:
use ;
wasm_plugin_main!
Pure-passthrough validators that emit no transformations can short-circuit with the convenience constructor:
Invoke
wasm_plugin_main!once per cdylib crate. It emits exports namedallocandprocess— symbols the host loader looks up by name. Two invocations cause a duplicate-symbol linker error onwasm32. If you need multiple plugins, build them as separate cdylib crates.
The macro saves you from writing this, but if you need finer control —
or you want to understand what the wire format looks like — here's the
expansion in raw extern "C" form:
use *;
pub extern "C"
pub extern "C"
// Required: advertise the ABI version so the host can confirm wire
// compatibility right after instantiation. Without this export the
// host refuses to run the plugin with a clear error rather than
// trapping opaquely once `process` misreads its input.
pub extern "C"
Building
# Install WASM target
# Build your plugin
The plugin will be at target/wasm32-unknown-unknown/release/my_plugin.wasm.
Using Your Plugin
In your beancount file:
plugin "path/to/my_plugin.wasm" "optional-config-string"
2024-01-01 open Assets:Bank USD
Types Overview
| Type | Description |
|---|---|
PluginInput |
Input from host: directives, options, config |
PluginOutput |
Output to host: ops: Vec<PluginOp> describing the resulting directive list, plus errors |
PluginOp |
One Keep/Modify/Insert/Delete operation against the input |
DirectiveWrapper |
Wrapper with date, source location, and data |
DirectiveData |
Enum of all directive types |
PluginError |
Error/warning with optional source location |
PluginOp Variants
PluginOutput.ops is an ordered list of operations, not a replacement
directive list. Every input index must appear in exactly one of
Keep, Modify, or Delete; the host validates this and emits a
plugin error if violated.
| Variant | Semantics |
|---|---|
Keep(i) |
Reuse input[i] unchanged. Span and file_id preserved. |
Modify(i, wrapper) |
Replace input[i]'s content with wrapper, inheriting input[i]'s source identity so errors still point at the original line. |
Insert(wrapper) |
Emit a fresh directive with synthesized location (SYNTHESIZED_FILE_ID, zero span). Use for directives the plugin invents. |
Delete(i) |
Drop input[i]. Must be explicit — omitting an index is a protocol violation. |
Creating Errors
use ;
// Simple error
let error = error;
// Warning with source location
let warning = warning
.at;
Memory Management
Plugins must export:
alloc(size: u32) -> *mut u8- Required. The host calls this to allocate memory for input data.__rustledger_abi_version() -> u32- Required. ReturnsABI_VERSION; the host checks it right after instantiation and refuses to run a guest built against an incompatibleplugin-types. Thewasm_plugin_main!/wasm_importer_main!macros emit it for you.
Plugins may optionally export:
dealloc(ptr: *mut u8, size: u32)- Optional. For freeing memory within the plugin.
WASM Importer Quick Start
Importers read source files (CSV, OFX, …) and emit directives. The host
loader is in rustledger-importer (WasmImporter::load); the wire format
lives in this crate.
Use the wasm_importer_main! macro to generate the required exports.
Enable it with the guest feature:
[]
= { = "0.15", = ["guest"] }
use ;
wasm_importer_main!
The macro emits the required exports (memory, alloc, metadata,
identify, extract, extract_enriched) gated on
#[cfg_attr(target_arch = "wasm32", ...)] so the host-target build of
your crate (used by tests) doesn't collide with the WASM linker's symbol
namespace.
See examples/wasm-importer-csv-example in the rustledger
repo for a complete reference implementation.
Importer ABI types
| Type | Description |
|---|---|
ImporterInput |
Input to extract/extract_enriched: path, content bytes, account, currency, options map |
IdentifyInput |
Input to identify: path only (content isn't read until extract) |
ImporterOutput |
Result of extract: directives + warnings + structured errors |
EnrichedImporterOutput |
Result of extract_enriched: (DirectiveWrapper, EnrichmentWrapper) pairs |
IdentifyOutput |
Result of identify: bool |
MetadataOutput |
Result of metadata: name + description, called once at load |
EnrichmentWrapper |
Per-directive categorization metadata (method, confidence, fingerprint, alternatives) |
AlternativeWrapper |
Alternative account categorization with confidence |
Categorization method strings
EnrichmentWrapper::method is a wire-format string that must match one of:
"rule", "merchant-dict" (hyphen, not underscore!), "ml", "llm",
"manual", "default". Unknown strings cause the host to emit a warning
and fall back to Default. The host pinning lives in
rustledger_ops::enrichment::CategorizationMethod::as_meta_value.
License
GPL-3.0-only