lib/validators/all.ex

defmodule Dsv.All do
  use Dsv.Validator

  @moduledoc """

  `Dsv.All` validator provides functions to validate that all elements in the given `List` or `String` meets all validation criteria.


  > #### Validate other types of data {: .tip}
  >
  > To validate arguments of other data types `FindAll` protocol must be implemented for this type.

  """

  message("Not all elements match all validators.")
  e_message("Not all elements match all validators.")

  @doc """
  Ensure that all validators pass the check for the given data.

  In the case of a string, this validator will iterate through all graphemes and check if each one passes the validation for all validators.
  In the case of a list, this validator will iterate through all elements in the list and check if each one passes the validation for all validators.

  Returns `:true` if all validators pass for all elements; otherwise, returns `:false`.

  ## Example
      iex> Dsv.All.valid?("abcd", format: ~r/^[a-z]$/, equal: "w")
      :false

      iex> Dsv.All.valid?("abcd", format: ~r/^[a-z]$/, equal: "a")
      :false

      iex> Dsv.All.valid?("abcd", format: ~r/^[a-d]$/, custom: &is_bitstring/1)
      :true

      iex> Dsv.All.valid?("abcd", format: ~r/^[a-c]$/, custom: &is_bitstring/1)
      :false

      iex> Dsv.All.valid?([1, 2, 3, "hello"], custom: &is_number/1, number: [gt: 2])
      :false

      iex> Dsv.All.valid?([1, 2, 3, "hello"], number: [gt: 0])
      :false

      iex> Dsv.All.valid?([1, 2, 3], custom: &is_number/1, number: [gte: 0, lte: 10])
      :true
  """
  def valid?(data, options),
    do:
      Iterable.iterate(data, :cont, fn elem ->
        if Dsv.valid?(elem, options), do: :cont, else: :halt
      end)
      |> (&(elem(&1, 1) == :empty)).()

  def valid?(data, options, binded_values),
    do:
      Iterable.iterate(data, :cont, fn elem ->
        if Dsv.valid?(elem, options, binded_values), do: :cont, else: :halt
      end)
      |> (&(elem(&1, 1) == :empty)).()

  @doc """
  Ensure that all validators pass the check for the given data.

  In the case of a string, this validator will iterate through all graphemes and check if each one passes the validation for all validators.
  In the case of a list, this validator will iterate through all elements in the list and check if each one passes the validation for all validators.

  Returns `:ok` if all validators pass for all elements; otherwise, returns error tuple with the error message or custome message if provided.

  ## Options
    * any validator name like
      * `:format`
      * `:custom`
      * `:number`
      * `:length`
      * `:equal`
      * `:data`
    * `:message` - (EEx string) message used in error tuple in case of validation failure.

  ## Example
      iex> Dsv.All.validate("abcd", format: ~r/^[a-z]$/, equal: "w")
      {:error, "Not all elements match all validators."}

      iex> Dsv.All.validate("abcd", format: ~r/^[a-z]$/, equal: "a")
      {:error, "Not all elements match all validators."}

      iex> Dsv.All.validate("abcd", format: ~r/^[a-d]$/, custom: &is_bitstring/1)
      :ok

      iex> Dsv.All.validate("abcd", format: ~r/^[a-c]$/, custom: &is_bitstring/1)
      {:error, "Not all elements match all validators."}

      iex> Dsv.All.validate([1, 2, 3, "hello"], custom: &is_number/1, number: [gt: 2])
      {:error, "Not all elements match all validators."}

      iex> Dsv.All.validate([1, 2, 3, "hello"], number: [gt: 0])
      {:error, "Not all elements match all validators."}

      iex> Dsv.All.validate([1, 2, 3], custom: &is_number/1, number: [gte: 0, lte: 10])
      :ok

      iex> Dsv.All.validate([1, 2, 3, "hello"], number: [gt: 0], format: ~r/^[a-z]$/)
      {:error, "Not all elements match all validators."}

  ## Example with custom message
      iex> Dsv.All.validate("abcd", format: ~r/^[a-z]$/, equal: "w", message: "All elements must be small letter 'w'.")
      {:error, "All elements must be small letter 'w'."}

      iex> Dsv.All.validate("abcd", format: ~r/^[a-z]$/, equal: "a", message: "All elements must be small letter 'a'.")
      {:error, "All elements must be small letter 'a'."}

      iex> Dsv.All.validate("abcd", format: ~r/^[a-d]$/, custom: &is_bitstring/1, message: "All elements must be one of the letter: 'a', 'b', 'c', 'd'.")
      :ok

      iex> Dsv.All.validate("abcd", format: ~r/^[a-c]$/, custom: &is_bitstring/1, message: "All elements must be one of the letter: 'a', 'b', 'c'.")
      {:error, "All elements must be one of the letter: 'a', 'b', 'c'."}

      iex> Dsv.All.validate([1, 2, 3, "hello"], custom: &is_number/1, number: [gt: 2], message: "All values must be a number greater that 2.")
      {:error, "All values must be a number greater that 2."}

      iex> Dsv.All.validate([1, 2, 3, "hello"], number: [gt: 0], message: "All values must be a number greater that 0.")
      {:error, "All values must be a number greater that 0."}

      iex> Dsv.All.validate([1, 2, 3], custom: &is_number/1, number: [gte: 0, lte: 10], message: "All values must be a number between 0 and 10.")
      :ok
  """
  def validate(data, options), do: super(data, options)
end