diff --git a/lib/unleash/strategy.ex b/lib/unleash/strategy.ex index 92478ae..9fd9987 100644 --- a/lib/unleash/strategy.ex +++ b/lib/unleash/strategy.ex @@ -16,6 +16,7 @@ defmodule Unleash.Strategy do require Logger alias Unleash.Config + alias Unleash.Strategy.Constraint defmacro __using__(opts) do name = opts[:name] @@ -49,7 +50,7 @@ defmodule Unleash.Strategy do * `parameters` - A map of paramters returned from the Unleash server. This can be whatever you like, such as a configured list of `userIds`. - * `context` - The context passed into `Unleash.enabled?/3`. + * `context` - The context passed into `Unleash.enabled?/3`. ## Examples @@ -72,7 +73,7 @@ defmodule Unleash.Strategy do Config.strategies() |> Enum.find(fn {n, _mod} -> n == name end) - module.check_enabled(strategy["parameters"], context) + check_constraints(strategy, context) and module.check_enabled(strategy["parameters"], context) end def enabled?(_strat, _context), do: false @@ -97,4 +98,9 @@ defmodule Unleash.Strategy do result end + + defp check_constraints(%{"constraints" => constraints}, context), + do: Constraint.verify_all(constraints, context) + + defp check_constraints(_strategy, _context), do: true end diff --git a/lib/unleash/strategy/constraint.ex b/lib/unleash/strategy/constraint.ex new file mode 100644 index 0000000..90ba267 --- /dev/null +++ b/lib/unleash/strategy/constraint.ex @@ -0,0 +1,35 @@ +defmodule Unleash.Strategy.Constraint do + @moduledoc """ + Module that is used to verify + [constraints](https://www.unleash-hosted.com/docs/strategy-constraints/) are + met. + + These constraints allow for very complex and specifc strategies to be + enacted by allowing users to specify context values to include or exclude. + """ + + def verify_all(constraints, context) do + Enum.all?(constraints, &verify(&1, context)) + end + + defp verify(%{"contextName" => name, "operator" => op, "values" => values}, context) do + context + |> find_value(name) + |> check(op, values) + end + + defp verify(%{}, _context), do: false + + defp check(value, "IN", values), do: value in values + defp check(value, "NOT_IN", values), do: value not in values + + defp find_value(nil, _name), do: nil + + defp find_value(ctx, name) do + Map.get( + ctx, + String.to_atom(Recase.to_snake(name)), + find_value(Map.get(ctx, :properties), name) + ) + end +end diff --git a/mix.exs b/mix.exs index 217cf7c..a491584 100644 --- a/mix.exs +++ b/mix.exs @@ -60,7 +60,7 @@ defmodule Unleash.MixProject do {:stream_data, "~> 0.4.3", only: [:test, :dev]}, {:excoveralls, "~> 0.8", only: :test}, {:mox, "~> 0.5.1", only: :test}, - {:recase, "~> 0.6.0", only: :test}, + {:recase, "~> 0.6.0"}, {:murmur, "~> 1.0"}, {:mojito, "~> 0.5.0"}, {:jason, "~> 1.1"}, diff --git a/test/unleash/client_specification_test.exs b/test/unleash/client_specification_test.exs index 57d9eb8..ea4b5ff 100644 --- a/test/unleash/client_specification_test.exs +++ b/test/unleash/client_specification_test.exs @@ -42,12 +42,6 @@ defmodule Unleash.ClientSpecificationTest do @expected expected @tag capture_log: true - if String.starts_with?(name, "09-strategy-constraints") do - @tag skip: true - else - @tag skip: false - end - test t do context = entity_from_file(@context) @@ -76,7 +70,11 @@ defmodule Unleash.ClientSpecificationTest do defp entity_from_file(e) do e - |> Enum.map(fn {k, v} -> {String.to_atom(Recase.to_snake(k)), v} end) + |> Enum.map(fn + {"payload", v} -> {:payload, v} + {k, v} when is_map(v) -> {String.to_atom(Recase.to_snake(k)), entity_from_file(v)} + {k, v} -> {String.to_atom(Recase.to_snake(k)), v} + end) |> Enum.into(%{}) end end