lib/validators/protocols/empty.ex

defprotocol Dsv.Empty do
  @moduledoc """
  A protocol to check if a provided value is empty.
  This protocol is used by the `Dsv.NotEmpty` validator.

  ## Default Implementations

  The protocol provides default implementations for the following data types:
  - `List`: Checks if the list has a length of 0.
  - `Map`: Checks if the map has no key-value pairs.
  - `Tuple`: Checks if the tuple has no elements.
  - `String`: Checks if the string has a length of 0.
  - `Integer`: Always return :false, integer can not be empty.
  - `Float`: Always return :false, float can not be empty.
  - `Date`: Always return :false, date can not be empty.
  - `DateTime`: Always return :false, date time can not be empty.
  - `NaiveDateTime`: Always return :false, naive date time can not be empty.
  - `Time`: Always return :false, time can not be empty.
  - `Atom`: Checks if the atom is `:nil` or `:false`.

  You can implement custom emptiness checks for other data types by providing specific protocol implementations.

  ## Example

      defmodule MyStruct do
        defstruct data: [:a, :b, :c]
      end

      defimpl Empty, for: MyStruct do
        def empty?(%MyStruct{data: data}), do: length(data) == 0
      end

  """

  @doc """
  Return `:true` if the value is empty. Otherwise, return `:false`.


  For `List`, it will return `:true` only for the `[]` list. In other cases, it will return `:false`.

  For `Map`, it will return `:true` only for the `%{}` map. In other cases, it will return `:false`.

  For `Tuple`, it will return `:true` only for `{}` tuple. In other cases, it will return `:false`.

  For `String`, it will return `:true` only for `\"\"` string. In other cases, it will return `:false`.

  For `Integer`, `Float`, `Date`, `DateTime`, `NaiveDateTime`, and `Time`, it will always return `:false` as if value of this type exists, it cannot be empty.

  For `Atom`, it will return `:true` for values `:false` and `nil`. In other cases, it will return `:false`.

  ## Example
      iex> Dsv.Empty.empty?(:false)
      :true

      iex> Dsv.Empty.empty?("")
      :true

      iex> Dsv.Empty.empty?("not empty")
      :false

      iex> Dsv.Empty.empty?([])
      :true

      iex> Dsv.Empty.empty?(:true)
      :false

      iex> Dsv.Empty.empty?(1.1)
      :false

      iex> Dsv.Empty.empty?(~D[2000-01-01])
      :false

      iex> Dsv.Empty.empty?(~U[2000-01-01 11:11:11Z])
      :false

      iex> Dsv.Empty.empty?(~N[2000-01-01 11:11:11])
      :false

      iex> Dsv.Empty.empty?(~T[11:11:11])
      :false

  """
  @spec empty?(any()) :: boolean()
  def empty?(data)
end

defimpl Dsv.Empty, for: List do
  def empty?([]), do: true
  def empty?([_]), do: false
end

defimpl Dsv.Empty, for: Map do
  def empty?(data), do: map_size(data) == 0
end

defimpl Dsv.Empty, for: Tuple do
  def empty?({}), do: true
  def empty?(_), do: false
end

defimpl Dsv.Empty, for: BitString do
  def empty?(""), do: true
  def empty?(_), do: false
end

defimpl Dsv.Empty, for: [Integer, Float] do
  def empty?(_), do: false
end

defimpl Dsv.Empty, for: [Date, DateTime, NaiveDateTime, Time] do
  def empty?(_), do: false
end

defimpl Dsv.Empty, for: Atom do
  def empty?(false), do: true
  def empty?(nil), do: true
  def empty?(_), do: false
end