lib/extensions/external_resources.ex

defmodule BridgeEx.Extensions.ExternalResources do
  @moduledoc """
  Preload a set of resources marking them as "external" and provide the related getter functions.

  ## Options

    * `resources` (required): enumerable of resource names (atoms) and their paths (strings).
      Each path is assumed to be relative to the module directory.

  ## Examples

  ```elixir
  defmodule MyBridge do
    use BridgeEx.Extensions.ExternalResources,
      resources: [
        my_query: "queries/query.graphql",
        my_mutation: "mutations/mutation.graphql"
      ]

    # it generates the following code:

    @external_resource "\#{__DIR__}/queries/query.graphql"
    @external_resource "\#{__DIR__}/mutations/mutation.graphql"

    @spec my_query() :: String.t()
    def my_query, do: Map.fetch!(external_resources(), :my_query)
    @spec my_mutation() :: String.t()
    def my_mutation, do: Map.fetch!(external_resources(), :my_mutation)

    defp external_resources do
      %{
        my_query: "contents of query.graphql",
        my_mutation: "contents of mutation.graphql"
      }
    end
  end
  ```
  """

  defmacro __using__(resources: resources) do
    dir = Path.dirname(__CALLER__.file)
    resources = for {name, path} <- resources, do: {name, Path.join(dir, path)}
    contents = for {name, path} <- resources, into: %{}, do: {name, File.read!(path)}

    getters =
      for {name, _path} <- resources do
        quote do
          @spec unquote(name)() :: String.t()
          def unquote(name)(), do: Map.fetch!(external_resources(), unquote(name))
        end
      end

    external_resource_attributes =
      for {_name, path} <- resources do
        quote do: @external_resource(unquote(path))
      end

    quote generated: true do
      unquote_splicing(external_resource_attributes)
      unquote_splicing(getters)
      defp external_resources, do: unquote(Macro.escape(contents))
    end
  end
end