lib/validators/length.ex

defmodule Dsv.Length do
  use Dsv.Validator

  @moduledoc """
  Dsv.Length module provides a functions to check if the length of an input value meets specified criteria.
  Value can be of the type: `String`, `List`, `Map`, `Tuple`, `Integer`, `Float`. To use values of different type this type need to implement `DataLength` protocol.

  """

  message({:min, "Value <%= inspect data %> is to short. Minimum lenght is <%= options[:min] %>"})
  message({:max, "Value <%= inspect data %> is to long. Maximum length is <%= options[:max] %>"})

  message(
    {[:min, :max],
     "Value <%= inspect data %> has wrong length. Minimum lenght is <%= options[:min] %>, maximum length is <%= options[:max] %>"}
  )

  message(
    {:range,
     "Value <%= inspect data %> has wrong length. Minimum lenght is <%= elem(options[:range], 0) %>, maximum length is <%= elem(options[:range], 1) %>"}
  )

  @doc """
  The `valid?/2` function checks if the length of a given input value meets the specified criteria.

  ## Parameters

    * `value` - The input value to be checked for length.
    * `rules` - A list of validation rules. Each rule is represented as a keyword list containing:
      - `:min` - The minimum allowed length.
      - `:max` - The maximum allowed length.
      - `:range` - A range of acceptable lengths.

  ## Returns

  A boolean value:

  - `true` if the length of `value` meets all of the specified criteria.
  - `false` if the length of `value` does not meet any of the specified criteria.

  ## Examples

  `:min` example:
      iex> Dsv.Length.valid?("abcd", min: 3)
      :true

      iex> Dsv.Length.valid?("abcd", min: 4)
      :true

      iex> Dsv.Length.valid?("abcd", min: 5)
      :false

  `:max` example:
      iex> Dsv.Length.valid?("ab", max: 3)
      :true

      iex> Dsv.Length.valid?("abcd", max: 4)
      :true

      iex> Dsv.Length.valid?("abcdef", max: 5)
      :false

  `:min & :max` example:
      iex> Dsv.Length.valid?("abcd", min: 3, max: 5)
      :true

      iex> Dsv.Length.valid?("abcd", min: 4, max: 5)
      :true

      iex> Dsv.Length.valid?("abcd", min: 2, max: 4)
      :true

      iex> Dsv.Length.valid?("a", min: 2, max: 4)
      :false

      iex> Dsv.Length.valid?("abcde", min: 2, max: 4)
      :false

  `:range` example:
      iex> Dsv.Length.valid?("abcd", range: {3, 5})
      :true

      iex> Dsv.Length.valid?("abcd", range: {4, 5})
      :true

      iex> Dsv.Length.valid?("abcd", range: {2, 4})
      :true

      iex> Dsv.Length.valid?("a", range: {2, 4})
      :false

      iex> Dsv.Length.valid?("abcde", range: {2, 4})
      :false

      iex> Dsv.Length.valid?("abcde", range: {4, 2})
      ** (RuntimeError) Min length (4) can't be greater that max length (2).
  """
  def valid?(data, [{:min, min_length}, {:max, max_length} | _]),
    do: valid?(data, range: {min_length, max_length})

  def valid?(data, [{:max, max_length} | _]), do: DataLength.len(data) <= max_length
  def valid?(data, [{:min, min_length} | _]), do: DataLength.len(data) >= min_length

  def valid?(data, [{:range, {min, max}} | _]) do
    validate_options(min, max)
    valid?(data, min: min) and valid?(data, max: max)
  end

  @doc """
  The `valid?/2` function checks if the length of a given input value meets the specified criteria.

  ## Parameters

    * `value` - The input value to be checked for length.
    * `rules` - A list of validation rules. Each rule is represented as a keyword list containing:
      - `:min` - The minimum allowed length.
      - `:max` - The maximum allowed length.
      - `:range` - A range of acceptable lengths.
    * `message` - An optional options to provide custom message that will be returned on error.

  ## Returns

  - `:ok` if the length of `value` meets all of the specified criteria.
  - `{:error, message}` if the length of `value` does not meet any of the specified criteria.

  ## Examples

  `:min` example:
      iex> Dsv.Length.validate("abcd", min: 3)
      :ok

      iex> Dsv.Length.validate("abcd", min: 4)
      :ok

      iex> Dsv.Length.validate("abcd", min: 5)
      {:error, ~s(Value "abcd" is to short. Minimum lenght is 5)}

      iex> Dsv.Length.validate("abcd", min: 5, message: "This value should not be shorter than 5.")
      {:error, "This value should not be shorter than 5."}

  `:max` example:
      iex> Dsv.Length.validate("ab", max: 3)
      :ok

      iex> Dsv.Length.validate("abcd", max: 4)
      :ok

      iex> Dsv.Length.validate("abcdef", max: 5)
      {:error, ~s(Value "abcdef" is to long. Maximum length is 5)}

      iex> Dsv.Length.validate("abcdef", max: 5, message: "This value should not be longer that 5.")
      {:error, "This value should not be longer that 5."}

  `:min & :max` example:
      iex> Dsv.Length.validate("abcd", min: 3, max: 5)
      :ok

      iex> Dsv.Length.validate("abcd", min: 4, max: 5)
      :ok

      iex> Dsv.Length.validate("abcd", min: 2, max: 4)
      :ok

      iex> Dsv.Length.validate("a", min: 2, max: 4)
      {:error, ~s(Value "a" has wrong length. Minimum lenght is 2, maximum length is 4)}

      iex> Dsv.Length.validate("abcde", min: 2, max: 4)
      {:error, ~s(Value "abcde" has wrong length. Minimum lenght is 2, maximum length is 4)}

      iex> Dsv.Length.validate("a", min: 2, max: 4, message: "This value must have length between 2 and 4.")
      {:error, "This value must have length between 2 and 4."}

      iex> Dsv.Length.validate("abcde", min: 2, max: 4, message: "This value must have length between 2 and 4.")
      {:error, "This value must have length between 2 and 4."}

  `:range` example:
      iex> Dsv.Length.validate("abcd", range: {3, 5})
      :ok

      iex> Dsv.Length.validate("abcd", range: {4, 5})
      :ok

      iex> Dsv.Length.validate("abcd", range: {2, 4})
      :ok

      iex> Dsv.Length.validate("a", range: {2, 4})
      {:error, ~s(Value "a" has wrong length. Minimum lenght is 2, maximum length is 4)}

      iex> Dsv.Length.validate("abcde", range: {2, 4})
      {:error, ~s(Value "abcde" has wrong length. Minimum lenght is 2, maximum length is 4)}

      iex> Dsv.Length.validate("a", range: {2, 4}, message: "This value must have length between 2 and 4.")
      {:error, "This value must have length between 2 and 4."}

      iex> Dsv.Length.validate("abcde", range: {2, 4}, message: "This value must have length between 2 and 4.")
      {:error, "This value must have length between 2 and 4."}

      iex> Dsv.Length.validate("abcde", range: {4, 2})
      ** (RuntimeError) Min length (4) can't be greater that max length (2).

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

  defp validate_options(min_length, max_length),
    do:
      if(min_length > max_length,
        do: raise("Min length (#{min_length}) can't be greater that max length (#{max_length}).")
      )
end