lib/framestamp_mixed_rate_arithmatic_error.ex

defmodule Vtc.Framestamp.MixedRateArithmaticError do
  @moduledoc """
  Exception returned when mixed-rate arithmatic was attempted without specifying which
  side of the operation's rate should be inherited.

  ## Struct Fields

  - `left_rate`: The rate that was found on the left side of the operation.

  - `right_rate`: The rate that was found on the right side of the operation.
  """

  alias Vtc.Framerate
  alias Vtc.Framestamp
  alias Vtc.Source.Frames

  @enforce_keys [:func_name, :left_rate, :right_rate]
  defexception @enforce_keys

  @typedoc """
  Type of `Framestamp.MixedRateError`.
  """
  @type t() :: %__MODULE__{
          func_name: :add | :sub,
          left_rate: Framerate.t(),
          right_rate: Framerate.t()
        }

  @doc """
  Returns a message for the error reason.
  """
  @spec message(t()) :: String.t()
  def message(error) do
    "attempted `Framestamp.#{error.func_name}(a, b)` where `a.rate` does not match" <>
      " `b.rate`. try `:inherit_rate` option to `:left` or `:right`." <>
      " alternatively, do your calculation in seconds, then cast back to `Framestamp`" <>
      " with the appropriate rate"
  end

  # Gets a the correct rate for a mixed rate operation.
  @doc false
  @spec get_rate(
          Framestamp.t() | Frames.t(),
          Framestamp.t() | Frames.t(),
          Framestamp.inherit_opt(),
          func_name :: atom()
        ) :: {:ok, Framerate.t()} | {:error, t()}
  def get_rate(a, b, opt, func_name) do
    with :ok <- get_rate_validate(a, b, opt, func_name) do
      case {a, b, opt} do
        {%Framestamp{rate: rate}, _, :left} -> {:ok, rate}
        {_, %Framestamp{rate: rate}, _} -> {:ok, rate}
        {%Framestamp{rate: rate}, _, _} -> {:ok, rate}
      end
    end
  end

  @spec get_rate_validate(
          Framestamp.t() | Frames.t(),
          Framestamp.t() | Frames.t(),
          Framestamp.inherit_opt(),
          func_name :: atom()
        ) ::
          :ok | {:error, t()}
  defp get_rate_validate(%Framestamp{rate: rate}, %Framestamp{rate: rate}, _, _), do: :ok
  defp get_rate_validate(_, _, :left, _), do: :ok
  defp get_rate_validate(_, _, :right, _), do: :ok
  defp get_rate_validate(_, b, _, _) when not is_struct(b, Framestamp), do: :ok
  defp get_rate_validate(a, _, _, _) when not is_struct(a, Framestamp), do: :ok

  defp get_rate_validate(a, b, _, func_name),
    do: {:error, %__MODULE__{func_name: func_name, left_rate: a.rate, right_rate: b.rate}}
end