lib/utils.ex

defmodule Tarams.Utils do
  @doc """
  A plug which do srubbing params

  **Use in Router**

      defmodule MyApp.Router do
        ...
        plug Tarams.plug_scrub
        ...
      end

  **Use in controller**

      plug Tarams.plug_scrub when action in [:index, :show]
      # or specify which field to scrub
      plug Tarams.plug_scrub, ["id", "keyword"] when action in [:index, :show]

  """
  def plug_scrub(conn, keys \\ []) do
    params =
      if keys == [] do
        scrub_param(conn.params)
      else
        Enum.reduce(keys, conn.params, fn key, params ->
          case Map.fetch(conn.params, key) do
            {:ok, value} -> Map.put(params, key, scrub_param(value))
            :error -> params
          end
        end)
      end

    %{conn | params: params}
  end

  @doc """
  Convert all parameter which value is empty string or string with all whitespace to nil. It works with nested map and list too.

  **Example**

      params = %{"keyword" => "   ", "email" => "", "type" => "customer"}
      Tarams.scrub_param(params)
      # => %{"keyword" => nil, "email" => nil, "type" => "customer"}

      params = %{user_ids: [1, 2, "", "  "]}
      Tarams.scrub_param(params)
      # => %{user_ids: [1, 2, nil, nil]}
  """
  def scrub_param(%{__struct__: mod} = struct) when is_atom(mod) do
    struct
  end

  def scrub_param(%{} = param) do
    Enum.reduce(param, %{}, fn {k, v}, acc ->
      Map.put(acc, k, scrub_param(v))
    end)
  end

  def scrub_param(param) when is_list(param) do
    Enum.map(param, &scrub_param/1)
  end

  def scrub_param(param) do
    if scrub?(param), do: nil, else: param
  end

  defp scrub?(" " <> rest), do: scrub?(rest)
  defp scrub?(""), do: true
  defp scrub?(_), do: false

  @doc """
  Clean all nil field from params, support nested map and list.

  **Example**

      params = %{"keyword" => nil, "email" => nil, "type" => "customer"}
      Tarams.clean_nil(params)
      # => %{"type" => "customer"}

      params = %{user_ids: [1, 2, nil]}
      Tarams.clean_nil(params)
      # => %{user_ids: [1, 2]}
  """
  @spec clean_nil(any) :: any
  def clean_nil(%{__struct__: mod} = param) when is_atom(mod) do
    param
  end

  def clean_nil(%{} = param) do
    Enum.reduce(param, %{}, fn {k, v}, acc ->
      if is_nil(v) do
        acc
      else
        Map.put(acc, k, clean_nil(v))
      end
    end)
  end

  def clean_nil(param) when is_list(param) do
    Enum.reduce(param, [], fn item, acc ->
      if is_nil(item) do
        acc
      else
        [clean_nil(item) | acc]
      end
    end)
    |> Enum.reverse()
  end

  def clean_nil(param), do: param
end