surgeventures/surgex

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

Summary

Maintainability
Test Coverage
defmodule Surgex.Parser.IncludeParser do
  @moduledoc """
  Parses the JSON API's include parameter according to the
  [JSON API spec](http://jsonapi.org/format/#fetching-includes).

  Produces a list of includes constrained to the provided relationship paths.
  """

  @type errors :: :invalid_relationship_path | :invalid_input
  @type path :: atom() | String.t()

  @doc false
  @spec call(term(), [path()]) :: {:ok, [atom()]} | {:error, errors()}
  def call(nil, _spec), do: {:ok, []}
  def call("", _spec), do: {:ok, []}

  def call(input, allowed_paths) when is_binary(input) do
    paths = String.split(input, ",")
    allowed_paths = Enum.map(allowed_paths, &convert_to_string/1)

    validate_relationship_path(paths, allowed_paths)
  end

  def call(_input, _paths), do: {:error, :invalid_input}

  defp convert_to_string(path) when is_binary(path), do: path
  defp convert_to_string(path) when is_atom(path), do: Atom.to_string(path)

  defp validate_relationship_path(paths, allowed_paths) do
    case Enum.all?(paths, &Enum.member?(allowed_paths, &1)) do
      true -> {:ok, Enum.map(paths, &String.to_atom/1)}
      false -> {:error, :invalid_relationship_path}
    end
  end

  @doc """
  Flattens the result of the parser (inclusion list) into multiple keys.

  ## Examples

      iex> IncludeParser.flatten({:ok, include: [:user]}, :include)
      {:ok, include_user: true}

  """
  @spec flatten({:ok, Keyword.t()}, String.t()) :: {:ok, Keyword.t()}
  def flatten({:ok, opts}, key) do
    case Keyword.pop(opts, key) do
      {nil, _} ->
        {:ok, opts}

      {value_list, rem_opts} when is_list(value_list) ->
        new_opts =
          value_list
          |> Enum.map(fn value -> {String.to_atom("#{key}_#{value}"), true} end)
          |> Keyword.merge(rem_opts)

        {:ok, new_opts}
    end
  end

  def flatten(input, _key), do: input
end