lib/plugin_manager/database/helper.ex

defmodule MishkaInstaller.Database.Helper do
  import Ecto.Changeset
  require Logger

  @spec translate_errors(Ecto.Changeset.t()) :: %{optional(atom) => [binary | map]}
  def translate_errors(changeset) do
    Ecto.Changeset.traverse_errors(changeset, fn {msg, opts} ->
        Enum.reduce(opts, msg, fn {key, value}, acc ->
          String.replace(acc, "%{#{key}}", to_string(value))
        end)
    end)
  end

  @spec convert_string_map_to_atom_map(map) :: map
  def convert_string_map_to_atom_map(map) do
    map
    |> Map.new(fn {k, v} ->
        {String.to_existing_atom(k), v}
    end)
  end

  @spec validate_binary_id(Ecto.Changeset.t(), atom, any) :: Ecto.Changeset.t()
  def validate_binary_id(changeset, field, options \\ []) do
    validate_change(changeset, field, fn _, uuid ->
      case uuid(uuid) do
        {:ok, :uuid, _record_id} -> []
        {:error, :uuid} -> [{field, options[:message] || "ID should be as a UUID type."}]
      end
    end)
  end

  @spec uuid(any) :: {:error, :uuid} | {:ok, :uuid, Ecto.UUID.t}
  def uuid(id) do
    case Ecto.UUID.cast(id) do
      {:ok, record_id} -> {:ok, :uuid, record_id}
      _ -> {:error, :uuid}
    end
  end

  # Ref: https://elixirforum.com/t/how-to-send-sandbox-allow-for-each-dynamic-supervisor-testing/46422/4
  def allow_if_sandbox(parent_pid, orphan_msg \\ :stop) do
    if sandbox_pool?() do
      monitor_parent(parent_pid, orphan_msg)
      # this addresses #1
      Ecto.Adapters.SQL.Sandbox.allow(MishkaInstaller.repo, parent_pid, self())
    end
  end

  def sandbox_pool?() do
    MishkaInstaller.repo == Ecto.Integration.TestRepo
  end

  defp monitor_parent(parent_pid, orphan_msg) do
    # this is part of addressing #2
    Process.monitor(parent_pid)

    if Process.alive?(parent_pid) do
      :ok
    else
      Logger.error("#{inspect(parent_pid)} down when booting #{inspect(self())}")
      # this addresses #3
      # the "throw" will work like an early "return"; see the GenServer docs
      throw(orphan_msg)
    end
  end

  def get_parent_pid(state) when is_nil(state.parent_pid), do: :ok

  def get_parent_pid(state) do
    if Mix.env() == :test do
      allow_if_sandbox(state.parent_pid)
    else
      :ok
    end
  end

end