Packages
macula
0.7.20
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
Current section
Files
src/macula_names.erl
%%%-------------------------------------------------------------------
%%% @doc
%%% Shared naming utilities for hierarchical dot-separated names.
%%% Used by both pub/sub topics and RPC procedure names.
%%% Supports DNS-style reverse notation: org.domain.service.method
%%% @end
%%%-------------------------------------------------------------------
-module(macula_names).
%% API
-export([
validate/1,
validate/2,
normalize/1,
namespace/1,
segment_count/1
]).
%% Types
-type name() :: binary().
-type options() :: #{allow_wildcards => boolean()}.
-export_type([name/0, options/0]).
%%%===================================================================
%%% API Functions
%%%===================================================================
%% @doc Validate name syntax with default options (no wildcards).
-spec validate(name()) -> ok | {error, invalid_name}.
validate(Name) ->
validate(Name, #{allow_wildcards => false}).
%% @doc Validate name syntax with options.
%% Valid names:
%% - Non-empty
%% - Segments separated by dots
%% - Segments contain alphanumeric, underscore, hyphen
%% - Optionally allow wildcards (* and #) for patterns
%% - No leading or trailing dots
%% - No double dots
-spec validate(name(), options()) -> ok | {error, invalid_name}.
validate(<<>>, _Opts) ->
{error, invalid_name};
validate(Name, Opts) ->
%% Check for leading/trailing dots
case binary:first(Name) of
$. -> {error, invalid_name};
_ ->
case binary:last(Name) of
$. -> {error, invalid_name};
_ -> validate_segments(Name, Opts)
end
end.
%% @doc Normalize name (lowercase, trim, remove double dots).
-spec normalize(name()) -> name().
normalize(Name) ->
%% Trim whitespace
Trimmed = string:trim(Name),
%% Lowercase
Lower = string:lowercase(Trimmed),
%% Remove double dots
remove_double_dots(Lower).
%% @doc Extract namespace (first segment).
-spec namespace(name()) -> binary().
namespace(<<>>) ->
<<>>;
namespace(Name) ->
case binary:split(Name, <<".">>) of
[Namespace] -> Namespace;
[Namespace, _Rest] -> Namespace
end.
%% @doc Count number of segments in name.
-spec segment_count(name()) -> non_neg_integer().
segment_count(<<>>) ->
0;
segment_count(Name) ->
Segments = binary:split(Name, <<".">>, [global]),
length(Segments).
%%%===================================================================
%%% Internal Functions
%%%===================================================================
%% @doc Validate all segments contain valid characters.
validate_segments(Name, Opts) ->
Segments = binary:split(Name, <<".">>, [global]),
case lists:all(fun(Seg) -> is_valid_segment(Seg, Opts) end, Segments) of
true -> ok;
false -> {error, invalid_name}
end.
%% @doc Check if segment contains only valid characters.
is_valid_segment(<<>>, _Opts) ->
false; % Empty segment not allowed
is_valid_segment(Segment, Opts) ->
AllowWildcards = maps:get(allow_wildcards, Opts, false),
is_valid_segment_chars(binary_to_list(Segment), AllowWildcards).
is_valid_segment_chars([], _AllowWildcards) ->
true;
is_valid_segment_chars([C | Rest], AllowWildcards) when (C >= $a andalso C =< $z) orelse
(C >= $A andalso C =< $Z) orelse
(C >= $0 andalso C =< $9) orelse
C =:= $_ orelse
C =:= $- ->
is_valid_segment_chars(Rest, AllowWildcards);
is_valid_segment_chars([C | Rest], true) when C =:= $* orelse C =:= $# ->
%% Allow wildcards if option is set
is_valid_segment_chars(Rest, true);
is_valid_segment_chars(_, _) ->
false.
%% @doc Remove consecutive dots.
remove_double_dots(Binary) ->
case binary:replace(Binary, <<"..">>, <<".">>, [global]) of
Binary -> Binary; % No change, done
Modified -> remove_double_dots(Modified) % Keep removing
end.