Cannot invoke local inside match

Nick Tomlin Source

I have a simple module responsible for communicating with an API. As a convenience to the user, I allow users to pass a "short" url /my/route or the full url https://api.mysite.com and handle it with HTTPoison's process_url method:

defmodule MyApp.MyApiHandler do
  use HTTPoison.base
  @endpoint = "https://api.mysite.com"

  def process_url(@endpoint <> _path = url), do: url
  def process_url(url), do: @endpoint <> url
end

This works perfectly! Urls that start with the endpoint are unmodified, and those don't have endpoint added.

The issue I'm encountering is when I try to move this endpoint from a module attribute to a method that reads from config at run time:

defmodule MyApp.MyApiHandler do
  use HTTPoison.base

  def process_url(endpoint() <> _path = url), do: url
  def process_url(url), do: endpoint() <> url

  defp endpoint do
    Application.get_env(:MyApp, __MODULE__)[:endpoint]
  end
end

I receive the following error

lib/my_api_handler.ex: cannot invoke local endpoint/0 inside match, called as endpoint()

I'd really prefer to store this config in config (so I can change it per environment). Is there a way to do that while preserving the matching?

The existing SO question for this error doesn't seem to cover my use case (I'm not attempting to assign in the match) but I could also be wrong there.

pattern-matchingelixir

Answers

answered 1 week ago YongHao Hu #1

You can define endpoint as a macro.

E.g,

defmodule Test do
  defmacro endpoint do
    Application.get_env(:MyApp, __MODULE__)[:endpoint]
  end
  def process_url(endpoint() <> _path = url), do: url
  def process_url(url), do: endpoint() <> url
end

answered 1 week ago vahid abdi #2

Put it in the module attribute:

@endpoint Application.get_env(:MyApp, __MODULE__)[:endpoint]

answered 1 week ago Dogbert #3

The two answers posted already will fetch the env variable at compile time instead of run time which means if you change the value using Application.put_env, the function will keep on using the old value. The simplest way I can think of to implement this with a value that can change at run time would be to use String.starts_with?/2:

def process_url(url) do
  endpoint = Application.get_env(...)
  if String.starts_with?(url, endpoint), do: url, else: endpoint <> url
end

comments powered by Disqus