Creating an SDK Pt. 3
Check out Part 1 here & Check out Part 2 here
We have OddsShark
module that will abstract away all the particulars we need to deal with when querying the OddsShark API. Let’s implement that actual functionality of interacting with the OddsShark API. I’ve reposted the function chart we derived from last time, as we’ll now actually implement those functions since we have a common OddsShark
module that will handle all the heavy HTTP-based lifting we need to do.
Endpoint | Params | Return Values |
/upcoming/us/<league>
|
<league>
|
[%Matchup{}]
|
/ticker/<league>
|
<league>
|
[%Ticker{}]
|
/scores/<league>/<date>
|
<league> , <date>
|
[%Score{}]
|
/gamecenter/<league>/<id>
|
<league> , <id>
|
%Gamecenter{}
|
/play_by_play/<league>
|
<league>
|
[%Play{}]
|
Let’s just start with the first row. We’ll name the function matchups
, since it will return a list of %Matchup
s. The one input parameter we’ll take is a string called league
, which in the context of OddsShark can be mlb
, nba
, ncaab
, nhl
, and nfl
(there may be a few more, but that’s a pretty decent list). Thinking about the internal process of this function call, it’ll go something like this:
build_endpoint ->
make_request ->
handle_response ->
build_list
We’ll make the request after building our endpoint (which we know will be /upcoming/us/<league>
), and return an :error
tuple if a status code other than 200
is returned. If a 200
is returned, we’ll return an :ok
tuple with the response.
Create a oddsshark
directory in lib/
- this will house most of our functionality, with each file corresponding to a response data shape. Within the new oddsshark
directory, create a matchup.ex
file. We’ll implement a simple get
function to hit the endpoint we’ve defined and return the result.
The skeleton of our matchup.ex
file is below:
defmodule OddsShark.Matchup do
def get(league) do
end
end
I’ve added the get(league)
, since we know we need the league
parameter. We also know that the endpoint we hit will be consistent, so let’s add @base_path "upcoming/us"
underneath the defmodule OddsShark.Matchup do
so that we can make clear which endpoint we’re hitting.
As we said before, we’ll need to build the endpoint. We know that we’ll always have one parameter, and that our endpoint is defined, so this will be pretty easy.
defp get_path(league) do
Path.join(@base_path, league)
end
We’ll also need a function to make the request and handle the response. We didn’t add any response handling in part 2 within our OddsShark
module, but it’ll probably something we’ll want in all of our individual files. Let’s switch over to oddsshark.ex
and add a handle_response
function.
The handle_response
will take in a response to a request. These responses come in the format of:
{status_code, data}
Following the HTTP spec, a success status code is 200
. A status code other than that, for all intents and purposes, indicates that an error occurred. We’ll want to return an error message if we do return an error. Since we don’t receive much information about errors from OddsShark (try passing in a league
that doesn’t exist), we’ll just return a generic error message. The first handle_response
function below is what we implemented last time - it matches responses that return a status_code
of 200. The second implementation matches an :ok
response (meaning that no unexpected errors occurred when making the request with HTTPosion
) but without a 200
status code, which to us means an error occurred.
def handle_response({:ok, %{body: body, status_code: 200}}) do
{:ok, process_response_body(body)}
end
def handle_response({:ok, %{}}) do
{:error, "an error occurred"}
end
We can now add a second private function to our Matchup
model, which will actually make the request and handle the response. The implementation is below - it uses the OddsShark
module we created in the last part.
defp make_request(url) do
get_path(league)
|> OddsShark.get_request
end
The /upcoming/us/<league>
call is interesting in that it returns a data shape where the information we want is nested within the data
key in the response Map. We’ll need to grab that data from the data
key if the request is successful. Because we handle the successful and failed responses, we know we’ll receive either an :ok
tuple or :error
tuple, so we can implement a simple case statement that’s so common in Elixir applications.
def get(league) do
case make_request(league) do
{:ok, res} -> {:ok, Map.get(res, "data")}
{:error, reason} -> {:error, reason}
end
end
This is really clear! We know if we receive a successful response, we need to grab the data
key. Otherwise, just pass through the formatted error from the handle_response
error handler we implemented in the OddsShark
module. This makes it easy for us to implement better error message in the future, with one central place for us to handle the errors and format the error message.
We’ll go through a similar process for all the other endpoints we defined above. You can see the full project here. Thanks for following along!