%%%----------------------------------------------------------------------------- %%% @doc Module to handle nested maps. %%% %%% @author William Fank Thomé [https://github.com/williamthome] %%% @todo docs %%% @end %%%----------------------------------------------------------------------------- -module(maps_in). -export([get/2, get/3, put/3, update/3, get_and_update/3]). -ifdef(TEST). -include_lib("eunit/include/eunit.hrl"). -endif. %%%============================================================================= %%% API %%%============================================================================= %%------------------------------------------------------------------------------ %% @doc get/2. %% @end %%------------------------------------------------------------------------------ -spec get(Path, Map) -> Value when Path :: [term()], Map :: map(), Value :: term(). get([Key], Map) -> maps:get(Key, Map); get([Key | Path], Map) -> get(Path, maps:get(Key, Map)). %%------------------------------------------------------------------------------ %% @doc get/3. %% @end %%------------------------------------------------------------------------------ -spec get(Path, Map, Default) -> Value | Default when Path :: [term()], Map :: map(), Default :: term(), Value :: term(). get([Key], Map, Default) -> maps:get(Key, Map, Default); get([Key | Path], Map, Default) -> get(Path, maps:get(Key, Map), Default). %%------------------------------------------------------------------------------ %% @doc put/3. %% @end %%------------------------------------------------------------------------------ -spec put(Path, Value, Map1) -> Map2 when Path :: [term()], Value :: term(), Map1 :: map(), Map2 :: map(). put([Key | Path], Value, Map) -> maps:put(Key, put(Path, Value, maps:get(Key, Map, #{})), Map); put([], Map, _) -> Map. %%------------------------------------------------------------------------------ %% @doc update/3. %% @end %%------------------------------------------------------------------------------ -spec update(Path, Value, Map1) -> Map2 when Path :: [term()], Value :: term(), Map1 :: map(), Map2 :: map(). update([Key | Path], Value, Map) -> maps:update(Key, update(Path, Value, maps:get(Key, Map, #{})), Map); update([], Map, _) -> Map. %%------------------------------------------------------------------------------ %% @doc get_and_update/3. %% @end %%------------------------------------------------------------------------------ -spec get_and_update(Path, Fun, Map1) -> Map2 when Path :: [term()], Fun :: fun((term()) -> term()), Map1 :: map(), Map2 :: map(). get_and_update([Key], Fun, Map) when is_function(Fun, 1) -> maps:update(Key, Fun(maps:get(Key, Map)), Map); get_and_update([Key | Path], Fun, Map) when is_function(Fun, 1) -> maps:update(Key, get_and_update(Path, Fun, maps:get(Key, Map, #{})), Map); get_and_update([], Map, _) -> Map. %%%============================================================================= %%% Test %%%============================================================================= -ifdef(TEST). %%%----------------------------------------------------------------------------- %%% Support %%%----------------------------------------------------------------------------- the_movie() -> #{joe => #{name => "Joe Armstrong", msg => "Hello, Robert"}, robert => #{name => "Robert Virding", msg => "Hello, Mike"}, mike => #{msg => "Hello, Robert and Joe"}}. %%%----------------------------------------------------------------------------- %%% Unit tests %%%----------------------------------------------------------------------------- get_2_test() -> [?assertEqual(#{name => "Joe Armstrong", msg => "Hello, Robert"}, get([joe], the_movie())), ?assertEqual("Hello, Mike", get([robert, msg], the_movie())), ?assertError({badkey, name}, get([mike, name], the_movie())), ?assertError({badmap, "Hello, Robert and Joe"}, get([mike, msg, hello], the_movie()))]. get_3_test() -> [?assertEqual("Hello, Mike", get([robert, msg], the_movie(), "Hello, Joe")), ?assertEqual("Mike Williams", get([mike, name], the_movie(), "Mike Williams"))]. put_3_test() -> [?assertEqual(#{joe => #{name => "Joe Armstrong", msg => "Hello, Robert"}, robert => #{name => "Robert Virding", msg => "Hello, Mike"}, mike => #{name => "Mike Williams", msg => "Hello, Robert and Joe"}}, put([mike, name], "Mike Williams", the_movie())), ?assertEqual(#{erlang => "The Movie"}, put([erlang], "The Movie", #{}))]. update_3_test() -> [?assertError({badkey, erlang}, update([erlang], "The Movie", #{})), ?assertEqual(#{erlang => "The Movie"}, update([erlang], "The Movie", #{erlang => ""}))]. get_and_update_3_test() -> [?assertError({badkey, erlang}, get_and_update([erlang], fun(_) -> error end, #{})), ?assertError(function_clause, get_and_update([erlang], "The Movie", #{erlang => ""})), ?assertEqual(#{erlang => "The Movie"}, get_and_update([erlang], fun("") -> "The Movie" end, #{erlang => ""})), ?assertEqual(#{erlang => #{the => #{movie => "The Movie"}}}, get_and_update([erlang, the, movie], fun("") -> "The Movie" end, #{erlang => #{the => #{movie => ""}}}))]. -endif.