defmodule Mercury.Client do @moduledoc """ Holds configuration for talking to the Mercury Banking API and is passed as the first argument to every function in `Mercury.Accounts`, `Mercury.Transactions`, and the other resource modules. Build one with `new/2`: client = Mercury.Client.new("secret-token:mercury_production_...") `Mercury.Client` is an immutable struct — it is cheap to build and safe to share across processes. """ alias Mercury.Retry @enforce_keys [:api_key, :base_url, :timeout, :retry, :req] defstruct [ :api_key, :base_url, :timeout, :retry, :req, :on_request, :on_response ] @type request_hook :: (method :: atom(), url :: String.t(), request_id :: String.t() -> any()) @type response_hook :: (method :: atom(), url :: String.t(), request_id :: String.t(), status :: pos_integer(), duration_ms :: non_neg_integer() -> any()) @type t :: %__MODULE__{ api_key: String.t(), base_url: String.t(), timeout: pos_integer(), retry: Retry.t(), req: Req.Request.t(), on_request: request_hook() | nil, on_response: response_hook() | nil } @default_base_url "https://api.mercury.com/api/v1" @default_timeout 30_000 @doc """ Builds a new `Mercury.Client`. `api_key` must include the `"secret-token:"` prefix, e.g. `"secret-token:mercury_production_..."`. ## Options * `:base_url` — override the API base URL. Default: `"#{@default_base_url}"`. * `:timeout` — per-request timeout in milliseconds. Default: `#{@default_timeout}`. * `:retry` — a `Mercury.Retry` struct. Default: `Mercury.Retry.default/0`. * `:on_request` — request hook, see `t:request_hook/0`. * `:on_response` — response hook, see `t:response_hook/0`. * `:req_options` — extra options merged into the underlying `Req.new/1` call (e.g. `:plug` for tests, `:connect_options` for proxies). ## Examples Mercury.Client.new("secret-token:mercury_production_...") Mercury.Client.new("secret-token:mercury_sandbox_...", base_url: "https://api-sandbox.mercury.com/api/v1", timeout: 10_000 ) """ @spec new(api_key :: String.t(), opts :: keyword()) :: t() def new(api_key, opts \\ []) def new(api_key, _opts) when not is_binary(api_key) or api_key == "" do raise ArgumentError, "Mercury.Client.new/2 requires a non-empty api_key string " <> "(expected the \"secret-token:...\" prefix)" end def new(api_key, opts) when is_binary(api_key) and is_list(opts) do base_url = Keyword.get(opts, :base_url, @default_base_url) timeout = Keyword.get(opts, :timeout, @default_timeout) retry = Keyword.get(opts, :retry, Retry.default()) on_request = Keyword.get(opts, :on_request) on_response = Keyword.get(opts, :on_response) req_options = Keyword.get(opts, :req_options, []) req = req_options |> Keyword.merge( base_url: base_url, receive_timeout: timeout, retry: false, headers: [ {"authorization", "Bearer " <> api_key}, {"accept", "application/json"}, {"x-mercury-sdk", Mercury.sdk_version()} ] ) |> Req.new() %__MODULE__{ api_key: api_key, base_url: base_url, timeout: timeout, retry: retry, req: req, on_request: on_request, on_response: on_response } end end