How to use anonymous functions in configuration files?

configuration
function
#1

Background

I have a configuration file called test.exs that does not work because it has an anonymous function:

# stub for fire function
config :my_aop,
  fire: fn(_pool_name, _endpoint) -> {:ok, :received} end

And it generates the following error:

** (ArgumentError) cannot inject attribute @default into function/macro because cannot escape #Function<12.128620087/2 in :erl_eval.expr/5>. The supported values are: lists, tuples, maps, atoms, numbers, bitstrings, PIDs and remote functions in the format &Mod.fun/arity
    (elixir) lib/kernel.ex:2879: Kernel.do_at/5
    (elixir) expanding macro: Kernel.@/1
    lib/bidtor/dispatcher.ex:36: Bidtor.Dispatcher.send/2
    (elixir) lib/kernel/parallel_compiler.ex:208: anonymous fn/4 in Kernel.ParallelCompiler.spawn_workers/6

Questions

  1. Can I not use anonymous functions in config files? (Is there a way this can be achieved or is it simply impossible?)
  2. How can I stub the given function if I cannot use anonymous functions in the configuration files?
#2

AFAIK it’s not possible to store anonymous functions in the config file, but I was not able to find more info quickly.

But it’s solvable. Put the logic into a module. Then put your “testing” logic into another module. Then just use the config to determine which module you want to use.

defmodule YourModule do
  def your_function(pool_name, endpoint) do
    # some real logic
  end
end

defmodule YourModuleMock do
  def your_function(_pool_name, _endpoint) do
    {:ok, :received}
  end
end

test.exs:

config :my_aop, fire: YourModuleMock

usage:

module = Application.get_env(:my_aop, :fire, YourModule)
module.your_function(param1, param2)
#3

I’ve tested it with releases (using Distillery) and it doesn’t work, which means you cannot use anonymous functions if you use releases at some point.

See also this discussion: Anonymous functions in config: why does it work?

#4

You may also want to borrow this from a typical Phoenix project.

In mix.exs:

  def project do
    [
      app: :form_demo,
      version: "0.1.0",
      elixir: "~> 1.5",
      elixirc_paths: elixirc_paths(Mix.env()),
      compilers: [:phoenix, :gettext] ++ Mix.compilers(),
      start_permanent: Mix.env() == :prod,
      deps: deps()
    ]
  end
]

In particular elixirc_paths. The elixirc_paths/1 function returns the compilation paths based on the mix environment. Further down:

  # Specifies which paths to compile per environment.
  defp elixirc_paths(:test), do: ["lib", "test/support"]
  defp elixirc_paths(_), do: ["lib"]

So for :test test/support is added to the compilation path. So all .ex files only used for testing go under test/support and those modules are then available for all the .exs testing scripts.

In combination with values set in config.exs and compile time use of Application.fetch_env!/2 the appropriate module can then be selected (example).

1 Like