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
46 changes: 34 additions & 12 deletions lib/speck/validation_metadata/attribute.ex
Original file line number Diff line number Diff line change
Expand Up @@ -19,38 +19,60 @@ 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)
_ -> List.replace_at(params2, index, new_item)
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
Expand Down
51 changes: 51 additions & 0 deletions test/validation_metadata/attribute_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -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 = %{
Expand Down