MapRewire (map_rewire v0.3.0)
MapRewire makes it easier to rewire maps, such as might be done when translating from an external API result to an internal value or taking the output of one external API and transforming it the input shape of an entirely different external API.
To rewire a map, build transformation rules and call rewire/3, or if
MapRewire has been imported, use the operator, <~>.
iex> map = %{"id" => "234923409", "title" => "asdf"}
iex> rules = "title=>name id=>shopify_id"
iex> map <~> rules
{%{"id" => "234923409", "title" => "asdf"}, %{"shopify_id" => "234923409", "name" => "asdf"}}
iex> MapRewire.rewire(map, rules) == (map <~> rules)
trueRewire Rules
The rewire rules have three basic forms.
A string containing string rename rules separated by whitespace.
iex> map = %{"id" => "234923409", "title" => "asdf"} iex> rules = "title=>name id=>shopify_id" iex> map <~> rules {%{"id" => "234923409", "title" => "asdf"}, %{"shopify_id" => "234923409", "name" => "asdf"}}Here,
rulesnormalizes to:[{"title", "name"}, {"id", "shopify_id"}].A list of strings with one string rename rule in each string.
iex> map = %{"id" => "234923409", "title" => "asdf"} iex> rules = ["title=>name", "id=>shopify_id"] iex> map <~> rules {%{"id" => "234923409", "title" => "asdf"}, %{"shopify_id" => "234923409", "name" => "asdf"}}Here, rules normalizes to:
[{"title", "name"}, {"id", "shopify_id"}].Any enumerable value that iterates as key/value tuples (map, keyword list, or a list of 2-tuples). These may be either rename rules, or may be more complex key transform rules.
iex> map = %{id: "234923409", title: "asdf"} iex> rules = [title: :name, id: :shopify_id] iex> map <~> rules {%{id: "234923409", title: "asdf"}, %{shopify_id: "234923409", name: "asdf"}} iex> map = %{"id" => "234923409", "title" => "asdf"} iex> rules = [{"title", "name"}, {"id", "shopify_id"}] iex> map <~> rules {%{"id" => "234923409", "title" => "asdf"}, %{"shopify_id" => "234923409", "name" => "asdf"}} iex> map = %{"id" => "234923409", "title" => "asdf"} iex> rules = %{"title" => :name, "id" => :shopify_id} iex> map <~> rules {%{"id" => "234923409", "title" => "asdf"}, %{shopify_id: "234923409", name: "asdf"}} # This is legal, but really ugly. Don't do it. iex> map = %{"id" => "234923409", "title" => "asdf"} iex> rules = ["title=>name", {"id", "shopify_id"}] iex> map <~> rules {%{"id" => "234923409", "title" => "asdf"}, %{"shopify_id" => "234923409", "name" => "asdf"}}
Rename Rules
Rename rules take the value of the old key from the source map and write it
to the target map as the new key, like "title=>name", %{"title" => "name"}, and [title: :name] that normalize to {old_key, new_key}. Both
old_key and new_key are typically atoms or strings, but may be any valid
Map key value, except for the forms noted below.
Advanced Rules
There are two types of advanced rules (keys with options and producer functions), which can only be provided when the rules are in an enumerable format such as a keyword list, map, or list of tuples.
Keys with Options
The new key is provided as a tuple {new_key, options}. Supported options
are :transform (expecting a transformer/0 function) and :default,
expecting any normal map value. The :default will work as the third
parameter of Map.get/3 and be used instead of key_missing/0.
iex> map = %{"title" => "asdf"}
iex> rules = %{"title" => {:name, transform: &String.reverse/1}}
iex> map <~> rules
{%{"title" => "asdf"}, %{name: "fdsa"}}
# If "title" could be missing from the source map, the `transform` function
# should be written to handle `key_missing/0` values or have its own safe
# `default` value.
iex> map = %{}
iex> rules = %{"title" => {:name, default: "unknown", transform: &String.reverse/1}}
iex> map <~> rules
{%{}, %{name: "nwonknu"}}Producer Functions
Producer functions (producer/0) take in the value and return zero or more
key/value tuples. It may be provided either as producer or {producer, options} as shown below.
iex> dcs = fn value -> ...> unless MapRewire.key_missing?(value) do ...> [dept, class, subclass] = ...> value ...> |> String.split("-", parts: 3) ...> |> Enum.map(&String.to_integer/1) ...> ...> Enum.to_list(%{"department" => dept, "class" => class, "subclass" => subclass}) ...> end ...> end iex> map = %{"title" => "asdf", "dcs" => "1-3-5"} iex> rules = %{"title" => "name", "dcs" => dcs} iex> map <~> rules {%{"title" => "asdf", "dcs" => "1-3-5"}, %{"name" => "asdf", "department" => 1, "class" => 3, "subclass" => 5}}
If "title" could be missing from the source map, the transform function
should be written to handle key_missing/0 values or have its own safe
default value.
iex> dcs = fn value -> ...> [dept, class, subclass] = ...> value ...> |> String.split("-", parts: 3) ...> |> Enum.map(&String.to_integer/1) ...> ...> Enum.to_list(%{"department" => dept, "class" => class, "subclass" => subclass}) ...> end iex> map = %{"title" => "asdf"} iex> rules = %{"title" => "name", "dcs" => {dcs, default: "0-0-0"}} iex> map <~> rules {%{"title" => "asdf"}, %{"name" => "asdf", "department" => 0, "class" => 0, "subclass" => 0}}
Link to this section Summary
Types
A function that, given a map value, produces zero or more key/value tuples.
A normalized rewire rule.
Advanced rewire rule options
Rewire rule target values.
The shape of MapRewire transformation rules.
A function that, given a map value, transforms it before insertion into the
target map.
Functions
The operator form of rewire/3, which remaps the map content and replaces
the key if it matches with an item in rewire_rules.
The value used in rewire operations if an old key does not exist in the source map.
Returns true if value is the same as key_missing/0.
Remaps the map content and replaces the key if it matches with an item in
rules.
Link to this section Types
producer()
Specs
A function that, given a map value, produces zero or more key/value tuples.
The value provided may be key_missing/0, so key_missing?/1 should be
used to compare before blindly operating on value.
If no keys are to be produced (possibly because value is key_missing/0),
either nil or an empty list ([]) should be returned.
fn value ->
unless MapRewire.key_missing?(value) do
[dept, class, subclass] =
value
|> String.split("-", parts: 3)
|> Enum.map(&String.to_integer/1)
Enum.to_list(%{"department" => dept, "class" => class, "subclass" => subclass})
end
end
rewire_rule()
Specs
rewire_rule() :: {old :: Map.key(), rewire_rule_target()}
A normalized rewire rule.
rewire_rule_options()
Specs
rewire_rule_options() :: [transform: transformer(), default: Map.value()]
Advanced rewire rule options
rewire_rule_target()
Specs
rewire_rule_target() :: Map.key() | producer() | {producer(), [{:default, Map.value()}]} | {Map.key(), rewire_rule_options()}
Rewire rule target values.
rewire_rules()
Specs
rewire_rules() :: String.t() | [String.t()] | keyword() | map() | [rewire_rule()]
The shape of MapRewire transformation rules.
Note that although keyword lists and maps may be used, the values must be
rewire_rule_target/0 values.
transformer()
Specs
A function that, given a map value, transforms it before insertion into the
target map.
The value may be key_missing/0, so key_missing?/1 should be used to
compare before blindly operating on value.
If the key should be omitted when rewire/3 is called, key_missing/0
should be returned.
fn value ->
cond do
MapRewire.key_missing?(value) ->
value
is_binary(value) ->
String.reverse(value)
true ->
String.reverse(to_string(value))
end
end
Link to this section Functions
content <~> rewire_rules
The operator form of rewire/3, which remaps the map content and replaces
the key if it matches with an item in rewire_rules.
key_missing()
Specs
key_missing() :: binary()
The value used in rewire operations if an old key does not exist in the source map.
Normally, when the rewired map is produced, keys containing this value will
be removed from the rewired map, but providing the option compact: false to
rewire/3 will replace this value with nil.
The value in key_missing/0 may be provided to producer/0 and
transformer/0 functions, so key_missing?/1 should be used to determine
the correct response if this value is received (see the documentation for
these function types).
Note that the value of key_missing/0 is a 45-byte binary value with a 13-byte
fixed head ("<~>NoMatch<~>") and a random value that changes whenever
MapRewire is recompiled.
key_missing?(value)
Specs
Returns true if value is the same as key_missing/0.
rewire(content, rules, options \\ [])
Specs
rewire(map(), rewire_rules(), keyword()) :: {old :: map(), new :: map()}
Remaps the map content and replaces the key if it matches with an item in
rules.
Accepts two options:
:debugcontrols the logging of the steps taken to transformcontentusingrules. The default isApplication.get_env(:map_rewire, :debug?).:compactwhich controls the removal of values from the result map for keys missing in thecontentmap. The default istrue.iex> map = %{"title" => "asdf"} iex> rules = %{"title" => :name, "missing" => :missing} iex> rewire(map, rules, compact: true) # the default {%{"title" => "asdf"}, %{name: "asdf"}} iex> map = %{"title" => "asdf"} iex> rules = %{"title" => :name, "missing" => :missing} iex> rewire(map, rules, compact: false) {%{"title" => "asdf"}, %{name: "asdf", missing: nil}}