lib/ex_open_sea/asset_offers/index.ex

defmodule ExOpenSea.AssetOffers.Index do
  @moduledoc """
  Return active offers on a given NFT

  https://docs.opensea.io/reference/asset-offers
  """

  alias ExOpenSea.Http

  @type api_key :: ExOpenSea.ApiKey.t()
  @type contract_address :: String.t()
  @type token_id :: non_neg_integer
  @type params :: %{
          optional(:limit) => String.t()
        }
  @type offer :: ExOpenSea.AssetOffer.t()
  @type error_reason :: :parse_result_item | String.t()
  @type raw_payload :: map
  @type result :: {:ok, [offer], raw_payload} | {:error, error_reason, raw_payload | nil}

  @spec get(api_key, contract_address, token_id) :: result
  @spec get(api_key, contract_address, token_id, params) :: result
  def get(api_key, contract_address, token_id, params \\ %{}) do
    "/api/v1/asset/#{contract_address}/#{token_id}/offers"
    |> Http.Request.for_path()
    |> Http.Request.with_query(params)
    |> Http.Request.with_auth(api_key)
    |> Http.Client.get()
    |> parse_response()
  end

  defp parse_response({:ok, %{"offers" => raw_offers} = raw_payload}) do
    {ok_or_error, offers_or_reason} =
      raw_offers
      |> Enum.map(&Mapail.map_to_struct(&1, ExOpenSea.AssetOffer))
      |> Enum.reduce(
        {:ok, []},
        fn
          {:ok, i}, {:ok, acc} -> {:ok, [i | acc]}
          _, _acc -> {:error, :parse_result_item}
        end
      )

    {ok_or_error, offers_or_reason, raw_payload}
  end

  defp parse_response({:error, raw_reasons}) when is_map(raw_reasons) do
    reasons =
      raw_reasons
      |> Enum.reduce(
        [],
        fn {k, v}, acc ->
          acc ++ [{k, v}]
        end
      )

    {:error, reasons, raw_reasons}
  end

  defp parse_response({:error, reason}) do
    {:error, reason, nil}
  end
end