if Code.ensure_loaded?(Ecto) do
  defmodule Loupe.Ecto.Definition do
    @moduledoc """
    Behaviour for defining an Ecto definition. This behaviours defines what the query
    builder can use to build the query.

    All function receives the context assigns, meaning that you could add a user's role
    to the assigns and filter down available schemas for this particular role (example,
    allow Users query only for Admins)

    ## Exmaple

    In the following example, the Ecto definition prevents non-admin to query User and
    only allow the `:name` field for the Game schema. Meaning that non-admin cannot query
    for other fields than `:name` when querying games.

        defmodule MyApp.Loupe.EctoDefinition do
          @behaviour Loupe.Ecto.Definition

          @impl Loupe.Ecto.Definition
          def schemas(%{role: "admin"}), do: %{"User" => MyApp.User, "Game" => MyApp.Game}
          def schemas(_), do: %{"Game" => MyApp.Game}

          @impl Loupe.Ecto.Definition
          def schema_fields(Game, %{role: role}) when role != "admin", do: [:name]
          def schema_fields(_, _), do: :all
          @impl Loupe.Ecto.Definition
          def scope_schemas(Game, %{role: role}) when role != "admin" do
          def scope_schemas(schema, _), do: schema
    alias Loupe.Ecto.Context
    @doc "Gets available schemas"
    @callback schemas(Context.assigns()) :: Context.schemas()

    @doc "Gets available field for a schema"
    @callback schema_fields(Context.schema(), Context.assigns()) :: {:only, [atom()]} | :all

    @doc "Scopes schema query builder"
    @callback scope_schema(Context.schema(), Context.assigns()) :: Ecto.Queryable.t()