WeaveDocs
Forum

Pattern Match

How Forum matches tuples against patterns today and where the matching model is heading.

What this page covers

The Pattern type, the current matching semantics, and patterns for building richer matching on top.

The Pattern type

#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct Pattern(pub Vec<u8>);

A pattern is a byte vector. The matcher returns true when tuple == pattern.0. There are no wildcards, no prefix matching, and no field-level destructuring in the core type today.

This is deliberate: the Strand stores tuples as opaque bytes, so the matching layer is a thin equality test. Richer matching is composed by encoding structure into the tuple payload (e.g. a versioned header plus a serialized body) and matching at the application layer.

Note

Matching is exact equality on bytes. To get prefix matching or field destructuring, design the tuple layout to put the routing key at a fixed offset and pre-filter in the application before calling take.

Reading without consuming

read(&Pattern) returns the first tuple equal to the pattern, without removing it.

use std::sync::Arc;
use tokio::sync::RwLock;
use strand::{Strand, StrandConfig};
use forum::{Forum, Pattern};

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let dir = tempfile::tempdir()?;
    let cfg = StrandConfig::default().with_storage(dir.path());
    let strand = Arc::new(RwLock::new(Strand::new(cfg).await?));
    let forum = Forum::new(strand);

    forum.write(b"ping".to_vec()).await?;

    // read does not consume — call it twice, get the same tuple twice.
    let a = forum.read(&Pattern(b"ping".to_vec())).await?;
    let b = forum.read(&Pattern(b"ping".to_vec())).await?;
    assert_eq!(a, b);
    assert_eq!(a.as_deref(), Some(b"ping".as_slice()));

    Ok(())
}

Building richer matching on top

The standard Weave pattern is a versioned header plus a body:

use serde::{Serialize, Deserialize};

#[derive(Serialize, Deserialize)]
struct JobEnvelope {
    version: u32,
    kind: String,        // "ingest", "render", "embed"
    payload: serde_json::Value,
}

fn encode(job: &JobEnvelope) -> Result<Vec<u8>, serde_json::Error> {
    serde_json::to_vec(job)
}

Consumers then read or take by full equality on a known canonical encoding, or scan the Strand directly:

use forum::Forum;
use serde::Deserialize;

#[derive(Deserialize)]
struct JobEnvelope {
    kind: String,
    payload: serde_json::Value,
}

async fn find_by_kind(_forum: &Forum, _kind: &str) -> Result<Vec<Vec<u8>>, Box<dyn std::error::Error>> {
    // For kind-prefix queries today, walk the Strand directly via your Forum's
    // wrapped strand handle and filter on deserialized headers.
    // The Forum public API will gain a `query` method that streams matches.
    Ok(Vec::new())
}

Matching limits

CapabilityToday
Exact bytes matchYes
Prefix matchNo
Field destructuringNo
Blocking read until matchNo
Subscribe to all writesNo — use WOVEN for fan-out

Practical guidance

  • Treat the Pattern API as exact-match. Build prefix logic in the caller.
  • Canonicalize your encoding (serde_json::to_vec is not byte-deterministic for all inputs — use serde_canonical or order keys manually for matching to work reliably).
  • For broadcast-style fan-out (every consumer sees every write), Forum is the wrong primitive — take removes the tuple. Use WOVEN for events.