lib/fusion_auth.ex

defmodule FusionAuth do
  require Logger

  @moduledoc """
  The `FusionAuth` module provides functions for building a dynamic HTTP client as well as standardizing the responses returned from the FusionAuth API.

  ## Examples

      iex> FusionAuth.client("http://localhost:9011", "sQ9wwELaI0whHQqyQUxAJmZvVzZqUL-hpfmAmPgbIu8", "6b40f9d6-cfd8-4312-bff8-b082ad45e93c")
      %Tesla.Client{
        adapter: {Tesla.Adapter.Hackney, :call, [[recv_timeout: 30000]]},
        fun: nil,
        post: [],
        pre: [
          {Tesla.Middleware.BaseUrl, :call, ["http://localhost:9011"]},
          {Tesla.Middleware.JSON, :call, [[]]},
          {Tesla.Middleware.Headers, :call,
          [
            [
              {"Authorization", "sQ9wwELaI0whHQqyQUxAJmZvVzZqUL-hpfmAmPgbIu8"},
              {"X-FusionAuth-TenantId", "6b40f9d6-cfd8-4312-bff8-b082ad45e93c"}
            ]
          ]}
        ]
      }

  """
  @type client() :: Tesla.Client.t()
  @type result() :: {:ok, map() | String.t(), Tesla.Env.t()} | {:error, map(), any}

  @doc """
  Builds a dynamic client for executing HTTP requests to the FusionAuth API using Application variables
  specified in `config/{env}.exs`.

  ```
    config :fusion_auth,
      api_key: "FUSION_AUTH_API_KEY",
      api_url: "FUSION_AUTH_URL",
      tenant_id: "FUSION_AUTH_TENANT_ID"
  ```
  """
  @spec client() :: client()
  def client(),
    do:
      client(
        Application.get_env(:fusion_auth, :api_url),
        Application.get_env(:fusion_auth, :api_key),
        Application.get_env(:fusion_auth, :tenant_id)
      )

  @doc """
  Builds a dynamic client for executing HTTP requests to the FusionAuth API based on runtime arguments.
  """
  @spec client(String.t(), String.t(), String.t()) :: client()
  def client(base_url, api_key, tenant_id) do
    middleware = [
      {Tesla.Middleware.BaseUrl, base_url},
      Tesla.Middleware.JSON,
      Tesla.Middleware.Telemetry,
      {Tesla.Middleware.Headers,
       [
         {"Authorization", api_key},
         {"X-FusionAuth-TenantId", tenant_id}
       ]}
    ]

    Tesla.client(middleware, adapter())
  end

  @doc """
  Standardizes the response to be returned from the FusionAuth API request.
  """
  def result(_)

  @spec result({:ok, Tesla.Env.t()}) :: result()
  def result({:ok, %{status: status, body: body} = env}) when status < 300 do
    {:ok, body, env}
  end

  @spec result({:ok, Tesla.Env.t()}) :: result()
  def result({:ok, %{status: status, body: body} = env}) when status >= 300 do
    Logger.warn("""
      FusionAuth request resulted in a status code >= 300.
      Env: #{inspect(env)}
    """)

    {:error, body, env}
  end

  @spec result({:error, any}) :: result()
  def result({:error, any}) do
    Logger.error("""
      FusionAuth request resulted in an error.
      Error: #{inspect(any)}
    """)

    {:error, %{}, any}
  end

  @doc false
  def adapter do
    case Application.get_env(:fusion_auth, :tesla) do
      nil ->
        {Tesla.Adapter.Hackney, [pool: false, recv_timeout: 30_000]}

      tesla ->
        tesla[:adapter]
    end
  end
end