Packages

A simple sliding window limiter in pure gleam

Current section

Files

Jump to
glimiter src glimiter.erl
Raw

src/glimiter.erl

-module(glimiter).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch]).
-export([new_limiter/2, limit_guard/4]).
-export_type([limiter/0]).
-opaque limiter() :: {limiter,
gleam@dict:dict(binary(), list(birl:time())),
integer(),
birl@duration:duration()}.
-spec new_limiter(integer(), birl@duration:duration()) -> limiter().
new_limiter(Count, Size) ->
{limiter, gleam@dict:new(), Count, Size}.
-spec add(limiter(), binary()) -> gleam@dict:dict(binary(), list(birl:time())).
add(Limiter, Key) ->
Now = birl:now(),
case gleam@dict:get(erlang:element(2, Limiter), Key) of
{ok, Timestamps} ->
gleam@dict:insert(
erlang:element(2, Limiter),
Key,
[Now | Timestamps]
);
{error, _} ->
gleam@dict:insert(erlang:element(2, Limiter), Key, [Now])
end.
-spec filter_prev_window(limiter(), binary()) -> gleam@dict:dict(binary(), list(birl:time())).
filter_prev_window(Limiter, Key) ->
_assert_subject = gleam@dict:get(erlang:element(2, Limiter), Key),
{ok, Times} = case _assert_subject of
{ok, _} -> _assert_subject;
_assert_fail ->
erlang:error(#{gleam_error => let_assert,
message => <<"Assertion pattern match failed"/utf8>>,
value => _assert_fail,
module => <<"glimiter"/utf8>>,
function => <<"filter_prev_window"/utf8>>,
line => 47})
end,
Now = birl:now(),
New_times = gleam@list:filter(
Times,
fun(Time) ->
End_time = birl:add(Time, erlang:element(4, Limiter)),
case birl:compare(Now, End_time) of
lt ->
true;
eq ->
true;
_ ->
false
end
end
),
gleam@dict:insert(erlang:element(2, Limiter), Key, New_times).
-spec limit_guard(limiter(), binary(), HFM, fun((limiter()) -> HFM)) -> HFM.
limit_guard(Limiter, Key, Consequence, Alternetive) ->
Limiter@1 = {limiter,
add(Limiter, Key),
erlang:element(3, Limiter),
erlang:element(4, Limiter)},
Limiter@2 = {limiter,
filter_prev_window(Limiter@1, Key),
erlang:element(3, Limiter@1),
erlang:element(4, Limiter@1)},
_assert_subject = gleam@dict:get(erlang:element(2, Limiter@2), Key),
{ok, Times} = case _assert_subject of
{ok, _} -> _assert_subject;
_assert_fail ->
erlang:error(#{gleam_error => let_assert,
message => <<"Assertion pattern match failed"/utf8>>,
value => _assert_fail,
module => <<"glimiter"/utf8>>,
function => <<"limit_guard"/utf8>>,
line => 25})
end,
case erlang:length(Times) < erlang:element(3, Limiter@2) of
true ->
Alternetive(Limiter@2);
false ->
Consequence
end.