lib/athena_web/schema/helpers.ex

defmodule AthenaWeb.Schema.Helpers do
  @moduledoc """
  Helpers
  """

  alias Absinthe.Relay.Connection
  alias Athena.Repo
  alias AthenaWeb.Schema.InvalidIdError
  alias Ecto.Changeset

  @spec connection_from_query(
          query,
          args :: map,
          list_fn :: (query -> [row]),
          count_fn :: (query -> row) | nil
        ) ::
          {:error, term()}
          | {:ok, Connection.t()}
        when query: Ecto.Queryable.t(),
             row: any
  def connection_from_query(
        query,
        args,
        list_fn \\ &Repo.all/1,
        count_fn \\ &Repo.aggregate(&1, :count)
      )
      when is_function(list_fn, 1) and (is_function(count_fn, 1) or is_nil(count_fn)) do
    Connection.from_query(
      query,
      list_fn,
      Map.take(args, [:first, :last, :before, :after]),
      case count_fn do
        nil -> []
        fun when is_function(fun, 1) -> [count: count_fn.(query)]
      end
    )
  end

  @spec many_dataloader :: Absinthe.Resolution.Helpers.dataloader_key_fun()
  def many_dataloader do
    fn parent, args, resolution ->
      parent
      |> Ecto.assoc(resolution.definition.schema_node.identifier)
      |> connection_from_query(args)
    end
  end

  defmacro payload_fields(output_type) do
    quote location: :keep do
      field(:successful, non_null(:boolean),
        description: "Indicates if the mutation completed successfully or not. "
      )

      field(:messages, list_of(:validation_message),
        description: "A list of failed validations. May be blank or null if mutation succeeded."
      )

      field(:result, unquote(output_type),
        description:
          "The object created/updated/deleted by the mutation. May be null if mutation failed."
      )
    end
  end

  @dialyzer {:no_contracts, {:changeset_result, 1}}
  @spec changeset_result(result :: {:ok, term}) :: {:ok, term}
  @spec changeset_result(result :: {:error, Ecto.Changeset.t()}) :: {:ok, Ecto.Changeset.t()}
  @spec changeset_result(result :: {:error, term}) :: {:error, term}
  def changeset_result({:ok, _result} = result), do: result
  def changeset_result({:error, %Changeset{} = changeset}), do: {:ok, changeset}
  def changeset_result({:error, _error} = result), do: result

  @spec from_global_id!(id :: String.t(), type :: atom) :: String.t()
  def from_global_id!(id, type) when is_atom(type) do
    %{id: id} = from_global_id!(id, [type])
    id
  end

  @spec from_global_id!(id :: String.t(), types :: [atom]) :: %{type: atom(), id: binary()}
  def from_global_id!(id, types) when is_list(types) do
    id
    |> Absinthe.Relay.Node.from_global_id(AthenaWeb.Schema)
    |> case do
      {:ok, %{type: type} = id_map} ->
        unless type in types do
          raise InvalidIdError, message: "Invalid ID Type"
        end

        id_map

      {:error, _reason} ->
        raise InvalidIdError, message: "Invalid ID"
    end
  end
end