lib/edgedb/types/range.ex

defmodule EdgeDB.Range do
  @moduledoc """
  A value representing some interval of values.

  ```elixir
  iex(1)> {:ok, pid} = EdgeDB.start_link()
  iex(2)> EdgeDB.query_required_single!(pid, "select range(1, 10)")
  #EdgeDB.Range<[1, 10)>
  ```
  """

  defstruct [
    :lower,
    :upper,
    inc_lower: true,
    inc_upper: false,
    is_empty: false
  ]

  @typedoc """
  A value representing some interval of values.

  Fields:

    * `:lower` - data for the lower range boundary.
    * `:upper` - data for the upper range boundary.
    * `:inc_lower` - flag whether the range should strictly include the lower boundary.
    * `:inc_upper` - flag whether the range should strictly include the upper boundary.
    * `:is_empty` - flag for an empty range.
  """
  @type t(value) :: %__MODULE__{
          lower: value | nil,
          upper: value | nil,
          inc_lower: boolean(),
          inc_upper: boolean(),
          is_empty: boolean()
        }

  @typedoc """
  A value representing some interval of values.
  """
  @type t() :: t(term())

  @typedoc """
  Options for `EdgeDB.Range.new/3` function.

  Supported options:

    * `:inc_lower` - flag whether the created range should strictly include the lower boundary.
    * `:inc_upper` - flag whether the created range should strictly include the upper boundary.
    * `:empty` - flag to create an empty range.
  """
  @type creation_option() ::
          {:inc_lower, boolean()}
          | {:inc_upper, boolean()}
          | {:empty, boolean()}

  @doc """
  Create an empty range.

    ```elixir
  iex(1)> EdgeDB.Range.empty()
  #EdgeDB.Range<empty>
  ```
  """
  @spec empty() :: t()
  def empty do
    new(nil, nil, empty: true, inc_lower: false)
  end

  @doc """
  Create new range.

    ```elixir
  iex(1)> EdgeDB.Range.new(1.1, 3.3, inc_upper: true)
  #EdgeDB.Range<[1.1, 3.3]>
  ```
  """
  @spec new(value, value, list(creation_option())) :: t(value) when value: term()
  def new(lower, upper, opts \\ []) do
    inc_lower? = Keyword.get(opts, :inc_lower, true)
    inc_upper? = Keyword.get(opts, :inc_upper, false)
    empty? = Keyword.get(opts, :empty, false)

    %__MODULE__{
      lower: lower,
      upper: upper,
      inc_lower: inc_lower?,
      inc_upper: inc_upper?,
      is_empty: empty?
    }
  end
end

defimpl Inspect, for: EdgeDB.Range do
  import Inspect.Algebra

  @impl Inspect
  def inspect(%EdgeDB.Range{is_empty: true}, _opts) do
    concat(["#EdgeDB.Range<empty>"])
  end

  @impl Inspect
  def inspect(%EdgeDB.Range{} = range, opts) do
    concat([
      "#EdgeDB.Range<",
      if(range.inc_lower, do: "[", else: "("),
      if(range.lower, do: Inspect.inspect(range.lower, opts), else: empty()),
      ",",
      if(range.upper, do: Inspect.inspect(range.upper, opts), else: empty()),
      if(range.inc_upper, do: "]", else: ")"),
      ">"
    ])
  end
end