rustledger-plugin-types 0.10.1

WASM plugin interface types for rustledger - use in your plugin crate
Documentation

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 {
    // Read input
    let input_bytes = unsafe {
        std::slice::from_raw_parts(input_ptr as *const u8, input_len as usize)
    };

    // Deserialize with error handling
    let input: PluginInput = match rmp_serde::from_slice(input_bytes) {
        Ok(i) => i,
        Err(e) => return error_response(&format!("Deserialize failed: {}", e)),
    };

    // Process directives (example: add a tag to all transactions)
    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());
        }
    }

    // Serialize output
    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)
}

/// Helper to return an error response
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

# Install WASM target
rustup target add wasm32-unknown-unknown

# Build your plugin
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};

// Simple error
let error = PluginError::error("Something went wrong");

// Warning with source location
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