Current section

Files

Jump to
split_client lib boundary data_source evaluator.ex
Raw

lib/boundary/data_source/evaluator.ex

defmodule SplitClient.Boundary.Evaluator do
# Split.io has an Evaluator component that can be deployed in your
# infrastructure that sits between your applications and Split.io
# servers. It provides a REST API for accessing Split.io
# data and is meant to be used in cases where Split.io has not
# created an SDK for a specific language. As of April 2022, there
# is no Elixir SDK for Split.io which necessitates relying on
# a deployed Evaluator instance.
# @see https://help.split.io/hc/en-us/articles/360020037072-Split-Evaluator
@moduledoc false
@behaviour SplitClient.Boundary.DataSourceBehaviour
alias SplitClient.Boundary.OrderedAttributes
alias SplitClient.Treatment
@impl true
def get_treatment(key, split_name, opts \\ []) do
make_treatment_request(
"client/get-treatment-with-config",
[
{"key", key},
{"split-name", split_name}
],
&format_single_treatment_response/1,
opts
)
end
@impl true
def get_treatments(key, split_names, opts \\ []) when is_list(split_names) do
make_treatment_request(
"client/get-treatments-with-config",
[
{"key", key},
{"split-names", Enum.join(split_names, ",")}
],
&format_map_response/1,
opts
)
end
@impl true
def get_all_treatments(keys, opts \\ []) when is_list(keys) do
make_treatment_request(
"client/get-all-treatments-with-config",
[
{"keys", format_keys(keys)}
],
&format_traffic_type_response/1,
opts
)
end
defp format_keys(keys) when is_map(keys) do
Enum.reduce(keys, %{}, fn {key, value}, acc ->
Map.put(acc, camelize(Atom.to_string(key)), value)
end)
|> Jason.encode!()
end
defp format_keys(composite_keys) do
keys =
Enum.map_join(composite_keys, ",", fn keys ->
format_keys(keys)
end)
"[#{keys}]"
end
defp camelize(value) do
value = Macro.camelize(value)
{first, rest} = String.split_at(value, 1)
String.downcase(first) <> rest
end
defp make_treatment_request(path, params, response_formatter, opts) do
params =
params
|> include_optional_params(opts)
|> URI.encode_query()
url = "#{evaluator_base_url()}/#{path}?#{params}"
headers = [auth_header()]
case http().get(url, headers) do
{:ok, %{body: body}} ->
decode_response(body) |> format_response(response_formatter)
error -> error
end
end
defp include_optional_params(params, opts) do
Enum.reduce(opts, params, fn {key, value}, acc ->
format_param(acc, key, value)
end)
end
defp format_param(params, :attributes, attributes) do
attributes = OrderedAttributes.new(attributes)
Enum.concat(params, [{"attributes", Jason.encode!(attributes)}])
end
defp format_param(params, :bucketing_key, key) do
Enum.concat(params, [{"bucketing-key", key}])
end
defp format_param(params, _key, _value), do: params
defp evaluator_base_url do
Application.fetch_env!(:split_client, :evaluator_url)
|> trim_trailing_slash()
end
defp trim_trailing_slash(url) do
String.trim_trailing(url, "/")
end
defp auth_header do
{"Authorization", evaluator_auth_token()}
end
defp evaluator_auth_token do
Application.fetch_env!(:split_client, :evaluator_auth_token)
end
defp http do
Application.get_env(:split_client, :http, SplitClient.Boundary.HTTP)
end
defp decode_response(body) do
case Jason.decode(body) do
{:ok, data} -> data
{:error, error} -> {:error, "Feature flag evaluation response is not decodeable. Got error: #{inspect(error)}"}
end
end
defp format_response({:error, message}, _formatter) do
{:error, message}
end
defp format_response(%{"error" => message}, _formatter) do
{:error, message}
end
defp format_response(data, formatter) do
{:ok, formatter.(data)}
end
defp format_single_treatment_response(data) do
Treatment.new(
split_name: data["splitName"],
treatment: data["treatment"],
config: Map.get(data, "config")
)
end
defp format_map_response(data) do
Enum.reduce(data, %{}, fn {key, value}, acc ->
Map.put(
acc,
key,
Treatment.new(
split_name: key,
treatment: value["treatment"],
config: Map.get(value, "config")
)
)
end)
end
defp format_traffic_type_response(data) do
Enum.reduce(data, %{}, fn {traffic_type, value}, acc ->
Map.put(acc, traffic_type, format_map_response(value))
end)
end
end