splicer 2.0.1

Plan and generate middleware splice operations for WebAssembly component composition graphs.
Documentation

splicer 🔍✂️🪡

Plan and generate middleware splice operations for WebAssembly component composition graphs.

splicer reads:

  • A composition graph (JSON)
  • A splice configuration (YAML)

It produces a modified plan that injects middleware components according to declarative rules.

This tool is designed to work with component-based systems such as WASI HTTP services, but is interface-agnostic and can splice across any interface edge in a component graph.


Why splicer?

When building component-based systems, middleware insertion often requires:

  • Rewriting instantiation chains
  • Re-threading handler references
  • Maintaining correct edge ordering
  • Traversing nested provider chains

splicer automates that planning step.

Instead of manually restructuring component wiring, you define:

  • What interface to target
  • Where to inject middleware
  • What middleware components to insert

And splicer generates the modified composition plan.

A demo of splicer can be run using: cargo run --example demo

A more in-depth usage of splicer is done in the external component-interposition repo.


Adapter Components

Most middleware doesn't need to match the exact type signature of the interface it's being placed on. A logging middleware that prints "before" and "after" around every call works the same whether the target interface is wasi:http/handler or my:service/adder, it only needs the function name.

Splicer generates adapter components that bridge between a generic middleware WIT interface and the specific target interface. The middleware author writes against a simple contract; splicer handles all the type plumbing at composition time.

Middleware Tiers

Tier Capability WIT Status
Tier 1 Name-only hooks (before-call, after-call, should-block-call): middleware sees function names but not types or data wit/tier1/world.wit Supported
Tier 2 Read-only reflection: middleware can inspect the types and serialized data being passed to/from the target, but cannot modify it wit/tier2/world.wit (planned) Planned
Tier 3 Read-write interception: middleware can inspect AND modify the data flowing through wit/tier3/world.wit (planned) Planned

Each tier strictly adds one capability. Middleware written for a lower tier works unchanged when higher tiers become available.

To write a tier-1 middleware, your component exports one or more of the interfaces defined in wit/tier1/world.wit.

When splicer splice detects that a middleware exports these interfaces (instead of the target interface directly), it automatically generates an adapter component and wires it into the composition.

For the full guide — including how to write a tier-1 middleware, how adapter detection works, and what the generated adapter does internally — see docs/adapter-components.md.


Installation

From source:

cargo build --release

Binary will be located at:

target/release/splicer

Usage

splicer <JSON_GRAPH> <SPLICE_CFG> [--output <FILE>]

Arguments

Argument Description
JSON_GRAPH Path to the composition graph in JSON format
SPLICE_CFG Path to the splice configuration YAML file
--output Optional output file (defaults to stdout)

Configuration Format

Splicing behavior is defined in a YAML configuration file.

See full specification:

docs/splice-config.md

Example Configuration

version: 1

rules:
  - before:
      interface: wasi:http/handler
      provider:
        name: auth
    inject:
        - middleware-a
        - middleware-b

  - between:
      interface: wasi:http/handler
      inner:
        name: auth
      outer:
        name: handler
    inject:
        - tracing

Splice Semantics

splicer operates on interface edges in the graph.

If no matches are found, the generated wac will produce an identity component (roundtrips to same component).

Two matching modes are supported:

1. Single-Target Injection

Inject middleware for a given interface, optionally scoped to a specific provider.

before:
  interface: wasi:http/handler
  provider:
    name: auth

If provider.name is omitted, all providers of that interface are matched.


2. Between Injection

Inject middleware between two specific components connected via an interface edge.

between:
   interface: wasi:http/handler
   inner:
     name: auth
   outer:
     name: handler

This replaces:

handler → auth

With:

handler → middleware → auth

Middleware chains are traversed in reverse order during injection to preserve declared ordering.


Rule Ordering

Rules are applied in file order.

Later rules operate on the graph after earlier modifications.

This allows intentional stacking:

auth → logging → metrics → handler

Validation

The configuration will fail if:

  • version is missing or unsupported
  • Required fields are absent
  • Middleware list is empty

Testing

In-process unit tests live under src/ and exercise the adapter generator, WAC emitter, and composition planner directly:

cargo test --lib

End-to-end fuzz + run harness

tests/fuzz_and_run.rs scaffolds provider, consumer, and middleware crates in a tempdir, drives them through the full splicer pipeline (compose + splice for both between and before rules), and invokes the result under wasmtime to check the composition actually executes.

Two entry points, both #[ignore]'d (they build real crates — slow):

  • test_canned — a hardcoded catalog of 22 value-type shapes * 2 async modes * 2 split-kind pipelines = 88 combos. Same shapes every run. Quick-to-bisect canary for regressions in a known shape.

  • test_fuzzarbitrary-driven random shapes. Reproducible via SPLICER_FUZZ_SEED so any failure can be replayed. Each iter prints [i/N] progress (requires --nocapture).

# Canned catalog — 88 combos, ~2 min
cargo test --test fuzz_and_run -- --ignored --nocapture test_canned

# Fuzz at PR CI config (25 iters × 2 modes × depth 5, ~2 min)
SPLICER_FUZZ_ITERS=25 SPLICER_FUZZ_DEPTH=5 \
  cargo test --test fuzz_and_run -- --ignored --nocapture test_fuzz

# Replay a single failing iteration
SPLICER_FUZZ_SEED=<seed_from_output> SPLICER_FUZZ_ITERS=1 \
  cargo test --test fuzz_and_run -- --ignored --nocapture test_fuzz

Env knobs:

var default effect
SPLICER_FUZZ_SEED 0xDEADBEEF base RNG seed; each iter's shape uses seed + iter_idx
SPLICER_FUZZ_ITERS 30 iterations per async mode (sync + async both run)
SPLICER_FUZZ_DEPTH 4 max recursion depth for compound shapes
SPLICER_KEEP_TMPDIR unset preserve the tempdir for post-mortem inspection

Project Structure

splicer/
├── src/
├── docs/
│   └── splice-config.md
├── README.md

Design Principles

  • Declarative configuration
  • Deterministic ordering
  • Interface-driven matching
  • Graph-aware edge replacement
  • Middleware-agnostic

splicer does not assume HTTP semantics — it operates on generic interface edges.


Future Evolution

The configuration format is versioned:

version: 1

Breaking changes will increment the version number.