Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,14 @@ lint:


hex-publish:
mix hex.publish


hex-publish-docs:
mix hex.publish docs


hex-publish-private:
mix hex.publish --organization shipworthy


Expand Down
17 changes: 14 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,9 @@ In this example, the name of the deployment environment is loaded from an enviro

`config/runtime.exs`
```elixir

# Determine the current environment.
# Is the service running on localhost? In the staging environment? In production?
current_deployment_environment =
System.get_env("DEPLOYMENT_ENVIRONMENT")
|> case do
Expand All @@ -41,15 +44,23 @@ current_deployment_environment =


config :simple_feature_flags, :flags, %{
# In which deployment environment (e.g., :production, :staging, :localhost, :test) is
# the code currently running?
current_deployment_environment: current_deployment_environment,

# Optional: list possible deployment environments, for additional validation and
# protection against typos.
known_deployment_environments: [:test, :staging, :production],

# In which of the environments do you want to enable each feature?
features: %{
new_algorithm: %{enabled_in: [:localhost, :staging]},
new_ui: %{enabled_in: [:staging]}
}
}
```

## Using Feature Flag in Your Code
## Using Feature Flags in Your Code

Wrap your feature logic in `SimpleFeatureFlags.enabled?/1`:

Expand Down Expand Up @@ -88,8 +99,8 @@ Here is an example of the output:
```text
Current Deployment Environment: :localhost
Features:
- new_algorithm is ON. Enabled in [:localhost, :staging]
- new_ui is OFF. Enabled in [:staging]
- new_algorithm is ON. Enabled in [:localhost, :staging].
- new_ui is OFF. Enabled in [:staging].
```


Expand Down
108 changes: 89 additions & 19 deletions lib/simple_feature_flags.ex
Original file line number Diff line number Diff line change
Expand Up @@ -4,36 +4,47 @@ defmodule SimpleFeatureFlags do
"""

@doc """
Is feature x enabled in the current deployment environment?
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 =
Application.get_env(:simple_feature_flags, :flags).features
|> Map.fetch!(feature)
|> Map.fetch!(:enabled_in)
features
|> Map.fetch(feature)
|> case do
:error ->
raise "Unknown feature '#{feature}'. Known features: #{Map.keys(features) |> Enum.join(", ")}."

current_deployment_environment =
Application.get_env(:simple_feature_flags, :flags).current_deployment_environment
{:ok, %{enabled_in: enabled_in}} ->
enabled_in
end

:all == enabled_in_these_environments or
[: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"
"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
Expand All @@ -42,23 +53,82 @@ defmodule SimpleFeatureFlags do
end

@doc false
def configuration_to_string(%{
current_deployment_environment: current_deployment_environment,
features: features
})
when is_atom(current_deployment_environment) and
current_deployment_environment != nil and
is_map(features) do
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)}
Features:
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)}
- #{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
Loading