defmodule Lob.Client do
@moduledoc """
Client responsible for making requests to Lob and handling the responses.
"""
alias HTTPoison.Error
alias Poison.Parser
use HTTPoison.Base
@client_version Mix.Project.config[:version]
@type client_response :: {:ok, map, list} | {:error, any}
defmodule MissingAPIKeyError do
@moduledoc """
Exception for when a request is made without an API key.
"""
defexception message: """
The api_key setting is required to make requests to Lob.
Please configure :api_key in config.exs or set the API_KEY
environment variable.
config :lob_elixir, api_key: API_KEY
"""
end
@spec client_version :: String.t
def client_version, do: @client_version
@spec api_key(atom) :: String.t
def api_key(env_key \\ :api_key) do
case Confex.get_env(:lob_elixir, env_key, System.get_env("API_KEY")) || :not_found do
:not_found -> raise MissingAPIKeyError
value -> value
end
end
@spec api_version :: String.t | nil
def api_version,
do: Application.get_env(:lob_elixir, :api_version, System.get_env("LOB_API_VERSION"))
# #########################
# HTTPoison.Base callbacks
# #########################
def process_request_headers(headers) do
api_version()
|> default_headers()
|> Map.merge(Map.new(headers))
|> Enum.into([])
end
def process_response_body(body) do
Parser.parse!(body, %{keys: :atoms})
end
# #########################
# Client API
# #########################
@spec get_request(String.t, HTTPoison.Base.headers) :: client_response
def get_request(url, headers \\ []) do
url
|> get(headers, build_options())
|> handle_response
end
@spec post_request(<<_::64, _::_*8>>, {:multipart, [any()]}, [{binary(), binary()}]) :: client_response
def post_request(url, body, headers \\ []) do
url
|> post(body, headers, build_options())
|> handle_response
end
@spec post_request_binary(<<_::64, _::_*8>>, binary(), [{binary(), binary()}]) :: client_response
def post_request_binary(url, body, headers \\ []) do
url
|> post(body, headers, build_options())
|> handle_response
end
@spec delete_request(String.t, HTTPoison.Base.headers) :: client_response
def delete_request(url, headers \\ []) do
url
|> delete(headers, build_options())
|> handle_response
end
# #########################
# Response handlers
# #########################
@spec handle_response({:ok, map} | {:error, Error.t}) :: client_response
defp handle_response({:ok, %{body: body, headers: headers, status_code: code}})
when code >= 200 and code < 300 do
{:ok, body, headers}
end
defp handle_response({:ok, %{body: body}}) do
{:error, body.error}
end
defp handle_response({:error, error = %Error{}}) do
{:error, %{message: Error.message(error)}}
end
@spec build_options(String.t) :: Keyword.t
defp build_options(api_key \\ api_key()) do
[hackney: [basic_auth: {api_key, ""}], recv_timeout: :infinity]
end
@spec default_headers(String.t | nil) :: %{String.t => String.t}
defp default_headers(nil), do: %{"User-Agent" => "Lob/v1 ElixirBindings/#{client_version()}"}
defp default_headers(api_version),
do: %{
"User-Agent" => "Lob/v1 ElixirBindings/#{client_version()}",
"Lob-Version" => api_version
}
end