lib/extract_query_params.ex

defmodule ExtractQueryParams do
  @moduledoc """
  Module for extracting query parameters from a list of keywords.
  """

  @supported_logical_operators [
    "AND",
    "OR",
    "NOT"
  ]

  @doc """
  Turns list of keywords to SQL statement variables, connected with an operator (defaults to "AND").

  ## Examples

      iex> ExtractQueryParams.to_variables(foo: "bar")
      {"foo = ?", ["bar"]}

  """
  @spec to_variables(list(keyword())) :: {binary(), list(any)}
  def to_variables([]), do: {"", [""]}

  def to_variables(keywords), do: to_variables(keywords, "AND")

  @spec to_variables(list(keyword()), binary()) :: {binary(), any()}
  def to_variables([], _), do: {"", [""]}

  def to_variables(keywords, logical_operator)
      when logical_operator in @supported_logical_operators do
    keywords_to_query_map(keywords)
    |> case do
      %{query: [], variables: []} ->
        ""

      %{query: query, variables: vars} ->
        {
          query |> Enum.reverse() |> Enum.join(" #{logical_operator} "),
          vars |> Enum.reverse()
        }
    end
  end

  def to_variables(_, _) do
    {"", [""]}
  end

  @doc """
  Turns list of keywords to SQL statement variables, connected with an operator (defaults to "AND").
  Raises an ArgumentError if the logical operator is not supported.

  ## Examples

      iex(1)> ExtractQueryParams.to_variables!([foo: "foo", bar: "bar"], "AND")
      {"foo = ? AND bar = ?", ["foo", "bar"]}

      iex(2)> ExtractQueryParams.to_variables!([foo: "foo", bar: "bar"], "UNSUPPORTED")
      ** (ArgumentError) logical operator "UNSUPPORTED" is not supported.

  """
  @spec to_variables!(list(keyword()), binary()) :: {binary(), list(any())}
  def to_variables!(keywords, logical_operator)
      when logical_operator in @supported_logical_operators do
    to_variables(keywords, logical_operator)
  end

  def to_variables!(_, logical_operator) do
    raise ArgumentError,
      message: "logical operator #{inspect(logical_operator)} is not supported."
  end

  defp keywords_to_query_map(keywords),
    do:
      keywords
      |> Enum.filter(fn
        {_, nil} -> false
        _ -> true
      end)
      |> Enum.map(fn {atom, variable} ->
        {Atom.to_string(atom) <> " = ?", variable}
      end)
      |> Enum.reduce(
        %{query: [], variables: []},
        fn {query, variable}, %{query: query_, variables: variables_} ->
          %{
            query: [query | query_],
            variables: [variable | variables_]
          }
        end
      )
end