defmodule Plaid.Client do
@moduledoc """
Functions to build a Tesla client for handling HTTP requests.
"""
defmodule Request do
@moduledoc """
Data structure for an HTTP request with convenience functions.
"""
defstruct body: %{}, endpoint: nil, method: nil, opts: %{}
@type t :: %__MODULE__{body: map, endpoint: String.t(), method: atom, opts: map}
@doc """
Convert `Request` to `options` format passed to `Tesla.request/2`.
"""
@spec to_options(Request.t()) :: keyword
def to_options(%Request{body: b, endpoint: e, method: m, opts: o}) do
[method: m, url: e, body: b, opts: Map.to_list(o)]
end
@doc """
Add telemetry metadata to `Request`.
Calling without the second argument adds default metadata. Custom metadata
is added by passing a map with a key `telemetry_metadata`.
Example
```
Request.add_metadata(request, %{telemetry_metadata: %{ins_id: "ins_1"}})
```
"""
@spec add_metadata(Request.t()) :: Request.t()
@spec add_metadata(Request.t(), map) :: Request.t()
def add_metadata(%Request{endpoint: e, method: m, opts: o} = request, config \\ %{}) do
metadata =
Map.new()
|> Map.put(:method, m)
|> Map.put(:path, e)
|> Map.put(:u, :native)
|> Map.merge(config[:telemetry_metadata] || %{})
%{request | opts: Map.put(o, :metadata, metadata)}
end
end
@doc """
Creates a new Tesla client.
Optional `config` argument sets the following values at runtime:
- base url (aka root uri)
- client_id
- secret
- `Tesla.Adapter`
- `Tesla.Middleware`
- HTTP options for selected `Tesla.Adapter`
Example
```
config = %{
root_uri: "https://sandbox.plaid.com/",
client_id: "my-client-id",
secret: "shhhh",
adapter: Tesla.Adapter.Httpc,
middleware: [Tesla.Middleware.Logger],
http_options: [recv_timeout: 10_000]
}
client = Client.new(config)
```
"""
@spec new(map) :: Tesla.Client.t()
def new(config \\ %{}) do
middleware =
[
{Tesla.Middleware.BaseUrl, get_base_url(config)},
{Tesla.Middleware.Headers,
[
{"Content-Type", "application/json"},
{"user-agent", "Elixir-SDK"},
{"Plaid-Version", "2020-09-14"},
{"PLAID-CLIENT-ID", get_client_id(config)},
{"PLAID-SECRET", get_secret(config)}
]},
Tesla.Middleware.JSON,
{Tesla.Middleware.Telemetry, get_metadata(config)}
] ++ get_middleware(config)
adapter = {get_adapter(config), get_http_options(config)}
Tesla.client(middleware, adapter)
end
defp get_base_url(config) do
case config[:root_uri] || Application.get_env(:plaid, :root_uri) do
nil ->
raise Plaid.MissingRootUriError
root_uri ->
root_uri
end
end
defp get_client_id(config) do
case config[:client_id] || Application.get_env(:plaid, :client_id) do
nil ->
raise Plaid.MissingClientIdError
client_id ->
client_id
end
end
defp get_secret(config) do
case config[:secret] || Application.get_env(:plaid, :secret) do
nil ->
raise Plaid.MissingSecretError
secret ->
secret
end
end
defp get_middleware(config) do
case config[:middleware] || Application.get_env(:plaid, :middleware) || [] do
middleware when is_list(middleware) ->
middleware
m ->
[m]
end
end
defp get_metadata(config) do
default_metadata = %{
u: :native,
service: :plaid
}
metadata = Map.merge(default_metadata, config[:telemetry_metadata] || %{})
%{metadata: metadata}
end
defp get_adapter(config) do
config[:adapter] || Application.get_env(:plaid, :adapter) || Tesla.Adapter.Hackney
end
defp get_http_options(config) do
Keyword.merge(
Application.get_env(:plaid, :http_options, []),
config[:http_options] || []
)
end
end