Packages

ConfigCat SDK for Elixir. Feature Flags created by developers for developers with ❤️. ConfigCat lets you manage feature flags across frontend, backend, mobile, and desktop apps without (re)deploying code. % rollouts, user targeting, segmentation. Feature toggle SDKs for all main languages.

Current section

Files

Jump to
configcat lib config_cat supervisor.ex
Raw

lib/config_cat/supervisor.ex

defmodule ConfigCat.Supervisor do
@moduledoc false
use Supervisor
alias ConfigCat.Cache
alias ConfigCat.CacheControlConfigFetcher
alias ConfigCat.CachePolicy
alias ConfigCat.Client
alias ConfigCat.Hooks
alias ConfigCat.InMemoryCache
alias ConfigCat.NullDataSource
alias ConfigCat.OverrideDataSource
require ConfigCat.ConfigCatLogger, as: ConfigCatLogger
@default_cache InMemoryCache
@spec start_link(keyword()) :: Supervisor.on_start()
def start_link(options) when is_list(options) do
options = Keyword.merge(default_options(), options)
sdk_key = options[:sdk_key]
validate_sdk_key(sdk_key, options)
ensure_unique_sdk_key(sdk_key)
options = put_cache_key(options, sdk_key)
# Rename name -> instance_id for everything downstream
{instance_id, options} = Keyword.pop!(options, :name)
options = Keyword.put(options, :instance_id, instance_id)
Supervisor.start_link(__MODULE__, options, name: via_tuple(instance_id, sdk_key))
end
defp validate_sdk_key(nil, _options), do: raise(ArgumentError, "SDK Key is required")
defp validate_sdk_key("", _options), do: raise(ArgumentError, "SDK Key is required")
defp validate_sdk_key(sdk_key, options) when is_binary(sdk_key) do
has_base_url? = !is_nil(options[:base_url])
overrides = options[:flag_overrides]
cond do
OverrideDataSource.behaviour(overrides) == :local_only ->
:ok
sdk_key =~ ~r[^.{22}/.{22}$] ->
:ok
sdk_key =~ ~r[^configcat-sdk-1/.{22}/.{22}$] ->
:ok
has_base_url? and sdk_key =~ ~r[^configcat-proxy/.+$] ->
:ok
true ->
raise ArgumentError, "SDK Key `#{sdk_key}` is invalid."
end
end
defp validate_sdk_key(sdk_key, _options) do
raise ArgumentError, "SDK Key `#{inspect(sdk_key)}` is invalid."
end
defp ensure_unique_sdk_key(sdk_key) do
ConfigCat.Registry
|> Registry.select([{{{__MODULE__, :"$1"}, :_, sdk_key}, [], [:"$1"]}])
|> case do
[] ->
:ok
[instance_id] ->
message =
"There is an existing ConfigCat instance for the specified SDK Key. " <>
"No new instance will be created and the specified options are ignored. " <>
"You can use the existing instance by passing `client: #{instance_id}` to the ConfigCat API functions. " <>
"SDK Key: '#{sdk_key}'."
ConfigCatLogger.warning(message, event_id: 3000)
raise ArgumentError, message
end
end
defp default_options,
do: [
cache: @default_cache,
cache_policy: CachePolicy.auto(),
flag_overrides: NullDataSource.new(),
name: ConfigCat,
offline: false
]
defp put_cache_key(options, sdk_key) do
Keyword.put(options, :cache_key, Cache.generate_key(sdk_key))
end
defp via_tuple(instance_id, sdk_key) do
{:via, Registry, {ConfigCat.Registry, {__MODULE__, instance_id}, sdk_key}}
end
@impl Supervisor
def init(options) do
override_behaviour = OverrideDataSource.behaviour(options[:flag_overrides])
children =
Enum.reject(
[
hooks(options),
cache(options),
config_fetcher(options, override_behaviour),
cache_policy(options, override_behaviour),
client(options, override_behaviour)
],
&is_nil/1
)
Supervisor.init(children, strategy: :one_for_one)
end
defp hooks(options) do
hooks_options = Keyword.take(options, [:hooks, :instance_id])
{Hooks, hooks_options}
end
defp cache(options) do
cache_options = Keyword.take(options, [:cache, :cache_key, :instance_id])
{Cache, cache_options}
end
defp config_fetcher(_options, :local_only), do: nil
defp config_fetcher(options, _override_behaviour) do
fetcher_options =
options
|> Keyword.put(:mode, options[:cache_policy].mode)
|> Keyword.take([
:base_url,
:http_proxy,
:connect_timeout_milliseconds,
:read_timeout_milliseconds,
:data_governance,
:instance_id,
:mode,
:sdk_key
])
{CacheControlConfigFetcher, fetcher_options}
end
defp cache_policy(_options, :local_only), do: nil
defp cache_policy(options, _override_behaviour) do
policy_options = Keyword.take(options, [:cache_policy, :instance_id, :offline])
{CachePolicy, policy_options}
end
defp client(options, override_behaviour) do
cache_policy = if override_behaviour == :local_only, do: CachePolicy.Null, else: CachePolicy
client_options =
options
|> Keyword.take([
:default_user,
:flag_overrides,
:instance_id
])
|> Keyword.put(:cache_policy, cache_policy)
{Client, client_options}
end
end