Skip to main content

lib/drone/error.ex

defmodule Drone.Error do
  @moduledoc """
  Error types and helpers for ex_drone.

  All errors in ex_drone follow explicit tuple conventions:

    - `{:error, reason}` for simple errors
    - `{:error, :safety, reason}` for safety rejections
    - `{:error, :invalid_command, details}` for command validation errors
  """

  @type safety_reason ::
          :command_not_allowed
          | :not_in_sdk_mode
          | :not_flying
          | :already_flying
          | :emergency_active
          | :max_altitude
          | :max_distance
          | :low_battery
          | :geofence_violation
          | :dangerous_without_prop_guards
          | :invalid_distance
          | :invalid_degrees
          | :invalid_speed
          | :invalid_seconds

  @type adapter_reason ::
          :timeout
          | :connection_error
          | :command_error
          | :not_in_sdk_mode
          | :not_flying
          | :already_flying
          | :emergency_active
          | :simulated_failure

  @type command_reason ::
          :invalid_direction
          | :invalid_distance
          | :invalid_rotation
          | :invalid_degrees
          | :invalid_speed
          | :invalid_flip_direction
          | :invalid_query_type

  @type reason :: safety_reason() | adapter_reason() | command_reason() | term()

  @doc """
  Creates a safety error tuple.
  """
  @spec safety(safety_reason()) :: {:error, :safety, safety_reason()}
  def safety(reason), do: {:error, :safety, reason}

  @doc """
  Creates an adapter error tuple.
  """
  @spec adapter(adapter_reason()) :: {:error, adapter_reason()}
  def adapter(reason), do: {:error, reason}

  @doc """
  Creates an invalid command error tuple.
  """
  @spec invalid_command(command_reason()) :: {:error, :invalid_command, command_reason()}
  def invalid_command(reason), do: {:error, :invalid_command, reason}

  @doc """
  Checks if an error is a safety error.
  """
  @spec safety_error?(term()) :: boolean()
  def safety_error?({:error, :safety, _}), do: true
  def safety_error?(_), do: false

  @doc """
  Checks if an error is an adapter error.
  """
  @spec adapter_error?(term()) :: boolean()
  def adapter_error?({:error, reason}) when is_atom(reason), do: true
  def adapter_error?(_), do: false

  @doc """
  Checks if an error is an invalid command error.
  """
  @spec invalid_command_error?(term()) :: boolean()
  def invalid_command_error?({:error, :invalid_command, _}), do: true
  def invalid_command_error?(_), do: false

  @doc """
  Extracts the reason from any error tuple.
  """
  @spec reason({:error, atom()} | {:error, :safety, atom()} | {:error, :invalid_command, atom()}) ::
          atom()
  def reason({:error, :safety, r}), do: r
  def reason({:error, :invalid_command, r}), do: r
  def reason({:error, r}), do: r
end