lib/rpc/request.ex

defmodule Soroban.RPC.Request do
  @moduledoc """
  A module to work with Soroban RPC requests.
  Requests are composed in a functional manner.
  The request does not happen until it is configured and passed to `perform/1`.
  """

  alias Soroban.RPC.{Client, Error, HTTPError}

  @default_url "https://rpc-futurenet.stellar.org:443/"

  @type endpoint :: String.t()
  @type headers :: [{binary(), binary()}]
  @type params :: map() | nil
  @type opts :: Keyword.t()
  @type response :: {:ok, map()} | {:error, Error.t()} | {:error, HTTPError.t()}
  @type parsed_response :: {:ok, struct()} | {:error, Error.t()} | {:error, HTTPError.t()}
  @type url :: String.t()

  @type t :: %__MODULE__{
          endpoint: endpoint(),
          url: url(),
          params: params(),
          headers: headers()
        }

  defstruct [
    :endpoint,
    :url,
    :params,
    :headers
  ]

  @spec new(endpoint :: endpoint(), opts :: opts()) :: t()
  def new(endpoint, opts \\ []) do
    url = Keyword.get(opts, :url, @default_url)

    %__MODULE__{
      endpoint: endpoint,
      url: url,
      params: nil,
      headers: []
    }
  end

  @spec add_headers(request :: t(), headers :: headers()) :: t()
  def add_headers(%__MODULE__{} = request, headers), do: %{request | headers: headers}

  @spec add_params(request :: t(), params :: params()) :: t()
  def add_params(%__MODULE__{} = request, params),
    do: %{request | params: params}

  @spec perform(request :: t()) :: response()
  def perform(%__MODULE__{endpoint: endpoint, url: url, headers: headers, params: params}),
    do: Client.request(endpoint, url, headers, params)

  @spec results(response :: response(), opts :: opts()) :: parsed_response()
  def results({:ok, results}, as: resource), do: {:ok, resource.new(results)}
  def results({:error, error}, _resource), do: {:error, error}
end