lib/hikvision/operation.ex

defmodule Hikvision.Operation do
  @moduledoc """
  Holds data necessary for an operation
  """

  import Hikvision.Http.Utils

  alias Hikvision.{Auth, Http.Client}

  @type method :: :get | :put | :post | :delete | :head

  @type t :: %__MODULE__{
          http_method: method(),
          path: binary(),
          params: map(),
          headers: list(),
          body: iodata() | nil,
          parser: (term() -> term()),
          download_to: Path.t()
        }

  defstruct http_method: :get,
            path: nil,
            params: %{},
            headers: [],
            body: nil,
            parser: &Function.identity/1,
            download_to: nil

  @doc """
  Create a new operation
  """
  @spec new(binary(), Keyword.t()) :: t()
  def new(path, opts \\ []) do
    struct(%__MODULE__{path: path}, opts)
  end

  @doc """
  Performs an operation on a Hikvision device.
  """
  @spec perform(t(), Hikvision.Config.t()) :: Hikvision.success() | Hikvision.error()
  def perform(%__MODULE__{http_method: method} = op, %{http_options: options} = config) do
    url = build_url(op, config)

    full_headers =
      Auth.inject_auth_headers(method, url, op.headers, config.username, config.password)

    with {:error, %{status: 401, headers: headers}} <-
           do_request(method, url, op.body, full_headers, op.parser, op.download_to, options) do
      full_headers =
        Auth.inject_auth_headers(
          method,
          url,
          op.headers,
          config.username,
          config.password,
          headers
        )

      do_request(method, url, op.body, full_headers, op.parser, op.download_to, options)
    end
  end

  defp do_request(http_method, url, body, headers, parser, download_to, http_options) do
    case Client.request(http_method, url, body, headers, download_to, http_options) do
      :ok ->
        :ok

      {:ok, %{status: status}} = resp when status in 200..299 ->
        parser.(resp)

      {:ok, %{status: 401} = resp} ->
        {:error, resp}

      {:ok, %{status: status} = resp} ->
        if status == 404,
          do: {:error, :not_found},
          else: {:error, Hikvision.Parsers.parse_response_status(resp)}

      {:error, error} ->
        {:error, error}
    end
  end
end