surgeventures/surgex

View on GitHub
lib/surgex/parser/parsers/resource_array_parser.ex

Summary

Maintainability
Test Coverage
defmodule Surgex.Parser.ResourceArrayParser do
  @moduledoc false

  @type errors :: :too_short | :too_long | :invalid_array
  @type option :: {:min, integer()} | {:max, integer()}

  @spec call(term(), (term() -> term()), [option()]) :: {:ok, list() | nil} | {:error, errors()}
  def call(list, item_parser, opts \\ [])
  def call(nil, _item_parser, _opts), do: {:ok, nil}
  def call("", _item_parser, _opts), do: {:ok, nil}

  def call(list, item_parser, opts) when is_list(list) do
    min = Keyword.get(opts, :min)
    max = Keyword.get(opts, :max)

    case validate_length(list, min, max) do
      {:ok, list} -> parse_array(list, item_parser)
      {:error, :too_short} -> {:error, :too_short}
      {:error, :too_long} -> {:error, :too_long}
    end
  end

  def call(_input, _, _), do: {:error, :invalid_array}

  defp validate_length(list, min, max) do
    cond do
      min && length(list) < min ->
        {:error, :too_short}

      max && length(list) > max ->
        {:error, :too_long}

      true ->
        {:ok, list}
    end
  end

  defp parse_array(list, item_parser) do
    list
    |> Enum.map(item_parser)
    |> Stream.with_index()
    |> Enum.reduce({[], []}, &reduce/2)
    |> close
  end

  defp reduce({{:ok, result}, _index}, {output, errors}) do
    {[result | output], errors}
  end

  defp reduce({{:error, error_type, pointers}, index}, {output, errors})
       when error_type in [:invalid_parameters, :invalid_pointers] do
    new_errors =
      Enum.map(pointers, fn {reason, pointer} ->
        {reason, "#{index}/#{pointer}"}
      end)

    {output, errors ++ new_errors}
  end

  defp close({output, []}), do: {:ok, Enum.reverse(output)}
  defp close({_output, errors}), do: {:error, errors}
end