Current section

Files

Jump to
hammer lib hammer.ex
Raw

lib/hammer.ex

defmodule Hammer do
@moduledoc """
Hammer is a rate-limiting library for Elixir.
It provides a simple way for creating rate limiters, and comes with a built-in ETS backend.
defmodule MyApp.RateLimit do
use Hammer, backend: :ets
end
# Start the rate limiter, in case of ETS it will create the ETS table and schedule cleanups
MyApp.RateLimit.start_link(clean_period: :timer.minutes(10))
# Check the rate limit allowing 10 requests per second
MyApp.RateLimit.hit("some-key", _scale = :timer.seconds(1), _limit = 10)
"""
@type key :: term
@type scale :: pos_integer
@type limit :: pos_integer
@type count :: pos_integer
@type increment :: non_neg_integer
@doc """
Checks if a key is allowed to perform an action, and increment the counter.
Same as `hit/4` with `increment` set to 1.
"""
@callback hit(key, scale, limit) :: {:allow, count} | {:deny, timeout}
@doc """
Optional callback to check if a key is allowed to perform an action, and increment the counter.
Returns `{:allow, count}` if the action is allowed, or `{:deny, timeout}` if the action is denied.
This is the only required callback.
"""
@callback hit(key, scale, limit, increment) :: {:allow, count} | {:deny, timeout}
@doc """
Same as `inc/3` with `increment` set to 1.
"""
@callback inc(key, scale) :: count
@doc """
Optional callback for incrementing a counter value for a kit without performing limit check.
Returns the new counter value.
"""
@callback inc(key, scale, increment) :: count
@doc """
Optional callback for setting the counter value for a key.
Returns the new counter value.
"""
@callback set(key, scale, count) :: count
@doc """
Optional callback for getting the counter value for a key.
Returns the current counter value.
"""
@callback get(key, scale) :: count
@optional_callbacks hit: 4, inc: 2, inc: 3, set: 3, get: 2
@doc """
Use the Hammer library in a module to create a rate limiter.
defmodule MyApp.RateLimit do
use Hammer, backend: :ets
end
"""
defmacro __using__(opts) do
quote bind_quoted: [opts: opts] do
@behaviour Hammer
@hammer_opts opts
backend =
Keyword.get(opts, :backend) ||
raise ArgumentError, """
Hammer requires a backend to be specified. Example:
use Hammer, backend: :ets
"""
# this allows :ets to be aliased to Hammer.ETS
backend =
case backend do
:ets -> Hammer.ETS
:atomic -> Hammer.Atomic
end
@before_compile backend
end
end
end