lib/salemove/http_client/decoder.ex

defmodule Salemove.HttpClient.Decoder do
  @moduledoc """
  A module, responsible for creating a `EngagementRouter.HttpClient.Response`
  from `Tesla` response, or, if request wasn't successful, for creating one
  of exception structures.
  """

  alias Salemove.HttpClient.Response
  alias Salemove.HttpClient.JSONError
  alias Salemove.HttpClient.ConnectionError
  alias Salemove.HttpClient.UnexpectedRedirectError
  alias Salemove.HttpClient.ClientError
  alias Salemove.HttpClient.ServerError
  alias Salemove.HttpClient.UnavailableError
  alias Salemove.HttpClient.UnsupportedProtocolError

  @type tesla_result :: {:ok, Tesla.Env.t()} | {:error, reason :: any}
  @type on_decode :: {:ok, Response.t()} | {:error, reason}
  @type reason ::
          JSONError.t()
          | ConnectionError.t()
          | UnexpectedRedirectError.t()
          | ClientError.t()
          | ServerError.t()
          | UnavailableError.t()
          | UnsupportedProtocolError.t()

  @doc """
  Accepts result of request to `Tesla` client and constructs
  `#{Response}` structure from it.
  """
  @spec decode(tesla_result) :: on_decode
  def decode(tesla_result)

  def decode({:ok, %Tesla.Env{} = env}) do
    decode_env(env)
  end

  def decode({:error, {Tesla.Middleware.JSON, :decode, %Jason.DecodeError{} = error}}) do
    decode_error(error)
  end

  def decode({:error, reason}) do
    decode_error(reason)
  end

  ##

  defp decode_env(%{status: status} = env) do
    cond do
      status in 200..299 -> {:ok, Response.new(env)}
      status in 300..399 -> {:error, UnexpectedRedirectError.new(env)}
      status in 400..499 -> {:error, ClientError.new(env)}
      status in 500..501 -> {:error, ServerError.new(env)}
      status > 501 -> {:error, UnavailableError.new(env)}
      true -> {:error, UnsupportedProtocolError.new(env)}
    end
  end

  defp decode_error(%Jason.DecodeError{} = error) do
    {:error, %JSONError{reason: Exception.message(error)}}
  end

  defp decode_error(reason) do
    {:error, %ConnectionError{reason: reason}}
  end
end