lib/maru/params/runtime.ex

defmodule Maru.Params.Runtime do
  defstruct name: nil,
            required: false,
            source: nil,
            nested: nil,
            children: [],
            blank_func: nil,
            parser_func: nil

  alias Maru.Params.ParseError

  def parse_params(params_runtime, params, options \\ []) do
    parse_params(params_runtime, params, options, %{})
  end

  def parse_params([], _params, _options, result), do: result

  def parse_params([h | t], params, options, result) do
    source =
      case Keyword.get(options, :keys, :strings) do
        :strings -> to_string(h.source)
        _ when is_atom(h.source) -> h.source
        :atoms when is_binary(h.source) -> String.to_atom(h.source)
        :atoms! when is_binary(h.source) -> String.to_existing_atom(h.source)
      end

    passed? = Map.has_key?(params, source)
    value = Map.get(params, source)
    nested = h.nested

    parsed =
      if value in [nil, "", '', %{}] do
        h.blank_func.({value, passed?})
      else
        h.parser_func.({:ok, value}, options)
      end

    case parsed do
      :ignore ->
        parse_params(t, params, options, result)

      {:error, step, reason} ->
        raise Maru.Params.ParseError, attribute: h.name, step: step, reason: reason

      {:ok, value} when nested == :map ->
        value = parse_params(h.children, value, options, %{})
        parse_params(t, params, options, Map.put(result, h.name, value))

      {:ok, value} when nested == :list_of_map ->
        value = Enum.map(value, fn item -> parse_params(h.children, item, options, %{}) end)
        parse_params(t, params, options, Map.put(result, h.name, value))

      {:ok, value} when nested == :list_of_single ->
        value =
          Enum.map(value, fn
            {:ok, item} ->
              item

            {:error, step, reason} ->
              raise ParseError, attribute: h.name, step: step, reason: reason
          end)

        parse_params(t, params, options, Map.put(result, h.name, value))

      {:ok, value} when nested == nil ->
        parse_params(t, params, options, Map.put(result, h.name, value))
    end
  end
end