Current section

Files

Jump to
speed_trap src speed_trap_template.erl
Raw

src/speed_trap_template.erl

-module(speed_trap_template).
-export([init/0, init/2, cleanup/0, options_from_id/1, current_id_patterns/0, current_templates/0]).
-type id_pattern() :: {bucket_id_pattern(), id()}.
-type id() :: term().
-type bucket_id_pattern() :: term().
-type templates() :: #{id() => speed_trap:options()}.
-define(PTERM_MATCHSPEC, {speed_trap, match_spec}).
-define(PTERM_TEMPLATES, {speed_trap, templates}).
-define(PTERM_ID_PATTERNS, {speed_trap, id_patterns}).
-export_type([id/0, templates/0]).
%% ---------------------------------------------------------------------------
%% API
-spec init() -> ok | {error, term()}.
init() ->
Templates = application:get_env(speed_trap, templates, #{}),
IdPatterns = application:get_env(speed_trap, id_patterns, []),
init(Templates, IdPatterns).
-spec init(#{id() => speed_trap:options()}, [id_pattern()]) -> ok | {error, term()}.
init(Templates, IdPatterns) ->
try
check_templates(Templates),
check_id_patterns(IdPatterns, Templates),
MatchSpec = to_match_spec(IdPatterns),
persistent_term:put(?PTERM_ID_PATTERNS, IdPatterns),
persistent_term:put(?PTERM_MATCHSPEC, MatchSpec),
persistent_term:put(?PTERM_TEMPLATES, Templates),
ok
catch
throw:Reason:_Stack ->
{error, Reason}
end.
-spec current_id_patterns() -> [id_pattern()].
current_id_patterns() ->
persistent_term:get(?PTERM_ID_PATTERNS).
-spec current_templates() -> templates().
current_templates() ->
persistent_term:get(?PTERM_TEMPLATES).
-spec cleanup() -> ok.
cleanup() ->
ok = speed_trap_token_bucket:delete_overrides(),
persistent_term:erase(?PTERM_MATCHSPEC),
persistent_term:erase(?PTERM_TEMPLATES),
ok.
-spec options_from_id(speed_trap:id()) ->
{ok, speed_trap_template:id(), speed_trap:options()} | not_found.
options_from_id(Id) ->
MatchSpec = persistent_term:get(?PTERM_MATCHSPEC),
Templates = persistent_term:get(?PTERM_TEMPLATES),
case template_from_id(Id, MatchSpec) of
{ok, TemplateId} ->
{ok, TemplateId, maps:get(TemplateId, Templates)};
false ->
not_found
end.
%% ---------------------------------------------------------------------------
%% Internal
-spec check_id_patterns([id_pattern()], #{id() => speed_trap:options()}) -> ok.
check_id_patterns(IdPatterns, Templates) ->
Patterns = lists:map(fun({Pattern, _}) -> Pattern end, IdPatterns),
case Patterns -- lists:usort(Patterns) of
[] ->
ok;
Dup ->
erlang:throw({duplicate_id_patterns, Dup})
end,
lists:foreach(fun({_BucketPattern, TemplateId}) ->
case maps:is_key(TemplateId, Templates) of
true ->
ok;
false ->
erlang:throw({missing_template, TemplateId})
end
end,
IdPatterns).
check_templates(Templates) ->
maps:foreach(fun(_Id, Options) -> ok = speed_trap_options:validate(Options, true) end, Templates).
-spec to_match_spec([id_pattern()]) -> ets:comp_match_spec() | undefined.
to_match_spec([]) ->
undefined;
to_match_spec(KeyPatterns) ->
Clauses =
lists:map(fun({BucketIdPattern, TemplateId}) -> {BucketIdPattern, [], [TemplateId]} end,
KeyPatterns),
ets:match_spec_compile(Clauses).
-spec template_from_id(speed_trap:id(), ets:comp_match_spec() | undefined) -> false | {ok, id()}.
template_from_id(_Id, undefined) ->
false;
template_from_id(Id, MS) ->
case ets:match_spec_run([Id], MS) of
[TemplateId] ->
{ok, TemplateId};
[] ->
false
end.