diff --git a/lib/speck/validation_metadata/attribute.ex b/lib/speck/validation_metadata/attribute.ex index 3877931..8de4783 100644 --- a/lib/speck/validation_metadata/attribute.ex +++ b/lib/speck/validation_metadata/attribute.ex @@ -19,28 +19,50 @@ defmodule Speck.ValidationMetadata.Attribute do @doc """ Merge the values from metadata attributes into a given map. + + ## Opts + - `:merge_strategy` - Determines whether a value in the params or attributes + takes priority when both are present. Defaults to `:param_priority`. """ - @spec merge(attributes :: [t], params :: map) :: map - def merge(attributes, params) do + @spec merge( + attributes :: [t], + params :: map, + opts :: [merge_strategy: :param_priority | :attribute_priority] + ) :: map + def merge(attributes, params, opts \\ []) do + merge_strategy = Keyword.get(opts, :merge_strategy, :param_priority) + Enum.reduce(attributes, params, fn {path, _status, value}, acc -> - merge(acc, path, value) + merge(acc, path, value, merge_strategy) end) end - defp merge(nil = _params, [path], value) do + defp merge(nil = _params, [path], value, _strategy) do %{path => value} end - defp merge(params, [path], value) when is_map(params) do - params - |> to_key_strings() - |> Map.put(path, value) + defp merge(nil = _params, [attribute | path], value, strategy) + when not is_integer(attribute) do + %{attribute => merge(%{}, path, value, strategy)} + end + + defp merge(params, [path], value, strategy) when is_map(params) do + params_with_key_strings = to_key_strings(params) + + skip_put = + strategy == :param_priority + && Map.has_key?(params_with_key_strings, path) + + case skip_put do + true -> params_with_key_strings + _ -> Map.put(params_with_key_strings, path, value) + end end - defp merge(params, [index | path], value) when is_integer(index) do + defp merge(params, [index | path], value, strategy) when is_integer(index) do params2 = params || [] item = Enum.at(params2, index) - new_item = merge(item, path, value) + new_item = merge(item, path, value, strategy) case item do nil -> List.insert_at(params2, -1, new_item) @@ -48,9 +70,9 @@ defmodule Speck.ValidationMetadata.Attribute do end end - defp merge(params, [attribute | path], value) do + defp merge(params, [attribute | path], value, strategy) do params2 = to_key_strings(params) - Map.put(params2, attribute, merge(params2[attribute], path, value)) + Map.put(params2, attribute, merge(params2[attribute], path, value, strategy)) end defp to_key_strings(map) do diff --git a/test/validation_metadata/attribute_test.exs b/test/validation_metadata/attribute_test.exs index ac4a17d..e652577 100644 --- a/test/validation_metadata/attribute_test.exs +++ b/test/validation_metadata/attribute_test.exs @@ -3,6 +3,57 @@ defmodule Speck.ValidationMetadata.Attribute.Test do alias Speck.ValidationMetadata.Attribute + test "merge adds nested maps that don't exist" do + attributes = [ + {["state", "reported", "serial"], :present, "sn1234"} + ] + + assert Attribute.merge(attributes, %{}) == + %{"state" => %{"reported" => %{"serial" => "sn1234"}}} + end + + describe "merge strategy" do + test "attribute priority" do + params = %{"name" => "Test Device"} + + attributes = [ + {["name"], :present, "My Device"} + ] + + assert Attribute.merge(attributes, params, merge_strategy: :attribute_priority) == + %{"name" => "My Device"} + + params = %{"state" => %{"reported" => %{"name" => "Test Device"}}} + + attributes = [ + {["state", "reported", "name"], :present, "My Device"} + ] + + assert Attribute.merge(attributes, params, merge_strategy: :attribute_priority) == + %{"state" => %{"reported" => %{"name" => "My Device"}}} + end + + test "param priority" do + params = %{"name" => "Test Device"} + + attributes = [ + {["name"], :present, "My Device"} + ] + + assert Attribute.merge(attributes, params, merge_strategy: :param_priority) == + %{"name" => "Test Device"} + + params = %{"state" => %{"reported" => %{"name" => "Test Device"}}} + + attributes = [ + {["state", "reported", "name"], :present, "My Device"} + ] + + assert Attribute.merge(attributes, params, merge_strategy: :param_priority) == + %{"state" => %{"reported" => %{"name" => "Test Device"}}} + end + end + describe "use case" do test "can merge unknown attributes back into a device shadow" do shadow_reported = %{