defmodule Plaid do
@moduledoc """
An HTTP Client for Plaid.
[Plaid API Docs](https://plaid.com/docs/api)
"""
alias Plaid.Client.Request
defmodule MissingClientIdError do
defexception message: """
The `client_id` is required for calls to Plaid. Please either configure `client_id`
in your config.exs file or pass it into the function via the `config` argument.
config :plaid, client_id: "your_client_id"
"""
end
defmodule MissingSecretError do
defexception message: """
The `secret` is required for calls to Plaid. Please either configure `secret`
in your config.exs file or pass it into the function via the `config` argument.
config :plaid, secret: "your_secret"
"""
end
defmodule MissingRootUriError do
defexception message: """
The root_uri is required to specify the Plaid environment to which you are
making calls, i.e. sandbox, development or production. Please configure root_uri in
your config.exs file.
config :plaid, root_uri: "https://sandbox.plaid.com/" (test)
config :plaid, root_uri: "https://development.plaid.com/" (development)
config :plaid, root_uri: "https://production.plaid.com/" (production)
"""
end
@type mapper :: (any() -> any())
@doc """
Send an HTTP request to Plaid.
Takes a data structure `Plaid.Request.t` and Tesla client built at runtime
and returns either `{:ok, Tesla.Env.t}` or `{:error, any()}`.
"""
@callback send_request(Request.t(), Tesla.Client.t()) :: {:ok, Tesla.Env.t()} | {:error, any()}
@doc """
Handle an HTTP response from Plaid.
Takes response argument in the form of `{:ok, Tesla.Env.t}` or `{:error, any()}`
and a 1-arity mapping function argument which is applied to the body of the
Tesla.Env in the success case to unmarshal JSON into structured data.
Error cases are diverted into `{:error, Plaid.Error.t}` and `{:error, any()}`
for handling Plaid and HTTP failure responses.
"""
@callback handle_response({:ok, Tesla.Env.t()} | {:error, any()}, mapper) ::
{:ok, term} | {:error, Plaid.Error.t()} | {:error, any()}
# Behaviour implementation
@doc false
def send_request(request, client) do
request
|> Request.to_options()
|> then(&Tesla.request(client, &1))
end
@doc false
def handle_response({:ok, %Tesla.Env{status: status} = env}, mapper) when status in 200..299 do
{:ok, mapper.(env.body)}
end
def handle_response({:ok, %Tesla.Env{} = env}, _mapper) do
error = Poison.Decode.transform(env.body, %{as: %Plaid.Error{}})
{:error, %{error | http_code: env.status}}
end
def handle_response({:error, _reason} = error, _mapper) do
error
end
end