Current section

Files

Jump to
simple_feature_flags lib simple_feature_flags.ex
Raw

lib/simple_feature_flags.ex

defmodule SimpleFeatureFlags do
@moduledoc """
This module includes functions for checking whether a feature is enabled (`enabled?/1`), and for introspecting configuration (`current_configuration_to_string/0`).
"""
@doc """
This function answers the question "Is feature X enabled in the current deployment environment?"
If feature X is not mentioned in your configuration, the function raises an exception.
## Examples
iex> SimpleFeatureFlags.enabled?(:test_feature_1)
true
iex> SimpleFeatureFlags.enabled?(:no_such_feature)
** (RuntimeError) Unknown feature 'no_such_feature'. Known features: test_feature_1, test_feature_2, test_feature_3, test_feature_4, test_feature_5, test_feature_6.
"""
def enabled?(feature) do
%{features: features, current_deployment_environment: current_deployment_environment} =
Application.get_env(:simple_feature_flags, :flags)
enabled_in_these_environments =
features
|> Map.fetch(feature)
|> case do
:error ->
raise "Unknown feature '#{feature}'. Known features: #{Map.keys(features) |> Enum.join(", ")}."
{:ok, %{enabled_in: enabled_in}} ->
enabled_in
end
:all == enabled_in_these_environments or
:all in enabled_in_these_environments or
current_deployment_environment in enabled_in_these_environments
end
@doc """
Return the current configuration as a human-friendly string.
If the configuration appears invalid, the function raises an exception.
## Examples
iex> SimpleFeatureFlags.current_configuration_to_string()
"Current Deployment Environment: :test.\\nFeatures:\\n - test_feature_1 is ON. Enabled in [:all].\\n - test_feature_2 is ON. Enabled in :all.\\n - test_feature_3 is ON. Enabled in [:test].\\n - test_feature_4 is ON. Enabled in [:staging, :test, :production].\\n - test_feature_5 is OFF. Enabled in [:staging, :production].\\n - test_feature_6 is OFF. Enabled in [].\\n"
"""
def current_configuration_to_string() do
Application.get_env(:simple_feature_flags, :flags)
|> configuration_to_string()
end
@doc false
def configuration_to_string(
%{
current_deployment_environment: current_deployment_environment,
features: features
} = configuration
)
when current_deployment_environment != nil and is_map(features) do
validate_deployment_environments(configuration)
"""
Current Deployment Environment: #{inspect(current_deployment_environment)}.
""" <>
known_environments(configuration) <>
"""
Features:
""" <>
Enum.map_join(features, "", fn {feature_name, %{enabled_in: enabled_in}} ->
on_or_off = if SimpleFeatureFlags.enabled?(feature_name), do: "ON", else: "OFF"
"""
- #{feature_name} is #{on_or_off}. Enabled in #{inspect(enabled_in)}.
"""
end)
end
defp known_environments(%{known_deployment_environments: known_environments}) do
"""
Known Deployment Environments: #{Enum.join(known_environments, ", ")}.
"""
end
defp known_environments(_), do: ""
defp validate_deployment_environments(%{current_deployment_environment: :all}) do
raise "Unexpected deployment environment, ':all'. This is a reserved name."
end
defp validate_deployment_environments(%{
known_deployment_environments: known_deployment_environments,
current_deployment_environment: current_deployment_environment,
features: features
}) do
for known_env <- known_deployment_environments do
if !is_atom(known_env) do
raise "Expecting atoms in 'known_deployment_environments'. '#{known_env}' is not an atom."
end
end
if current_deployment_environment not in known_deployment_environments do
raise "Unknown deployment environment '#{current_deployment_environment}'. Known environments: #{Enum.join(known_deployment_environments, ", ")}."
end
for {feature_name, %{enabled_in: enabled_in}} <- features, enabled_in != :all do
for feature_enabled_in_env <- enabled_in do
if feature_enabled_in_env not in [:all | known_deployment_environments] do
raise "Feature '#{feature_name}' is marked as 'enabled' in environment '#{feature_enabled_in_env}', which is not a known deployment environment. Known environments: #{Enum.join(known_deployment_environments, ", ")}."
end
end
end
:ok
end
defp validate_deployment_environments(%{
current_deployment_environment: current_deployment_environment,
features: features
})
when is_atom(current_deployment_environment) and current_deployment_environment != nil and
is_map(features) do
:ok
end
defp validate_deployment_environments(%{
current_deployment_environment: current_deployment_environment
})
when not is_atom(current_deployment_environment) do
raise "'current_deployment_environment' is expected to be an atom. '#{current_deployment_environment}' is not an atom."
end
end