lib/loupe/ecto/filter.ex

if Code.ensure_loaded?(Ecto) do
  defmodule Loupe.Ecto.Filter do
    @moduledoc """
    Module to implement various filter depending on field type. This is
    how we build query with Ecto depending on the kind of field we receive
    """
    alias Loupe.Ecto.Context
    alias Loupe.Language.Ast

    @callback apply_bounded_filter(Ast.predicate(), Context.t()) :: Ecto.Query.t()

    defmacro __using__(_) do
      quote do
        @behaviour Loupe.Ecto.Filter
        import Ecto.Query
        import Loupe.Ecto.Filter
      end
    end

    @doc """
    Access a composite field dynamically
    """
    defmacro composite_access(field, composite_field) do
      quote do
        fragment("(?).?", unquote(field), literal(unquote(composite_field)))
      end
    end

    @doc "Unwraps literal"
    @spec unwrap(Ast.literal() | {:list, Ast.literal()} | boolean(), Context.t()) :: any()
    def unwrap({:sigil, string}, context) do
      Context.cast_sigil(context, string)
    end

    def unwrap({:list, items}, context) do
      Enum.map(items, &unwrap(&1, context))
    end

    def unwrap({:identifier, identifier}, %Context{variables: variables}) do
      Map.fetch!(variables, identifier)
    end

    def unwrap(boolean, _context) when is_boolean(boolean), do: boolean

    def unwrap(literal, _context) do
      {unwrapped, _} = Ast.unwrap_literal(literal, MapSet.new())
      unwrapped
    end
  end
end