Current section
Files
Jump to
Current section
Files
lib/nba/base.ex
defmodule NBA.API.Base do
@moduledoc false
alias Req
@doc false
def request(base_url, endpoint, params, opts) do
# Sort parameters by key to ensure consistent ordering
# This matters for some requests that are sensitive to parameter order
sorted_params = Enum.sort_by(params, fn {k, _} -> to_string(k) end)
# Encode parameters into a query string and construct the full URL
# - `URI.encode_query/1` is used to encode the parameters into a query string
# - `then/2` is used to create the final URL
# Example:
# params = [Season: "2022-23", PlayerID: 2544]
# sorted_params = [PlayerID: 2544, Season: "2022-23"]
# encoded_params = URI.encode_query(sorted_params)
# url = "https://stats.nba.com/stats/#{endpoint}?#{encoded_params}"
# url = "https://stats.nba.com/stats/playerawards?PlayerID=2544&Season=2022-23"
#
url =
if sorted_params == [] do
"#{base_url}/#{endpoint}"
else
URI.encode_query(sorted_params)
|> then(&"#{base_url}/#{endpoint}?#{&1}")
end
# IO.inspect(url, label: "URL")
# IO.inspect(opts[:headers], label: "Headers")
# Make the HTTP GET request and parse the response
case Req.get(url, opts) do
{:ok, %Req.Response{status: status, body: %{"resultSets" => sets}}}
when is_list(sets) ->
formatted =
sets
|> Enum.map(fn %{"name" => name, "headers" => headers, "rowSet" => rows} ->
{
name,
Enum.map(rows, fn row -> Enum.zip(headers, row) |> Map.new() end)
}
end)
|> Enum.into(%{})
{:ok, %{status: status, data: formatted}}
# Stats API responses are usually in the form of a JSON object
# with a "resultSets" key containing the data
{:ok,
%Req.Response{
status: status,
body: %{"resultSets" => [%{"rowSet" => rows, "headers" => headers}]}
}} ->
formatted =
rows
|> Enum.map(&Enum.zip(headers, &1))
|> Enum.map(&Enum.into(&1, %{}))
{:ok, %{status: status, data: formatted}}
{:ok,
%Req.Response{
status: status,
body: %{"resultSet" => %{"rowSet" => rows, "headers" => headers}}
}} ->
formatted =
rows
|> Enum.map(&Enum.zip(headers, &1))
|> Enum.map(&Enum.into(&1, %{}))
{:ok, %{status: status, data: formatted}}
# Live BoxScore and PBP API responses are usually in the form of a
# JSON object with a "game" key containing the data
{:ok,
%Req.Response{
status: status,
body: %{"game" => game}
}} ->
{:ok, %{status: status, data: game}}
# Live Odds API responses are usually in the form of a
# JSON object with a "games" key containing the data
{:ok,
%Req.Response{
status: status,
body: %{"games" => games}
}} ->
{:ok, %{status: status, data: games}}
# Live Scoreboard API responses are usually in plain text format
# with a "scoreboard" key containing the data
{:ok, %Req.Response{status: status, body: body}} when is_binary(body) ->
case Jason.decode(body) do
{:ok, %{"scoreboard" => scoreboard}} ->
{:ok, %{status: status, data: scoreboard}}
{:ok, decoded} ->
{:error, "Unexpected JSON structure: #{inspect(decoded)}"}
{:error, err} ->
{:error, err}
end
{:ok, %Req.Response{status: 400}} ->
{:error, "Bad request (400). Check your query parameters."}
{:ok, %Req.Response{status: 401}} ->
{:error, "Unauthorized (401). NBA may be rejecting requests without proper headers."}
{:ok, %Req.Response{status: 403}} ->
{:error, "Forbidden (403). You may be blocked or missing required headers."}
{:ok, %Req.Response{status: 404}} ->
{:error, "Endpoint not found (404). Check the endpoint name."}
{:ok, %Req.Response{status: 429}} ->
{:error, "Rate limited (429). Too many requests — try again later."}
{:ok, %Req.Response{status: 500..599 = status}} ->
{:error, "NBA API server error (#{status}). Try again later."}
# Handle unexpected JSON structures
{:ok, %Req.Response{status: status, body: nil}} ->
{:error, "Empty response body (#{status})."}
{:ok, %Req.Response{status: status, body: body}} when is_map(body) ->
{:ok, %{status: status, data: body}}
{:ok, %Req.Response{status: status, body: body}} ->
{:error, "Unrecognized JSON structure (#{status}): #{inspect(body)}"}
{:error, err} ->
{:error, err}
end
end
end