lib/chalk/exchange_credentials.ex

defmodule Chalk.Client.ExchangeCredentials do
  alias Chalk.Client
  alias Chalk.Client.Request

  defmodule Req do
    @derive Jason.Encoder
    defstruct client_id: nil, client_secret: nil, grant_type: nil

    @type t :: %__MODULE__{
            client_id: String.t(),
            client_secret: String.t(),
            grant_type: String.t()
          }
  end

  defmodule Token do
    @derive Jason.Encoder
    # @enforce_keys [:access_token, :expires_in, :expires_at]
    defstruct access_token: nil,
              primary_environment: nil,
              token_type: nil,
              expires_in: nil,
              expires_at: nil,
              api_server: nil,
              engines: [{nil, nil}]

    @type t :: %__MODULE__{
            access_token: String.t(),
            token_type: String.t(),
            primary_environment: String.t(),
            expires_in: integer(),
            expires_at: integer(),
            api_server: String.t(),
            engines: [{String.t(), String.t()}]
          }

    def valid?(%__MODULE__{} = token) do
      {:ok, exp, _} = DateTime.from_iso8601(token.expires_at)
      exp >= DateTime.utc_now() |> DateTime.add(5 * 60, :second)
    end
  end

  def exchange_credentials(params, config \\ %{}) do
    config = config |> Map.put(:unauthenticated, true)
    c = config[:client] || Chalk

    Request
    |> struct(method: :post, endpoint: "v1/oauth/token", body: params)
    |> Request.add_metadata(config)
    |> c.send_request(Client.new(config))
    |> c.handle_response(&map_exchange_credentials(&1))
  end

  defp map_exchange_credentials(body) do
    Poison.Decode.decode(body, %{as: %Token{}})
  end
end