lib/schemer.ex

defmodule Schemer do
  @moduledoc """
  Resolve identifier to a value.
  """

  alias Schemer.Resolution
  alias Schemer.Schema

  @type result_t :: term()

  @type run_opts :: [
          context: %{},
          root_value: term(),
          resolve_name: atom()
        ]

  @type run_result() :: {:ok, result_t()} | {:error, atom()}
  @type run_result(t) :: {:ok, t} | {:error, atom()}

  @seperator "."

  @spec run(String.t(), Schema.t(), run_opts) :: run_result()
  def run(identifier, %Schema{} = schema, opts \\ []) do
    options = options(Keyword.put(opts, :schema, schema))
    path_str = String.split(identifier, @seperator, trim: true)

    case Resolution.resolve(make_resolution(options), path_str) do
      {:ok, path, res} ->
        current_node = hd(path)

        if current_node.type in [:leaf, :leaf_like] do
          result = get_in(res.result, Resolution.path(path))
          {:ok, result.execution.value}
        else
          {:error, :invalid_leaf_node}
        end

      error ->
        error
    end
  end

  @defaults [
    resolve_name: :default,
    context: %{},
    root_value: %{}
  ]

  defp options(overrides) do
    Keyword.merge(@defaults, overrides)
  end

  defp make_resolution(opts) do
    %Resolution{
      schema: Keyword.fetch!(opts, :schema),
      context: Keyword.fetch!(opts, :context),
      resolve_name: Keyword.fetch!(opts, :resolve_name),
      options: opts
    }
  end
end