lib/validators/any.ex

defmodule Dsv.Any do
  use Dsv.Validator

  @moduledoc """

  Ensure that at least one element passes the check for all validators.
  In the case of a string, this validator will iterate through all graphemes and check if at least one passes the validation for all validator.
  In the case of a list, this validator will iterate through all elements and check if at least one passes the validation for all validators.


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


  """

  message("There is no element matching all validators.")

  @doc """

  Ensure that at least one element passes the check for all validators.

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

  Returns `:true` if all validators pass for at least one element; otherwise, returns `:false`.

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

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

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

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

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

      iex> Dsv.Any.valid?([1, 2, "test", 3], custom: &is_number/1, number: [gte: 3, lte: 5])
      :true

      iex> Dsv.Any.valid?([1, {:a, 1}, "test", 3], custom: &is_tuple/1, custom: &({:a, 1} == &1))
      :true

      iex> Dsv.Any.valid?([1, {:a, 2}, "test", 3], custom: &is_tuple/1, custom: &({:a, 1} == &1))
      :false

      iex> Dsv.Any.valid?([1, "{:a, 1}", "test", 3], custom: &is_tuple/1, custom: &({:a, 1} == &1))
      :false

      iex> Dsv.Any.valid?({1, 2, 3}, equal: 3)
      :true


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

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

  @doc """

  Ensure that at least one element passes the check for all validators.

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

  Returns `:ok` if all validators pass for at least one element; otherwise, returns error tuple with the error message.

  ## Example
      iex> Dsv.Any.validate("abcd", format: ~r/^[a-z]$/, equal: "w")
      {:error, "There is no element matching all validators."}

      iex> Dsv.Any.validate("abcd", format: ~r/^[a-z]$/, equal: "a")
      :ok

      iex> Dsv.Any.validate("abcd", format: ~r/^[k-z]$/, equal: "a")
      {:error, "There is no element matching all validators."}

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

      iex> Dsv.Any.validate("abcd", format: ~r/^[g-j]$/, custom: &is_bitstring/1)
      {:error, "There is no element matching all validators."}

      iex> Dsv.Any.validate([1, 2, "test", 3], custom: &is_number/1, number: [gte: 3, lte: 5])
      :ok

      iex> Dsv.Any.validate([1, {:a, 1}, "test", 3], custom: &is_tuple/1, custom: &({:a, 1} == &1))
      :ok

      iex> Dsv.Any.validate([1, {:a, 2}, "test", 3], custom: &is_tuple/1, custom: &({:a, 1} == &1))
      {:error, "There is no element matching all validators."}

      iex> Dsv.Any.validate([1, "{:a, 1}", "test", 3], custom: &is_tuple/1, custom: &({:a, 1} == &1))
      {:error, "There is no element matching all validators."}

  To return custom message, use :message key in the last argument.

  ## Example
      iex> Dsv.Any.validate("abcd", format: ~r/^[a-z]$/, equal: "w", message: "At least one letter must be 'w'.")
      {:error, "At least one letter must be 'w'."}

      iex> Dsv.Any.validate("abcd", format: ~r/^[a-z]$/, equal: "a", message: "At least one letter must be 'a'.")
      :ok

      iex> Dsv.Any.validate("abcd", format: ~r/^[k-z]$/, equal: "a", message: "At least one letter must be 'a' and any letter between k and z at the same moment. Sorry.")
      {:error, "At least one letter must be 'a' and any letter between k and z at the same moment. Sorry."}

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

      iex> Dsv.Any.validate("abcd", format: ~r/^[g-j]$/, custom: &is_bitstring/1, message: "At least one letter must be 'g', 'h', 'i' or 'j'.")
      {:error, "At least one letter must be 'g', 'h', 'i' or 'j'."}

      iex> Dsv.Any.validate([1, 2, "test", 3], custom: &is_number/1, number: [gte: 3, lte: 5], message: "At least one number must be between 3 and 5.")
      :ok

      iex> Dsv.Any.validate([1, {:a, 1}, "test", 3], custom: &is_tuple/1, custom: &({:a, 1} == &1), message: "At least on element must be a tuple `{:a, 1}`.")
      :ok

      iex> Dsv.Any.validate([1, {:a, 2}, "test", 3], custom: &is_tuple/1, custom: &({:a, 1} == &1), message: "At least on element must be a tuple `{:a, 1}`.")
      {:error, "At least on element must be a tuple `{:a, 1}`."}

      iex> Dsv.Any.validate([1, "{:a, 1}", "test", 3], custom: &is_tuple/1, custom: &({:a, 1} == &1), message: "At least on element from the list `<%= inspect data %>` must be a tuple `{:a, 1}`.")
      {:error, ~s(At least on element from the list `[1, "{:a, 1}", "test", 3]` must be a tuple `{:a, 1}`.)}


  """
  def validate(data, options), do: super(data, options)
end