Skip to content

beryl/topic

Pattern matching for topic routing.

Topics are string identifiers that clients join (e.g., "room:lobby"). Patterns define how topics are routed to the app's update function. Patterns can be exact, legacy trailing prefix wildcards, or segment-aware wildcards where "*" occupies a complete colon-delimited segment.

pub type ExtractError {
NoWildcard
TopicMismatch
ExpectedOneWildcard(Int)
EmptyNamespace
}

Errors from extracting wildcard values from a topic pattern.

NoWildcard

The pattern has no wildcard to extract.

TopicMismatch

The topic does not match the pattern.

ExpectedOneWildcard(Int)

extract_id expected exactly one wildcard value but found this many.

EmptyNamespace

namespace was called with an empty topic.

pub type TopicError {
EmptyTopic
InvalidFormat(String)
}

Errors returned when validating a topic or topic pattern.

The variants are exhaustive; match each error explicitly. Adding a variant affects API compatibility and requires updating exhaustive matches.

EmptyTopic

The topic or pattern was an empty string.

InvalidFormat(String)

The topic, pattern, or event was malformed; the wrapped String describes the problem (e.g. leading/trailing : or control characters).

pub type TopicPattern {
Exact(String)
Wildcard(prefix: String)
SegmentWildcard(segments: List(String))
}

A topic pattern for routing.

Exact(String)

Exact match. "room:lobby" matches only "room:lobby".

Wildcard(prefix: String)

Wildcard suffix. "room:*" matches "room:lobby", "room:123", and other values with the same prefix.

SegmentWildcard(segments: List(String))

Segment wildcard. "document:*:ops" matches topics with the same number of ":" segments. "*" occupies one complete segment.

pub fn extract_id(
TopicPattern,
String
) -> Result(String, ExtractError)

Extract the wildcard part of a topic.

extract_id(Wildcard("room:"), "room:lobby") // -> Ok("lobby")
extract_id(Wildcard("doc:"), "doc:abc:123") // -> Ok("abc:123")
extract_id(SegmentWildcard(["doc", "*", "ops"]), "doc:abc:ops") // -> Ok("abc")
extract_id(Exact("room:lobby"), "room:lobby") // -> Error(NoWildcard)
pub fn extract_wildcards(
TopicPattern,
String
) -> Result(List(String), ExtractError)

Extract values captured by wildcard segments.

For legacy prefix wildcards, this function returns the suffix as one value. For segment wildcards, it returns each topic segment matched by "*".

extract_wildcards(parse_pattern("document:*:*"), "document:tenant-a:doc-42")
// -> Ok(["tenant-a", "doc-42"])
pub fn from_segments(List(String)) -> String

Build a topic from segments.

from_segments(["room", "lobby"]) // -> "room:lobby"
from_segments(["doc", "tenant", "123"]) // -> "doc:tenant:123"
pub fn matches(
TopicPattern,
String
) -> Bool

Return whether a topic matches a pattern.

matches(Wildcard("room:"), "room:lobby") // -> True
matches(Wildcard("room:"), "user:123") // -> False
matches(Exact("room:lobby"), "room:lobby") // -> True
matches(Exact("room:lobby"), "room:other") // -> False
matches(parse_pattern("document:*:ops"), "document:tenant-a:ops") // -> True
matches(parse_pattern("document:*:ops"), "document:tenant-a:view") // -> False
pub fn namespace(String) -> Result(String, ExtractError)

Return the first segment (namespace) of a topic.

namespace("room:lobby") // -> Ok("room")
namespace("") // -> Error(EmptyNamespace)
pub fn parse_pattern(String) -> TopicPattern

Parse a pattern string into TopicPattern.

parse_pattern("room:*") // -> Wildcard("room:")
parse_pattern("room:lobby") // -> Exact("room:lobby")
parse_pattern("document:*:ops") // -> SegmentWildcard(["document", "*", "ops"])
parse_pattern("document:*:*") // -> SegmentWildcard(["document", "*", "*"])
parse_pattern("document:tenant-a:*") // -> Wildcard("document:tenant-a:")
pub fn segments(String) -> List(String)

Split a topic into segments at each ":".

segments("room:lobby") // -> ["room", "lobby"]
segments("doc:tenant:123:ops") // -> ["doc", "tenant", "123", "ops"]
pub fn validate(String) -> Result(String, TopicError)

Validate a topic string.

A topic must:

  • not be empty;
  • not contain control characters (codepoints 0–31 or 127); and
  • not start or end with ":".
pub fn validate_event(String) -> Result(String, TopicError)

Validate an event name.

An event name must:

  • not be empty; and
  • not contain control characters (codepoints 0–31 or 127).
pub fn validate_pattern(String) -> Result(String, TopicError)

Validate a topic pattern string.

A pattern must:

  • not be empty; and
  • not contain control characters (codepoints 0–31 or 127).

The bare pattern "*" is valid: it parses to a catch-all wildcard that matches every topic.