Current section
Files
Jump to
Current section
Files
src/macula_rpc_uri.erl
%%%-------------------------------------------------------------------
%%% @doc
%%% URI validation and utilities for RPC procedures.
%%% Uses reverse DNS notation: org.domain.service.procedure
%%% @end
%%%-------------------------------------------------------------------
-module(macula_rpc_uri).
%% API
-export([
validate/1,
matches/2,
normalize/1,
namespace/1,
segment_count/1
]).
%% Types
-type uri() :: binary().
-export_type([uri/0]).
%%%===================================================================
%%% API Functions
%%%===================================================================
%% @doc Validate URI syntax.
%% Valid URIs:
%% - Non-empty
%% - Segments separated by dots
%% - Segments contain alphanumeric, underscore, hyphen
%% - No leading or trailing dots
%% - No double dots
-spec validate(uri()) -> ok | {error, invalid_uri}.
validate(<<>>) ->
{error, invalid_uri};
validate(Uri) ->
%% Check for leading/trailing dots
case binary:first(Uri) of
$. -> {error, invalid_uri};
_ ->
case binary:last(Uri) of
$. -> {error, invalid_uri};
_ -> validate_segments(Uri)
end
end.
%% @doc Check if URI matches pattern.
%% For now, only exact matching (no wildcards).
%% Future: Could add wildcard patterns if needed.
-spec matches(uri(), uri()) -> boolean().
matches(Uri, Pattern) ->
Uri =:= Pattern.
%% @doc Normalize URI (lowercase, trim, remove double dots).
-spec normalize(uri()) -> uri().
normalize(Uri) ->
%% Trim whitespace
Trimmed = string:trim(Uri),
%% Lowercase
Lower = string:lowercase(Trimmed),
%% Remove double dots
remove_double_dots(Lower).
%% @doc Extract namespace (first segment).
-spec namespace(uri()) -> binary().
namespace(<<>>) ->
<<>>;
namespace(Uri) ->
case binary:split(Uri, <<".">>) of
[Namespace] -> Namespace;
[Namespace, _Rest] -> Namespace
end.
%% @doc Count number of segments in URI.
-spec segment_count(uri()) -> non_neg_integer().
segment_count(<<>>) ->
0;
segment_count(Uri) ->
Segments = binary:split(Uri, <<".">>, [global]),
length(Segments).
%%%===================================================================
%%% Internal Functions
%%%===================================================================
%% @doc Validate all segments contain valid characters.
validate_segments(Uri) ->
Segments = binary:split(Uri, <<".">>, [global]),
case lists:all(fun is_valid_segment/1, Segments) of
true -> ok;
false -> {error, invalid_uri}
end.
%% @doc Check if segment contains only valid characters.
is_valid_segment(<<>>) ->
false; % Empty segment not allowed
is_valid_segment(Segment) ->
%% Allow alphanumeric, underscore, hyphen
is_valid_segment_chars(binary_to_list(Segment)).
is_valid_segment_chars([]) ->
true;
is_valid_segment_chars([C | Rest]) 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);
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.