Packages

macula

0.20.6
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_did_cache.erl
Raw

src/macula_did_cache.erl

%% @doc DID Parsing Cache Module.
%%
%% Provides high-performance caching for parsed DID (Decentralized Identifier)
%% results using `persistent_term' for fast reads. This avoids repeated parsing
%% of DIDs during authorization checks.
%%
%% == Usage ==
%%
%% Parse and cache a DID (or get from cache):
%%
%% `{ok, Parsed} = macula_did_cache:get_or_parse(DID).'
%%
%% Returns a map with method, identity, parts, and depth.
%%
%% Invalidate a specific DID from cache:
%%
%% `ok = macula_did_cache:invalidate(DID).'
%%
%% Clear entire cache:
%%
%% `ok = macula_did_cache:clear().'
%%
%% == Performance ==
%%
%% `persistent_term' provides O(1) lookups with zero garbage collection impact,
%% making it ideal for frequently-accessed, rarely-changed data like parsed DIDs.
%%
%% Note: `persistent_term:put/2' triggers a global GC, so this cache is optimized
%% for read-heavy workloads where DIDs are parsed once and looked up many times.
%%
%% == Cache Key Format ==
%%
%% Keys are stored as `{macula_did_cache, DID}' tuples to avoid namespace conflicts.
%%
%% @author macula
-module(macula_did_cache).
%% API
-export([
get_or_parse/1,
invalidate/1,
clear/0,
cache_size/0
]).
%% Internal (exported for testing)
-export([
cache_key/1
]).
%%====================================================================
%% Types
%%====================================================================
-type did() :: binary().
-type parsed_did() :: #{
binary() => binary() | [binary()] | integer()
}.
-export_type([did/0, parsed_did/0]).
%%====================================================================
%% API Functions
%%====================================================================
%% @doc Get parsed DID from cache, or parse and cache if not present.
%%
%% This is the main entry point for cached DID parsing. If the DID has been
%% parsed before, returns the cached result. Otherwise, parses the DID,
%% caches the result, and returns it.
%%
%% Returns `{ok, ParsedDID}' on success, `{error, invalid_did}' if parsing fails.
-spec get_or_parse(DID :: did()) -> {ok, parsed_did()} | {error, invalid_did}.
get_or_parse(DID) when is_binary(DID) ->
Key = cache_key(DID),
case persistent_term:get(Key, undefined) of
undefined ->
parse_and_cache(DID, Key);
Cached ->
{ok, Cached}
end;
get_or_parse(_) ->
{error, invalid_did}.
%% @doc Invalidate (remove) a specific DID from the cache.
%%
%% Use this when a DID's parsing result may have changed (rare in practice,
%% as DIDs are typically immutable identifiers).
-spec invalidate(DID :: did()) -> ok.
invalidate(DID) when is_binary(DID) ->
Key = cache_key(DID),
_ = persistent_term:erase(Key),
ok;
invalidate(_) ->
ok.
%% @doc Clear all cached DIDs.
%%
%% Warning: This triggers a global GC for each erased term. Use sparingly.
-spec clear() -> ok.
clear() ->
AllKeys = persistent_term:get(),
lists:foreach(
fun({Key, _Value}) ->
case Key of
{macula_did_cache, _DID} ->
persistent_term:erase(Key);
_ ->
ok
end
end,
AllKeys
),
ok.
%% @doc Get the current number of cached DIDs.
%%
%% Useful for monitoring and debugging.
-spec cache_size() -> non_neg_integer().
cache_size() ->
AllKeys = persistent_term:get(),
length([Key || {Key, _Value} <- AllKeys, is_cache_key(Key)]).
%%====================================================================
%% Internal Functions
%%====================================================================
%% @private Generate cache key for a DID.
-spec cache_key(DID :: did()) -> {macula_did_cache, did()}.
cache_key(DID) ->
{macula_did_cache, DID}.
%% @private Check if a key is a DID cache key.
-spec is_cache_key(term()) -> boolean().
is_cache_key({macula_did_cache, _}) -> true;
is_cache_key(_) -> false.
%% @private Parse DID and cache the result.
-spec parse_and_cache(DID :: did(), Key :: {macula_did_cache, did()}) ->
{ok, parsed_did()} | {error, invalid_did}.
parse_and_cache(DID, Key) ->
case parse_did(DID) of
{ok, Parsed} ->
persistent_term:put(Key, Parsed),
{ok, Parsed};
{error, invalid_did} = Error ->
Error
end.
%%====================================================================
%% Inline DID Parsing (same as macula_authorization)
%%====================================================================
%% @private Parse a DID string and extract its components.
%% Identical to macula_authorization:parse_did/1 for consistency.
-spec parse_did(DID :: binary()) ->
{ok, parsed_did()} | {error, invalid_did}.
parse_did(DID) when is_binary(DID) ->
case binary:split(DID, <<":">>, [global]) of
[<<"did">>, <<"macula">>, Identity] ->
Parts = binary:split(Identity, <<".">>, [global]),
{ok, #{
<<"method">> => <<"macula">>,
<<"identity">> => Identity,
<<"parts">> => Parts,
<<"depth">> => length(Parts)
}};
_ ->
{error, invalid_did}
end;
parse_did(_) ->
{error, invalid_did}.