lib/pg_rest/order.ex

defmodule PgRest.Order do
  @moduledoc """
  Applies parsed order directives to Ecto queries.
  """

  import Ecto.Query
  import PgRest.Utils, only: [safe_to_atom: 1]

  @doc """
  Applies parsed order directives to an Ecto query.

  Supports `:asc`/`:desc` directions with optional `:nulls_first`/`:nulls_last`.
  """
  @spec apply_order(Ecto.Queryable.t(), [map()] | nil) :: Ecto.Queryable.t() | Ecto.Query.t()
  def apply_order(query, nil), do: query

  def apply_order(query, directives) when is_list(directives) do
    Enum.reduce(directives, query, &apply_directive/2)
  end

  defp apply_directive(%{field: field, direction: direction, nulls: nulls}, query) do
    field_atom = safe_to_atom(field)

    case {direction, nulls} do
      {:asc, nil} ->
        order_by(query, [r], asc: field(r, ^field_atom))

      {:desc, nil} ->
        order_by(query, [r], desc: field(r, ^field_atom))

      {:asc, :first} ->
        order_by(query, [r], asc_nulls_first: field(r, ^field_atom))

      {:asc, :last} ->
        order_by(query, [r], asc_nulls_last: field(r, ^field_atom))

      {:desc, :first} ->
        order_by(query, [r], desc_nulls_first: field(r, ^field_atom))

      {:desc, :last} ->
        order_by(query, [r], desc_nulls_last: field(r, ^field_atom))
    end
  end
end