lib/typesense.ex

defmodule Typesense do
  @moduledoc """
  HTTP client for the Typesense API.
  """

  alias HTTPoison.{AsyncResponse, Base, Error, Request, Response}

  use Base

  @default_headers [{"Content-Type", "application/json"}]
  @default_options [parse: :json]

  @impl Base
  def process_request_options(options) do
    Keyword.merge(@default_options, options)
  end

  @impl Base
  def process_url(url) do
    Application.fetch_env!(:ecto_typesense, :url) <> url
  end

  @impl Base
  def process_request_headers(headers) do
    api_key = Application.fetch_env!(:ecto_typesense, :api_key)
    @default_headers ++ [{"X-TYPESENSE-API-KEY", api_key}] ++ headers
  end

  @impl Base
  def process_request_body(body) when is_map(body) do
    Jason.encode!(body)
  end

  def process_request_body(body) do
    body
  end

  @impl Base
  def process_response(%Response{
        status_code: status,
        body: body,
        request: %Request{
          options: options
        }
      })
      when status in 200..299 do
    case options[:parse] do
      :json -> json(body)
      :jsonl -> jsonl(body)
      _ -> {:ok, body}
    end
  end

  @impl Base
  def process_response(response), do: response

  @spec health() :: {:ok, Response.t() | AsyncResponse.t()} | {:error, Error.t()}
  def health, do: HTTPoison.get("/health")

  defp jsonl(string) do
    string
    |> String.split("\n")
    |> Enum.map(&json/1)
  end

  defp json(string) do
    case Jason.decode(string) do
      {:ok, json} -> json
      {:error, _} -> string
    end
  end
end