frenzy/lib/frenzy/keypath.ex

74 lines
2.2 KiB
Elixir

defmodule Frenzy.Keypath do
@moduledoc """
Utilities for accessing or updating values using keypaths.
A keypath is a list of map keys or list indices representing the path through nested maps/lists.
"""
@type container() :: map() | list()
@type t() :: [Map.key() | non_neg_integer()]
@doc """
Gets the value in the given container at the given keypath. Raises on index out of bounds/missing map key.
## Examples
iex> Frenzy.Keypath.get(%{foo: %{bar: "baz"}}, [:foo, :bar])
"baz"
iex> Frenzy.Keypath.get([%{"foo" => "bar"}, %{"foo" => "baz"}], [1, "foo"])
"baz"
"""
@spec get(container(), t()) :: any()
def get(value, []), do: value
def get(map, [key | rest]) when is_map(map) do
get(Map.fetch!(map, key), rest)
end
def get(list, [index | rest]) when is_list(list) and is_integer(index) and index >= 0 do
if index >= length(list) do
raise KeyError, "Index #{index} out of bounds (>= #{length(list)})"
else
get(Enum.at(list, index), rest)
end
end
@doc """
Sets the vlaue in the given container at the given keypath. Raises on index out of bounds/missing map key.
## Examples
iex> Frenzy.Keypath.set(%{foo: %{bar: "baz"}}, [:foo, :bar], "blah")
%{foo: %{bar: "blah"}}
iex> Frenzy.Keypath.set([%{"foo" => "bar"}, %{"list" => ["a", "b"]}], [1, "list", 0], "c")
[%{"foo" => "bar"}, %{"list" => ["c", "b"]}]
"""
@spec set(container(), t(), any()) :: map()
def set(map, [key], value) when is_map(map) do
Map.put(map, key, value)
end
def set(list, [index], value) when is_list(list) and is_integer(index) and index >= 0 do
if index >= length(list) do
raise KeyError, "Index #{index} out of bounds (>= #{length(list)})"
else
List.replace_at(list, index, value)
end
end
def set(map, [key | rest], value) when is_map(map) do
Map.put(map, key, set(Map.fetch!(map, key), rest, value))
end
def set(list, [index | rest], value) when is_list(list) and is_integer(index) and index >= 0 do
if index >= length(list) do
raise KeyError, "Index #{index} out of bounds (>= #{length(list)})"
else
List.replace_at(list, index, set(Enum.at(list, index), rest, value))
end
end
end