-module(glimit@rate_limiter). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "src/glimit/rate_limiter.gleam"). -export([hit/2, set_now/2, new/3]). -export_type([hit_error/0, state/1, message/1]). -if(?OTP_RELEASE >= 27). -define(MODULEDOC(Str), -moduledoc(Str)). -define(DOC(Str), -doc(Str)). -else. -define(MODULEDOC(Str), -compile([])). -define(DOC(Str), -compile([])). -endif. ?MODULEDOC(false). -type hit_error() :: rate_limited | unavailable | store_lock_failed. -type state(FWW) :: {state, fun((FWW) -> integer()), fun((FWW) -> integer()), glimit@bucket:store(), gleam@option:option(integer())}. -type message(FWX) :: {hit, FWX, gleam@erlang@process:subject({ok, nil} | {error, hit_error()})} | {set_now, integer(), gleam@erlang@process:subject(nil)}. -file("src/glimit/rate_limiter.gleam", 59). ?DOC(false). -spec get_now(state(any())) -> integer(). get_now(State) -> case erlang:element(5, State) of {some, Now} -> Now; none -> glimit@utils:now() end. -file("src/glimit/rate_limiter.gleam", 117). ?DOC(false). -spec compute_ttl(glimit@bucket:bucket_state()) -> integer(). compute_ttl(B) -> case erlang:element(3, B) > 0 of true -> Refill_seconds = case erlang:element(3, B) of 0 -> 0; Gleam@denominator -> ((erlang:element(2, B) + erlang:element( 3, B )) - 1) div Gleam@denominator end, gleam@int:max(Refill_seconds * 2, 60); false -> 60 end. -file("src/glimit/rate_limiter.gleam", 176). ?DOC(false). -spec hit(gleam@erlang@process:subject(message(FXT)), FXT) -> {ok, nil} | {error, hit_error()}. hit(Rate_limiter, Identifier) -> case glimit@utils:safe_call( Rate_limiter, fun(_capture) -> {hit, Identifier, _capture} end, 1000 ) of {ok, {ok, nil}} -> {ok, nil}; {ok, {error, Err}} -> {error, Err}; {error, nil} -> {error, unavailable} end. -file("src/glimit/rate_limiter.gleam", 190). ?DOC(false). -spec set_now(gleam@erlang@process:subject(message(any())), integer()) -> nil. set_now(Rate_limiter, Now) -> _ = glimit@utils:safe_call( Rate_limiter, fun(_capture) -> {set_now, Now, _capture} end, 1000 ), nil. -file("src/glimit/rate_limiter.gleam", 113). ?DOC(false). -spec string_key(any()) -> binary(). string_key(Identifier) -> <<"glimit:"/utf8, (gleam@string:inspect(Identifier))/binary>>. -file("src/glimit/rate_limiter.gleam", 66). ?DOC(false). -spec handle_store_hit(state(FXD), FXD) -> {ok, nil} | {error, hit_error()}. handle_store_hit(State, Identifier) -> Key = string_key(Identifier), case (erlang:element(4, erlang:element(4, State)))(Key) of {error, _} -> {error, store_lock_failed}; {ok, _} -> Bucket_result = case (erlang:element(2, erlang:element(4, State)))( Key ) of {ok, {some, B}} -> {ok, B}; {ok, none} -> gleam@result:'try'( glimit_ffi:rescue( fun() -> (erlang:element(2, State))(Identifier) end ), fun(Max) -> gleam@result:'try'( glimit_ffi:rescue( fun() -> (erlang:element(3, State))(Identifier) end ), fun(Rate) -> glimit@bucket:new(Max, Rate) end ) end ); {error, _} -> _ = (erlang:element(5, erlang:element(4, State)))(Key), {error, nil} end, case Bucket_result of {error, _} -> _ = (erlang:element(5, erlang:element(4, State)))(Key), {error, unavailable}; {ok, B@1} -> Now = get_now(State), {Hit_result, New_b} = glimit@bucket:hit(B@1, Now), Ttl = compute_ttl(New_b), _ = (erlang:element(3, erlang:element(4, State)))( Key, New_b, Ttl ), _ = (erlang:element(5, erlang:element(4, State)))(Key), case Hit_result of {ok, nil} -> {ok, nil}; {error, nil} -> {error, rate_limited} end end end. -file("src/glimit/rate_limiter.gleam", 129). ?DOC(false). -spec handle_message(state(FXI), message(FXI)) -> gleam@otp@actor:next(state(FXI), message(FXI)). handle_message(State, Message) -> case Message of {hit, Identifier, Client} -> Result = handle_store_hit(State, Identifier), gleam@otp@actor:send(Client, Result), gleam@otp@actor:continue(State); {set_now, Now, Client@1} -> gleam@otp@actor:send(Client@1, nil), gleam@otp@actor:continue( {state, erlang:element(2, State), erlang:element(3, State), erlang:element(4, State), {some, Now}} ) end. -file("src/glimit/rate_limiter.gleam", 149). ?DOC(false). -spec new( fun((FXP) -> integer()), fun((FXP) -> integer()), glimit@bucket:store() ) -> {ok, gleam@erlang@process:subject(message(FXP))} | {error, nil}. new(Per_second, Burst_limit, Store) -> _pipe@1 = gleam@otp@actor:new_with_initialiser( 1000, fun(Self_subject) -> State = {state, Burst_limit, Per_second, Store, none}, {ok, begin _pipe = gleam@otp@actor:initialised(State), gleam@otp@actor:returning(_pipe, Self_subject) end} end ), _pipe@2 = gleam@otp@actor:on_message(_pipe@1, fun handle_message/2), _pipe@3 = gleam@otp@actor:start(_pipe@2), _pipe@4 = gleam@result:map( _pipe@3, fun(Started) -> erlang:element(3, Started) end ), gleam@result:map_error(_pipe@4, fun(_) -> nil end).