Current section

Files

Jump to
glimit src glimit.erl
Raw

src/glimit.erl

-module(glimit).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch]).
-export([new/0, per_second/2, per_minute/2, per_hour/2, handler/2, identifier/2, build/1, apply/2, stop/1]).
-export_type([message/1, rate_limiter/2, rate_limiter_builder/3, state/3]).
-type message(HNG) :: shutdown |
{hit, HNG, gleam@erlang@process:subject({ok, nil} | {error, nil})}.
-type rate_limiter(HNH, HNI) :: {rate_limiter,
gleam@erlang@process:subject(message(HNH)),
fun((HNH) -> HNI)}.
-type rate_limiter_builder(HNJ, HNK, HNL) :: {rate_limiter_builder,
gleam@option:option(integer()),
gleam@option:option(integer()),
gleam@option:option(integer()),
fun((HNJ) -> HNL),
fun((HNJ) -> HNK)}.
-type state(HNM, HNN, HNO) :: {rate_limiter_state,
gleam@dict:dict(HNO, list(integer())),
gleam@option:option(integer()),
gleam@option:option(integer()),
gleam@option:option(integer()),
fun((HNM) -> HNO),
fun((HNM) -> HNN)}.
-spec handle_message(message(HNP), state(HNP, HNR, HNS)) -> gleam@otp@actor:next(message(HNP), state(HNP, HNR, HNS)).
handle_message(Message, State) ->
case Message of
shutdown ->
{stop, normal};
{hit, Input, Client} ->
Identifier = (erlang:element(6, State))(Input),
Timestamp = glimit@utils:now(),
Hits = begin
_pipe = erlang:element(2, State),
_pipe@1 = gleam@dict:get(_pipe, Identifier),
_pipe@2 = gleam@result:unwrap(_pipe@1, []),
_pipe@3 = gleam@list:filter(
_pipe@2,
fun(Hit) -> Hit >= (Timestamp - (60 * 60)) end
),
lists:append(_pipe@3, [Timestamp])
end,
Hit_log = begin
_pipe@4 = erlang:element(2, State),
gleam@dict:insert(_pipe@4, Identifier, Hits)
end,
State@1 = erlang:setelement(2, State, Hit_log),
Hits_last_hour = begin
_pipe@5 = Hits,
erlang:length(_pipe@5)
end,
Hits_last_minute = begin
_pipe@6 = Hits,
_pipe@7 = gleam@list:filter(
_pipe@6,
fun(Hit@1) -> Hit@1 >= (Timestamp - 60) end
),
erlang:length(_pipe@7)
end,
Hits_last_second = begin
_pipe@8 = Hits,
_pipe@9 = gleam@list:filter(
_pipe@8,
fun(Hit@2) -> Hit@2 >= (Timestamp - 1) end
),
erlang:length(_pipe@9)
end,
Limit_reached = ((case erlang:element(5, State@1) of
{some, Limit} ->
Hits_last_hour > Limit;
none ->
false
end orelse case erlang:element(4, State@1) of
{some, Limit@1} ->
Hits_last_minute > Limit@1;
none ->
false
end) orelse case erlang:element(3, State@1) of
{some, Limit@2} ->
Hits_last_second > Limit@2;
none ->
false
end),
case Limit_reached of
true ->
gleam@erlang@process:send(Client, {error, nil});
false ->
gleam@erlang@process:send(Client, {ok, nil})
end,
gleam@otp@actor:continue(State@1)
end.
-spec new() -> rate_limiter_builder(any(), any(), any()).
new() ->
{rate_limiter_builder,
none,
none,
none,
fun(_) -> erlang:error(#{gleam_error => panic,
message => <<"No identifier configured"/utf8>>,
module => <<"glimit"/utf8>>,
function => <<"new"/utf8>>,
line => 125}) end,
fun(_) -> erlang:error(#{gleam_error => panic,
message => <<"Rate limit reached"/utf8>>,
module => <<"glimit"/utf8>>,
function => <<"new"/utf8>>,
line => 126}) end}.
-spec per_second(rate_limiter_builder(HOI, HOJ, HOK), integer()) -> rate_limiter_builder(HOI, HOJ, HOK).
per_second(Limiter, Limit) ->
erlang:setelement(2, Limiter, {some, Limit}).
-spec per_minute(rate_limiter_builder(HOR, HOS, HOT), integer()) -> rate_limiter_builder(HOR, HOS, HOT).
per_minute(Limiter, Limit) ->
erlang:setelement(3, Limiter, {some, Limit}).
-spec per_hour(rate_limiter_builder(HPA, HPB, HPC), integer()) -> rate_limiter_builder(HPA, HPB, HPC).
per_hour(Limiter, Limit) ->
erlang:setelement(4, Limiter, {some, Limit}).
-spec handler(rate_limiter_builder(HPJ, HPK, HPL), fun((HPJ) -> HPK)) -> rate_limiter_builder(HPJ, HPK, HPL).
handler(Limiter, Handler) ->
erlang:setelement(6, Limiter, Handler).
-spec identifier(rate_limiter_builder(HPS, HPT, HPU), fun((HPS) -> HPU)) -> rate_limiter_builder(HPS, HPT, HPU).
identifier(Limiter, Identifier) ->
erlang:setelement(5, Limiter, Identifier).
-spec build(rate_limiter_builder(HQB, HQC, any())) -> rate_limiter(HQB, HQC).
build(Config) ->
State = {rate_limiter_state,
gleam@dict:new(),
erlang:element(2, Config),
erlang:element(3, Config),
erlang:element(4, Config),
erlang:element(5, Config),
erlang:element(6, Config)},
Subject = case gleam@otp@actor:start(State, fun handle_message/2) of
{ok, Actor} ->
Actor;
{error, _} ->
erlang:error(#{gleam_error => panic,
message => <<"Failed to start rate limiter actor"/utf8>>,
module => <<"glimit"/utf8>>,
function => <<"build"/utf8>>,
line => 189})
end,
{rate_limiter, Subject, erlang:element(6, Config)}.
-spec apply(fun((HQJ) -> HQK), rate_limiter(HQJ, HQK)) -> fun((HQJ) -> HQK).
apply(Func, Limiter) ->
fun(Input) ->
case gleam@otp@actor:call(
erlang:element(2, Limiter),
fun(_capture) -> {hit, Input, _capture} end,
10
) of
{ok, nil} ->
Func(Input);
{error, nil} ->
(erlang:element(3, Limiter))(Input)
end
end.
-spec stop(rate_limiter(any(), any())) -> nil.
stop(Limiter) ->
gleam@otp@actor:send(erlang:element(2, Limiter), shutdown).