lib/confispex/type/integer.ex

defmodule Confispex.Type.Integer do
  @moduledoc """
  An integer type.

  Casts input string to `Integer`.

  ### Options

  * `:scope` - can be `:positive`, requires parsed integer value to be > 0

  ## Examples

      iex> Confispex.Type.cast("-42", Confispex.Type.Integer)
      {:ok, -42}

      iex> Confispex.Type.cast("-42", {Confispex.Type.Integer, scope: :positive})
      {:error, {"-42", {Confispex.Type.Integer, [scope: :positive]}, [validation: "expected a positive integer"]}}

      iex> Confispex.Type.cast("42 monkeys", Confispex.Type.Integer)
      {:error,
       {"42 monkeys", Confispex.Type.Integer,
        [parsing: ["unexpected substring ", {:highlight, ~s|" monkeys"|}]]}}
  """
  @behaviour Confispex.Type

  @impl true

  def cast(value, opts) when is_integer(value) do
    cast(to_string(value), opts)
  end

  def cast(value, opts) when is_binary(value) do
    case Integer.parse(value) do
      {integer, ""} ->
        if Keyword.get(opts, :scope) == :positive and integer <= 0 do
          {:error, validation: "expected a positive integer"}
        else
          {:ok, integer}
        end

      {_, remainder} ->
        {:error, parsing: ["unexpected substring ", {:highlight, inspect(remainder)}]}

      :error ->
        :error
    end
  end
end