Skip to main content

lib/kagi/http.ex

defmodule Kagi.HTTP do
  @moduledoc false

  alias Kagi.Client
  alias Kagi.Error

  @type response :: %{status: pos_integer(), body: term()}

  @spec get(Client.t(), String.t(), keyword()) :: {:ok, response()} | {:error, Error.t()}
  def get(%Client{} = client, url, options) when is_binary(url) and is_list(options) do
    request =
      client.req_options
      |> Keyword.put(:url, url)
      |> Keyword.put(:method, :get)
      |> Req.new()
      |> Req.merge(options)

    with {:ok, %Req.Response{status: status, body: body}} <-
           request |> CloakedReq.attach() |> Req.request() |> normalize_request_error() do
      handle_status(status, body)
    end
  end

  @spec normalize_request_error({:ok, Req.Response.t()} | {:error, Exception.t()}) ::
          {:ok, Req.Response.t()} | {:error, Error.t()}
  defp normalize_request_error({:ok, %Req.Response{} = response}), do: {:ok, response}

  defp normalize_request_error({:error, exception}) do
    {:error, Error.new(:request_failed, Exception.message(exception))}
  end

  @spec handle_status(pos_integer(), term()) :: {:ok, response()} | {:error, Error.t()}
  defp handle_status(status, _body) when status in [401, 403] do
    {:error, Error.new(:unauthorized, "invalid or expired session token")}
  end

  defp handle_status(429, _body), do: {:error, Error.new(:rate_limited, "rate limited")}

  defp handle_status(status, body) when status >= 200 and status <= 299 do
    {:ok, %{status: status, body: body}}
  end

  defp handle_status(status, _body), do: {:error, Error.new(:http_error, "HTTP #{status}")}
end