diff --git a/lib/atomic_map.ex b/lib/atomic_map.ex index 0f57c02..2b0f206 100644 --- a/lib/atomic_map.ex +++ b/lib/atomic_map.ex @@ -1,29 +1,50 @@ +defmodule AtomicMap.Opts do + @moduledoc ~S""" + Set any value to `false` to disable checking for that kind of key. + """ + defstruct safe: true, + underscore: true +end + defmodule AtomicMap do + def convert(v, opts \\ %{}) - def convert(v, opts \\ []) - def convert(struct=%{__struct__: type}, opts) do + def convert(struct=%{__struct__: type}, opts=%AtomicMap.Opts{}) do struct |> Map.from_struct |> convert(opts) |> Map.put(:__struct__, type) end - def convert(map, opts) when is_map(map) do - safe = Keyword.get(opts, :safe, true) + def convert(map, opts=%AtomicMap.Opts{}) when is_map(map) do map |> Enum.reduce(%{}, fn({k,v}, acc)-> - k = as_atom(k, safe) - v = convert(v, opts) + k = k |> convert_key(opts) + v = v |> convert(opts) acc |> Map.put(k, v) end) end - def convert(list, opts) when is_list(list) do + def convert(list, opts=%AtomicMap.Opts{}) when is_list(list) do list |> Enum.map(fn(x)-> convert(x, opts) end) end - def convert(tuple, opts) when is_tuple(tuple) do + def convert(tuple, opts=%AtomicMap.Opts{}) when is_tuple(tuple) do tuple |> Tuple.to_list |> convert(opts) |> List.to_tuple end - def convert(v, _opts), do: v + def convert(v, _opts=%AtomicMap.Opts{}), do: v + + # if you pass a plain map or keyword list as opts, those will match and convert it to struct + def convert(v, opts=%{}), do: convert(v, struct(AtomicMap.Opts, opts)) + def convert(v, opts) when is_list(opts), do: convert(v, Enum.into(opts, %{})) + + defp convert_key(k, opts) do + k + |> as_underscore(opts.underscore) + |> as_atom(opts.safe) + end + + defp as_atom(s, true) when is_binary(s), do: s |> String.to_existing_atom + defp as_atom(s, false) when is_binary(s), do: s |> String.to_atom + defp as_atom(s, _), do: s - defp as_atom(s, true) when is_binary(s), do: s |> String.to_existing_atom - defp as_atom(s, false) when is_binary(s), do: s |> String.to_atom - defp as_atom(s, _), do: s + def as_underscore(s, true) when is_binary(s), do: s |> Macro.underscore + def as_underscore(s, true) when is_atom(s), do: s |> Atom.to_string |> as_underscore(true) + def as_underscore(s, false), do: s end diff --git a/test/atomic_map_test.exs b/test/atomic_map_test.exs index cc62b1b..e8a5fd6 100644 --- a/test/atomic_map_test.exs +++ b/test/atomic_map_test.exs @@ -9,7 +9,7 @@ defmodule AtomicMapTest do test "works with maps" do input = %{"a" => 2, "b" => %{"c" => 4}} expected = %{a: 2, b: %{c: 4}} - assert AtomicMap.convert(input, safe: true) == expected + assert AtomicMap.convert(input, %{safe: true}) == expected end test "works with maps with lists" do @@ -60,6 +60,12 @@ defmodule AtomicMapTest do assert AtomicMap.convert(input) == expected end + test "convertes keys to underscore by default (attention: safe: false needed here)" do + input = %{ "firstKey" => {1,2}, :secondKey => 4} + expected = %{first_key: {1, 2}, second_key: 4} + assert AtomicMap.convert(input, safe: false) == expected + end + test "raises for not existing atoms" do assert_raise ArgumentError, fn -> input = %{"a" => 2, "b" => %{"c" => 4}, "__not___existing__" => 5}