Trouble plugging plugs with generated options in guards in a Phoenix controller

What I am trying to do: Plug a rate limiter plug for a list of actions with generated individual options for each.

What I have:

defmodule Pleroma.Web.MastodonAPI.TimelineController do
  use Pleroma.Web, :controller
  ...
  for timeline_action <- [:direct, :home, :public, :hashtag, :list] do
    bucket_name = :"#{timeline_action}_timeline"

    plug(
      RateLimiter,
      [name: :timeline, bucket_name: bucket_name] when action == timeline_action
    )
  end
  ...
end

However this fails to compile with

warning: variable "timeline_action" does not exist and is being expanded to "timeline_action()", please use parentheses to remove the ambiguity or change the variable name

and later

== Compilation error in file lib/pleroma/web/mastodon_api/controllers/timeline_controller.ex ==
** (CompileError) lib/pleroma/web/mastodon_api/controllers/timeline_controller.ex:24: cannot find or invoke local timeline_action/0 inside guard. Only macros can be invoked in a guard and they must be defined before their invocation. Called as: timeline_action()

As I understand the problem is that the plug macro is evaluated before timeline_action is an actual variable, but I am unsure how to sovle this.

Thanks in advance and apologies if the solution is obvious.

Try putting unquote(timeline_action) in the guard.

Doesn’t help unfortunately.

With the following code the error stays the same:

defmodule Pleroma.Web.MastodonAPI.TimelineController do
  use Pleroma.Web, :controller
  ...
  for timeline_action <- [:direct, :home, :public, :hashtag, :list] do
    bucket_name = :"#{timeline_action}_timeline"

    plug(
      RateLimiter,
      [name: :timeline, bucket_name: bucket_name] when action == unquote(timeline_action)
    )
  end
  ...
end
defmodule Pleroma.Web.MastodonAPI.TimelineController do
  use Pleroma.Web, :controller
  ...
  for timeline_action <- [:direct, :home, :public, :hashtag, :list] do
    bucket_name = :"#{timeline_action}_timeline"

    plug(
      RateLimiter,
      [name: :timeline, bucket_name: bucket_name]
    ) when action == timeline_action
  end
  ...
end

The guard should be inside the call to plug/2 (https://hexdocs.pm/phoenix/Phoenix.Controller.html#module-guards) though? Otherwise I just get undefined function when/2

Write that plug line statically, as you’d do by hand. Then wrap it in a quote block and try to figure out how elixir needs all the syntax in place.

It turned out to be a phoenix bug. https://github.com/phoenixframework/phoenix/issues/3688

2 Likes