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.
Installation
Add to your plugin's Cargo.toml:
[package]
name = "my-plugin"
edition = "2021"
[lib]
crate-type = ["cdylib"]
[dependencies]
rustledger-plugin-types = "0.10"
rmp-serde = "1"
Version compatibility: Use the same minor version as your target rustledger host (e.g., 0.10.x types for rustledger 0.10.x).
Quick Start
use rustledger_plugin_types::*;
#[no_mangle]
pub extern "C" fn alloc(size: u32) -> *mut u8 {
let layout = std::alloc::Layout::from_size_align(size as usize, 1).unwrap();
unsafe { std::alloc::alloc(layout) }
}
#[no_mangle]
pub extern "C" fn process(input_ptr: u32, input_len: u32) -> u64 {
let input_bytes = unsafe {
std::slice::from_raw_parts(input_ptr as *const u8, input_len as usize)
};
let input: PluginInput = match rmp_serde::from_slice(input_bytes) {
Ok(i) => i,
Err(e) => return error_response(&format!("Deserialize failed: {}", e)),
};
let mut directives = input.directives;
for wrapper in &mut directives {
if let DirectiveData::Transaction(ref mut txn) = wrapper.data {
txn.tags.push("processed".to_string());
}
}
let output = PluginOutput { directives, errors: vec![] };
let output_bytes = match rmp_serde::to_vec(&output) {
Ok(b) => b,
Err(e) => return error_response(&format!("Serialize failed: {}", e)),
};
let output_ptr = alloc(output_bytes.len() as u32);
unsafe {
std::ptr::copy_nonoverlapping(
output_bytes.as_ptr(),
output_ptr,
output_bytes.len(),
);
}
((output_ptr as u64) << 32) | (output_bytes.len() as u64)
}
fn error_response(message: &str) -> u64 {
let output = PluginOutput {
directives: vec![],
errors: vec![PluginError::error(message)],
};
let bytes = rmp_serde::to_vec(&output).unwrap_or_default();
let ptr = alloc(bytes.len() as u32);
unsafe {
std::ptr::copy_nonoverlapping(bytes.as_ptr(), ptr, bytes.len());
}
((ptr as u64) << 32) | (bytes.len() as u64)
}
Building
rustup target add wasm32-unknown-unknown
cargo build --target wasm32-unknown-unknown --release
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: processed directives, errors |
DirectiveWrapper |
Wrapper with date, source location, and data |
DirectiveData |
Enum of all directive types |
PluginError |
Error/warning with optional source location |
Creating Errors
use rustledger_plugin_types::{PluginError, PluginErrorSeverity};
let error = PluginError::error("Something went wrong");
let warning = PluginError::warning("Duplicate entry")
.at("ledger.beancount", 42);
Memory Management
Plugins must export:
alloc(size: u32) -> *mut u8 - Required. The host calls this to allocate memory for input data.
Plugins may optionally export:
dealloc(ptr: *mut u8, size: u32) - Optional. For freeing memory within the plugin.
License
GPL-3.0-only