Can I create a macro for a plug call like `plug Module, :stuff when action == :create`? I'm getting the error: `cannot find or invoke local action/0 inside guard`

I’m using the Hammer lib to add rate limiting. In order to do that you need to add this plug to your controller:

plug Hammer.Plug,
       [
         rate_limit: {"video:upload", 60_000, 10},
         by: {:session, :user_id}
       ]
       when action == :create

I’d like to wrap this in a macro so I can call it like:

rate_limit :create, 60_000, 10

I created this module:

defmodule RateLimiter do
  defmacro rate_limit(method, time, num_reqs) do
    quote do
      plug Hammer.Plug,
           [
             rate_limit: {"video:upload", unquote(time), unquote(num_reqs)},
             by: {:session, :user_id}
           ]
           when action == unquote(method)
    end
  end
end

And added an import RateLimiter to my def controller function in MyAppWeb.

This gives me the following error:

cannot find or invoke local action/0 inside guard. Only macros can be invoked in a guard and they must be defined before their invocation. Called as: action()

It looks like it can’t pick up action. Am I writing the macro wrong? I tried unquote(action), but still got an error.

It is because of macro hygiene. What you need to use is var!(action) to make it work as expected.

3 Likes

Thank you!