Skip to content

beryl/topic

Topic - Pattern matching for channel routing

Topics are string identifiers that clients join (e.g., "room:lobby"). Patterns define how topics are routed to channel handlers. Patterns can be exact, prefix wildcards, or segment wildcards where "*" occupies a complete colon-delimited segment.

Errors from extracting wildcard values from a topic pattern.

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

The pattern has no wildcard to extract.

The topic does not match the pattern.

extract_id expected exactly one wildcard value but found this many.

namespace was called with an empty topic.

Errors returned when validating a topic or topic pattern.

pub type TopicError {
EmptyTopic
InvalidFormat(String)
}

The topic or pattern was an empty string.

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

Topic pattern for routing

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

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

Prefix wildcard: "room:*" matches "room:lobby", "room:123", etc.

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

Extract the wildcard portion from 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_id(
TopicPattern,
String
) -> Result(String, ExtractError)

Extract values captured by wildcard segments.

For prefix wildcards, returns the suffix as a single value. For segment wildcards, returns each topic segment matched by "*".

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

Build a topic from segments

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

Check if 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 matches(
TopicPattern,
String
) -> Bool

Get the first segment (namespace) of a topic

namespace("room:lobby") // -> Ok("room")
namespace("") // -> Error(EmptyNamespace)
pub fn namespace(String) -> Result(String, ExtractError)

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 parse_pattern(String) -> TopicPattern

Parse a topic into segments by splitting on ":"

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

Validate a topic string

Topics must:

  • Not be empty
  • Not contain control characters (codepoints 0–31 or 127)
  • Not start or end with ":"
pub fn validate(String) -> Result(String, TopicError)

Validate an event name string

Event names must:

  • Not be empty
  • Not contain control characters (codepoints 0–31 or 127)
pub fn validate_event(String) -> Result(String, TopicError)

Validate a topic pattern string

Patterns must:

  • Not be empty
  • 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.

pub fn validate_pattern(String) -> Result(String, TopicError)