首页 > 解决方案 > 将宏作为 Plug 选项传递无法编译

问题描述

在我的 Phoenix 应用程序中,我正在使用我编写的几个宏来向日志数据添加一些额外的自定义格式 - 但是,当我使用该use行为时,我在让应用程序正确编译时遇到了一些麻烦,我添加负责将 HTTP 访问记录直接记录到plugrouter.ex管道中的 a 的宏。

首先,这是我尝试使用的宏的简化版本:

defmodule MacroTest do
  defmacro __using__(_) do
    quote do
      require Logger

      defmacro execute(data) do
        quote do: Logger.log(:info, unquote(data))
      end

    end
  end
end

在我的router.ex中,我想将execute/1宏提供给Plug.Accesslog作为其fun选项:

defmodule MyApp.Router do
  use MyApp.Web., :router
  use MacroTest

  pipeline :api do
    plug Plug.AccessLog,
      format: :combined,
      formatters: [ Plug.AccessLog.DefaultFormatter ],
      fun: &execute/1
  end
end

当我像这样编译应用程序时,出现以下错误:

** (CompileError) web/router.ex:21: undefined function execute/1
  (stdlib) lists.erl:1338: :lists.foreach/2
  (stdlib) erl_eval.erl:670: :erl_eval.do_apply/6
  (elixir) lib/kernel/parallel_compiler.ex:198: anonymous fn/4 in Kernel.ParallelCompiler.spawn_workers/6

但是,我发现如果我定义一个辅助函数并在那里调用宏,这将起作用:

defmodule MyApp.Router do
  use MyApp.Web., :router
  use MacroTest

  def my_fun(data), do: execute(data)

  pipeline :api do
    plug Plug.AccessLog,
      format: :combined,
      formatters: [ Plug.AccessLog.DefaultFormatter ],
      fun: &__MODULE__.my_fun/1
  end
end

这是我可以在这里完成我需要的唯一方法,还是我可以更改一些东西以允许我execute/1直接传递到plug而无需包装它?如果这是唯一的解决方案,那没什么大不了的,但如果可能的话,我宁愿避免使用辅助功能。尽管我无法真正阐明为什么会发生这种情况,但我确实很欣赏将宏传递给另一个宏的潜在编译复杂性。

谢谢!

标签: elixirphoenix-framework

解决方案


您根本不需要宏或包装器,只需注入一个函数:

defmodule MacroTest do
  defmacro __using__(_) do
    quote do
      require Logger

      def execute(data) do
        Logger.log(:info, data)
      end
    end
  end
end

和:

defmodule MyApp.Router do
  use MyApp.Web, :router
  use MacroTest

  pipeline :api do
    plug Plug.AccessLog,
      format: :combined,
      formatters: [ Plug.AccessLog.DefaultFormatter ],
      fun: &__MODULE__.execute/1
  end
end

推荐阅读