-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).