Current section
Files
Jump to
Current section
Files
lib/alpaca/client.ex
defmodule Alpaca.Client do
@moduledoc """
Client responsible for making requests to Alpaca and handling the responses.
This client allows us to define all of our HTTP request logic and configuration
in one place so that we can easily change or adapt this away from all of the other
business logic of our library.
We have access to HTTP request types on this client and it will automatically use a base
url as well as set all the necessary headers for us.
We have access to GET, POST, PATCH, and DELETE HTTP requests using this client.
### Example usage
```
Alpaca.Client.get("/path", %{query_param: "value"})
Alpaca.Client.post("/path", %{param1: "value"})
Alpaca.Client.patch("/path", %{param1: "value"})
Alpaca.Client.delete("/path")
```
"""
defmodule MissingCredentialsError do
@moduledoc """
Exception for when a request is made without a Client ID and Secret.
"""
defexception message: """
The client_id and client_secret settings are required to make requests to Alpaca.
Please configure :client_id and :client_secret in config.exs or set the ALPACA_CLIENT_ID
and ALPACA_CLIENT_SECRET environment variable.
config :alpaca_elixir, client_id: CLIENT_ID, client_secret: CLIENT_SECRET
"""
end
@spec client_id(atom) :: String.t()
defp client_id(env_key \\ :client_id) do
case Confex.get_env(:alpaca_elixir, env_key, System.get_env("ALPACA_CLIENT_ID")) || :not_found do
:not_found -> raise MissingCredentialsError
value -> value
end
end
@spec client_secret(atom()) :: String.t()
defp client_secret(env_key \\ :client_secret) do
case Confex.get_env(:alpaca_elixir, env_key, System.get_env("ALPACA_CLIENT_SECRET")) ||
:not_found do
:not_found -> raise MissingCredentialsError
value -> value
end
end
@spec api_host :: String.t()
defp api_host,
do: Application.get_env(:alpaca_elixir, :api_host, System.get_env("ALPACA_API_HOST"))
@doc """
Issue a get request using the HTTP client
Accepts path which is the url path of the request, will be added to the end of the base_url
and a map of params which will become the query list for the get request
"""
@spec get(String.t(), map()) :: {:ok, map()} | {:error, map()}
def get(path, params \\ %{}) do
create()
|> Tesla.get(path, query: map_to_klist(params))
end
@doc """
Issue a post request using the HTTP client
Accepts path which is the url path of the request, will be added to the end of the base_url
and a map of params which will be the body of the post request
"""
@spec post(String.t(), map()) :: {:ok, map()} | {:error, map()}
def post(path, params \\ %{}) do
create()
|> Tesla.post(path, params)
end
@doc """
Issue a patch request using the HTTP client
Accepts path which is the url path of the request, will be added to the end of the base_url
and a map of params which will be the body of the post request
"""
@spec patch(String.t(), map()) :: {:ok, map()} | {:error, map()}
def patch(path, params \\ %{}) do
create()
|> Tesla.patch(path, params)
end
@doc """
Issue a delete request using the HTTP client
Accepts path which is the url path of the request, will be added to the end of the base_url
"""
@spec delete(String.t()) :: {:ok, map()} | {:error, map()}
def delete(path) do
create()
|> Tesla.delete(path)
end
defp map_to_klist(dict) do
Enum.map(dict, fn {key, value} -> {to_atom(key), value} end)
end
defp to_atom(atom) when is_atom(atom), do: atom
defp to_atom(string), do: String.to_atom(string)
defp create() do
middleware = [
{Tesla.Middleware.BaseUrl, api_host()},
{Tesla.Middleware.Headers, default_headers()},
Tesla.Middleware.EncodeJson,
Alpaca.ResponseMiddleware
]
adapter = {Tesla.Adapter.Hackney, [recv_timeout: 30_000]}
Tesla.client(middleware, adapter)
end
@spec default_headers() :: %{String.t() => String.t()}
defp default_headers() do
[
{"APCA-API-KEY-ID", client_id()},
{"APCA-API-SECRET-KEY", client_secret()}
]
end
end