lib/elasticsearch_api/utils.ex

defmodule ElasticsearchApi.Utils do
  @moduledoc """
  Provides some utilities to interact with Elasticsearch data.
  """

  ## Typespecs

  @type response :: ElasticsearchApi.Client.response()

  ## Public functions

  @spec extract_body!({:ok, response()}) :: any()
  def extract_body!({:ok, %{status: status, body: body}}) when status in 200..299 do
    body
  end

  def extract_body!({:ok, %{status: status, body: body}}) when status in 400..499 do
    raise ElasticsearchApi.Error, body
  end

  @spec extract_hits!({:ok, response()} | {:error, any()}) :: [map()]
  def extract_hits!(response) do
    response |> extract_body!() |> then(fn %{"hits" => %{"hits" => hits}, "timed_out" => false} -> hits end)
  end

  @spec json_headers() :: map()
  def json_headers() do
    %{"accept" => "application/json", "content-type" => "application/json"}
  end

  @spec generate_path(keyword(), (nil | binary() -> binary())) :: {binary() | URI.t(), keyword()}
  def generate_path(opts, path_fn) do
    {opts, params} = extract_options(opts)
    path = opts |> get_targets() |> path_fn.() |> do_generate_path(params)

    {path, opts}
  end

  ## Private functions

  @spec do_generate_path(binary(), keyword()) :: binary() | URI.t()
  defp do_generate_path(path, []) do
    path
  end

  defp do_generate_path(path, params) do
    encoded_params = URI.encode_query(params)

    %URI{} |> URI.append_path(path) |> URI.append_query(encoded_params)
  end

  @spec extract_options(keyword()) :: {keyword(), keyword()}
  defp extract_options(opts) do
    Keyword.split(opts, [:target, :cluster])
  end

  @spec get_targets(keyword()) :: nil | binary()
  defp get_targets(opts) do
    case Keyword.get_values(opts, :target) do
      [] ->
        nil

      values ->
        values |> List.flatten() |> Enum.join(",")
    end
  end
end