lib/assent.ex

defmodule Assent do
  @moduledoc false

  defmodule CallbackError do
    defexception [:message, :error, :error_uri]
  end

  defmodule CallbackCSRFError do
    defexception [:message]

    @spec new(binary()) :: %__MODULE__{}
    def new(key) do
      %__MODULE__{message: "CSRF detected with param key #{inspect key}"}
    end
  end

  defmodule MissingParamError do
    defexception [:message, :params]

    @spec new(binary(), map()) :: %__MODULE__{}
    def new(key, params) do
      %__MODULE__{
        message: "Expected #{inspect key} to exist in params, but only found the following keys: #{inspect Map.keys(params)}",
        params: params
      }
    end
  end

  defmodule RequestError do
    defexception [:message, :error]

    alias Assent.HTTPAdapter.HTTPResponse

    @spec unexpected(HTTPResponse.t()) :: %__MODULE__{}
    def unexpected(response) do
      %__MODULE__{
        message:
          """
          An unexpected success response was received:

          #{inspect response.body}
          """,
        error: :unexpected_response
      }
    end

    @spec invalid(HTTPResponse.t()) :: %__MODULE__{}
    def invalid(response) do
      %__MODULE__{
        message:
          """
          Server responded with status: #{response.status}

          Headers:#{Enum.reduce(response.headers, "", fn {k, v}, acc -> acc <> "\n#{k}: #{v}" end)}

          Body:
          #{inspect response.body}
          """,
        error: :invalid_server_response
      }
    end

    @spec unreachable(atom(), binary(), term()) :: %__MODULE__{}
    def unreachable(adapter, url, error) do
      %__MODULE__{
        message:
          """
          Server was unreachable with #{inspect adapter}.

          Failed with the following error:
          #{inspect error}

          URL: #{url}
          """,
        error: :unreachable
      }
    end
  end

  use Bitwise

  @doc false
  @spec constant_time_compare(binary(), binary()) :: boolean()
  def constant_time_compare(left, right) when is_binary(left) and is_binary(right) do
    byte_size(left) == byte_size(right) and constant_time_compare(left, right, 0)
  end

  defp constant_time_compare(<<x, left::binary>>, <<y, right::binary>>, acc) do
    xorred = bxor(x, y)
    constant_time_compare(left, right, acc ||| xorred)
  end

  defp constant_time_compare(<<>>, <<>>, acc) do
    acc === 0
  end
end