lib/config_cat/config.ex

defmodule ConfigCat.Config do
  @moduledoc """
  Defines configuration-related types used in the rest of the library.
  """
  alias ConfigCat.Config.Preferences
  alias ConfigCat.Config.Segment
  alias ConfigCat.Config.Setting

  @typedoc false
  @type comparator :: non_neg_integer()

  @typedoc "The name of a configuration setting."
  @type key :: String.t()

  @typedoc false
  @type opt :: {:preferences, Preferences.t()} | {:settings, settings()}

  @typedoc false
  @type salt :: String.t()

  @typedoc false
  @type settings :: %{String.t() => Setting.t()}

  @typedoc "A collection of configuration settings and preferences."
  @type t :: %{String.t() => map()}

  @typedoc false
  @type url :: String.t()

  @typedoc "The actual value of a configuration setting."
  @type value :: String.t() | boolean() | number()

  @typedoc "The name of a variation being tested."
  @type variation_id :: String.t()

  @settings "f"
  @preferences "p"
  @segments "s"

  @doc false
  @spec new([opt]) :: t()
  def new(opts \\ []) do
    settings = Keyword.get(opts, :settings, %{})
    preferences = Keyword.get_lazy(opts, :preferences, &Preferences.new/0)

    %{@settings => settings, @preferences => preferences}
  end

  @doc false
  @spec preferences(t()) :: Preferences.t()
  def preferences(config) do
    Map.get_lazy(config, @preferences, &Preferences.new/0)
  end

  @doc false
  @spec segments(t()) :: [Segment.t()]
  def segments(config) do
    Map.get(config, @segments, [])
  end

  @doc false
  @spec settings(t()) :: settings()
  def settings(config) do
    Map.get(config, @settings, %{})
  end

  @doc false
  @spec fetch_settings(t()) :: {:ok, settings()} | {:error, :not_found}
  def fetch_settings(config) do
    case Map.fetch(config, @settings) do
      {:ok, settings} -> {:ok, settings}
      :error -> {:error, :not_found}
    end
  end

  @doc false
  @spec merge(left :: t(), right :: t()) :: t()
  def merge(left, right) do
    left_flags = settings(left)
    right_flags = settings(right)

    Map.put(left, @settings, Map.merge(left_flags, right_flags))
  end

  @doc false
  @spec inline_salt_and_segments(t()) :: t()
  def inline_salt_and_segments(config) do
    salt = config |> preferences() |> Preferences.salt()
    segments = segments(config)

    Map.update(
      config,
      @settings,
      %{},
      &Map.new(&1, fn {key, setting} -> {key, Setting.inline_salt_and_segments(setting, salt, segments)} end)
    )
  end
end