Packages

macula

0.28.3
7.0.0 6.0.0 5.2.2 5.2.1 5.2.0 5.1.0 5.0.0 4.8.0 4.7.1 4.7.0 4.6.0 4.5.0 4.4.10 4.4.9 4.4.8 4.4.7 4.4.6 4.4.5 4.4.4 4.4.3 4.4.2 4.4.1 4.4.0 4.3.1 4.3.0 4.2.9 4.2.8 4.2.7 4.2.6 4.2.5 4.2.4 4.2.3 4.2.2 4.2.1 4.2.0 4.1.1 4.1.0 4.0.0 3.16.0 3.15.3 3.15.2 3.15.1 3.14.0 3.13.0 3.12.1 3.12.0 3.11.1 3.11.0 3.10.3 3.10.2 3.10.1 3.9.0 3.8.0 3.7.0 3.5.0 3.4.0 3.3.0 3.2.0 3.1.0 3.0.0 2.1.1 2.1.0 2.0.0 1.5.2 1.5.1 1.4.30 1.4.29 1.4.28 1.4.27 1.4.26 1.4.25 1.4.24 1.4.23 1.4.22 1.4.21 1.4.20 1.4.19 1.4.18 1.4.17 1.4.16 1.4.15 1.4.14 1.4.13 1.4.11 1.4.10 1.4.9 1.4.8 1.4.7 1.4.6 1.4.5 1.4.4 1.4.3 1.4.2 1.4.1 1.4.0 1.3.1 1.3.0 1.2.0 1.1.0 1.0.10 1.0.9 1.0.8 1.0.7 1.0.6 1.0.5 1.0.4 1.0.3 1.0.2 1.0.1 1.0.0 0.48.6 0.48.5 0.48.4 0.48.3 0.48.2 0.48.1 0.48.0 0.47.1 0.47.0 0.46.3 0.46.1 0.46.0 0.45.3 0.45.2 0.45.1 0.45.0 0.44.2 0.44.1 0.44.0 0.43.3 0.43.2 0.43.1 0.43.0 0.42.9 0.42.8 0.42.7 0.42.6 0.42.5 0.42.4 0.42.3 0.42.2 0.42.1 0.42.0 0.41.1 0.41.0 0.40.1 0.40.0 0.39.9 0.39.8 0.39.7 0.39.6 0.39.5 0.39.4 0.39.3 0.39.2 0.39.1 0.39.0 0.38.8 0.38.7 0.38.6 0.38.5 0.38.4 0.38.3 0.38.2 0.38.1 0.38.0 0.37.7 0.37.6 0.37.5 0.37.4 0.37.3 0.37.2 0.37.1 0.37.0 0.36.6 0.36.5 0.36.4 0.36.3 0.36.2 0.36.1 0.36.0 0.35.4 0.35.3 0.35.2 0.35.1 0.35.0 0.34.1 0.34.0 0.33.1 0.33.0 0.32.5 0.32.4 0.32.3 0.32.2 0.32.1 0.32.0 0.31.9 0.31.8 0.31.7 0.31.6 0.31.5 0.31.4 0.31.3 0.31.2 0.31.1 0.31.0 0.30.10 0.30.9 0.30.8 0.30.7 0.30.6 0.30.5 0.30.4 0.30.3 0.30.2 0.30.1 0.30.0 0.29.0 0.28.3 0.28.2 0.28.1 0.28.0 0.27.1 0.27.0 0.26.1 0.26.0 0.25.6 0.25.5 0.25.4 0.25.3 0.25.2 0.25.1 0.25.0 0.24.6 0.24.5 0.24.4 0.24.3 0.24.2 0.24.1 0.24.0 0.23.3 0.23.2 0.23.1 0.23.0 0.22.12 0.22.11 0.22.10 0.22.9 0.22.8 0.22.7 0.22.6 0.22.5 0.22.4 0.22.3 0.22.2 0.22.1 0.22.0 0.21.7 0.21.6 0.21.5 0.21.4 0.21.2 0.21.1 0.21.0 0.20.25 0.20.24 0.20.23 0.20.22 0.20.21 0.20.20 0.20.19 0.20.18 0.20.17 0.20.16 0.20.15 0.20.14 0.20.13 0.20.12 0.20.11 0.20.10 0.20.9 0.20.8 0.20.7 0.20.6 0.20.5 0.20.3 0.20.2 0.20.1 0.20.0 0.19.2 0.19.1 0.19.0 0.18.1 0.18.0 0.17.4 0.17.3 0.17.2 0.17.1 0.17.0 0.16.6 0.16.5 0.16.4 0.16.3 0.16.2 0.16.1 0.16.0 0.15.1 0.15.0 0.14.3 0.14.2 0.14.1 0.14.0 0.12.6 0.12.5 0.12.3 0.11.3 0.10.2 0.10.1 0.10.0 0.9.2 0.9.1 0.9.0 0.8.25 0.8.24 0.8.23 0.8.22 0.8.21 0.8.20 0.8.19 0.8.18 0.8.17 0.8.16 0.8.15 0.8.14 0.8.13 0.8.12 0.8.11 0.8.10 0.8.9 0.8.8 0.8.7 0.8.6 0.8.5 0.8.4 0.8.3 0.8.2 0.8.1 0.8.0 0.7.30 0.7.29 0.7.28 0.7.27 0.7.26 0.7.25 0.7.24 0.7.23 0.7.22 0.7.21 0.7.20 0.7.19 0.7.18 0.7.17 0.7.16 0.7.15 0.7.14 0.7.13 0.7.12 0.7.11 0.7.10 0.7.9 0.7.8 0.7.7 0.7.6 0.7.5 0.7.4 0.7.3 0.7.2 0.7.1 0.7.0 0.6.7 0.6.6 0.6.5 0.6.4 0.6.3 0.6.2 0.6.1 0.6.0 0.5.0 0.4.4 0.4.3 0.4.2 0.4.1 0.4.0 0.3.4 0.3.3 0.3.2 0.3.1

Macula HTTP/3 Mesh SDK — connect, subscribe, publish, call, advertise

Current section

Files

Jump to
macula src macula_pubsub_system macula_pubsub_topic.erl
Raw

src/macula_pubsub_system/macula_pubsub_topic.erl

%%%-------------------------------------------------------------------
%%% @doc
%%% Topic utilities for pub/sub system.
%%% Handles topic validation, pattern matching, and normalization.
%%% Supports MQTT-style wildcards: * (single-level) and # (multi-level).
%%% @end
%%%-------------------------------------------------------------------
-module(macula_pubsub_topic).
%% API
-export([
validate/1,
matches/2,
normalize/1,
namespace/1,
segment_count/1
]).
%% Types
-type topic() :: binary().
-type pattern() :: binary().
-export_type([topic/0, pattern/0]).
%%%===================================================================
%%% API Functions
%%%===================================================================
%% @doc Validate topic syntax.
%% Valid topics:
%% - Non-empty
%% - Segments separated by dots
%% - Segments contain alphanumeric, underscore, hyphen, wildcards
%% - No leading or trailing dots
-spec validate(topic()) -> ok | {error, invalid_topic}.
validate(Topic) ->
map_validation_error(macula_names:validate(Topic, #{allow_wildcards => true})).
%% @doc Map validation error to topic-specific error.
map_validation_error(ok) -> ok;
map_validation_error({error, invalid_name}) -> {error, invalid_topic}.
%% @doc Check if topic matches pattern.
%% Patterns can contain:
%% - * matches exactly one segment
%% - # matches zero or more segments
-spec matches(topic(), pattern()) -> boolean().
matches(Topic, Pattern) ->
TopicSegments = binary:split(Topic, <<".">>, [global]),
PatternSegments = binary:split(Pattern, <<".">>, [global]),
match_segments(TopicSegments, PatternSegments).
%% @doc Normalize topic (lowercase, trim, remove double dots).
-spec normalize(topic()) -> topic().
normalize(Topic) ->
macula_names:normalize(Topic).
%% @doc Extract namespace (first segment).
-spec namespace(topic()) -> binary().
namespace(Topic) ->
macula_names:namespace(Topic).
%% @doc Count number of segments in topic.
-spec segment_count(topic()) -> non_neg_integer().
segment_count(Topic) ->
macula_names:segment_count(Topic).
%%%===================================================================
%%% Internal Functions
%%%===================================================================
%% @doc Match topic segments against pattern segments.
match_segments([], []) ->
true; % Both exhausted, match
match_segments(_TopicSegs, [<<"#">>]) ->
true; % Multi-level wildcard at end matches rest
match_segments([], _PatternSegs) ->
false; % Topic exhausted, pattern has more
match_segments(_TopicSegs, []) ->
false; % Pattern exhausted, topic has more
match_segments([_TSeg | TRest], [<<"*">> | PRest]) ->
%% Single-level wildcard matches one segment
match_segments(TRest, PRest);
match_segments(TopicSegs, [<<"#">> | PRest]) ->
%% Multi-level wildcard in middle
%% Try matching rest of pattern at various positions
match_multi_level(TopicSegs, PRest);
match_segments([TSeg | TRest], [PSeg | PRest]) when TSeg =:= PSeg ->
%% Exact match
match_segments(TRest, PRest);
match_segments(_TopicSegs, _PatternSegs) ->
false. % No match
%% @doc Match multi-level wildcard in middle of pattern.
match_multi_level(_TopicSegs, []) ->
%% # at end matches everything
true;
match_multi_level([], PatternSegs) ->
%% Topic exhausted, check if pattern can match zero segments
is_empty_pattern(PatternSegs);
match_multi_level([_TSeg | TRest] = TopicSegs, PatternSegs) ->
%% Try matching pattern at current position, or try next position
match_segments(TopicSegs, PatternSegs) orelse
match_multi_level(TRest, PatternSegs).
%% @doc Check if pattern is empty.
is_empty_pattern([]) -> true;
is_empty_pattern(_) -> false.