lib/kungfuig/backend.ex

defmodule Kungfuig.Backend do
  @moduledoc "The scaffold for the backend watching the external config source."

  @doc "The key this particular config would be stored under, defaults to module name"
  @callback key :: atom()

  @doc "The implementation of the call to remote that retrieves the data"
  @callback get([Kungfuig.option()]) :: {:ok, any()} | {:error, any()}

  @doc "The transformer that converts the retrieved data to internal representation"
  @callback transform(any()) :: {:ok, any()} | {:error, any()}

  @doc "The implementation of error reporting"
  @callback report(any()) :: :ok

  @doc false
  defmacro __using__(opts \\ []) do
    quote location: :keep, generated: true, bind_quoted: [opts: opts] do
      @behaviour Kungfuig.Backend

      {key, opts} =
        Keyword.pop(
          opts,
          :key,
          __MODULE__ |> Module.split() |> List.last() |> Macro.underscore() |> String.to_atom()
        )

      {report, opts} = Keyword.pop(opts, :report, :none)

      @impl Kungfuig.Backend
      def key, do: unquote(key)

      @impl Kungfuig.Backend
      def transform(any), do: {:ok, any}

      @impl Kungfuig.Backend
      case report do
        :logger ->
          require Logger

          def report(error),
            do:
              Logger.info(fn ->
                "Failed to retrieve config in #{key()}. Error: #{inspect(error)}."
              end)

        _ ->
          def report(_any), do: :ok
      end

      defoverridable Kungfuig.Backend

      @opts opts
      use Kungfuig, @opts

      @impl Kungfuig
      def update_config(%Kungfuig{__meta__: meta, state: %{} = state}) do
        with {:ok, result} <- get(meta),
             {:ok, result} <- transform(result) do
          Map.put(state, key(), result)
        else
          {:error, error} ->
            report(error)
            state
        end
      end

      defp smart_validate(nil, options), do: {:ok, options}
      defp smart_validate(Kungfuig.Validators.Void, options), do: {:ok, options}

      defp smart_validate(validator, options) do
        with {:ok, validated} <- validator.validate(options[key()]),
             do: {:ok, %{options | key() => validated}}
      end

      defp report_error(error), do: report(error)
    end
  end
end