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