Skip to main content

lib/direnv_config/merge.ex

defmodule DirenvConfig.Merge do
  @moduledoc """
  Deep merge for YAML configuration layers with tombstone support.
  """

  @spec deep_merge(term(), term()) :: term()
  def deep_merge(base, overlay) when is_map(base) and is_map(overlay) do
    Map.merge(base, overlay, fn _key, base_val, overlay_val ->
      deep_merge(base_val, overlay_val)
    end)
  end

  def deep_merge(_base, overlay), do: overlay

  @spec deep_merge_multi([term()]) :: term() | nil
  def deep_merge_multi([]), do: nil

  def deep_merge_multi([single]) do
    strip_tombstones(single)
  end

  def deep_merge_multi(layers) do
    layers
    |> Enum.reduce(fn overlay, acc -> deep_merge(acc, overlay) end)
    |> strip_tombstones()
  end

  @spec strip_tombstones(term()) :: term()
  def strip_tombstones(%{"_dc_pruned" => true}), do: nil

  def strip_tombstones(val) when is_map(val) do
    val
    |> Enum.reduce(%{}, fn {k, v}, acc ->
      stripped = strip_tombstones(v)

      if stripped == nil and is_map(v) and Map.has_key?(v, "_dc_pruned") do
        acc
      else
        Map.put(acc, k, stripped)
      end
    end)
  end

  def strip_tombstones(val) when is_list(val) do
    Enum.map(val, &strip_tombstones/1)
  end

  def strip_tombstones(val), do: val
end